def test_instorage_retype_from_mirror_to_none_replication(self): # Set replication target self.driver.configuration.set_override('replication_device', [self.rep_target]) self.driver.do_setup(self.ctxt) host = {'host': 'openstack@mcs#openstack'} volume, model_update = self._create_test_volume(self.mm_type) self.assertEqual('enabled', model_update['replication_status']) diff, _equal = volume_types.volume_types_diff( self.ctxt, self.mm_type['id'], self.gm_type['id']) # Change the mirror type self.assertRaises(exception.VolumeDriverException, self.driver.retype, self.ctxt, volume, self.gm_type, diff, host) diff, _equal = volume_types.volume_types_diff( self.ctxt, self.non_replica_type['id'], self.mm_type['id']) # Disable replica retyped, model_update = self.driver.retype( self.ctxt, volume, self.non_replica_type, diff, host) self.assertEqual('disabled', model_update['replication_status']) self._assert_vol_exists( instorage_const.REPLICA_AUX_VOL_PREFIX + volume['name'], False) self.driver.delete_volume(volume) self._assert_vol_exists(volume['name'], False) rel_info = self.driver._assistant.get_relationship_info(volume['name']) self.assertIsNone(rel_info)
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_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 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_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_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 test_instorage_retype_from_none_to_mirror_replication(self): # Set replication target self.driver.configuration.set_override('replication_device', [self.rep_target]) self.driver.do_setup(self.ctxt) host = {'host': 'openstack@mcs#openstack'} diff, _equal = volume_types.volume_types_diff( self.ctxt, self.non_replica_type['id'], self.mm_type['id']) volume, model_update = self._create_test_volume(self.non_replica_type) self.assertIsNone(model_update) # Enable replica retyped, model_update = self.driver.retype( self.ctxt, volume, self.mm_type, diff, host) volume['volume_type_id'] = self.mm_type['id'] self.assertEqual(fields.ReplicationStatus.ENABLED, model_update['replication_status']) self._validate_replic_vol_creation(volume) self.driver.delete_volume(volume)
def test_retype_fail_on_exception(self): """Test that retype fails on exception.""" 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} old_type_ref = volume_types.create(ctxt, 'old', key_specs_old) new_type_ref = volume_types.create(ctxt, '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) self.assertRaises( KeyError, self.driver.retype, ctxt, volume, new_type, diff, host )
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': 'ibm_storage_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.assertEqual('1', volume['easytier'])
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_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 test_retype_fail_on_exception(self): """Test that retype fails on exception.""" self.driver.do_setup(None) # prepare parameters ctxt = context.get_admin_context() host = {"host": "foo", "capabilities": {"location_info": "ibm_storage_fake_1", "extent_size": "1024"}} key_specs_old = {"easytier": False, "warning": 2, "autoexpand": True} old_type_ref = volume_types.create(ctxt, "old", key_specs_old) new_type_ref = volume_types.create(ctxt, "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) self.assertRaises(KeyError, self.driver.retype, ctxt, volume, new_type, diff, host)
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_volume_types_diff(self): # type_ref 1 and 2 have the same extra_specs, while 3 has different keyvals1 = {"key1": "val1", "key2": "val2"} keyvals2 = {"key1": "val0", "key2": "val2"} type_ref1 = volume_types.create(self.ctxt, "type1", keyvals1) type_ref2 = volume_types.create(self.ctxt, "type2", keyvals1) type_ref3 = volume_types.create(self.ctxt, "type3", keyvals2) # Check equality with only extra_specs diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref2['id']) self.assertTrue(same) self.assertEqual(('val1', 'val1'), diff['extra_specs']['key1']) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref3['id']) self.assertFalse(same) self.assertEqual(('val1', 'val0'), diff['extra_specs']['key1']) # qos_ref 1 and 2 have the same specs, while 3 has different qos_keyvals1 = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} qos_keyvals2 = {'k1': 'v0', 'k2': 'v2', 'k3': 'v3'} qos_ref1 = qos_specs.create(self.ctxt, 'qos-specs-1', qos_keyvals1) qos_ref2 = qos_specs.create(self.ctxt, 'qos-specs-2', qos_keyvals1) qos_ref3 = qos_specs.create(self.ctxt, 'qos-specs-3', qos_keyvals2) # Check equality with qos specs too qos_specs.associate_qos_with_type(self.ctxt, qos_ref1['id'], type_ref1['id']) qos_specs.associate_qos_with_type(self.ctxt, qos_ref2['id'], type_ref2['id']) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref2['id']) self.assertTrue(same) self.assertEqual(('val1', 'val1'), diff['extra_specs']['key1']) self.assertEqual(('v1', 'v1'), diff['qos_specs']['k1']) qos_specs.disassociate_qos_specs(self.ctxt, qos_ref2['id'], type_ref2['id']) qos_specs.associate_qos_with_type(self.ctxt, qos_ref3['id'], type_ref2['id']) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref2['id']) self.assertFalse(same) self.assertEqual(('val1', 'val1'), diff['extra_specs']['key1']) self.assertEqual(('v1', 'v0'), diff['qos_specs']['k1']) qos_specs.disassociate_qos_specs(self.ctxt, qos_ref3['id'], type_ref2['id']) qos_specs.associate_qos_with_type(self.ctxt, qos_ref2['id'], type_ref2['id']) # And add encryption for good measure enc_keyvals1 = {'cipher': 'c1', 'key_size': 256, 'provider': 'p1', 'control_location': 'front-end', 'encryption_id': 'uuid1'} enc_keyvals2 = {'cipher': 'c1', 'key_size': 128, 'provider': 'p1', 'control_location': 'front-end', 'encryption_id': 'uuid2'} db.volume_type_encryption_create(self.ctxt, type_ref1['id'], enc_keyvals1) db.volume_type_encryption_create(self.ctxt, type_ref2['id'], enc_keyvals2) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref2['id']) self.assertFalse(same) self.assertEqual(('val1', 'val1'), diff['extra_specs']['key1']) self.assertEqual(('v1', 'v1'), diff['qos_specs']['k1']) self.assertEqual((256, 128), diff['encryption']['key_size']) # Check diff equals type specs when one type is None diff, same = volume_types.volume_types_diff(self.ctxt, None, type_ref1['id']) self.assertFalse(same) self.assertEqual({'key1': (None, 'val1'), 'key2': (None, 'val2')}, diff['extra_specs']) self.assertEqual({'consumer': (None, 'back-end'), 'k1': (None, 'v1'), 'k2': (None, 'v2'), 'k3': (None, 'v3')}, diff['qos_specs']) self.assertEqual({'cipher': (None, 'c1'), 'control_location': (None, 'front-end'), 'deleted': (None, False), 'key_size': (None, 256), 'provider': (None, 'p1'), 'encryption_id': (None, 'uuid1')}, diff['encryption'])
def test_volume_types_diff(self): # type_ref 1 and 2 have the same extra_specs, while 3 has different keyvals1 = {"key1": "val1", "key2": "val2"} keyvals2 = {"key1": "val0", "key2": "val2"} type_ref1 = volume_types.create(self.ctxt, "type1", keyvals1) type_ref2 = volume_types.create(self.ctxt, "type2", keyvals1) type_ref3 = volume_types.create(self.ctxt, "type3", keyvals2) # Check equality with only extra_specs diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1["id"], type_ref2["id"]) self.assertTrue(same) self.assertEqual(diff["extra_specs"]["key1"], ("val1", "val1")) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1["id"], type_ref3["id"]) self.assertFalse(same) self.assertEqual(diff["extra_specs"]["key1"], ("val1", "val0")) # qos_ref 1 and 2 have the same specs, while 3 has different qos_keyvals1 = {"k1": "v1", "k2": "v2", "k3": "v3"} qos_keyvals2 = {"k1": "v0", "k2": "v2", "k3": "v3"} qos_ref1 = qos_specs.create(self.ctxt, "qos-specs-1", qos_keyvals1) qos_ref2 = qos_specs.create(self.ctxt, "qos-specs-2", qos_keyvals1) qos_ref3 = qos_specs.create(self.ctxt, "qos-specs-3", qos_keyvals2) # Check equality with qos specs too qos_specs.associate_qos_with_type(self.ctxt, qos_ref1["id"], type_ref1["id"]) qos_specs.associate_qos_with_type(self.ctxt, qos_ref2["id"], type_ref2["id"]) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1["id"], type_ref2["id"]) self.assertTrue(same) self.assertEqual(diff["extra_specs"]["key1"], ("val1", "val1")) self.assertEqual(diff["qos_specs"]["k1"], ("v1", "v1")) qos_specs.disassociate_qos_specs(self.ctxt, qos_ref2["id"], type_ref2["id"]) qos_specs.associate_qos_with_type(self.ctxt, qos_ref3["id"], type_ref2["id"]) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1["id"], type_ref2["id"]) self.assertFalse(same) self.assertEqual(diff["extra_specs"]["key1"], ("val1", "val1")) self.assertEqual(diff["qos_specs"]["k1"], ("v1", "v0")) qos_specs.disassociate_qos_specs(self.ctxt, qos_ref3["id"], type_ref2["id"]) qos_specs.associate_qos_with_type(self.ctxt, qos_ref2["id"], type_ref2["id"]) # And add encryption for good measure enc_keyvals1 = { "cipher": "c1", "key_size": 256, "provider": "p1", "control_location": "front-end", "encryption_id": "uuid1", } enc_keyvals2 = { "cipher": "c1", "key_size": 128, "provider": "p1", "control_location": "front-end", "encryption_id": "uuid2", } db.volume_type_encryption_create(self.ctxt, type_ref1["id"], enc_keyvals1) db.volume_type_encryption_create(self.ctxt, type_ref2["id"], enc_keyvals2) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1["id"], type_ref2["id"]) self.assertFalse(same) self.assertEqual(diff["extra_specs"]["key1"], ("val1", "val1")) self.assertEqual(diff["qos_specs"]["k1"], ("v1", "v1")) self.assertEqual(diff["encryption"]["key_size"], (256, 128)) # Check diff equals type specs when one type is None diff, same = volume_types.volume_types_diff(self.ctxt, None, type_ref1["id"]) self.assertFalse(same) self.assertEqual(diff["extra_specs"], {"key1": (None, "val1"), "key2": (None, "val2")}) self.assertEqual( diff["qos_specs"], {"consumer": (None, "back-end"), "k1": (None, "v1"), "k2": (None, "v2"), "k3": (None, "v3")}, ) self.assertEqual( diff["encryption"], { "cipher": (None, "c1"), "control_location": (None, "front-end"), "deleted": (None, False), "key_size": (None, 256), "provider": (None, "p1"), "encryption_id": (None, "uuid1"), }, )
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 test_volume_types_diff(self): #type_ref 1 and 2 have the same extra_specs, while 3 has different keyvals1 = {"key1": "val1", "key2": "val2"} keyvals2 = {"key1": "val0", "key2": "val2"} type_ref1 = volume_types.create(self.ctxt, "type1", keyvals1) type_ref2 = volume_types.create(self.ctxt, "type2", keyvals1) type_ref3 = volume_types.create(self.ctxt, "type3", keyvals2) # Check equality with only extra_specs diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref2['id']) self.assertEqual(True, same) self.assertEqual(('val1', 'val1'), diff['extra_specs']['key1']) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref3['id']) self.assertEqual(False, same) self.assertEqual(('val1', 'val0'), diff['extra_specs']['key1']) #qos_ref 1 and 2 have the same specs, while 3 has different qos_keyvals1 = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} qos_keyvals2 = {'k1': 'v0', 'k2': 'v2', 'k3': 'v3'} qos_ref1 = qos_specs.create(self.ctxt, 'qos-specs-1', qos_keyvals1) qos_ref2 = qos_specs.create(self.ctxt, 'qos-specs-2', qos_keyvals1) qos_ref3 = qos_specs.create(self.ctxt, 'qos-specs-3', qos_keyvals2) # Check equality with qos specs too qos_specs.associate_qos_with_type(self.ctxt, qos_ref1['id'], type_ref1['id']) qos_specs.associate_qos_with_type(self.ctxt, qos_ref2['id'], type_ref2['id']) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref2['id']) self.assertEqual(True, same) self.assertEqual(('val1', 'val1'), diff['extra_specs']['key1']) self.assertEqual(('v1', 'v1'), diff['qos_specs']['k1']) qos_specs.disassociate_qos_specs(self.ctxt, qos_ref2['id'], type_ref2['id']) qos_specs.associate_qos_with_type(self.ctxt, qos_ref3['id'], type_ref2['id']) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref2['id']) self.assertEqual(False, same) self.assertEqual(('val1', 'val1'), diff['extra_specs']['key1']) self.assertEqual(('v1', 'v0'), diff['qos_specs']['k1']) qos_specs.disassociate_qos_specs(self.ctxt, qos_ref3['id'], type_ref2['id']) qos_specs.associate_qos_with_type(self.ctxt, qos_ref2['id'], type_ref2['id']) # And add encryption for good measure enc_keyvals1 = {'cipher': 'c1', 'key_size': 256, 'provider': 'p1', 'control_location': 'front-end'} enc_keyvals2 = {'cipher': 'c1', 'key_size': 128, 'provider': 'p1', 'control_location': 'front-end'} db.volume_type_encryption_create(self.ctxt, type_ref1['id'], enc_keyvals1) db.volume_type_encryption_create(self.ctxt, type_ref2['id'], enc_keyvals2) diff, same = volume_types.volume_types_diff(self.ctxt, type_ref1['id'], type_ref2['id']) self.assertEqual(False, same) self.assertEqual(('val1', 'val1'), diff['extra_specs']['key1']) self.assertEqual(('v1', 'v1'), diff['qos_specs']['k1']) self.assertEqual((256, 128), diff['encryption']['key_size'])
def retype(self, ctxt, volume_id, new_type_id, host, migration_policy="never", reservations=None): def _retype_error(context, volume_id, old_reservations, new_reservations, status_update): try: self.db.volume_update(context, volume_id, status_update) finally: QUOTAS.rollback(context, old_reservations) QUOTAS.rollback(context, new_reservations) context = ctxt.elevated() volume_ref = self.db.volume_get(ctxt, volume_id) status_update = {"status": self._get_original_status(volume_ref)} if context.project_id != volume_ref["project_id"]: project_id = volume_ref["project_id"] else: project_id = context.project_id try: # NOTE(flaper87): Verify the driver is enabled # before going forward. The exception will be caught # and the volume status updated. utils.require_driver_initialized(self.driver) except exception.DriverNotInitialized: with excutils.save_and_reraise_exception(): # NOTE(flaper87): Other exceptions in this method don't # set the volume status to error. Should that be done # here? Setting the volume back to it's original status # for now. self.db.volume_update(context, volume_id, status_update) # Get old reservations try: reserve_opts = {"volumes": -1, "gigabytes": -volume_ref["size"]} QUOTAS.add_volume_type_opts(context, reserve_opts, volume_ref.get("volume_type_id")) old_reservations = QUOTAS.reserve(context, project_id=project_id, **reserve_opts) except Exception: old_reservations = None self.db.volume_update(context, volume_id, status_update) LOG.exception(_("Failed to update usages while retyping volume.")) raise exception.CinderException(_("Failed to get old volume type" " quota reservations")) # We already got the new reservations new_reservations = reservations # If volume types have the same contents, no need to do anything retyped = False diff, all_equal = volume_types.volume_types_diff(context, volume_ref.get("volume_type_id"), new_type_id) if all_equal: retyped = True # Call driver to try and change the type if not retyped: try: new_type = volume_types.get_volume_type(context, new_type_id) retyped = self.driver.retype(context, volume_ref, new_type, diff, host) if retyped: LOG.info(_("Volume %s: retyped succesfully"), volume_id) except Exception as ex: retyped = False LOG.error( _("Volume %s: driver error when trying to retype, " "falling back to generic mechanism."), volume_ref["id"], ) LOG.exception(ex) # We could not change the type, so we need to migrate the volume, where # the destination volume will be of the new type if not retyped: if migration_policy == "never": _retype_error(context, volume_id, old_reservations, new_reservations, status_update) msg = _("Retype requires migration but is not allowed.") raise exception.VolumeMigrationFailed(reason=msg) snaps = self.db.snapshot_get_all_for_volume(context, volume_ref["id"]) if snaps: _retype_error(context, volume_id, old_reservations, new_reservations, status_update) msg = _("Volume must not have snapshots.") LOG.error(msg) raise exception.InvalidVolume(reason=msg) self.db.volume_update(context, volume_ref["id"], {"migration_status": "starting"}) try: self.migrate_volume(context, volume_id, host, new_type_id=new_type_id) except Exception: with excutils.save_and_reraise_exception(): _retype_error(context, volume_id, old_reservations, new_reservations, status_update) self.db.volume_update( context, volume_id, {"volume_type_id": new_type_id, "host": host["host"], "status": status_update["status"]} ) if old_reservations: QUOTAS.commit(context, old_reservations, project_id=project_id) if new_reservations: QUOTAS.commit(context, new_reservations, project_id=project_id) self.publish_service_capabilities(context)