Merge "Add secondary account capability to SolidFire"

This commit is contained in:
Jenkins
2015-06-13 15:40:23 +00:00
committed by Gerrit Code Review
2 changed files with 400 additions and 229 deletions

View File

@@ -180,6 +180,8 @@ class SolidFireVolumeTestCase(test.TestCase):
'qos': None, 'qos': None,
'iqn': test_name}]}} 'iqn': test_name}]}}
return result return result
elif method is 'DeleteSnapshot':
return {'result': {}}
else: else:
# Crap, unimplemented API call in Fake # Crap, unimplemented API call in Fake
return None return None
@@ -206,13 +208,13 @@ class SolidFireVolumeTestCase(test.TestCase):
def fake_get_model_info(self, account, vid): def fake_get_model_info(self, account, vid):
return {'fake': 'fake-model'} return {'fake': 'fake-model'}
def test_create_with_qos_type(self): @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
self.stubs.Set(solidfire.SolidFireDriver, @mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
'_issue_api_request', def test_create_volume_with_qos_type(self,
self.fake_issue_api_request) _mock_create_template_account,
self.stubs.Set(solidfire.SolidFireDriver, _mock_issue_api_request):
'_set_qos_by_volume_type', _mock_issue_api_request.return_value = self.mock_stats_data
self.fake_set_qos_by_volume_type) _mock_create_template_account.return_value = 1
testvol = {'project_id': 'testprjid', testvol = {'project_id': 'testprjid',
'name': 'testvol', 'name': 'testvol',
'size': 1, 'size': 1,
@@ -220,52 +222,128 @@ class SolidFireVolumeTestCase(test.TestCase):
'volume_type_id': 'fast', 'volume_type_id': 'fast',
'created_at': timeutils.utcnow()} 'created_at': timeutils.utcnow()}
sfv = solidfire.SolidFireDriver(configuration=self.configuration) fake_sfaccounts = [{'accountID': 5,
model_update = sfv.create_volume(testvol) 'name': 'testprjid',
self.assertIsNotNone(model_update) 'targetSecret': 'shhhh',
'username': 'john-wayne'}]
def test_create_volume(self): test_type = {'name': 'sf-1',
self.stubs.Set(solidfire.SolidFireDriver, 'qos_specs_id': 'fb0576d7-b4b5-4cad-85dc-ca92e6a497d1',
'_issue_api_request', 'deleted': False,
self.fake_issue_api_request) 'created_at': '2014-02-06 04:58:11',
'updated_at': None,
'extra_specs': {},
'deleted_at': None,
'id': 'e730e97b-bc7d-4af3-934a-32e59b218e81'}
test_qos_spec = {'id': 'asdfafdasdf',
'specs': {'minIOPS': '1000',
'maxIOPS': '2000',
'burstIOPS': '3000'}}
def _fake_get_volume_type(ctxt, type_id):
return test_type
def _fake_get_qos_spec(ctxt, spec_id):
return test_qos_spec
def _fake_do_volume_create(account, params):
return params
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
with mock.patch.object(sfv,
'_get_sfaccounts_for_tenant',
return_value=fake_sfaccounts), \
mock.patch.object(sfv,
'_issue_api_request',
side_effect=self.fake_issue_api_request), \
mock.patch.object(sfv,
'_get_account_create_availability',
return_value=fake_sfaccounts[0]), \
mock.patch.object(sfv,
'_do_volume_create',
side_effect=_fake_do_volume_create), \
mock.patch.object(volume_types,
'get_volume_type',
side_effect=_fake_get_volume_type), \
mock.patch.object(qos_specs,
'get_qos_specs',
side_effect=_fake_get_qos_spec):
self.assertEqual({'burstIOPS': 3000,
'minIOPS': 1000,
'maxIOPS': 2000},
sfv.create_volume(testvol)['qos'])
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
@mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
def test_create_volume(self,
_mock_create_template_account,
_mock_issue_api_request):
_mock_issue_api_request.return_value = self.mock_stats_data
_mock_create_template_account.return_value = 1
testvol = {'project_id': 'testprjid', testvol = {'project_id': 'testprjid',
'name': 'testvol', 'name': 'testvol',
'size': 1, 'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66', 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'volume_type_id': None, 'volume_type_id': None,
'created_at': timeutils.utcnow()} 'created_at': timeutils.utcnow()}
fake_sfaccounts = [{'accountID': 5,
'name': 'testprjid',
'targetSecret': 'shhhh',
'username': 'john-wayne'}]
sfv = solidfire.SolidFireDriver(configuration=self.configuration) sfv = solidfire.SolidFireDriver(configuration=self.configuration)
model_update = sfv.create_volume(testvol) with mock.patch.object(sfv,
self.assertIsNotNone(model_update) '_get_sfaccounts_for_tenant',
self.assertIsNone(model_update.get('provider_geometry', None)) return_value=fake_sfaccounts), \
mock.patch.object(sfv,
'_issue_api_request',
side_effect=self.fake_issue_api_request), \
mock.patch.object(sfv,
'_get_account_create_availability',
return_value=fake_sfaccounts[0]):
def test_create_volume_non_512(self): model_update = sfv.create_volume(testvol)
self.stubs.Set(solidfire.SolidFireDriver, self.assertIsNotNone(model_update)
'_issue_api_request', self.assertIsNone(model_update.get('provider_geometry', None))
self.fake_issue_api_request)
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
@mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
def test_create_volume_non_512e(self,
_mock_create_template_account,
_mock_issue_api_request):
_mock_issue_api_request.return_value = self.mock_stats_data
_mock_create_template_account.return_value = 1
testvol = {'project_id': 'testprjid', testvol = {'project_id': 'testprjid',
'name': 'testvol', 'name': 'testvol',
'size': 1, 'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66', 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'volume_type_id': None, 'volume_type_id': None,
'created_at': timeutils.utcnow()} 'created_at': timeutils.utcnow()}
fake_sfaccounts = [{'accountID': 5,
'name': 'testprjid',
'targetSecret': 'shhhh',
'username': 'john-wayne'}]
self.configuration.sf_emulate_512 = False
sfv = solidfire.SolidFireDriver(configuration=self.configuration) sfv = solidfire.SolidFireDriver(configuration=self.configuration)
model_update = sfv.create_volume(testvol) with mock.patch.object(sfv,
self.assertEqual(model_update.get('provider_geometry', None), '_get_sfaccounts_for_tenant',
'4096 4096') return_value=fake_sfaccounts), \
self.configuration.sf_emulate_512 = True mock.patch.object(sfv,
'_issue_api_request',
side_effect=self.fake_issue_api_request), \
mock.patch.object(sfv,
'_get_account_create_availability',
return_value=fake_sfaccounts[0]):
self.configuration.sf_emulate_512 = False
model_update = sfv.create_volume(testvol)
self.configuration.sf_emulate_512 = True
self.assertEqual(model_update.get('provider_geometry', None),
'4096 4096')
def test_create_delete_snapshot(self): def test_create_delete_snapshot(self):
testvol = {'project_id': 'testprjid',
'name': 'testvol',
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'volume_type_id': None,
'created_at': timeutils.utcnow()}
testsnap = {'project_id': 'testprjid', testsnap = {'project_id': 'testprjid',
'name': 'testvol', 'name': 'testvol',
'volume_size': 1, 'volume_size': 1,
@@ -275,21 +353,26 @@ class SolidFireVolumeTestCase(test.TestCase):
'created_at': timeutils.utcnow()} 'created_at': timeutils.utcnow()}
sfv = solidfire.SolidFireDriver(configuration=self.configuration) sfv = solidfire.SolidFireDriver(configuration=self.configuration)
sfv.create_volume(testvol)
sfv.create_snapshot(testsnap) sfv.create_snapshot(testsnap)
with mock.patch.object(solidfire.SolidFireDriver, with mock.patch.object(solidfire.SolidFireDriver,
'_get_sf_snapshots', '_get_sf_snapshots',
return_value=[{'snapshotID': '1', return_value=[{'snapshotID': '1',
'name': 'testvol'}]): 'name': 'UUID-b831c4d1-d1f0-11e1-9b23-0800200c9a66'}]), \
mock.patch.object(sfv,
'_get_sfaccounts_for_tenant',
return_value=[{'accountID': 5,
'name': 'testprjid'}]):
sfv.delete_snapshot(testsnap) sfv.delete_snapshot(testsnap)
def test_create_clone(self): @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
self.stubs.Set(solidfire.SolidFireDriver, @mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
'_issue_api_request', def test_create_clone(self,
self.fake_issue_api_request) _mock_create_template_account,
self.stubs.Set(solidfire.SolidFireDriver, _mock_issue_api_request):
'_get_model_info', _mock_issue_api_request.return_value = self.mock_stats_data
self.fake_get_model_info) _mock_create_template_account.return_value = 1
_fake_get_snaps = [{'snapshotID': 5, 'name': 'testvol'}]
testvol = {'project_id': 'testprjid', testvol = {'project_id': 'testprjid',
'name': 'testvol', 'name': 'testvol',
'size': 1, 'size': 1,
@@ -304,10 +387,19 @@ class SolidFireVolumeTestCase(test.TestCase):
'volume_type_id': None, 'volume_type_id': None,
'created_at': timeutils.utcnow()} 'created_at': timeutils.utcnow()}
with mock.patch.object(solidfire.SolidFireDriver, sfv = solidfire.SolidFireDriver(configuration=self.configuration)
with mock.patch.object(sfv,
'_get_sf_snapshots', '_get_sf_snapshots',
return_value=[]): return_value=_fake_get_snaps), \
sfv = solidfire.SolidFireDriver(configuration=self.configuration) mock.patch.object(sfv,
'_issue_api_request',
side_effect=self.fake_issue_api_request), \
mock.patch.object(sfv,
'_get_sfaccounts_for_tenant',
return_value=[]), \
mock.patch.object(sfv,
'_get_model_info',
return_value={}):
sfv.create_cloned_volume(testvol_b, testvol) sfv.create_cloned_volume(testvol_b, testvol)
def test_initialize_connector_with_blocksizes(self): def test_initialize_connector_with_blocksizes(self):
@@ -331,25 +423,6 @@ class SolidFireVolumeTestCase(test.TestCase):
self.assertEqual('4096', properties['data']['physical_block_size']) self.assertEqual('4096', properties['data']['physical_block_size'])
self.assertEqual('4096', properties['data']['logical_block_size']) self.assertEqual('4096', properties['data']['logical_block_size'])
def test_create_volume_with_qos(self):
preset_qos = {}
preset_qos['qos'] = 'fast'
self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request',
self.fake_issue_api_request)
testvol = {'project_id': 'testprjid',
'name': 'testvol',
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'metadata': [preset_qos],
'volume_type_id': None,
'created_at': timeutils.utcnow()}
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
model_update = sfv.create_volume(testvol)
self.assertIsNotNone(model_update)
def test_create_volume_fails(self): def test_create_volume_fails(self):
# NOTE(JDG) This test just fakes update_cluster_status # NOTE(JDG) This test just fakes update_cluster_status
# this is inentional for this test # this is inentional for this test
@@ -403,18 +476,41 @@ class SolidFireVolumeTestCase(test.TestCase):
account = sfv._get_sfaccount_by_name('some-name') account = sfv._get_sfaccount_by_name('some-name')
self.assertIsNone(account) self.assertIsNone(account)
def test_delete_volume(self): @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
self.stubs.Set(solidfire.SolidFireDriver, @mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
'_issue_api_request', def test_delete_volume(self,
self.fake_issue_api_request) _mock_create_template_account,
_mock_issue_api_request):
_mock_issue_api_request.return_value = self.mock_stats_data
_mock_create_template_account.return_value = 1
testvol = {'project_id': 'testprjid', testvol = {'project_id': 'testprjid',
'name': 'test_volume', 'name': 'test_volume',
'size': 1, 'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66', 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'created_at': timeutils.utcnow()} 'created_at': timeutils.utcnow()}
fake_sfaccounts = [{'accountID': 5,
'name': 'testprjid',
'targetSecret': 'shhhh',
'username': 'john-wayne'}]
def _fake_do_v_create(project_id, params):
return project_id, params
sfv = solidfire.SolidFireDriver(configuration=self.configuration) sfv = solidfire.SolidFireDriver(configuration=self.configuration)
sfv.delete_volume(testvol) with mock.patch.object(sfv,
'_get_sfaccounts_for_tenant',
return_value=fake_sfaccounts), \
mock.patch.object(sfv,
'_issue_api_request',
side_effect=self.fake_issue_api_request), \
mock.patch.object(sfv,
'_get_account_create_availability',
return_value=fake_sfaccounts[0]), \
mock.patch.object(sfv,
'_do_volume_create',
side_effect=_fake_do_v_create):
sfv.delete_volume(testvol)
def test_delete_volume_fails_no_volume(self): def test_delete_volume_fails_no_volume(self):
self.stubs.Set(solidfire.SolidFireDriver, self.stubs.Set(solidfire.SolidFireDriver,
@@ -433,26 +529,6 @@ class SolidFireVolumeTestCase(test.TestCase):
except Exception: except Exception:
pass pass
def test_delete_volume_fails_account_lookup(self):
# NOTE(JDG) This test just fakes update_cluster_status
# this is inentional for this test
self.stubs.Set(solidfire.SolidFireDriver,
'_update_cluster_status',
self.fake_update_cluster_status)
self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request',
self.fake_issue_api_request_fails)
testvol = {'project_id': 'testprjid',
'name': 'no-name',
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'created_at': timeutils.utcnow()}
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
self.assertRaises(exception.SolidFireAccountNotFound,
sfv.delete_volume,
testvol)
def test_get_cluster_info(self): def test_get_cluster_info(self):
self.stubs.Set(solidfire.SolidFireDriver, self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request', '_issue_api_request',
@@ -690,16 +766,13 @@ class SolidFireVolumeTestCase(test.TestCase):
self.assertIsNotNone(model_update) self.assertIsNotNone(model_update)
self.assertIsNone(model_update.get('provider_geometry', None)) self.assertIsNone(model_update.get('provider_geometry', None))
def test_create_volume_for_migration(self): @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
def _fake_do_v_create(self, project_id, params): @mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
return project_id, params def test_create_volume_for_migration(self,
_mock_create_template_account,
self.stubs.Set(solidfire.SolidFireDriver, _mock_issue_api_request):
'_issue_api_request', _mock_issue_api_request.return_value = self.mock_stats_data
self.fake_issue_api_request) _mock_create_template_account.return_value = 1
self.stubs.Set(solidfire.SolidFireDriver,
'_do_volume_create', _fake_do_v_create)
testvol = {'project_id': 'testprjid', testvol = {'project_id': 'testprjid',
'name': 'testvol', 'name': 'testvol',
'size': 1, 'size': 1,
@@ -708,15 +781,35 @@ class SolidFireVolumeTestCase(test.TestCase):
'created_at': timeutils.utcnow(), 'created_at': timeutils.utcnow(),
'migration_status': 'target:' 'migration_status': 'target:'
'a720b3c0-d1f0-11e1-9b23-0800200c9a66'} 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
fake_sfaccounts = [{'accountID': 5,
'name': 'testprjid',
'targetSecret': 'shhhh',
'username': 'john-wayne'}]
def _fake_do_v_create(project_id, params):
return project_id, params
sfv = solidfire.SolidFireDriver(configuration=self.configuration) sfv = solidfire.SolidFireDriver(configuration=self.configuration)
proj_id, sf_vol_object = sfv.create_volume(testvol) with mock.patch.object(sfv,
self.assertEqual('a720b3c0-d1f0-11e1-9b23-0800200c9a66', '_get_sfaccounts_for_tenant',
sf_vol_object['attributes']['uuid']) return_value=fake_sfaccounts), \
self.assertEqual('b830b3c0-d1f0-11e1-9b23-1900200c9a77', mock.patch.object(sfv,
sf_vol_object['attributes']['migration_uuid']) '_issue_api_request',
self.assertEqual('UUID-a720b3c0-d1f0-11e1-9b23-0800200c9a66', side_effect=self.fake_issue_api_request), \
sf_vol_object['name']) mock.patch.object(sfv,
'_get_account_create_availability',
return_value=fake_sfaccounts[0]), \
mock.patch.object(sfv,
'_do_volume_create',
side_effect=_fake_do_v_create):
proj_id, sf_vol_object = sfv.create_volume(testvol)
self.assertEqual('a720b3c0-d1f0-11e1-9b23-0800200c9a66',
sf_vol_object['attributes']['uuid'])
self.assertEqual('b830b3c0-d1f0-11e1-9b23-1900200c9a77',
sf_vol_object['attributes']['migration_uuid'])
self.assertEqual('UUID-a720b3c0-d1f0-11e1-9b23-0800200c9a66',
sf_vol_object['name'])
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request') @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
@mock.patch.object(solidfire.SolidFireDriver, '_get_sfaccount') @mock.patch.object(solidfire.SolidFireDriver, '_get_sfaccount')
@@ -808,9 +901,10 @@ class SolidFireVolumeTestCase(test.TestCase):
self.fake_image_meta, self.fake_image_meta,
'fake')) 'fake'))
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request') @mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
def test_clone_image_authorization(self, _mock_issue_api_request): def test_clone_image_authorization(self, _mock_create_template_account):
_mock_issue_api_request.return_value = self.mock_stats_data _mock_create_template_account.return_value = 1
self.configuration.sf_allow_template_caching = True self.configuration.sf_allow_template_caching = True
sfv = solidfire.SolidFireDriver(configuration=self.configuration) sfv = solidfire.SolidFireDriver(configuration=self.configuration)
@@ -824,33 +918,40 @@ class SolidFireVolumeTestCase(test.TestCase):
'properties': {'virtual_size': 1}, 'properties': {'virtual_size': 1},
'is_public': False, 'is_public': False,
'owner': 'wrong-owner'} 'owner': 'wrong-owner'}
self.assertEqual((None, False), with mock.patch.object(sfv, '_do_clone_volume',
sfv.clone_image(self.ctxt, return_value=('fe', 'fi', 'fo')):
self.mock_volume, self.assertEqual((None, False),
'fake', sfv.clone_image(self.ctxt,
_fake_image_meta, self.mock_volume,
'fake')) 'fake',
_fake_image_meta,
'fake'))
# And is_public False, but the correct owner does work # And is_public False, but the correct owner does work
# expect raise AccountNotFound as that's the next call after _fake_image_meta['owner'] = 'testprjid'
# auth checks self.assertEqual(('fo', True), sfv.clone_image(self.ctxt,
_fake_image_meta['owner'] = 'testprjid' self.mock_volume,
self.assertRaises(exception.SolidFireAccountNotFound, 'fake',
sfv.clone_image, self.ctxt, _fake_image_meta,
self.mock_volume, 'fake', 'fake'))
_fake_image_meta, 'fake')
# And is_public True, even if not the correct owner # And is_public True, even if not the correct owner
_fake_image_meta['is_public'] = True _fake_image_meta['is_public'] = True
_fake_image_meta['owner'] = 'wrong-owner' _fake_image_meta['owner'] = 'wrong-owner'
self.assertRaises(exception.SolidFireAccountNotFound, self.assertEqual(('fo', True), sfv.clone_image(self.ctxt,
sfv.clone_image, self.ctxt, self.mock_volume,
self.mock_volume, 'fake', 'fake',
_fake_image_meta, 'fake') _fake_image_meta,
'fake'))
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request') @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
def test_clone_image_virt_size_not_set(self, _mock_issue_api_request): @mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
def test_clone_image_virt_size_not_set(self,
_mock_create_template_account,
_mock_issue_api_request):
_mock_issue_api_request.return_value = self.mock_stats_data _mock_issue_api_request.return_value = self.mock_stats_data
_mock_create_template_account.return_value = 1
self.configuration.sf_allow_template_caching = True self.configuration.sf_allow_template_caching = True
sfv = solidfire.SolidFireDriver(configuration=self.configuration) sfv = solidfire.SolidFireDriver(configuration=self.configuration)

View File

@@ -112,10 +112,11 @@ class SolidFireDriver(san.SanISCSIDriver):
1.2.2 - Catch VolumeNotFound on accept xfr 1.2.2 - Catch VolumeNotFound on accept xfr
2.0.0 - Move from httplib to requests 2.0.0 - Move from httplib to requests
2.0.1 - Implement SolidFire Snapshots 2.0.1 - Implement SolidFire Snapshots
2.0.2 - Implement secondary account
""" """
VERSION = '2.0.1' VERSION = '2.0.2'
sf_qos_dict = {'slow': {'minIOPS': 100, sf_qos_dict = {'slow': {'minIOPS': 100,
'maxIOPS': 200, 'maxIOPS': 200,
@@ -146,27 +147,29 @@ class SolidFireDriver(san.SanISCSIDriver):
super(SolidFireDriver, self).__init__(*args, **kwargs) super(SolidFireDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(sf_opts) self.configuration.append_config_values(sf_opts)
self._endpoint = self._build_endpoint_info() self._endpoint = self._build_endpoint_info()
self.template_account_id = None
self.max_volumes_per_account = 1990
try: try:
self._update_cluster_status() self._update_cluster_status()
except exception.SolidFireAPIException: except exception.SolidFireAPIException:
pass pass
if self.configuration.sf_allow_template_caching: if self.configuration.sf_allow_template_caching:
account = self.configuration.sf_template_account_name account = self.configuration.sf_template_account_name
self._create_template_account(account) self.template_account_id = self._create_template_account(account)
def _create_template_account(self, account_name): def _create_template_account(self, account_name):
chap_secret = self._generate_random_string(12) id = self._issue_api_request(
params = {'username': account_name, 'GetAccountByName',
'initiatorSecret': chap_secret, {'username': account_name})['result']['account']['accountID']
'targetSecret': chap_secret, if not id:
'attributes': {}} chap_secret = self._generate_random_string(12)
try: params = {'username': account_name,
self._issue_api_request('AddAccount', params) 'initiatorSecret': chap_secret,
except exception.SolidFireAPIException as ex: 'targetSecret': chap_secret,
if 'DuplicateUsername' in ex.msg: 'attributes': {}}
pass id = self._issue_api_request('AddAccount',
else: params)['result']['accountID']
raise return id
def _build_endpoint_info(self, **kwargs): def _build_endpoint_info(self, **kwargs):
endpoint = {} endpoint = {}
@@ -347,64 +350,59 @@ class SolidFireDriver(san.SanISCSIDriver):
def _do_clone_volume(self, src_uuid, def _do_clone_volume(self, src_uuid,
src_project_id, src_project_id,
v_ref): vref):
"""Create a clone of an existing volume. """Create a clone of an existing volume or snapshot. """
Currently snapshots are the same as clones on the SF cluster.
Due to the way the SF cluster works there's no loss in efficiency
or space usage between the two. The only thing different right
now is the restore snapshot functionality which has not been
implemented in the pre-release version of the SolidFire Cluster.
"""
attributes = {} attributes = {}
qos = {} qos = {}
sfaccount = self._get_sfaccount(src_project_id) sf_accounts = self._get_sfaccounts_for_tenant(vref['project_id'])
if src_project_id != v_ref['project_id']: if not sf_accounts:
sfaccount = self._create_sfaccount(v_ref['project_id']) sf_account = self._create_sfaccount(vref['project_id'])
if v_ref.get('size', None):
new_size = v_ref['size']
else: else:
new_size = v_ref['volume_size'] # Check availability for creates
sf_account = self._get_account_create_availability(sf_accounts)
if not sf_account:
# TODO(jdg): We're not doing tertiaries, so fail
msg = _('volumes/account exceeded on both primary '
'and secondary SolidFire accounts')
raise exception.SolidFireDriverException(msg)
params = {'name': 'UUID-%s' % v_ref['id'], params = {'name': 'UUID-%s' % vref['id'],
'newSize': int(new_size * units.Gi), 'newAccountID': sf_account['accountID']}
'newAccountID': sfaccount['accountID']}
# NOTE(jdg): First check the SF snapshots # NOTE(jdg): First check the SF snapshots
# if we don't find a snap by the given name, just move on to check # if we don't find a snap by the given name, just move on to check
# volumes. This may be a running system that was updated from # volumes. This may be a running system that was updated from
# before we did snapshots, so need to check both # before we did snapshots, so need to check both
is_clone = False
snap_name = 'UUID-%s' % src_uuid snap_name = 'UUID-%s' % src_uuid
snaps = self._get_sf_snapshots() snaps = self._get_sf_snapshots()
snap = next((s for s in snaps if s["name"] == snap_name), None) snap = next((s for s in snaps if s["name"] == snap_name), None)
is_clone = False
if snap: if snap:
params['snapshotID'] = int(snap['snapshotID']) params['snapshotID'] = int(snap['snapshotID'])
params['volumeID'] = int(snap['volumeID']) params['volumeID'] = int(snap['volumeID'])
params['newSize'] = int(vref['size'] * units.Gi)
else: else:
sf_vol = self._get_sf_volume( sf_vol = self._get_sf_volume(
src_uuid, {'accountID': sfaccount['accountID']}) src_uuid, {'accountID': sf_account['accountID']})
if sf_vol is None: if sf_vol is None:
raise exception.VolumeNotFound(volume_id=src_uuid) raise exception.VolumeNotFound(volume_id=src_uuid)
params['volumeID'] = int(sf_vol['volumeID']) params['volumeID'] = int(sf_vol['volumeID'])
params['newSize'] = int(vref['size'] * units.Gi)
is_clone = True is_clone = True
data = self._issue_api_request('CloneVolume', params, version='6.0') data = self._issue_api_request('CloneVolume', params, version='6.0')
if (('result' not in data) or ('volumeID' not in data['result'])): if (('result' not in data) or ('volumeID' not in data['result'])):
msg = _("API response: %s") % data msg = _("API response: %s") % data
raise exception.SolidFireAPIException(msg) raise exception.SolidFireAPIException(msg)
sf_volume_id = data['result']['volumeID']
sf_volume_id = data['result']['volumeID']
if (self.configuration.sf_allow_tenant_qos and if (self.configuration.sf_allow_tenant_qos and
v_ref.get('volume_metadata')is not None): vref.get('volume_metadata')is not None):
qos = self._set_qos_presets(v_ref) qos = self._set_qos_presets(vref)
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
type_id = v_ref.get('volume_type_id', None) type_id = vref.get('volume_type_id', None)
if type_id is not None: if type_id is not None:
qos = self._set_qos_by_volume_type(ctxt, type_id) qos = self._set_qos_by_volume_type(ctxt, type_id)
@@ -412,8 +410,8 @@ class SolidFireDriver(san.SanISCSIDriver):
# to set any that were provided # to set any that were provided
params = {'volumeID': sf_volume_id} params = {'volumeID': sf_volume_id}
create_time = v_ref['created_at'].isoformat() create_time = vref['created_at'].isoformat()
attributes = {'uuid': v_ref['id'], attributes = {'uuid': vref['id'],
'is_clone': 'True', 'is_clone': 'True',
'src_uuid': src_uuid, 'src_uuid': src_uuid,
'created_at': create_time} 'created_at': create_time}
@@ -425,7 +423,7 @@ class SolidFireDriver(san.SanISCSIDriver):
params['attributes'] = attributes params['attributes'] = attributes
data = self._issue_api_request('ModifyVolume', params) data = self._issue_api_request('ModifyVolume', params)
model_update = self._get_model_info(sfaccount, sf_volume_id) model_update = self._get_model_info(sf_account, sf_volume_id)
if model_update is None: if model_update is None:
mesg = _('Failed to get model update from clone') mesg = _('Failed to get model update from clone')
raise exception.SolidFireAPIException(mesg) raise exception.SolidFireAPIException(mesg)
@@ -433,27 +431,27 @@ class SolidFireDriver(san.SanISCSIDriver):
# Increment the usage count, just for data collection # Increment the usage count, just for data collection
# We're only doing this for clones, not create_from snaps # We're only doing this for clones, not create_from snaps
if is_clone: if is_clone:
cloned_count = sf_vol['attributes'].get('cloned_count', 0) data = self._update_attributes(sf_vol)
cloned_count += 1 return (data, sf_account, model_update)
attributes = sf_vol['attributes']
attributes['cloned_count'] = cloned_count
params = {'volumeID': int(sf_vol['volumeID'])} def _update_attributes(self, sf_vol):
params['attributes'] = attributes cloned_count = sf_vol['attributes'].get('cloned_count', 0)
data = self._issue_api_request('ModifyVolume', params) cloned_count += 1
return (data, sfaccount, model_update) attributes = sf_vol['attributes']
attributes['cloned_count'] = cloned_count
def _do_volume_create(self, project_id, params): params = {'volumeID': int(sf_vol['volumeID'])}
sfaccount = self._create_sfaccount(project_id) params['attributes'] = attributes
params['accountID'] = sfaccount['accountID'] return self._issue_api_request('ModifyVolume', params)
def _do_volume_create(self, sf_account, params):
data = self._issue_api_request('CreateVolume', params) data = self._issue_api_request('CreateVolume', params)
if (('result' not in data) or ('volumeID' not in data['result'])): if (('result' not in data) or ('volumeID' not in data['result'])):
msg = _("Failed volume create: %s") % data msg = _("Failed volume create: %s") % data
raise exception.SolidFireAPIException(msg) raise exception.SolidFireAPIException(msg)
sf_volume_id = data['result']['volumeID'] sf_volume_id = data['result']['volumeID']
return self._get_model_info(sfaccount, sf_volume_id) return self._get_model_info(sf_account, sf_volume_id)
def _do_snapshot_create(self, params): def _do_snapshot_create(self, params):
data = self._issue_api_request('CreateSnapshot', params, version='6.0') data = self._issue_api_request('CreateSnapshot', params, version='6.0')
@@ -569,17 +567,19 @@ class SolidFireDriver(san.SanISCSIDriver):
attributes['image_info']['image_created_at'] =\ attributes['image_info']['image_created_at'] =\
image_meta['created_at'].isoformat() image_meta['created_at'].isoformat()
attributes['image_info']['image_id'] = image_meta['id'] attributes['image_info']['image_id'] = image_meta['id']
params = {'name': 'OpenStackIMG-%s' % image_id, params = {'name': 'OpenStackIMG-%s' % image_id,
'accountID': None, 'accountID': self.template_account_id,
'sliceCount': 1, 'sliceCount': 1,
'totalSize': int(min_sz_in_bytes), 'totalSize': int(min_sz_in_bytes),
'enable512e': self.configuration.sf_emulate_512, 'enable512e': self.configuration.sf_emulate_512,
'attributes': attributes, 'attributes': attributes,
'qos': {}} 'qos': {}}
account = self.configuration.sf_template_account_name sf_account = self._issue_api_request(
template_vol = self._do_volume_create(account, params) 'GetAccountByID',
{'accountID': self.template_account_id})
template_vol = self._do_volume_create(sf_account, params)
tvol = {} tvol = {}
tvol['id'] = image_id tvol['id'] = image_id
tvol['provider_location'] = template_vol['provider_location'] tvol['provider_location'] = template_vol['provider_location']
@@ -588,9 +588,6 @@ class SolidFireDriver(san.SanISCSIDriver):
connector = 'na' connector = 'na'
conn = self.initialize_connection(tvol, connector) conn = self.initialize_connection(tvol, connector)
attach_info = super(SolidFireDriver, self)._connect_device(conn) attach_info = super(SolidFireDriver, self)._connect_device(conn)
sfaccount = self._get_sfaccount(account)
params = {'accountID': sfaccount['accountID']}
properties = 'na' properties = 'na'
try: try:
@@ -625,10 +622,7 @@ class SolidFireDriver(san.SanISCSIDriver):
# If it's out of date, just delete it and we'll create a new one # If it's out of date, just delete it and we'll create a new one
# Any other case we don't care and just return without doing anything # Any other case we don't care and just return without doing anything
sfaccount = self._get_sfaccount( params = {'accountID': self.template_account_id}
self.configuration.sf_template_account_name)
params = {'accountID': sfaccount['accountID']}
sf_vol = self._get_sf_volume(image_meta['id'], params) sf_vol = self._get_sf_volume(image_meta['id'], params)
if sf_vol is None: if sf_vol is None:
return return
@@ -639,7 +633,7 @@ class SolidFireDriver(san.SanISCSIDriver):
return return
else: else:
# Bummer, it's been updated, delete it # Bummer, it's been updated, delete it
params = {'accountID': sfaccount['accountID']} params = {'accountID': self.template_account_id}
params['volumeID'] = sf_vol['volumeID'] params['volumeID'] = sf_vol['volumeID']
data = self._issue_api_request('DeleteVolume', params) data = self._issue_api_request('DeleteVolume', params)
if 'result' not in data: if 'result' not in data:
@@ -653,6 +647,75 @@ class SolidFireDriver(san.SanISCSIDriver):
msg = _("Failed to create SolidFire Image-Volume") msg = _("Failed to create SolidFire Image-Volume")
raise exception.SolidFireAPIException(msg) raise exception.SolidFireAPIException(msg)
def _get_sfaccounts_for_tenant(self, cinder_project_id):
data = self._issue_api_request('ListAccounts', {})
if 'result' not in data:
msg = _("API response: %s") % data
raise exception.SolidFireAPIException(msg)
# Note(jdg): On SF we map account-name to OpenStack's tenant ID
# we use tenantID in here to get secondaries that might exist
# Also: we expect this to be sorted, so we get the primary first
# in the list
return sorted([acc for acc in data['result']['accounts'] if
cinder_project_id in acc['username']])
def _get_all_active_volumes(self, cinder_uuid=None):
params = {}
data = self._issue_api_request('ListActiveVolumes',
params)
if 'result' not in data:
msg = _("Failed get active SolidFire volumes: %s") % data
raise exception.SolidFireAPIException(msg)
if cinder_uuid:
deleted_vols = ([v for v in data['result']['volumes'] if
cinder_uuid in v.name])
else:
deleted_vols = [v for v in data['result']['volumes']]
return deleted_vols
def _get_all_deleted_volumes(self, cinder_uuid=None):
params = {}
data = self._issue_api_request('ListDeletedVolumes',
params)
if 'result' not in data:
msg = _("Failed get Deleted SolidFire volumes: %s") % data
raise exception.SolidFireAPIException(msg)
if cinder_uuid:
deleted_vols = ([v for v in data['result']['volumes'] if
cinder_uuid in v['name']])
else:
deleted_vols = [v for v in data['result']['volumes']]
return deleted_vols
def _get_account_create_availability(self, accounts):
# we'll check both the primary and the secondary
# if it exists and return whichever one has count
# available.
for acc in accounts:
if self._get_volumes_for_account(
acc['accountID']) > self.max_volumes_per_account:
return acc
if len(accounts) == 1:
sfaccount = self._create_sfaccount(accounts[0]['name'] + '_')
return sfaccount
return None
def _get_volumes_for_account(self, sf_account_id, cinder_uuid=None):
# ListVolumesForAccount gives both Active and Deleted
# we require the solidfire accountID, uuid of volume
# is optional
params = {'accountID': sf_account_id}
response = self._issue_api_request('ListVolumesForAccount',
params)
if cinder_uuid:
vlist = [v for v in response['result']['volumes'] if
cinder_uuid in v['name']]
else:
vlist = [v for v in response['result']['volumes']]
vlist = sorted(vlist, key=lambda k: k['volumeID'])
return vlist
def clone_image(self, context, def clone_image(self, context,
volume, image_location, volume, image_location,
image_meta, image_service): image_meta, image_service):
@@ -739,8 +802,14 @@ class SolidFireDriver(san.SanISCSIDriver):
for k, v in qos.items(): for k, v in qos.items():
attributes[k] = str(v) attributes[k] = str(v)
sf_accounts = self._get_sfaccounts_for_tenant(volume['project_id'])
if not sf_accounts:
sf_account = self._create_sfaccount(volume['project_id'])
else:
sf_account = self._get_account_create_availability(sf_accounts)
params = {'name': 'UUID-%s' % volume['id'], params = {'name': 'UUID-%s' % volume['id'],
'accountID': None, 'accountID': sf_account['accountID'],
'sliceCount': slice_count, 'sliceCount': slice_count,
'totalSize': int(volume['size'] * units.Gi), 'totalSize': int(volume['size'] * units.Gi),
'enable512e': self.configuration.sf_emulate_512, 'enable512e': self.configuration.sf_emulate_512,
@@ -755,8 +824,7 @@ class SolidFireDriver(san.SanISCSIDriver):
params['name'] = 'UUID-%s' % v params['name'] = 'UUID-%s' % v
params['attributes']['migration_uuid'] = volume['id'] params['attributes']['migration_uuid'] = volume['id']
params['attributes']['uuid'] = v params['attributes']['uuid'] = v
return self._do_volume_create(sf_account, params)
return self._do_volume_create(volume['project_id'], params)
def create_cloned_volume(self, volume, src_vref): def create_cloned_volume(self, volume, src_vref):
"""Create a clone of an existing volume.""" """Create a clone of an existing volume."""
@@ -774,8 +842,8 @@ class SolidFireDriver(san.SanISCSIDriver):
volumeID is what's guaranteed unique. volumeID is what's guaranteed unique.
""" """
sfaccount = self._get_sfaccount(volume['project_id']) accounts = self._get_sfaccounts_for_tenant(volume['project_id'])
if sfaccount is None: if accounts is None:
LOG.error(_LE("Account for Volume ID %s was not found on " LOG.error(_LE("Account for Volume ID %s was not found on "
"the SolidFire Cluster while attempting " "the SolidFire Cluster while attempting "
"delete_volume operation!"), volume['id']) "delete_volume operation!"), volume['id'])
@@ -783,8 +851,11 @@ class SolidFireDriver(san.SanISCSIDriver):
"successfully created.")) "successfully created."))
return return
params = {'accountID': sfaccount['accountID']} for acc in accounts:
sf_vol = self._get_sf_volume(volume['id'], params) sf_vol = self._get_volumes_for_account(acc['accountID'],
volume['id'])[0]
if sf_vol:
break
if sf_vol is not None: if sf_vol is not None:
params = {'volumeID': sf_vol['volumeID']} params = {'volumeID': sf_vol['volumeID']}
@@ -812,28 +883,27 @@ class SolidFireDriver(san.SanISCSIDriver):
def delete_snapshot(self, snapshot): def delete_snapshot(self, snapshot):
"""Delete the specified snapshot from the SolidFire cluster.""" """Delete the specified snapshot from the SolidFire cluster."""
sf_snap_name = 'UUID-%s' % snapshot['id'] sf_snap_name = 'UUID-%s' % snapshot['id']
sfaccount = self._get_sfaccount(snapshot['project_id']) accounts = self._get_sfaccounts_for_tenant(snapshot['project_id'])
params = {'accountID': sfaccount['accountID'], snap = None
'name': sf_snap_name} for a in accounts:
params = {'accountID': sfaccount['accountID']} params = {'accountID': a['accountID']}
sf_vol = self._get_sf_volume(snapshot['volume_id'], params)
# Get the parent volume of the snapshot sf_snaps = self._get_sf_snapshots(sf_vol['volumeID'])
sf_vol = self._get_sf_volume(snapshot['volume_id'], params) snap = next((s for s in sf_snaps if s["name"] == sf_snap_name),
sf_snaps = self._get_sf_snapshots(sf_vol['volumeID']) None)
snap = next((s for s in sf_snaps if s["name"] == sf_snap_name), None) if snap:
if snap: params = {'snapshotID': snap['snapshotID']}
params = {'snapshotID': snap['snapshotID']} data = self._issue_api_request('DeleteSnapshot',
data = self._issue_api_request('DeleteSnapshot', params,
params, version='6.0')
version='6.0') if 'result' not in data:
if 'result' not in data: msg = (_("Failed to delete SolidFire Snapshot: %s") %
msg = (_("Failed to delete SolidFire Snapshot: %s") % data)
data) raise exception.SolidFireAPIException(msg)
raise exception.SolidFireAPIException(msg) return
else: # Make sure it's not "old style" using clones as snaps
# Make sure it's not "old style" using clones as snaps LOG.debug("Snapshot not found, checking old style clones.")
LOG.debug("Snapshot not found, checking old style clones.") self.delete_volume(snapshot)
self.delete_volume(snapshot)
def create_snapshot(self, snapshot): def create_snapshot(self, snapshot):
sfaccount = self._get_sfaccount(snapshot['project_id']) sfaccount = self._get_sfaccount(snapshot['project_id'])