
From: Paulo Vital <pvital@linux.vnet.ibm.com> Add back-end support to create new storage volume (new virtual disk) when attaching disk to a guest created before. There are three essential parameters to create the new volume: * vol: Storage volume name of disk image, that should be 'new_vol'. * capacity: The total space which can be used to store new volumes. The unit is bytes. * format: The format of the defined Storage Volume. Only used when creating a storage volume with 'capacity'. Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- src/wok/plugins/kimchi/API.json | 12 ++++++++++++ src/wok/plugins/kimchi/docs/API.md | 6 +++++- src/wok/plugins/kimchi/i18n.py | 1 + src/wok/plugins/kimchi/model/vmstorages.py | 28 +++++++++++++++++++++++++++- src/wok/plugins/kimchi/tests/test_model.py | 25 +++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/wok/plugins/kimchi/API.json b/src/wok/plugins/kimchi/API.json index 961f35f..8822520 100644 --- a/src/wok/plugins/kimchi/API.json +++ b/src/wok/plugins/kimchi/API.json @@ -564,6 +564,18 @@ "type": "string", "pattern": "^((/)|(http)[s]?:|[t]?(ftp)[s]?:)+.*$", "error": "KCHVMSTOR0003E" + }, + "capacity": { + "description": "The total size (MiB) of the storage volume", + "type": "number", + "minimum": 1, + "error": "KCHVOL0020E" + }, + "format": { + "description": "The format of the volume", + "type": "string", + "pattern": "^(|bochs|cloop|cow|dmg|qcow|qcow2|qed|raw|vmdk|vpc)$", + "error": "KCHVOL0015E" } } }, diff --git a/src/wok/plugins/kimchi/docs/API.md b/src/wok/plugins/kimchi/docs/API.md index 52368b7..d00c2fe 100644 --- a/src/wok/plugins/kimchi/docs/API.md +++ b/src/wok/plugins/kimchi/docs/API.md @@ -205,7 +205,11 @@ Represents a snapshot of the Virtual Machine's primary monitor. * type: The type of the storage (currently support 'cdrom' and 'disk'). * path: Path of cdrom iso. * pool: Storage pool which disk image file locate in. - * vol: Storage volume name of disk image. + * vol: Storage volume name of disk image ('new_vol' for create a new volume). + * capacity: The total space which can be used to store new volumes. + The unit is bytes. + * format: The format of the defined Storage Volume. Only used when creating + a storage volume with 'capacity'. ### Sub-resource: storage **URI:** /plugins/kimchi/vms/*:name*/storages/*:dev* diff --git a/src/wok/plugins/kimchi/i18n.py b/src/wok/plugins/kimchi/i18n.py index 42a5e16..53a0b6b 100644 --- a/src/wok/plugins/kimchi/i18n.py +++ b/src/wok/plugins/kimchi/i18n.py @@ -278,6 +278,7 @@ messages = { "KCHVMSTOR0016E": _("Volume already in use by other virtual machine."), "KCHVMSTOR0017E": _("Only one of path or pool/volume can be specified to add a new virtual machine disk"), "KCHVMSTOR0018E": _("Volume chosen with format %(format)s does not fit in the storage type %(type)s"), + "KCHVMSTOR0019E": _("The format msust be used only when creating a new storage volume with 'capacity'."), "KCHSNAP0001E": _("Virtual machine '%(vm)s' must be stopped before creating a snapshot of it."), "KCHSNAP0002E": _("Unable to create snapshot '%(name)s' on virtual machine '%(vm)s'. Details: %(err)s"), diff --git a/src/wok/plugins/kimchi/model/vmstorages.py b/src/wok/plugins/kimchi/model/vmstorages.py index 23db0a6..29cdc73 100644 --- a/src/wok/plugins/kimchi/model/vmstorages.py +++ b/src/wok/plugins/kimchi/model/vmstorages.py @@ -22,12 +22,14 @@ from lxml import etree from wok.exception import InvalidOperation, InvalidParameter, NotFoundError from wok.exception import OperationFailed +from wok.model.tasks import TaskModel from wok.utils import wok_log from wok.plugins.kimchi.model.config import CapabilitiesModel from wok.plugins.kimchi.model.diskutils import get_disk_used_by from wok.plugins.kimchi.model.diskutils import set_disk_used_by from wok.plugins.kimchi.model.storagevolumes import StorageVolumeModel +from wok.plugins.kimchi.model.storagevolumes import StorageVolumesModel from wok.plugins.kimchi.model.utils import get_vm_config_flag from wok.plugins.kimchi.model.vms import DOM_STATE_MAP, VMModel from wok.plugins.kimchi.osinfo import lookup @@ -51,6 +53,7 @@ class VMStoragesModel(object): self.conn = kargs['conn'] self.objstore = kargs['objstore'] self.caps = CapabilitiesModel(**kargs) + self.task = TaskModel(**kargs) def _get_available_bus_address(self, bus_type, vm_name): if bus_type not in ['ide']: @@ -85,9 +88,12 @@ class VMStoragesModel(object): if not ('vol' in params) ^ ('path' in params): raise InvalidParameter("KCHVMSTOR0017E") + if ('format' in params) and ('capacity' not in params): + raise InvalidParameter("KCHVMSTOR0019E") + dom = VMModel.get_vm(vm_name, self.conn) params['bus'] = _get_device_bus(params['type'], dom) - params['format'] = 'raw' + params['format'] = params.get('format', 'raw') dev_list = [dev for dev, bus in get_vm_disks(dom).iteritems() if bus == params['bus']] @@ -102,6 +108,26 @@ class VMStoragesModel(object): DOM_STATE_MAP[dom.info()[0]] != 'shutoff'): raise InvalidOperation('KCHVMSTOR0011E') + if params.get('capacity'): + # If 'capacity' is in the parameters a new storage volume will + # be created and allocated to the VM. + if params['vol'] == 'new_vol': + # Set the name of the volume as the same pattern used when + # creating a VM based on Template (VM UUID + disk index). + # Otherwise, use the name provided by the user. + params['vol'] = "%s-%s.img" % (dom.UUIDString(), + params['index']) + vol_info = dict(name=params['vol'], + type=params['type'], + capacity=params['capacity'], + allocation=params['capacity'], + format=params['format'], + ) + storage_volumes = StorageVolumesModel(conn=self.conn, + objstore=self.objstore) + mytask = storage_volumes.create(params['pool'], vol_info) + self.task.wait(mytask['id']) + if params.get('vol'): try: pool = params['pool'] diff --git a/src/wok/plugins/kimchi/tests/test_model.py b/src/wok/plugins/kimchi/tests/test_model.py index fdbe3b4..8cc3f14 100644 --- a/src/wok/plugins/kimchi/tests/test_model.py +++ b/src/wok/plugins/kimchi/tests/test_model.py @@ -406,6 +406,19 @@ class ModelTests(unittest.TestCase): self.assertEquals(expect_bus, disk_info['bus']) return disk + def _attach_new_disk(expect_bus=modern_disk_bus): + disk_args = {"type": "disk", + "pool": pool, + "vol": 'new_vol', + "format": 'qcow2', + "capacity": 1024} + new_disk = inst.vmstorages_create(vm_name, disk_args) + + # Check the bus type to be 'virtio' + disk_info = inst.vmstorage_lookup(vm_name, new_disk) + self.assertEquals(expect_bus, disk_info['bus']) + return new_disk + inst = model.Model(objstore_loc=self.tmp_store) with RollbackContext() as rollback: path = os.path.join(TMP_DIR, 'kimchi-images') @@ -466,6 +479,18 @@ class ModelTests(unittest.TestCase): self.assertEquals(u'disk', disk_info['type']) inst.vmstorage_delete(vm_name, disk) + # Create a new volume when attaching a disks + inst.vm_start(vm_name) + prev_disk_count = len(inst.vmstorages_get_list(vm_name)) + new_disk = _attach_new_disk() + curr_disk_count = len(inst.vmstorages_get_list(vm_name)) + self.assertEquals(prev_disk_count+1, curr_disk_count) + # The number of disks must be the same after VM off. + inst.vm_poweroff(vm_name) + inst.vmstorage_delete(vm_name, new_disk) + after_disk_count = len(inst.vmstorages_get_list(vm_name)) + self.assertEquals(prev_disk_count, after_disk_count) + # Specifying pool and path at same time will fail disk_args = {"type": "disk", "pool": pool, -- 2.4.3