diff --git a/api-daemon/pvcapid/flaskapi.py b/api-daemon/pvcapid/flaskapi.py
index cc1820ab..53829075 100755
--- a/api-daemon/pvcapid/flaskapi.py
+++ b/api-daemon/pvcapid/flaskapi.py
@@ -3336,8 +3336,11 @@ api.add_resource(API_Storage_Ceph_Volume_Element_Clone, '/storage/ceph/volume/
//upload
class API_Storage_Ceph_Volume_Element_Upload(Resource):
+ @RequestParser([
+ { 'name': 'image_format' }
+ ])
@Authenticator
- def post(self, pool, volume):
+ def post(self, pool, volume, reqargs):
"""
Upload a disk image to Ceph volume {volume} in pool {pool}
---
@@ -3345,6 +3348,18 @@ class API_Storage_Ceph_Volume_Element_Upload(Resource):
- storage / ceph
parameters:
- in: query
+ name: image_format
+ type: string
+ required: true
+ description: The type of source image file
+ enum:
+ - raw
+ - vmdk
+ - qcow2
+ - qed
+ - vdi
+ - vpc
+ - in: formdata
name: file
type: binary
required: true
@@ -3374,10 +3389,12 @@ class API_Storage_Ceph_Volume_Element_Upload(Resource):
image_data = data.get('file', None)
if not image_data:
return {'message':'An image file contents must be specified'}, 400
+
return api_helper.ceph_volume_upload(
pool,
volume,
- image_data
+ image_data,
+ reqargs.get('image_format', None)
)
api.add_resource(API_Storage_Ceph_Volume_Element_Upload, '/storage/ceph/volume///upload')
diff --git a/api-daemon/pvcapid/helper.py b/api-daemon/pvcapid/helper.py
index 05107793..d39a43e8 100755
--- a/api-daemon/pvcapid/helper.py
+++ b/api-daemon/pvcapid/helper.py
@@ -1327,48 +1327,143 @@ def ceph_volume_remove(pool, name):
}
return output, retcode
-def ceph_volume_upload(pool, volume, data):
+def ceph_volume_upload(pool, volume, data, img_type):
"""
Upload a raw file via HTTP post to a PVC Ceph volume
"""
- # Map the target blockdev
- zk_conn = pvc_common.startZKConnection(config['coordinators'])
- retflag, retdata = pvc_ceph.map_volume(zk_conn, pool, volume)
- pvc_common.stopZKConnection(zk_conn)
- if not retflag:
+ # Determine the image conversion options
+ if img_type not in ['raw', 'vmdk', 'qcow2', 'qed', 'vdi', 'vpc']:
output = {
- 'message': retdata.replace('\"', '\'')
- }
- retcode = 400
- return output, retcode
- blockdev = retdata
-
- output = {
- 'message': "Wrote uploaded file to volume '{}' in pool '{}'.".format(volume, pool)
- }
- retcode = 200
-
- # Save the data to the blockdev
- try:
- data.save(blockdev)
- except:
- output = {
- 'message': "ERROR: Failed to write image file to volume."
- }
- retcode = 400
-
- # Unmap the target blockdev
- zk_conn = pvc_common.startZKConnection(config['coordinators'])
- retflag, retdata = pvc_ceph.unmap_volume(zk_conn, pool, volume)
- pvc_common.stopZKConnection(zk_conn)
- if not retflag:
- output = {
- 'message': retdata.replace('\"', '\'')
+ "message": "ERROR: Image type '{}' is not valid.".format(img_type)
}
retcode = 400
return output, retcode
- return output, retcode
+ # Get the size of the target block device
+ zk_conn = pvc_common.startZKConnection(config['coordinators'])
+ retcode, retdata = pvc_ceph.get_list_volume(zk_conn, pool, volume, is_fuzzy=False)
+ pvc_common.stopZKConnection(zk_conn)
+ # If there's no target, return failure
+ if not retcode or len(retdata) < 1:
+ output = {
+ "message": "ERROR: Target volume '{}' does not exist in pool '{}'.".format(volume, pool)
+ }
+ retcode = 400
+ return output, retcode
+ dev_size = retdata[0]['stats']['size']
+
+ def cleanup_maps_and_volumes():
+ zk_conn = pvc_common.startZKConnection(config['coordinators'])
+ # Unmap the target blockdev
+ retflag, retdata = pvc_ceph.unmap_volume(zk_conn, pool, volume)
+ # Unmap the temporary blockdev
+ retflag, retdata = pvc_ceph.unmap_volume(zk_conn, pool, "{}_tmp".format(volume))
+ # Remove the temporary blockdev
+ retflag, retdata = pvc_ceph.remove_volume(zk_conn, pool, "{}_tmp".format(volume))
+ pvc_common.stopZKConnection(zk_conn)
+
+ # Create a temporary block device to store non-raw images
+ if img_type == 'raw':
+ # Map the target blockdev
+ zk_conn = pvc_common.startZKConnection(config['coordinators'])
+ retflag, retdata = pvc_ceph.map_volume(zk_conn, pool, volume)
+ pvc_common.stopZKConnection(zk_conn)
+ if not retflag:
+ output = {
+ 'message': retdata.replace('\"', '\'')
+ }
+ retcode = 400
+ cleanup_maps_and_volumes()
+ return output, retcode
+ dest_blockdev = retdata
+
+ # Save the data to the blockdev directly
+ try:
+ data.save(dest_blockdev)
+ except:
+ output = {
+ 'message': "ERROR: Failed to write image file to volume."
+ }
+ retcode = 400
+ cleanup_maps_and_volumes()
+ return output, retcode
+
+ output = {
+ 'message': "Wrote uploaded file to volume '{}' in pool '{}'.".format(volume, pool)
+ }
+ retcode = 200
+ cleanup_maps_and_volumes()
+ return output, retcode
+
+ # Write the image directly to the blockdev
+ else:
+ # Create a temporary blockdev
+ zk_conn = pvc_common.startZKConnection(config['coordinators'])
+ retflag, retdata = pvc_ceph.add_volume(zk_conn, pool, "{}_tmp".format(volume), dev_size)
+ pvc_common.stopZKConnection(zk_conn)
+ if not retflag:
+ output = {
+ 'message': retdata.replace('\"', '\'')
+ }
+ retcode = 400
+ cleanup_maps_and_volumes()
+ return output, retcode
+
+ # Map the temporary target blockdev
+ zk_conn = pvc_common.startZKConnection(config['coordinators'])
+ retflag, retdata = pvc_ceph.map_volume(zk_conn, pool, "{}_tmp".format(volume))
+ pvc_common.stopZKConnection(zk_conn)
+ if not retflag:
+ output = {
+ 'message': retdata.replace('\"', '\'')
+ }
+ retcode = 400
+ cleanup_maps_and_volumes()
+ return output, retcode
+ temp_blockdev = retdata
+
+ # Map the target blockdev
+ zk_conn = pvc_common.startZKConnection(config['coordinators'])
+ retflag, retdata = pvc_ceph.map_volume(zk_conn, pool, volume)
+ pvc_common.stopZKConnection(zk_conn)
+ if not retflag:
+ output = {
+ 'message': retdata.replace('\"', '\'')
+ }
+ retcode = 400
+ cleanup_maps_and_volumes()
+ return output, retcode
+ dest_blockdev = retdata
+
+ # Save the data to the temporary blockdev directly
+ try:
+ data.save(temp_blockdev)
+ except:
+ output = {
+ 'message': "ERROR: Failed to write image file to temporary volume."
+ }
+ retcode = 400
+ cleanup_maps_and_volumes()
+ return output, retcode
+
+ # Convert from the temporary to destination format on the blockdevs
+ retcode, stdout, stderr = pvc_common.run_os_command(
+ 'qemu-img convert -C -f {} -O raw {} {}'.format(img_type, temp_blockdev, dest_blockdev)
+ )
+ if retcode:
+ output = {
+ 'message': "ERROR: Failed to convert image format from '{}' to 'raw': {}".format(img_type, stderr)
+ }
+ retcode = 400
+ cleanup_maps_and_volumes()
+ return output, retcode
+
+ output = {
+ 'message': "Converted and wrote uploaded file to volume '{}' in pool '{}'.".format(volume, pool)
+ }
+ retcode = 200
+ cleanup_maps_and_volumes()
+ return output, retcode
def ceph_volume_snapshot_list(pool=None, volume=None, limit=None, is_fuzzy=True):
"""
diff --git a/daemon-common/common.py b/daemon-common/common.py
index c665961f..cbdb19cf 100644
--- a/daemon-common/common.py
+++ b/daemon-common/common.py
@@ -38,11 +38,12 @@ import daemon_lib.zkhandler as zkhandler
#
# Run a local OS command via shell
#
-def run_os_command(command_string, background=False, environment=None, timeout=None):
+def run_os_command(command_string, background=False, environment=None, timeout=None, shell=False):
command = shlex.split(command_string)
try:
command_output = subprocess.run(
command,
+ shell=shell,
env=environment,
timeout=timeout,
stdout=subprocess.PIPE,
diff --git a/docs/manuals/swagger.json b/docs/manuals/swagger.json
index d79709d0..475782a6 100644
--- a/docs/manuals/swagger.json
+++ b/docs/manuals/swagger.json
@@ -4696,8 +4696,23 @@
"description": "",
"parameters": [
{
- "description": "The raw binary contents of the file",
+ "description": "The type of source image file",
+ "enum": [
+ "raw",
+ "vmdk",
+ "qcow2",
+ "qed",
+ "vdi",
+ "vpc"
+ ],
"in": "query",
+ "name": "image_format",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "description": "The raw binary contents of the file",
+ "in": "formdata",
"name": "file",
"required": true,
"type": "binary"