The current storage volume download implementation relies on writing the
new volume as a file. But that's not the case for some storage pool
types (e.g. logical), which don't treat their volumes as plain files.
Use the libvirt API to write data to a storage volume instead of writing
it directly to a file. The libvirt API requires an existing volume to
write the data to it, and a volume requires a capacity to be created.
As some remote URLs don't provide the header 'Content-length', sometimes
we need to download a file without knowing its size in advance. Due to
those cases, instead of downloading and writing the data directly to the
storage volume, this patch makes it so a temporary file is always
created - so we can get its size - and then that file is transfered to a
storage volume.
Fix issue #544 ("Download to logical storage pool fails").
Signed-off-by: Crístian Viana <vianac(a)linux.vnet.ibm.com>
---
src/kimchi/model/storagevolumes.py | 52 +++++++++++++++++++++++++++++++++++---
1 file changed, 49 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/model/storagevolumes.py b/src/kimchi/model/storagevolumes.py
index 4bf029a..67fcaf0 100644
--- a/src/kimchi/model/storagevolumes.py
+++ b/src/kimchi/model/storagevolumes.py
@@ -20,6 +20,7 @@
import contextlib
import lxml.etree as ET
import os
+import tempfile
import time
import urllib2
from lxml.builder import E
@@ -203,7 +204,11 @@ class StorageVolumesModel(object):
pool_model = StoragePoolModel(conn=self.conn,
objstore=self.objstore)
pool = pool_model.lookup(pool_name)
- file_path = os.path.join(pool['path'], name)
+
+ if pool['type'] in ['dir', 'netfs']:
+ file_path = os.path.join(pool['path'], name)
+ else:
+ file_path = tempfile.mkstemp(prefix=name)[1]
with contextlib.closing(urllib2.urlopen(url)) as response:
with open(file_path, 'w') as volume_file:
@@ -219,14 +224,55 @@ class StorageVolumesModel(object):
volume_file.write(chunk_data)
downloaded_size += len(chunk_data)
cb('%s/%s' % (downloaded_size, remote_size))
- except Exception, e:
+ except (IOError, libvirt.libvirtError) as e:
if os.path.isfile(file_path):
os.remove(file_path)
+
raise OperationFailed('KCHVOL0007E', {'name': name,
'pool': pool_name,
'err': e.message})
- StoragePoolModel.get_storagepool(pool_name, self.conn).refresh(0)
+ if pool['type'] in ['dir', 'netfs']:
+ virt_pool = StoragePoolModel.get_storagepool(pool_name, self.conn)
+ virt_pool.refresh(0)
+ else:
+ def _stream_handler(stream, nbytes, fd):
+ return fd.read(nbytes)
+
+ virt_stream = virt_vol = None
+
+ try:
+ task = self.create(pool_name, {'name': name,
+ 'format': 'raw',
+ 'capacity': downloaded_size,
+ 'allocation': downloaded_size})
+ self.task.wait(task['id'])
+ virt_vol = StorageVolumeModel.get_storagevolume(pool_name,
+ name,
+ self.conn)
+
+ virt_stream = self.conn.get().newStream(0)
+ virt_vol.upload(virt_stream, 0, downloaded_size, 0)
+
+ with open(file_path) as fd:
+ virt_stream.sendAll(_stream_handler, fd)
+
+ virt_stream.finish()
+ except (IOError, libvirt.libvirtError) as e:
+ try:
+ if virt_stream:
+ virt_stream.abort()
+ if virt_vol:
+ virt_vol.delete(0)
+ except libvirt.libvirtError, virt_e:
+ kimchi_log.error(virt_e.message)
+ finally:
+ raise OperationFailed('KCHVOL0007E', {'name': name,
+ 'pool': pool_name,
+ 'err': e.message})
+ finally:
+ os.remove(file_path)
+
cb('OK', True)
def get_list(self, pool_name):
--
2.1.0