def test_retype_volume_different_backend(self): ctxt = self.context loc = 'GPFSDriver:%s:testpath' % self.driver._cluster_id cap = {'location_info': loc} host = {'host': 'foo', 'capabilities': cap} key_specs_old = {'capabilities:storage_pool': 'bronze', 'volume_backend_name': 'backend1'} key_specs_new = {'capabilities:storage_pool': 'gold', 'volume_backend_name': 'backend2'} old_type_ref = volume_types.create(ctxt, 'old', key_specs_old) new_type_ref = volume_types.create(ctxt, 'new', key_specs_new) old_type = volume_types.get_volume_type(ctxt, old_type_ref['id']) new_type = volume_types.get_volume_type(ctxt, new_type_ref['id']) diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'], new_type_ref['id']) # set volume host to match target host volume = test_utils.create_volume(ctxt, host=host['host']) volume['volume_type_id'] = old_type['id'] with mock.patch('cinder.utils.execute'): LOG.debug('Retype different backends, cannot migrate. ' 'Expected rv = False.') self.driver.create_volume(volume) rv = self.driver.retype(ctxt, volume, old_type, diff, host) self.assertFalse(rv) self.driver.delete_volume(volume) LOG.debug('Retype different backends, cannot migrate, ' 'rv = %s.' % rv)
def test_retype(self): """Test that retype returns successfully.""" self.driver.do_setup(None) # prepare parameters ctxt = context.get_admin_context() host = {"host": "foo", "capabilities": {"location_info": "xiv_ds8k_fake_1", "extent_size": "1024"}} key_specs_old = {"easytier": False, "warning": 2, "autoexpand": True} key_specs_new = {"easytier": True, "warning": 5, "autoexpand": False} old_type_ref = volume_types.create(ctxt, "old", key_specs_old) new_type_ref = volume_types.create(ctxt, "new", key_specs_new) diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref["id"], new_type_ref["id"]) volume = copy.deepcopy(VOLUME) old_type = volume_types.get_volume_type(ctxt, old_type_ref["id"]) volume["volume_type"] = old_type volume["host"] = host new_type = volume_types.get_volume_type(ctxt, new_type_ref["id"]) self.driver.create_volume(volume) ret = self.driver.retype(ctxt, volume, new_type, diff, host) self.assertTrue(ret) self.assertTrue(volume["easytier"])
def test_retype_with_no_LH_extra_specs(self): # setup drive with default configuration # and return the mock HTTP LeftHand client mock_client = self.setup_driver() ctxt = context.get_admin_context() host = {'host': self.serverName} key_specs_old = {'foo': False, 'bar': 2, 'error': True} key_specs_new = {'foo': True, 'bar': 5, 'error': False} old_type_ref = volume_types.create(ctxt, 'old', key_specs_old) new_type_ref = volume_types.create(ctxt, 'new', key_specs_new) diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'], new_type_ref['id']) volume = dict.copy(self.volume) old_type = volume_types.get_volume_type(ctxt, old_type_ref['id']) volume['volume_type'] = old_type volume['host'] = host new_type = volume_types.get_volume_type(ctxt, new_type_ref['id']) self.driver.retype(ctxt, volume, new_type, diff, host) expected = self.driver_startup_call_stack + [ mock.call.getVolumeByName('fakevolume')] # validate call chain mock_client.assert_has_calls(expected)
def test_retype_same_extra_specs(self): # setup drive with default configuration # and return the mock HTTP LeftHand client mock_client = self.setup_driver() mock_client.getVolumeByName.return_value = {'id': self.volume_id} ctxt = context.get_admin_context() host = {'host': self.serverName} key_specs_old = {'hplh:provisioning': 'full', 'hplh:ao': 'true'} key_specs_new = {'hplh:provisioning': 'full', 'hplh:ao': 'false'} old_type_ref = volume_types.create(ctxt, 'old', key_specs_old) new_type_ref = volume_types.create(ctxt, 'new', key_specs_new) diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'], new_type_ref['id']) volume = dict.copy(self.volume) old_type = volume_types.get_volume_type(ctxt, old_type_ref['id']) volume['volume_type'] = old_type volume['host'] = host new_type = volume_types.get_volume_type(ctxt, new_type_ref['id']) self.driver.retype(ctxt, volume, new_type, diff, host) expected = self.driver_startup_call_stack + [ mock.call.getVolumeByName('fakevolume'), mock.call.modifyVolume( 1, {'isAdaptiveOptimizationEnabled': False})] # validate call chain mock_client.assert_has_calls(expected)
def _update(self, req, id, body): # Update description for a given volume type. context = req.environ["cinder.context"] authorize(context) if not self.is_valid_body(body, "volume_type"): raise webob.exc.HTTPBadRequest() vol_type = body["volume_type"] description = vol_type.get("description", None) if description is None: msg = _("Specify the description to update.") raise webob.exc.HTTPBadRequest(explanation=msg) try: # check it exists vol_type = volume_types.get_volume_type(context, id) volume_types.update(context, id, description) # get the updated vol_type = volume_types.get_volume_type(context, id) req.cache_resource(vol_type, name="types") self._notify_volume_type_info(context, "volume_type.update", vol_type) except exception.VolumeTypeNotFound as err: self._notify_volume_type_error(context, "volume_type.update", err, id=id) raise webob.exc.HTTPNotFound(explanation=six.text_type(err)) except exception.VolumeTypeExists as err: self._notify_volume_type_error(context, "volume_type.update", err, volume_type=vol_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.VolumeTypeUpdateFailed as err: self._notify_volume_type_error(context, "volume_type.update", err, volume_type=vol_type) raise webob.exc.HTTPInternalServerError(explanation=six.text_type(err)) return self._view_builder.show(req, vol_type)
def test_retype_volume_different_pool_and_host(self): ctxt = self.context loc = 'GPFSDriver:%s:testpath' % self.driver._cluster_id cap = {'location_info': loc} host = {'host': 'foo', 'capabilities': cap} key_specs_old = {'capabilities:storage_pool': 'bronze', 'volume_backend_name': 'backend1'} key_specs_new = {'capabilities:storage_pool': 'gold', 'volume_backend_name': 'backend1'} old_type_ref = volume_types.create(ctxt, 'old', key_specs_old) new_type_ref = volume_types.create(ctxt, 'new', key_specs_new) old_type = volume_types.get_volume_type(ctxt, old_type_ref['id']) new_type = volume_types.get_volume_type(ctxt, new_type_ref['id']) diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'], new_type_ref['id']) # set volume host to be different from target host volume = test_utils.create_volume(ctxt, host=CONF.host) volume['volume_type_id'] = old_type['id'] with mock.patch('cinder.utils.execute'): # different host different pool LOG.debug('Retype different pools and hosts, expected rv = True.') self.driver.db = mock.Mock() self.driver.create_volume(volume) rv = self.driver.retype(ctxt, volume, new_type, diff, host) self.assertTrue(rv) self.driver.delete_volume(volume) LOG.debug('Retype different pools and hosts, rv = %s.' % rv)
def testGetVolumeTypeExtraSpec_NoOverridePrefixInExtraSpecKey(self): volume = {'volume_type_id': 1} volume_type = {'extra_specs': {'test_key': 'test_value'}} self.m.StubOutWithMock(context, 'get_admin_context') self.m.StubOutWithMock(volume_types, 'get_volume_type') context.get_admin_context().AndReturn(None) volume_types.get_volume_type(None, 1).AndReturn(volume_type) self.m.ReplayAll() result = self.driver._get_volume_type_extra_spec(volume, 'test_key') self.assertEqual(result, 'test_value') self.m.VerifyAll()
def migrate_volume(self, context, volume, host, force_host_copy): """Migrate the volume to the specified host.""" # We only handle "available" volumes for now if volume['status'] != "available": msg = _("status must be available") LOG.error(msg) raise exception.InvalidVolume(reason=msg) # We only handle volumes without snapshots for now snaps = self.db.snapshot_get_all_for_volume(context, volume['id']) if snaps: msg = _("volume must not have snapshots") LOG.error(msg) raise exception.InvalidVolume(reason=msg) # Make sure the host is in the list of available hosts elevated = context.elevated() topic = CONF.volume_topic services = self.db.service_get_all_by_topic(elevated, topic) found = False for service in services: if utils.service_is_up(service) and service['host'] == host: found = True if not found: msg = (_('No available service named %s') % host) LOG.error(msg) raise exception.InvalidHost(reason=msg) # Make sure the destination host is different than the current one if host == volume['host']: msg = _('Destination host must be different than current host') LOG.error(msg) raise exception.InvalidHost(reason=msg) self.update(context, volume, {'status': 'migrating'}) # Call the scheduler to ensure that the host exists and that it can # accept the volume volume_type = {} if volume['volume_type_id']: volume_types.get_volume_type(context, volume['volume_type_id']) request_spec = {'volume_properties': volume, 'volume_type': volume_type, 'volume_id': volume['id']} self.scheduler_rpcapi.migrate_volume_to_host(context, CONF.volume_topic, volume['id'], host, force_host_copy, request_spec)
def _delete(self, req, id): """Deletes an existing volume type.""" context = req.environ['cinder.context'] authorize(context) try: vol_type = volume_types.get_volume_type(context, id) volume_types.destroy(context, vol_type['id']) notifier_info = dict(volume_types=vol_type) notifier_api.notify(context, 'volumeType', 'volume_type.delete', notifier_api.INFO, notifier_info) except exception.VolumeTypeInUse as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_voloume_type_error(context, 'volume_type.delete', notifier_err) msg = 'Target volume type is still in use.' raise webob.exc.HTTPBadRequest(explanation=msg) except exception.NotFound as err: notifier_err = dict(id=id, error_message=str(err)) self._notify_voloume_type_error(context, 'volume_type.delete', notifier_err) raise webob.exc.HTTPNotFound() return webob.Response(status_int=202)
def get_vdisk_params(self, config, state, type_id, volume_type=None, volume_metadata=None): """Return the parameters for creating the vdisk. Takes volume type and defaults from config options into account. """ opts = self.build_default_opts(config) ctxt = context.get_admin_context() if volume_type is None and type_id is not None: volume_type = volume_types.get_volume_type(ctxt, type_id) if volume_type: qos_specs_id = volume_type.get('qos_specs_id') specs = dict(volume_type).get('extra_specs') # NOTE(vhou): We prefer the qos_specs association # and over-ride any existing # extra-specs settings if present if qos_specs_id is not None: kvs = qos_specs.get_qos_specs(ctxt, qos_specs_id)['specs'] # Merge the qos_specs into extra_specs and qos_specs has higher # priority than extra_specs if they have different values for # the same key. specs.update(kvs) opts = self._get_opts_from_specs(opts, specs) if (opts['qos'] is None and config.storwize_svc_allow_tenant_qos and volume_metadata): qos = self._get_qos_from_volume_metadata(volume_metadata) if len(qos) != 0: opts['qos'] = qos self.check_vdisk_opts(state, opts) return opts
def _get_volume_type_extra_spec(self, volume, spec_key): """ Code adapted from examples in cinder/volume/drivers/solidfire.py and cinder/openstack/common/scheduler/filters/capabilities_filter.py. """ spec_value = None ctxt = context.get_admin_context() typeid = volume['volume_type_id'] if typeid: volume_type = volume_types.get_volume_type(ctxt, typeid) volume_specs = volume_type.get('extra_specs') for key, val in volume_specs.iteritems(): # Havana release altered extra_specs to require a # prefix on all non-host-capability related extra # specs, so that prefix is dropped here before # checking the key. # if ':' in key: scope = key.split(':') if scope[1] == spec_key: spec_value = val break return spec_value
def test_volume_type_get_by_id_and_name(self): """Ensure volume types get returns same entry.""" volume_types.create(self.ctxt, self.vol_type1_name, self.vol_type1_specs) new = volume_types.get_volume_type_by_name(self.ctxt, self.vol_type1_name) new2 = volume_types.get_volume_type(self.ctxt, new["id"]) self.assertEqual(new, new2)
def _update(self, req, id, body): # Update description for a given volume type. context = req.environ['cinder.context'] authorize(context) self.assert_valid_body(body, 'volume_type') vol_type = body['volume_type'] description = vol_type.get('description') name = vol_type.get('name') is_public = vol_type.get('is_public') # Name and description can not be both None. # If name specified, name can not be empty. if name and len(name.strip()) == 0: msg = _("Volume type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) if name is None and description is None and is_public is None: msg = _("Specify volume type name, description, is_public or " "a combination thereof.") raise webob.exc.HTTPBadRequest(explanation=msg) if is_public is not None and not utils.is_valid_boolstr(is_public): msg = _("Invalid value '%s' for is_public. Accepted values: " "True or False.") % is_public raise webob.exc.HTTPBadRequest(explanation=msg) if name: utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: volume_types.update(context, id, name, description, is_public=is_public) # Get the updated vol_type = volume_types.get_volume_type(context, id) req.cache_resource(vol_type, name='types') self._notify_volume_type_info( context, 'volume_type.update', vol_type) except exception.VolumeTypeNotFound as err: self._notify_volume_type_error( context, 'volume_type.update', err, id=id) raise webob.exc.HTTPNotFound(explanation=six.text_type(err)) except exception.VolumeTypeExists as err: self._notify_volume_type_error( context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.VolumeTypeUpdateFailed as err: self._notify_volume_type_error( context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPInternalServerError( explanation=six.text_type(err)) return self._view_builder.show(req, vol_type)
def _create_cg_from_source_cg(self, context, group, source_cg): try: source_vols = self.db.volume_get_all_by_group(context, source_cg.id) if not source_vols: msg = _("Source CG is empty. No consistency group " "will be created.") raise exception.InvalidConsistencyGroup(reason=msg) for source_vol in source_vols: kwargs = {} kwargs['availability_zone'] = group.availability_zone kwargs['source_cg'] = source_cg kwargs['consistencygroup'] = group kwargs['source_volume'] = source_vol volume_type_id = source_vol.get('volume_type_id') if volume_type_id: kwargs['volume_type'] = volume_types.get_volume_type( context, volume_type_id) # Since source_cg is passed in, the following call will # create a db entry for the volume, but will not call the # volume manager to create a real volume in the backend yet. # If error happens, taskflow will handle rollback of quota # and removal of volume entry in the db. try: self.volume_api.create(context, source_vol['size'], None, None, **kwargs) except exception.CinderException: with excutils.save_and_reraise_exception(): LOG.error(_LE("Error occurred when creating cloned " "volume in the process of creating " "consistency group %(group)s from " "source CG %(source_cg)s."), {'group': group.id, 'source_cg': source_cg.id}) except Exception: with excutils.save_and_reraise_exception(): try: group.destroy() finally: LOG.error(_LE("Error occurred when creating consistency " "group %(group)s from source CG " "%(source_cg)s."), {'group': group.id, 'source_cg': source_cg.id}) volumes = self.db.volume_get_all_by_group(context, group.id) for vol in volumes: # Update the host field for the volume. self.db.volume_update(context, vol['id'], {'host': group.host}) self.volume_rpcapi.create_consistencygroup_from_src(context, group, None, source_cg)
def get_volume_type_from_volume(volume): """Provides volume type associated with volume.""" type_id = volume.get('volume_type_id') if type_id is None: return {} ctxt = context.get_admin_context() return volume_types.get_volume_type(ctxt, type_id)
def _get_qos_by_volume_type(self, ctxt, type_id): """Get the properties which can be QoS or file system related.""" update_qos_group_params = {} update_file_system_params = {} volume_type = volume_types.get_volume_type(ctxt, type_id) qos_specs_id = volume_type.get('qos_specs_id') extra_specs = volume_type.get('extra_specs') if qos_specs_id is not None: specs = qos_specs.get_qos_specs(ctxt, qos_specs_id)['specs'] # Override extra specs with specs # Hence specs will prefer QoS than extra specs extra_specs.update(specs) for key, value in extra_specs.items(): if ':' in key: fields = key.split(':') key = fields[1] if key in self.configuration.cb_update_qos_group: update_qos_group_params[key] = value elif key in self.configuration.cb_update_file_system: update_file_system_params[key] = value return update_qos_group_params, update_file_system_params
def migrate_volume(self, ctxt, volume, host): """Migrate directly if source and dest are managed by same storage. We create a new vdisk copy in the desired pool, and add the original vdisk copy to the admin_metadata of the volume to be deleted. The deletion will occur using a periodic task once the new copy is synced. :param ctxt: Context :param volume: A dictionary describing the volume to migrate :param host: A dictionary describing the host to migrate to, where host['host'] is its name, and host['capabilities'] is a dictionary of its reported capabilities. """ LOG.debug('enter: migrate_volume: id=%(id)s, host=%(host)s' % {'id': volume['id'], 'host': host['host']}) false_ret = (False, None) dest_pool = self._helpers.can_migrate_to_host(host, self._state) if dest_pool is None: return false_ret ctxt = context.get_admin_context() if volume['volume_type_id'] is not None: volume_type_id = volume['volume_type_id'] vol_type = volume_types.get_volume_type(ctxt, volume_type_id) else: vol_type = None self._check_volume_copy_ops() new_op = self.add_vdisk_copy(volume['name'], dest_pool, vol_type) self._add_vdisk_copy_op(ctxt, volume, new_op) LOG.debug('leave: migrate_volume: id=%(id)s, host=%(host)s' % {'id': volume['id'], 'host': host['host']}) return (True, None)
def _create_cg_from_cgsnapshot(self, context, group, cgsnapshot): try: snapshots = objects.SnapshotList.get_all_for_cgsnapshot( context, cgsnapshot.id) if not snapshots: msg = _("Cgsnahost is empty. No consistency group " "will be created.") raise exception.InvalidConsistencyGroup(reason=msg) for snapshot in snapshots: kwargs = {} kwargs['availability_zone'] = group.availability_zone kwargs['cgsnapshot'] = cgsnapshot kwargs['consistencygroup'] = group kwargs['snapshot'] = snapshot volume_type_id = snapshot.volume_type_id if volume_type_id: kwargs['volume_type'] = volume_types.get_volume_type( context, volume_type_id) # Since cgsnapshot is passed in, the following call will # create a db entry for the volume, but will not call the # volume manager to create a real volume in the backend yet. # If error happens, taskflow will handle rollback of quota # and removal of volume entry in the db. try: self.volume_api.create(context, snapshot.volume_size, None, None, **kwargs) except exception.CinderException: with excutils.save_and_reraise_exception(): LOG.error(_LE("Error occurred when creating volume " "entry from snapshot in the process of " "creating consistency group %(group)s " "from cgsnapshot %(cgsnap)s."), {'group': group.id, 'cgsnap': cgsnapshot.id}) except Exception: with excutils.save_and_reraise_exception(): try: group.destroy() finally: LOG.error(_LE("Error occurred when creating consistency " "group %(group)s from cgsnapshot " "%(cgsnap)s."), {'group': group.id, 'cgsnap': cgsnapshot.id}) volumes = self.db.volume_get_all_by_group(context, group.id) for vol in volumes: # Update the host field for the volume. self.db.volume_update(context, vol['id'], {'host': group.get('host')}) self.volume_rpcapi.create_consistencygroup_from_src( context, group, cgsnapshot)
def _get_violin_extra_spec(self, volume, spec_key): """Parse data stored in a volume_type's extra_specs table. and extract the value of the specified violin key. Code adapted from examples in cinder/volume/drivers/solidfire.py and cinder/openstack/common/scheduler/filters/capabilities_filter.py. Arguments: volume -- volume object containing volume_type to query spec_key -- the metadata key to search for Returns: spec_value -- string value associated with spec_key """ spec_value = None ctxt = context.get_admin_context() typeid = volume['volume_type_id'] if typeid: volume_type = volume_types.get_volume_type(ctxt, typeid) volume_specs = volume_type.get('extra_specs') for key, val in volume_specs.iteritems(): # Strip the prefix "violin" if ':' in key: scope = key.split(':') key = scope[1] if scope[0] == "violin" and key == spec_key: spec_value = val break return spec_value
def _migrate_volume(self, volume, host, new_type=None): if not self._check_migration_valid(host, volume): return (False, None) type_id = volume['volume_type_id'] volume_type = None if type_id: volume_type = volume_types.get_volume_type(None, type_id) pool_name = host['capabilities']['pool_name'] pools = self.restclient.find_all_pools() pool_info = self.restclient.find_pool_info(pool_name, pools) src_volume_name = huawei_utils.encode_name(volume['id']) dst_volume_name = six.text_type(hash(src_volume_name)) src_id = volume.get('provider_location', None) src_lun_params = self.restclient.get_lun_info(src_id) opts = None qos = None if new_type: # If new type exists, use new type. opts = huawei_utils._get_extra_spec_value( new_type['extra_specs']) opts = smartx.SmartX().get_smartx_specs_opts(opts) if 'LUNType' not in opts: opts['LUNType'] = huawei_utils.find_luntype_in_xml( self.xml_file_path) qos = huawei_utils.get_qos_by_volume_type(new_type) elif volume_type: qos = huawei_utils.get_qos_by_volume_type(volume_type) if not opts: opts = huawei_utils.get_volume_params(volume) opts = smartx.SmartX().get_smartx_specs_opts(opts) lun_info = self._create_lun_with_extra_feature(pool_info, dst_volume_name, src_lun_params, opts) lun_id = lun_info['ID'] if qos: LOG.info(_LI('QoS: %s.'), qos) SmartQos = smartx.SmartQos(self.restclient) SmartQos.create_qos(qos, lun_id) if opts: smartpartition = smartx.SmartPartition(self.restclient) smartpartition.add(opts, lun_id) smartcache = smartx.SmartCache(self.restclient) smartcache.add(opts, lun_id) dst_id = lun_info['ID'] self._wait_volume_ready(dst_id) moved = self._migrate_lun(src_id, dst_id) return moved, {}
def _set_qos_by_volume_type(self, ctxt, type_id): qos = {} volume_type = volume_types.get_volume_type(ctxt, type_id) specs = volume_type.get('extra_specs') for key, value in specs.iteritems(): if key in self.sf_qos_keys: qos[key] = int(value) return qos
def _fake_volume_type(*args, **kwargs): ctxt = context.get_admin_context() type_ref = volume_types.create(ctxt, "qos_extra_specs", {}) qos_ref = qos_specs.create(ctxt, 'qos-specs', {}) qos_specs.associate_qos_with_type(ctxt, qos_ref['id'], type_ref['id']) qos_type = volume_types.get_volume_type(ctxt, type_ref['id']) return qos_type
def _get_ss_type(self, volume): """Get the storage service type for a volume.""" id = volume['volume_type_id'] if not id: return None volume_type = volume_types.get_volume_type(None, id) if not volume_type: return None return volume_type['name']
def show(self, req, id): """Return a single volume type item.""" context = req.environ['cinder.context'] # Not found exception will be handled at the wsgi level vol_type = volume_types.get_volume_type(context, id) req.cache_resource(vol_type, name='types') return self._view_builder.show(req, vol_type)
def get_volume_extra_specs(volume): """Provides extra specs associated with volume.""" ctxt = context.get_admin_context() type_id = volume.get('volume_type_id') specs = None if type_id is not None: volume_type = volume_types.get_volume_type(ctxt, type_id) specs = volume_type.get('extra_specs') return specs
def _get_volume_extra_specs(self, volume): """Get extra specs from a volume.""" extra_specs = {} type_id = volume.get('volume_type_id', None) if type_id is not None: ctxt = context.get_admin_context() volume_type = volume_types.get_volume_type(ctxt, type_id) extra_specs = volume_type.get('extra_specs') return extra_specs
def _get_qos_type(self, volume): """Get the storage service type for a volume.""" type_id = volume["volume_type_id"] if not type_id: return None volume_type = volume_types.get_volume_type(None, type_id) if not volume_type: return None return volume_type["name"]
def create_replica(self, ctxt, volume): conf = self.driver.configuration vol_type = volume['volume_type_id'] vol_type = volume_types.get_volume_type(ctxt, vol_type) dest_pool = conf.storwize_svc_stretched_cluster_partner self.driver.add_vdisk_copy(volume['name'], dest_pool, vol_type) vol_update = {'replication_status': 'copying'} return vol_update
def _update_volume_info_from_volume_type(self, volume_info, volume_type_id): if not volume_type_id: return else: volume_type = volume_types.get_volume_type(context.get_admin_context(), volume_type_id) extra_specs = volume_type.get("extra_specs") self._update_volume_info_from_extra_specs(volume_info, extra_specs) qos_specs = volume_types.get_volume_type_qos_specs(volume_type_id) self._update_volume_info_from_qos_specs(volume_info, qos_specs)
def get_volume_qos(volume): qos = {} ctxt = context.get_admin_context() type_id = volume['volume_type_id'] if type_id is not None: volume_type = volume_types.get_volume_type(ctxt, type_id) qos = get_qos_by_volume_type(volume_type) return qos
def test_volume_type_create_then_destroy_with_non_admin(self): """Ensure volume types can be created and deleted by non-admin user. If a non-admn user is authorized at API, volume type operations should be permitted. """ prev_all_vtypes = volume_types.get_all_types(self.ctxt) self.ctxt = context.RequestContext('fake', 'fake', is_admin=False) # create type_ref = volume_types.create(self.ctxt, self.vol_type1_name, self.vol_type1_specs, description=self.vol_type1_description) new = volume_types.get_volume_type_by_name(self.ctxt, self.vol_type1_name) self.assertEqual(self.vol_type1_description, new['description']) new_all_vtypes = volume_types.get_all_types(self.ctxt) self.assertEqual(len(prev_all_vtypes) + 1, len(new_all_vtypes), 'drive type was not created') # update new_type_name = self.vol_type1_name + '_updated' new_type_desc = self.vol_type1_description + '_updated' volume_types.update(self.ctxt, type_ref.id, new_type_name, new_type_desc) type_ref_updated = volume_types.get_volume_type(self.ctxt, type_ref.id) self.assertEqual(new_type_name, type_ref_updated['name']) self.assertEqual(new_type_desc, type_ref_updated['description']) # destroy volume_types.destroy(self.ctxt, type_ref['id']) new_all_vtypes = volume_types.get_all_types(self.ctxt) self.assertEqual(prev_all_vtypes, new_all_vtypes, 'drive type was not deleted')
def _get_volume_type_extra_spec(self, volume, spec_key): """Parse data stored in a volume_type's extra_specs table. :param volume: volume object containing volume_type to query :param spec_key: the metadata key to search for :returns: string value associated with spec_key """ spec_value = None ctxt = context.get_admin_context() typeid = volume['volume_type_id'] if typeid: volume_type = volume_types.get_volume_type(ctxt, typeid) volume_specs = volume_type.get('extra_specs') for key, val in volume_specs.items(): # Strip the prefix "capabilities" if ':' in key: scope = key.split(':') key = scope[1] if key == spec_key: spec_value = val break return spec_value
def _get_volume_type_extra_spec(self, volume, spec_key): """ Parse data stored in a volume_type's extra_specs table. Code adapted from examples in cinder/volume/drivers/solidfire.py and cinder/openstack/common/scheduler/filters/capabilities_filter.py. Arguments: volume -- volume object containing volume_type to query spec_key -- the metadata key to search for Returns: spec_value -- string value associated with spec_key """ spec_value = None ctxt = context.get_admin_context() typeid = volume['volume_type_id'] if typeid: volume_type = volume_types.get_volume_type(ctxt, typeid) volume_specs = volume_type.get('extra_specs') for key, val in volume_specs.iteritems(): # Havana release altered extra_specs to require a # prefix on all non-host-capability related extra # specs, so that prefix is stripped here before # checking the key. # if ':' in key: scope = key.split(':') key = scope[1] if key == spec_key: spec_value = val break return spec_value
def create(self, req, type_id, body=None): context = req.environ['cinder.context'] authorize(context) self._allow_update(context, type_id) self.assert_valid_body(body, 'extra_specs') self._check_type(context, type_id) specs = body['extra_specs'] self._check_key_names(specs.keys()) utils.validate_dictionary_string_length(specs) db.volume_type_extra_specs_update_or_create(context, type_id, specs) # Get created_at and updated_at for notification volume_type = volume_types.get_volume_type(context, type_id) notifier_info = dict(type_id=type_id, specs=specs, created_at=volume_type['created_at'], updated_at=volume_type['updated_at']) notifier = rpc.get_notifier('volumeTypeExtraSpecs') notifier.info(context, 'volume_type_extra_specs.create', notifier_info) return body
def update(self, req, type_id, id, body): context = req.environ['cinder.context'] context.authorize(policy.UPDATE_POLICY) self._allow_update(context, type_id) self._check_type(context, type_id) if id not in body: expl = _('Request body and URI mismatch') raise webob.exc.HTTPBadRequest(explanation=expl) if 'image_service:store_id' in body: image_service_store_id = body['image_service:store_id'] image_utils.validate_stores_id(context, image_service_store_id) db.volume_type_extra_specs_update_or_create(context, type_id, body) # Get created_at and updated_at for notification volume_type = volume_types.get_volume_type(context, type_id) notifier_info = dict(type_id=type_id, id=id, created_at=volume_type['created_at'], updated_at=volume_type['updated_at']) notifier = rpc.get_notifier('volumeTypeExtraSpecs') notifier.info(context, 'volume_type_extra_specs.update', notifier_info) return body
def _check_type(self, context, type_id): try: volume_types.get_volume_type(context, type_id) except exception.NotFound as ex: raise webob.exc.HTTPNotFound(explanation=ex.msg)
def create(self, req, body): """Instruct Cinder to manage a storage object. Manages an existing backend storage object (e.g. a Linux logical volume or a SAN disk) by creating the Cinder objects required to manage it, and possibly renaming the backend storage object (driver dependent) From an API perspective, this operation behaves very much like a volume creation operation, except that properties such as image, snapshot and volume references don't make sense, because we are taking an existing storage object into Cinder management. Required HTTP Body: { 'volume': { 'host': <Cinder host on which the existing storage resides>, 'ref': <Driver-specific reference to the existing storage object>, } } See the appropriate Cinder drivers' implementations of the manage_volume method to find out the accepted format of 'ref'. This API call will return with an error if any of the above elements are missing from the request, or if the 'host' element refers to a cinder host that is not registered. The volume will later enter the error state if it is discovered that 'ref' is bad. Optional elements to 'volume' are: name A name for the new volume. description A description for the new volume. volume_type ID or name of a volume type to associate with the new Cinder volume. Does not necessarily guarantee that the managed volume will have the properties described in the volume_type. The driver may choose to fail if it identifies that the specified volume_type is not compatible with the backend storage object. metadata Key/value pairs to be associated with the new volume. availability_zone The availability zone to associate with the new volume. bootable If set to True, marks the volume as bootable. """ context = req.environ['cinder.context'] authorize(context) self.assert_valid_body(body, 'volume') volume = body['volume'] # Check that the required keys are present, return an error if they # are not. required_keys = set(['ref', 'host']) missing_keys = list(required_keys - set(volume.keys())) if missing_keys: msg = _("The following elements are required: %s") % \ ', '.join(missing_keys) raise exc.HTTPBadRequest(explanation=msg) LOG.debug('Manage volume request body: %s', body) kwargs = {} req_volume_type = volume.get('volume_type', None) if req_volume_type: try: if not uuidutils.is_uuid_like(req_volume_type): kwargs['volume_type'] = \ volume_types.get_volume_type_by_name( context, req_volume_type) else: kwargs['volume_type'] = volume_types.get_volume_type( context, req_volume_type) except exception.VolumeTypeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) else: kwargs['volume_type'] = {} kwargs['name'] = volume.get('name', None) kwargs['description'] = volume.get('description', None) kwargs['metadata'] = volume.get('metadata', None) kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['bootable'] = volume.get('bootable', False) try: new_volume = self.volume_api.manage_existing( context, volume['host'], volume['ref'], **kwargs) except exception.ServiceNotFound: msg = _("Service not found.") raise exc.HTTPNotFound(explanation=msg) new_volume = dict(new_volume) utils.add_visible_admin_metadata(new_volume) return self._view_builder.detail(req, new_volume)
def _create_group_from_source_group(self, context, group, source_group_id): try: source_group = objects.Group.get_by_id(context, source_group_id) source_vols = objects.VolumeList.get_all_by_generic_group( context, source_group.id) if not source_vols: msg = _("Source Group is empty. No group " "will be created.") raise exception.InvalidGroup(reason=msg) for source_vol in source_vols: kwargs = {} kwargs['availability_zone'] = group.availability_zone kwargs['source_group'] = source_group kwargs['group'] = group kwargs['source_volume'] = source_vol volume_type_id = source_vol.volume_type_id if volume_type_id: kwargs['volume_type'] = volume_types.get_volume_type( context, volume_type_id) # Create group volume_type mapping entries try: db.group_volume_type_mapping_create( context, group.id, volume_type_id) except exception.GroupVolumeTypeMappingExists: # Only need to create one group volume_type mapping # entry for the same combination, skipping. LOG.info( _LI("A mapping entry already exists for group" " %(grp)s and volume type %(vol_type)s. " "Do not need to create again."), { 'grp': group.id, 'vol_type': volume_type_id }) pass # Since source_group is passed in, the following call will # create a db entry for the volume, but will not call the # volume manager to create a real volume in the backend yet. # If error happens, taskflow will handle rollback of quota # and removal of volume entry in the db. try: self.volume_api.create(context, source_vol.size, None, None, **kwargs) except exception.CinderException: with excutils.save_and_reraise_exception(): LOG.error( _LE("Error occurred when creating cloned " "volume in the process of creating " "group %(group)s from " "source group %(source_group)s."), { 'group': group.id, 'source_group': source_group.id }) except Exception: with excutils.save_and_reraise_exception(): try: group.destroy() finally: LOG.error( _LE("Error occurred when creating " "group %(group)s from source group " "%(source_group)s."), { 'group': group.id, 'source_group': source_group.id }) volumes = objects.VolumeList.get_all_by_generic_group( context, group.id) for vol in volumes: # Update the host field for the volume. vol.host = group.host vol.save() self.volume_rpcapi.create_group_from_src(context, group, None, source_group)
def create(self, req, body): """Creates a new volume. :param req: the request :param body: the request body :returns: dict -- the new volume dictionary :raises: HTTPNotFound, HTTPBadRequest """ self.assert_valid_body(body, 'volume') LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] req_version = req.api_version_request # Remove group_id from body if max version is less than 3.13. if req_version.matches(None, "3.12"): # NOTE(xyang): The group_id is from a group created with a # group_type. So with this group_id, we've got a group_type # for this volume. Also if group_id is passed in, that means # we already know which backend is hosting the group and the # volume will be created on the same backend as well. So it # won't go through the scheduler again if a group_id is # passed in. try: body.get('volume', {}).pop('group_id', None) except AttributeError: msg = (_("Invalid body provided for creating volume. " "Request API version: %s.") % req_version) raise exc.HTTPBadRequest(explanation=msg) volume = body['volume'] kwargs = {} self.validate_name_and_description(volume) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in volume: volume['display_name'] = volume.pop('name') # NOTE(thingee): v2 API allows description instead of # display_description if 'description' in volume: volume['display_description'] = volume.pop('description') if 'image_id' in volume: volume['imageRef'] = volume.pop('image_id') req_volume_type = volume.get('volume_type', None) if req_volume_type: # Not found exception will be handled at the wsgi level if not uuidutils.is_uuid_like(req_volume_type): kwargs['volume_type'] = (volume_types.get_volume_type_by_name( context, req_volume_type)) else: kwargs['volume_type'] = volume_types.get_volume_type( context, req_volume_type) kwargs['metadata'] = volume.get('metadata', None) snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: # Not found exception will be handled at the wsgi level kwargs['snapshot'] = self.volume_api.get_snapshot( context, snapshot_id) else: kwargs['snapshot'] = None source_volid = volume.get('source_volid') if source_volid is not None: # Not found exception will be handled at the wsgi level kwargs['source_volume'] = (self.volume_api.get_volume( context, source_volid)) else: kwargs['source_volume'] = None source_replica = volume.get('source_replica') if source_replica is not None: # Not found exception will be handled at the wsgi level src_vol = self.volume_api.get_volume(context, source_replica) if src_vol['replication_status'] == 'disabled': explanation = _('source volume id:%s is not' ' replicated') % source_replica raise exc.HTTPBadRequest(explanation=explanation) kwargs['source_replica'] = src_vol else: kwargs['source_replica'] = None consistencygroup_id = volume.get('consistencygroup_id') if consistencygroup_id is not None: # Not found exception will be handled at the wsgi level kwargs['consistencygroup'] = (self.consistencygroup_api.get( context, consistencygroup_id)) else: kwargs['consistencygroup'] = None # Get group_id if volume is in a group. group_id = volume.get('group_id') if group_id is not None: try: kwargs['group'] = self.group_api.get(context, group_id) except exception.GroupNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) size = volume.get('size', None) if size is None and kwargs['snapshot'] is not None: size = kwargs['snapshot']['volume_size'] elif size is None and kwargs['source_volume'] is not None: size = kwargs['source_volume']['size'] elif size is None and kwargs['source_replica'] is not None: size = kwargs['source_replica']['size'] LOG.info(_LI("Create volume of %s GB"), size) if self.ext_mgr.is_loaded('os-image-create'): image_ref = volume.get('imageRef') if image_ref is not None: image_uuid = self._image_uuid_from_ref(image_ref, context) kwargs['image_id'] = image_uuid kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['scheduler_hints'] = volume.get('scheduler_hints', None) multiattach = volume.get('multiattach', False) kwargs['multiattach'] = multiattach new_volume = self.volume_api.create(context, size, volume.get('display_name'), volume.get('display_description'), **kwargs) retval = self._view_builder.detail(req, new_volume) return retval
def _update(self, req, id, body): # Update description for a given volume type. context = req.environ['cinder.context'] authorize(context) self.assert_valid_body(body, 'volume_type') vol_type = body['volume_type'] description = vol_type.get('description') name = vol_type.get('name') is_public = vol_type.get('is_public') # Name and description can not be both None. # If name specified, name can not be empty. if name and len(name.strip()) == 0: msg = _("Volume type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) if name is None and description is None and is_public is None: msg = _("Specify volume type name, description, is_public or " "a combination thereof.") raise webob.exc.HTTPBadRequest(explanation=msg) if is_public is not None and not strutils.is_valid_boolstr(is_public): msg = _("Invalid value '%s' for is_public. Accepted values: " "True or False.") % is_public raise webob.exc.HTTPBadRequest(explanation=msg) if name: utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: volume_types.update(context, id, name, description, is_public=is_public) # Get the updated vol_type = volume_types.get_volume_type(context, id) req.cache_resource(vol_type, name='types') self._notify_volume_type_info( context, 'volume_type.update', vol_type) except exception.VolumeTypeNotFound as err: self._notify_volume_type_error( context, 'volume_type.update', err, id=id) # Not found exception will be handled at the wsgi level raise except exception.VolumeTypeExists as err: self._notify_volume_type_error( context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.VolumeTypeUpdateFailed as err: self._notify_volume_type_error( context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPInternalServerError( explanation=six.text_type(err)) return self._view_builder.show(req, vol_type)
def _create_cg_from_cgsnapshot(self, context, group, cgsnapshot): try: snapshots = objects.SnapshotList.get_all_for_cgsnapshot( context, cgsnapshot['id']) if not snapshots: msg = _("Cgsnahost is empty. No consistency group " "will be created.") raise exception.InvalidConsistencyGroup(reason=msg) for snapshot in snapshots: kwargs = {} kwargs['availability_zone'] = group.get('availability_zone') kwargs['cgsnapshot'] = cgsnapshot kwargs['consistencygroup'] = group kwargs['snapshot'] = snapshot volume_type_id = snapshot.get('volume_type_id') if volume_type_id: kwargs['volume_type'] = volume_types.get_volume_type( context, volume_type_id) # Since cgsnapshot is passed in, the following call will # create a db entry for the volume, but will not call the # volume manager to create a real volume in the backend yet. # If error happens, taskflow will handle rollback of quota # and removal of volume entry in the db. try: self.volume_api.create(context, snapshot['volume_size'], None, None, **kwargs) except exception.CinderException: with excutils.save_and_reraise_exception(): LOG.error( _LE("Error occurred when creating volume " "entry from snapshot in the process of " "creating consistency group %(group)s " "from cgsnapshot %(cgsnap)s."), { 'group': group['id'], 'cgsnap': cgsnapshot['id'] }) except Exception: with excutils.save_and_reraise_exception(): try: self.db.consistencygroup_destroy(context.elevated(), group['id']) finally: LOG.error( _LE("Error occurred when creating consistency " "group %(group)s from cgsnapshot " "%(cgsnap)s."), { 'group': group['id'], 'cgsnap': cgsnapshot['id'] }) volumes = self.db.volume_get_all_by_group(context, group['id']) for vol in volumes: # Update the host field for the volume. self.db.volume_update(context, vol['id'], {'host': group.get('host')}) self.volume_rpcapi.create_consistencygroup_from_src( context, group, group['host'], cgsnapshot)
def retype(self, context, volume, new_type, migration_policy=None): """Attempt to modify the type associated with an existing volume.""" if volume['status'] not in ['available', 'in-use']: msg = _('Unable to update type due to incorrect status ' 'on volume: %s') % volume['id'] LOG.error(msg) raise exception.InvalidVolume(reason=msg) if volume['migration_status'] is not None: msg = (_("Volume %s is already part of an active migration.") % volume['id']) LOG.error(msg) raise exception.InvalidVolume(reason=msg) if migration_policy and migration_policy not in ['on-demand', 'never']: msg = _('migration_policy must be \'on-demand\' or \'never\', ' 'passed: %s') % new_type LOG.error(msg) raise exception.InvalidInput(reason=msg) # Support specifying volume type by ID or name try: if uuidutils.is_uuid_like(new_type): vol_type = volume_types.get_volume_type(context, new_type) else: vol_type = volume_types.get_volume_type_by_name( context, new_type) except exception.InvalidVolumeType: msg = _('Invalid volume_type passed: %s') % new_type LOG.error(msg) raise exception.InvalidInput(reason=msg) vol_type_id = vol_type['id'] vol_type_qos_id = vol_type['qos_specs_id'] old_vol_type = None old_vol_type_id = volume['volume_type_id'] old_vol_type_qos_id = None # Error if the original and new type are the same if volume['volume_type_id'] == vol_type_id: msg = (_('New volume_type same as original: %s') % new_type) LOG.error(msg) raise exception.InvalidInput(reason=msg) if volume['volume_type_id']: old_vol_type = volume_types.get_volume_type( context, old_vol_type_id) old_vol_type_qos_id = old_vol_type['qos_specs_id'] # We don't support changing encryption requirements yet old_enc = volume_types.get_volume_type_encryption( context, old_vol_type_id) new_enc = volume_types.get_volume_type_encryption(context, vol_type_id) if old_enc != new_enc: msg = _('Retype cannot change encryption requirements') raise exception.InvalidInput(reason=msg) # We don't support changing QoS at the front-end yet for in-use volumes # TODO(avishay): Call Nova to change QoS setting (libvirt has support # - virDomainSetBlockIoTune() - Nova does not have support yet). if (volume['status'] != 'available' and old_vol_type_qos_id != vol_type_qos_id): for qos_id in [old_vol_type_qos_id, vol_type_qos_id]: if qos_id: specs = qos_specs.get_qos_specs(context.elevated(), qos_id) if specs['qos_specs']['consumer'] != 'back-end': msg = _('Retype cannot change front-end qos specs for ' 'in-use volumes') raise exception.InvalidInput(reason=msg) # We're checking here in so that we can report any quota issues as # early as possible, but won't commit until we change the type. We # pass the reservations onward in case we need to roll back. reservations = quota_utils.get_volume_type_reservation( context, volume, vol_type_id) self.update(context, volume, {'status': 'retyping'}) request_spec = { 'volume_properties': volume, 'volume_id': volume['id'], 'volume_type': vol_type, 'migration_policy': migration_policy, 'quota_reservations': reservations } self.scheduler_rpcapi.retype(context, CONF.volume_topic, volume['id'], request_spec=request_spec, filter_properties={})
def create(self, req, body): """Creates a new volume.""" if not self.is_valid_body(body, 'volume'): raise exc.HTTPBadRequest() LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] volume = body['volume'] kwargs = {} # NOTE(thingee): v2 API allows name instead of display_name if volume.get('name'): volume['display_name'] = volume.get('name') del volume['name'] # NOTE(thingee): v2 API allows description instead of description if volume.get('description'): volume['display_description'] = volume.get('description') del volume['description'] req_volume_type = volume.get('volume_type', None) if req_volume_type: try: kwargs['volume_type'] = volume_types.get_volume_type( context, req_volume_type) except exception.VolumeTypeNotFound: explanation = 'Volume type not found.' raise exc.HTTPNotFound(explanation=explanation) kwargs['metadata'] = volume.get('metadata', None) snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: kwargs['snapshot'] = self.volume_api.get_snapshot( context, snapshot_id) else: kwargs['snapshot'] = None source_volid = volume.get('source_volid') if source_volid is not None: kwargs['source_volume'] = self.volume_api.get_volume( context, source_volid) else: kwargs['source_volume'] = None size = volume.get('size', None) if size is None and kwargs['snapshot'] is not None: size = kwargs['snapshot']['volume_size'] elif size is None and kwargs['source_volume'] is not None: size = kwargs['source_volume']['size'] LOG.audit(_("Create volume of %s GB"), size, context=context) image_href = None image_uuid = None if self.ext_mgr.is_loaded('os-image-create'): image_href = volume.get('imageRef') if image_href: image_uuid = self._image_uuid_from_href(image_href) kwargs['image_id'] = image_uuid kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['scheduler_hints'] = volume.get('scheduler_hints', None) new_volume = self.volume_api.create(context, size, volume.get('display_name'), volume.get('display_description'), **kwargs) # TODO(vish): Instance should be None at db layer instead of # trying to lazy load, but for now we turn it into # a dict to avoid an error. retval = self._view_builder.summary(req, dict(new_volume.iteritems())) return retval
def _get_volume_type(volume): if volume.volume_type: return volume.volume_type if volume.volume_type_id: return volume_types.get_volume_type(None, volume.volume_type_id)
def _update(self, req, id, body): # Update description for a given volume type. context = req.environ['cinder.context'] authorize(context) if not self.is_valid_body(body, 'volume_type'): raise webob.exc.HTTPBadRequest() vol_type = body['volume_type'] description = vol_type.get('description') name = vol_type.get('name') # Name and description can not be both None. # If name specified, name can not be empty. if name and len(name.strip()) == 0: msg = _("Volume type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) if name is None and description is None: msg = _("Specify either volume type name and/or description.") raise webob.exc.HTTPBadRequest(explanation=msg) if name: utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: volume_types.update(context, id, name, description) # Get the updated vol_type = volume_types.get_volume_type(context, id) req.cache_resource(vol_type, name='types') self._notify_volume_type_info(context, 'volume_type.update', vol_type) except exception.VolumeTypeNotFound as err: self._notify_volume_type_error(context, 'volume_type.update', err, id=id) raise webob.exc.HTTPNotFound(explanation=six.text_type(err)) except exception.VolumeTypeExists as err: self._notify_volume_type_error(context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.VolumeTypeUpdateFailed as err: self._notify_volume_type_error(context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPInternalServerError( explanation=six.text_type(err)) return self._view_builder.show(req, vol_type)
def _clone_image_2_2(self, context, volume, image_location, image_meta, image_service): # We're not going to fast image clone if the feature is not enabled # and/or we can't reach the image being requested if (not self.image_cache or not self._image_accessible(context, volume, image_meta)): return None, False # Check to make sure we're working with a valid volume type try: found = volume_types.get_volume_type(context, self.image_type) except (exception.VolumeTypeNotFound, exception.InvalidVolumeType): found = None if not found: msg = "Invalid volume type: %s" LOG.error(msg, self.image_type) raise ValueError( _("Option datera_image_cache_volume_type_id must" " be set to a valid volume_type id")) # Check image format fmt = image_meta.get('disk_format', '') if fmt.lower() != 'raw': LOG.debug( "Image format is not RAW, image requires conversion " "before clone. Image format: [%s]", fmt) return None, False LOG.debug("Starting fast image clone") # TODO(_alastor_): determine if Datera is already an image backend # for this request and direct clone instead of caching # Dummy volume, untracked by Cinder src_vol = { 'id': image_meta['id'], 'volume_type_id': self.image_type, 'size': volume['size'], 'project_id': volume['project_id'] } # Determine if we have a cached version of the image cached = self._vol_exists_2_2(src_vol) if cached: tenant = self.get_tenant(src_vol['project_id']) ai = self.cvol_to_ai(src_vol, tenant=tenant) metadata = ai.metadata.get(tenant=tenant) # Check to see if the master image has changed since we created # The cached version ts = self._get_vol_timestamp_2_2(src_vol) mts = time.mktime(image_meta['updated_at'].timetuple()) LOG.debug("Original image timestamp: %s, cache timestamp %s", mts, ts) # If the image is created by Glance, we'll trust that even if the # timestamps don't match up, the data is ok to clone as it's not # managed by this driver if metadata.get('type') == 'image': LOG.debug("Found Glance volume-backed image for %s", src_vol['id']) # If the master image time is greater than the volume creation # time, we invalidate the cache and delete the volume. The # exception is if the cached volume was created by Glance. We # NEVER want to delete this volume. It's annotated with # 'type': 'image' in the metadata, so we'll check for that elif mts > ts and metadata.get('type') != 'image': LOG.debug("Cache is older than original image, deleting cache") cached = False self._delete_volume_2_2(src_vol) # If we don't have the image, we'll cache it if not cached: LOG.debug("No image cache found for: %s, caching image", image_meta['id']) self._cache_vol_2_2(context, src_vol, image_meta, image_service) # Now perform the clone of the found image or newly cached image self._create_cloned_volume_2_2(volume, src_vol) # Force volume resize vol_size = volume['size'] volume['size'] = 0 self._extend_volume_2_2(volume, vol_size) volume['size'] = vol_size # Determine if we need to retype the newly created volume vtype_id = volume.get('volume_type_id') if vtype_id and self.image_type and vtype_id != self.image_type: vtype = volume_types.get_volume_type(context, vtype_id) LOG.debug("Retyping newly cloned volume from type: %s to type: %s", self.image_type, vtype_id) diff, discard = volume_types.volume_types_diff( context, self.image_type, vtype_id) host = {'capabilities': {'vendor_name': self.backend_name}} self._retype_2_2(context, volume, vtype, diff, host) return None, True
def create(self, req, body): """Creates a new volume.""" if not self.is_valid_body(body, 'volume'): raise exc.HTTPUnprocessableEntity() LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] volume = body['volume'] kwargs = {} req_volume_type = volume.get('volume_type', None) if req_volume_type: try: if not uuidutils.is_uuid_like(req_volume_type): kwargs['volume_type'] = \ volume_types.get_volume_type_by_name( context, req_volume_type) else: kwargs['volume_type'] = volume_types.get_volume_type( context, req_volume_type) except exception.VolumeTypeNotFound: explanation = 'Volume type not found.' raise exc.HTTPNotFound(explanation=explanation) kwargs['metadata'] = volume.get('metadata', None) snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: kwargs['snapshot'] = self.volume_api.get_snapshot(context, snapshot_id) else: kwargs['snapshot'] = None source_volid = volume.get('source_volid') if source_volid is not None: kwargs['source_volume'] = self.volume_api.get_volume(context, source_volid) else: kwargs['source_volume'] = None size = volume.get('size', None) if size is None and kwargs['snapshot'] is not None: size = kwargs['snapshot']['volume_size'] elif size is None and kwargs['source_volume'] is not None: size = kwargs['source_volume']['size'] LOG.audit(_("Create volume of %s GB"), size, context=context) image_href = None image_uuid = None if self.ext_mgr.is_loaded('os-image-create'): # NOTE(jdg): misleading name "imageRef" as it's an image-id image_href = volume.get('imageRef') if image_href: image_uuid = self._image_uuid_from_href(image_href) kwargs['image_id'] = image_uuid kwargs['availability_zone'] = volume.get('availability_zone', None) new_volume = self.volume_api.create(context, size, volume.get('display_name'), volume.get('display_description'), **kwargs) # TODO(vish): Instance should be None at db layer instead of # trying to lazy load, but for now we turn it into # a dict to avoid an error. new_volume = dict(new_volume.iteritems()) self._add_visible_admin_metadata(context, new_volume) retval = _translate_volume_detail_view(context, new_volume, image_uuid) return {'volume': retval}
def _create_cg_from_source_cg(self, context, group, source_cgid): try: source_cg = objects.ConsistencyGroup.get_by_id( context, source_cgid) source_vols = self.db.volume_get_all_by_group( context, source_cg.id) if not source_vols: msg = _("Source CG is empty. No consistency group " "will be created.") raise exception.InvalidConsistencyGroup(reason=msg) for source_vol in source_vols: kwargs = {} kwargs['availability_zone'] = group.availability_zone kwargs['source_cg'] = source_cg kwargs['consistencygroup'] = group kwargs['source_volume'] = source_vol volume_type_id = source_vol.get('volume_type_id') if volume_type_id: kwargs['volume_type'] = volume_types.get_volume_type( context, volume_type_id) # Since source_cg is passed in, the following call will # create a db entry for the volume, but will not call the # volume manager to create a real volume in the backend yet. # If error happens, taskflow will handle rollback of quota # and removal of volume entry in the db. try: self.volume_api.create(context, source_vol['size'], None, None, **kwargs) except exception.CinderException: with excutils.save_and_reraise_exception(): LOG.error( _LE("Error occurred when creating cloned " "volume in the process of creating " "consistency group %(group)s from " "source CG %(source_cg)s."), { 'group': group.id, 'source_cg': source_cg.id }) except Exception: with excutils.save_and_reraise_exception(): try: group.destroy() finally: LOG.error( _LE("Error occurred when creating consistency " "group %(group)s from source CG " "%(source_cg)s."), { 'group': group.id, 'source_cg': source_cg.id }) volumes = self.db.volume_get_all_by_group(context, group.id) for vol in volumes: # Update the host field for the volume. self.db.volume_update(context, vol['id'], {'host': group.host}) self.volume_rpcapi.create_consistencygroup_from_src( context, group, None, source_cg)
def create(self, req, body): """Creates a new volume.""" self.assert_valid_body(body, 'volume') LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] volume = body['volume'] kwargs = {} # NOTE(thingee): v2 API allows name instead of display_name if volume.get('name'): volume['display_name'] = volume.get('name') del volume['name'] # NOTE(thingee): v2 API allows description instead of # display_description if volume.get('description'): volume['display_description'] = volume.get('description') del volume['description'] if 'image_id' in volume: volume['imageRef'] = volume.get('image_id') del volume['image_id'] req_volume_type = volume.get('volume_type', None) if req_volume_type: try: if not uuidutils.is_uuid_like(req_volume_type): kwargs['volume_type'] = \ volume_types.get_volume_type_by_name( context, req_volume_type) else: kwargs['volume_type'] = volume_types.get_volume_type( context, req_volume_type) except exception.VolumeTypeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) kwargs['metadata'] = volume.get('metadata', None) snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: try: kwargs['snapshot'] = self.volume_api.get_snapshot(context, snapshot_id) except exception.SnapshotNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) else: kwargs['snapshot'] = None source_volid = volume.get('source_volid') if source_volid is not None: try: kwargs['source_volume'] = \ self.volume_api.get_volume(context, source_volid) except exception.VolumeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) else: kwargs['source_volume'] = None source_replica = volume.get('source_replica') if source_replica is not None: try: src_vol = self.volume_api.get_volume(context, source_replica) if src_vol['replication_status'] == 'disabled': explanation = _('source volume id:%s is not' ' replicated') % source_volid raise exc.HTTPBadRequest(explanation=explanation) kwargs['source_replica'] = src_vol except exception.VolumeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) else: kwargs['source_replica'] = None consistencygroup_id = volume.get('consistencygroup_id') if consistencygroup_id is not None: try: kwargs['consistencygroup'] = \ self.consistencygroup_api.get(context, consistencygroup_id) except exception.ConsistencyGroupNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) else: kwargs['consistencygroup'] = None size = volume.get('size', None) if size is None and kwargs['snapshot'] is not None: size = kwargs['snapshot']['volume_size'] elif size is None and kwargs['source_volume'] is not None: size = kwargs['source_volume']['size'] elif size is None and kwargs['source_replica'] is not None: size = kwargs['source_replica']['size'] LOG.info(_LI("Create volume of %s GB"), size, context=context) if self.ext_mgr.is_loaded('os-image-create'): image_ref = volume.get('imageRef') if image_ref is not None: image_uuid = self._image_uuid_from_ref(image_ref, context) kwargs['image_id'] = image_uuid kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['scheduler_hints'] = volume.get('scheduler_hints', None) multiattach = volume.get('multiattach', False) kwargs['multiattach'] = multiattach new_volume = self.volume_api.create(context, size, volume.get('display_name'), volume.get('display_description'), **kwargs) # TODO(vish): Instance should be None at db layer instead of # trying to lazy load, but for now we turn it into # a dict to avoid an error. new_volume = dict(new_volume) retval = self._view_builder.detail(req, new_volume) return retval
def _check_type(self, context, type_id): # Not found exception will be handled at the wsgi level volume_types.get_volume_type(context, type_id)
def create(self, req, body): """Creates a new volume.""" self.assert_valid_body(body, 'volume') LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] volume = body['volume'] kwargs = {} self.validate_name_and_description(volume) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in volume: volume['display_name'] = volume.pop('name') # NOTE(thingee): v2 API allows description instead of # display_description if 'description' in volume: volume['display_description'] = volume.pop('description') if 'image_id' in volume: volume['imageRef'] = volume.pop('image_id') req_volume_type = volume.get('volume_type', None) if req_volume_type: # Not found exception will be handled at the wsgi level if not uuidutils.is_uuid_like(req_volume_type): kwargs['volume_type'] = \ volume_types.get_volume_type_by_name( context, req_volume_type) else: kwargs['volume_type'] = volume_types.get_volume_type( context, req_volume_type) kwargs['metadata'] = volume.get('metadata', None) snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: # Not found exception will be handled at the wsgi level kwargs['snapshot'] = self.volume_api.get_snapshot( context, snapshot_id) else: kwargs['snapshot'] = None source_volid = volume.get('source_volid') if source_volid is not None: # Not found exception will be handled at the wsgi level kwargs['source_volume'] = \ self.volume_api.get_volume(context, source_volid) else: kwargs['source_volume'] = None source_replica = volume.get('source_replica') if source_replica is not None: # Not found exception will be handled at the wsgi level src_vol = self.volume_api.get_volume(context, source_replica) if src_vol['replication_status'] == 'disabled': explanation = _('source volume id:%s is not' ' replicated') % source_replica raise exc.HTTPBadRequest(explanation=explanation) kwargs['source_replica'] = src_vol else: kwargs['source_replica'] = None consistencygroup_id = volume.get('consistencygroup_id') if consistencygroup_id is not None: # Not found exception will be handled at the wsgi level kwargs['consistencygroup'] = \ self.consistencygroup_api.get(context, consistencygroup_id) else: kwargs['consistencygroup'] = None size = volume.get('size', None) if size is None and kwargs['snapshot'] is not None: size = kwargs['snapshot']['volume_size'] elif size is None and kwargs['source_volume'] is not None: size = kwargs['source_volume']['size'] elif size is None and kwargs['source_replica'] is not None: size = kwargs['source_replica']['size'] LOG.info(_LI("Create volume of %s GB"), size) if self.ext_mgr.is_loaded('os-image-create'): image_ref = volume.get('imageRef') if image_ref is not None: image_uuid = self._image_uuid_from_ref(image_ref, context) kwargs['image_id'] = image_uuid kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['scheduler_hints'] = volume.get('scheduler_hints', None) multiattach = volume.get('multiattach', False) kwargs['multiattach'] = multiattach new_volume = self.volume_api.create(context, size, volume.get('display_name'), volume.get('display_description'), **kwargs) retval = self._view_builder.detail(req, new_volume) return retval
def create(self, req, body): """Creates a new volume.""" if not self.is_valid_body(body, 'volume'): raise exc.HTTPUnprocessableEntity() LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] volume = body['volume'] kwargs = {} req_volume_type = volume.get('volume_type', None) if req_volume_type: try: if not uuidutils.is_uuid_like(req_volume_type): kwargs['volume_type'] = \ volume_types.get_volume_type_by_name( context, req_volume_type) else: kwargs['volume_type'] = volume_types.get_volume_type( context, req_volume_type) except exception.VolumeTypeNotFound: explanation = 'Volume type not found.' raise exc.HTTPNotFound(explanation=explanation) kwargs['metadata'] = volume.get('metadata', None) snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: try: kwargs['snapshot'] = self.volume_api.get_snapshot( context, snapshot_id) except exception.NotFound: explanation = _('snapshot id:%s not found') % snapshot_id raise exc.HTTPNotFound(explanation=explanation) else: kwargs['snapshot'] = None source_volid = volume.get('source_volid') if source_volid is not None: try: kwargs['source_volume'] = \ self.volume_api.get_volume(context, source_volid) except exception.NotFound: explanation = _('source vol id:%s not found') % source_volid raise exc.HTTPNotFound(explanation=explanation) else: kwargs['source_volume'] = None size = volume.get('size', None) if size is None and kwargs['snapshot'] is not None: size = kwargs['snapshot']['volume_size'] elif size is None and kwargs['source_volume'] is not None: size = kwargs['source_volume']['size'] LOG.info(_LI("Create volume of %s GB"), size, context=context) multiattach = volume.get('multiattach', False) kwargs['multiattach'] = multiattach image_href = None image_uuid = None if self.ext_mgr.is_loaded('os-image-create'): # NOTE(jdg): misleading name "imageRef" as it's an image-id image_href = volume.get('imageRef') if image_href is not None: image_uuid = self._image_uuid_from_href(image_href) kwargs['image_id'] = image_uuid kwargs['availability_zone'] = volume.get('availability_zone', None) new_volume = self.volume_api.create(context, size, volume.get('display_name'), volume.get('display_description'), **kwargs) retval = _translate_volume_detail_view(context, new_volume, image_uuid) return {'volume': retval}
def _get_volume_type(self, type_id): ctxt = context.get_admin_context() return volume_types.get_volume_type(ctxt, type_id)