In order to simplify the creation of a remote image in Kimchi, the user
will be able to provide a remote URL and the Kimchi server will download
it and put it on a storage pool.
Download a remote image to a storage pool
Signed-off-by: Crístian Viana <vianac(a)linux.vnet.ibm.com>
---
docs/API.md | 1 +
src/kimchi/mockmodel.py | 20 ++++++++++++++++++++
src/kimchi/model/storagevolumes.py | 32 ++++++++++++++++++++++++++++++++
tests/test_model.py | 24 ++++++++++++++++++++++++
tests/test_rest.py | 12 ++++++++++++
5 files changed, 89 insertions(+)
diff --git a/docs/API.md b/docs/API.md
index 298441f..282c8e0 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -418,6 +418,7 @@ A interface represents available network interface on VM.
* capacity: The total space which can be used to store volumes
The unit is MBytes
* format: The format of the defined Storage Volume
+ * url: URL to be downloaded
### Resource: Storage Volume
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
index 05e3fa4..9fab036 100644
--- a/src/kimchi/mockmodel.py
+++ b/src/kimchi/mockmodel.py
@@ -524,6 +524,26 @@ class MockModel(object):
pool._volumes[name] = volume
cb('OK', True)
+ def _create_volume_with_url(self, cb, params):
+ pool_name = params['pool']
+ name = params['name']
+ url = params['url']
+
+ pool = self._get_storagepool(pool_name)
+
+ file_path = os.path.join(pool.info['path'], name)
+
+ with open(file_path, 'w') as file:
+ file.write(url)
+
+ params['path'] = file_path
+ params['type'] = 'file'
+
+ volume = MockStorageVolume(pool, name, params)
+ pool._volumes[name] = volume
+
+ cb('OK', True)
+
def storagevolume_lookup(self, pool, name):
if self._get_storagepool(pool).info['state'] != 'active':
raise InvalidOperation("KCHVOL0005E", {'pool': pool,
diff --git a/src/kimchi/model/storagevolumes.py b/src/kimchi/model/storagevolumes.py
index 6b001f7..3062e78 100644
--- a/src/kimchi/model/storagevolumes.py
+++ b/src/kimchi/model/storagevolumes.py
@@ -17,7 +17,9 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import contextlib
import os
+import urllib2
import libvirt
@@ -39,6 +41,9 @@ VOLUME_TYPE_MAP = {0: 'file',
3: 'network'}
+DOWNLOAD_CHUNK_SIZE = 1048576 # 1 MiB
+
+
class StorageVolumesModel(object):
def __init__(self, **kargs):
self.conn = kargs['conn']
@@ -112,6 +117,33 @@ class StorageVolumesModel(object):
cb('', True)
+ def _create_volume_with_url(self, cb, params):
+ pool_name = params['pool']
+ name = params['name']
+ url = params['url']
+
+ pool_model = StoragePoolModel(conn=self.conn,
+ objstore=self.objstore)
+ pool = pool_model.lookup(pool_name)
+ file_path = os.path.join(pool['path'], name)
+
+ with contextlib.closing(urllib2.urlopen(url)) as response,\
+ open(file_path, 'w') as volume_file:
+ try:
+ while True:
+ chunk_data = response.read(DOWNLOAD_CHUNK_SIZE)
+ if not chunk_data:
+ break
+
+ volume_file.write(chunk_data)
+ except Exception, e:
+ raise OperationFailed('KCHVOL0007E', {'name': name,
+ 'pool': pool_name,
+ 'err': e.message})
+
+ StoragePoolModel.get_storagepool(pool_name, self.conn).refresh()
+ cb('OK', True)
+
def get_list(self, pool_name):
pool = StoragePoolModel.get_storagepool(pool_name, self.conn)
if not pool.isActive():
diff --git a/tests/test_model.py b/tests/test_model.py
index 5ee824d..4e9ba97 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -36,6 +36,7 @@ import iso_gen
import kimchi.objectstore
import utils
from kimchi import netinfo
+from kimchi.config import paths
from kimchi.exception import InvalidOperation, InvalidParameter
from kimchi.exception import NotFoundError, OperationFailed
from kimchi.iscsi import TargetClient
@@ -557,6 +558,29 @@ class ModelTests(unittest.TestCase):
poolinfo = inst.storagepool_lookup(pool)
self.assertEquals(len(vols), poolinfo['nr_volumes'])
+ # download remote volume
+ # 1) try an invalid URL
+ params = {'name': 'foo', 'url':
'http://www.invalid.url'}
+ taskid = inst.storagevolumes_create(pool, params)['id']
+ self._wait_task(inst, taskid)
+ self.assertEquals('failed',
inst.task_lookup(taskid)['status'])
+ # 2) download Kimchi's "COPYING" from Github and compare its
+ # content to the corresponding local file's
+ url = 'https://github.com/kimchi-project/kimchi/raw/master/COPYING'
+ params = {'name': 'copying', 'url': url}
+ taskid = inst.storagevolumes_create(pool, params)['id']
+ self._wait_task(inst, taskid)
+ self.assertEquals('finished',
inst.task_lookup(taskid)['status'])
+ rollback.prependDefer(inst.storagevolume_delete, pool,
+ params['name'])
+ vol_path = os.path.join(args['path'], params['name'])
+ self.assertTrue(os.path.isfile(vol_path))
+ with open(vol_path) as vol_file:
+ vol_content = vol_file.read()
+ with open(os.path.join(paths.get_prefix(), 'COPYING')) as cp_file:
+ cp_content = cp_file.read()
+ self.assertEquals(vol_content, cp_content)
+
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
def test_template_storage_customise(self):
inst = model.Model(objstore_loc=self.tmp_store)
diff --git a/tests/test_rest.py b/tests/test_rest.py
index 9e14b6d..b325201 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -1026,6 +1026,18 @@ class RestTests(unittest.TestCase):
self.assertEquals('/var/lib/libvirt/images/volume-1',
storagevolume['path'])
+ req = json.dumps({'name': 'downloaded',
+ 'url': 'https://anyurl.wor.kz'})
+ resp = self.request('/storagepools/pool-1/storagevolumes', req,
'POST')
+ self.assertEquals(202, resp.status)
+ task = json.loads(resp.read())
+ self._wait_task(task['id'])
+ task = json.loads(self.request('/tasks/%s' % task['id']).read())
+ self.assertEquals('finished', task['status'])
+ resp = self.request('/storagepools/pool-1/storagevolumes/downloaded',
+ '{}', 'GET')
+ self.assertEquals(200, resp.status)
+
# Now remove the StoragePool from mock model
self._delete_pool('pool-1')
--
1.9.3