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,
'iqn': test_name}]}}
return result
elif method is 'DeleteSnapshot':
return {'result': {}}
else:
# Crap, unimplemented API call in Fake
return None
@@ -206,13 +208,13 @@ class SolidFireVolumeTestCase(test.TestCase):
def fake_get_model_info(self, account, vid):
return {'fake': 'fake-model'}
def test_create_with_qos_type(self):
self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request',
self.fake_issue_api_request)
self.stubs.Set(solidfire.SolidFireDriver,
'_set_qos_by_volume_type',
self.fake_set_qos_by_volume_type)
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
@mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
def test_create_volume_with_qos_type(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',
'name': 'testvol',
'size': 1,
@@ -220,52 +222,128 @@ class SolidFireVolumeTestCase(test.TestCase):
'volume_type_id': 'fast',
'created_at': timeutils.utcnow()}
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
model_update = sfv.create_volume(testvol)
self.assertIsNotNone(model_update)
fake_sfaccounts = [{'accountID': 5,
'name': 'testprjid',
'targetSecret': 'shhhh',
'username': 'john-wayne'}]
def test_create_volume(self):
self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request',
self.fake_issue_api_request)
test_type = {'name': 'sf-1',
'qos_specs_id': 'fb0576d7-b4b5-4cad-85dc-ca92e6a497d1',
'deleted': False,
'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',
'name': 'testvol',
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'volume_type_id': None,
'created_at': timeutils.utcnow()}
fake_sfaccounts = [{'accountID': 5,
'name': 'testprjid',
'targetSecret': 'shhhh',
'username': 'john-wayne'}]
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
model_update = sfv.create_volume(testvol)
self.assertIsNotNone(model_update)
self.assertIsNone(model_update.get('provider_geometry', None))
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]):
def test_create_volume_non_512(self):
self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request',
self.fake_issue_api_request)
model_update = sfv.create_volume(testvol)
self.assertIsNotNone(model_update)
self.assertIsNone(model_update.get('provider_geometry', None))
@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',
'name': 'testvol',
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'volume_type_id': None,
'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)
model_update = sfv.create_volume(testvol)
self.assertEqual(model_update.get('provider_geometry', None),
'4096 4096')
self.configuration.sf_emulate_512 = True
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]):
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):
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',
'name': 'testvol',
'volume_size': 1,
@@ -275,21 +353,26 @@ class SolidFireVolumeTestCase(test.TestCase):
'created_at': timeutils.utcnow()}
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
sfv.create_volume(testvol)
sfv.create_snapshot(testsnap)
with mock.patch.object(solidfire.SolidFireDriver,
'_get_sf_snapshots',
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)
def test_create_clone(self):
self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request',
self.fake_issue_api_request)
self.stubs.Set(solidfire.SolidFireDriver,
'_get_model_info',
self.fake_get_model_info)
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
@mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
def test_create_clone(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
_fake_get_snaps = [{'snapshotID': 5, 'name': 'testvol'}]
testvol = {'project_id': 'testprjid',
'name': 'testvol',
'size': 1,
@@ -304,10 +387,19 @@ class SolidFireVolumeTestCase(test.TestCase):
'volume_type_id': None,
'created_at': timeutils.utcnow()}
with mock.patch.object(solidfire.SolidFireDriver,
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
with mock.patch.object(sfv,
'_get_sf_snapshots',
return_value=[]):
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
return_value=_fake_get_snaps), \
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)
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']['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):
# NOTE(JDG) This test just fakes update_cluster_status
# this is inentional for this test
@@ -403,18 +476,41 @@ class SolidFireVolumeTestCase(test.TestCase):
account = sfv._get_sfaccount_by_name('some-name')
self.assertIsNone(account)
def test_delete_volume(self):
self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request',
self.fake_issue_api_request)
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
@mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
def test_delete_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',
'name': 'test_volume',
'size': 1,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
'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.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):
self.stubs.Set(solidfire.SolidFireDriver,
@@ -433,26 +529,6 @@ class SolidFireVolumeTestCase(test.TestCase):
except Exception:
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):
self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request',
@@ -690,16 +766,13 @@ class SolidFireVolumeTestCase(test.TestCase):
self.assertIsNotNone(model_update)
self.assertIsNone(model_update.get('provider_geometry', None))
def test_create_volume_for_migration(self):
def _fake_do_v_create(self, project_id, params):
return project_id, params
self.stubs.Set(solidfire.SolidFireDriver,
'_issue_api_request',
self.fake_issue_api_request)
self.stubs.Set(solidfire.SolidFireDriver,
'_do_volume_create', _fake_do_v_create)
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
@mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
def test_create_volume_for_migration(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',
'name': 'testvol',
'size': 1,
@@ -708,15 +781,35 @@ class SolidFireVolumeTestCase(test.TestCase):
'created_at': timeutils.utcnow(),
'migration_status': 'target:'
'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)
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'])
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):
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, '_get_sfaccount')
@@ -808,9 +901,10 @@ class SolidFireVolumeTestCase(test.TestCase):
self.fake_image_meta,
'fake'))
@mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request')
def test_clone_image_authorization(self, _mock_issue_api_request):
_mock_issue_api_request.return_value = self.mock_stats_data
@mock.patch.object(solidfire.SolidFireDriver, '_create_template_account')
def test_clone_image_authorization(self, _mock_create_template_account):
_mock_create_template_account.return_value = 1
self.configuration.sf_allow_template_caching = True
sfv = solidfire.SolidFireDriver(configuration=self.configuration)
@@ -824,33 +918,40 @@ class SolidFireVolumeTestCase(test.TestCase):
'properties': {'virtual_size': 1},
'is_public': False,
'owner': 'wrong-owner'}
self.assertEqual((None, False),
sfv.clone_image(self.ctxt,
self.mock_volume,
'fake',
_fake_image_meta,
'fake'))
with mock.patch.object(sfv, '_do_clone_volume',
return_value=('fe', 'fi', 'fo')):
self.assertEqual((None, False),
sfv.clone_image(self.ctxt,
self.mock_volume,
'fake',
_fake_image_meta,
'fake'))
# And is_public False, but the correct owner does work
# expect raise AccountNotFound as that's the next call after
# auth checks
_fake_image_meta['owner'] = 'testprjid'
self.assertRaises(exception.SolidFireAccountNotFound,
sfv.clone_image, self.ctxt,
self.mock_volume, 'fake',
_fake_image_meta, 'fake')
# And is_public False, but the correct owner does work
_fake_image_meta['owner'] = 'testprjid'
self.assertEqual(('fo', True), sfv.clone_image(self.ctxt,
self.mock_volume,
'fake',
_fake_image_meta,
'fake'))
# And is_public True, even if not the correct owner
_fake_image_meta['is_public'] = True
_fake_image_meta['owner'] = 'wrong-owner'
self.assertRaises(exception.SolidFireAccountNotFound,
sfv.clone_image, self.ctxt,
self.mock_volume, 'fake',
_fake_image_meta, 'fake')
# And is_public True, even if not the correct owner
_fake_image_meta['is_public'] = True
_fake_image_meta['owner'] = 'wrong-owner'
self.assertEqual(('fo', True), sfv.clone_image(self.ctxt,
self.mock_volume,
'fake',
_fake_image_meta,
'fake'))
@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_create_template_account.return_value = 1
self.configuration.sf_allow_template_caching = True
sfv = solidfire.SolidFireDriver(configuration=self.configuration)

View File

@@ -112,10 +112,11 @@ class SolidFireDriver(san.SanISCSIDriver):
1.2.2 - Catch VolumeNotFound on accept xfr
2.0.0 - Move from httplib to requests
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,
'maxIOPS': 200,
@@ -146,27 +147,29 @@ class SolidFireDriver(san.SanISCSIDriver):
super(SolidFireDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(sf_opts)
self._endpoint = self._build_endpoint_info()
self.template_account_id = None
self.max_volumes_per_account = 1990
try:
self._update_cluster_status()
except exception.SolidFireAPIException:
pass
if self.configuration.sf_allow_template_caching:
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):
chap_secret = self._generate_random_string(12)
params = {'username': account_name,
'initiatorSecret': chap_secret,
'targetSecret': chap_secret,
'attributes': {}}
try:
self._issue_api_request('AddAccount', params)
except exception.SolidFireAPIException as ex:
if 'DuplicateUsername' in ex.msg:
pass
else:
raise
id = self._issue_api_request(
'GetAccountByName',
{'username': account_name})['result']['account']['accountID']
if not id:
chap_secret = self._generate_random_string(12)
params = {'username': account_name,
'initiatorSecret': chap_secret,
'targetSecret': chap_secret,
'attributes': {}}
id = self._issue_api_request('AddAccount',
params)['result']['accountID']
return id
def _build_endpoint_info(self, **kwargs):
endpoint = {}
@@ -347,64 +350,59 @@ class SolidFireDriver(san.SanISCSIDriver):
def _do_clone_volume(self, src_uuid,
src_project_id,
v_ref):
"""Create a clone of an existing volume.
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.
"""
vref):
"""Create a clone of an existing volume or snapshot. """
attributes = {}
qos = {}
sfaccount = self._get_sfaccount(src_project_id)
if src_project_id != v_ref['project_id']:
sfaccount = self._create_sfaccount(v_ref['project_id'])
if v_ref.get('size', None):
new_size = v_ref['size']
sf_accounts = self._get_sfaccounts_for_tenant(vref['project_id'])
if not sf_accounts:
sf_account = self._create_sfaccount(vref['project_id'])
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'],
'newSize': int(new_size * units.Gi),
'newAccountID': sfaccount['accountID']}
params = {'name': 'UUID-%s' % vref['id'],
'newAccountID': sf_account['accountID']}
# NOTE(jdg): First check the SF snapshots
# 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
# before we did snapshots, so need to check both
is_clone = False
snap_name = 'UUID-%s' % src_uuid
snaps = self._get_sf_snapshots()
snap = next((s for s in snaps if s["name"] == snap_name), None)
is_clone = False
if snap:
params['snapshotID'] = int(snap['snapshotID'])
params['volumeID'] = int(snap['volumeID'])
params['newSize'] = int(vref['size'] * units.Gi)
else:
sf_vol = self._get_sf_volume(
src_uuid, {'accountID': sfaccount['accountID']})
src_uuid, {'accountID': sf_account['accountID']})
if sf_vol is None:
raise exception.VolumeNotFound(volume_id=src_uuid)
params['volumeID'] = int(sf_vol['volumeID'])
params['newSize'] = int(vref['size'] * units.Gi)
is_clone = True
data = self._issue_api_request('CloneVolume', params, version='6.0')
if (('result' not in data) or ('volumeID' not in data['result'])):
msg = _("API response: %s") % data
raise exception.SolidFireAPIException(msg)
sf_volume_id = data['result']['volumeID']
sf_volume_id = data['result']['volumeID']
if (self.configuration.sf_allow_tenant_qos and
v_ref.get('volume_metadata')is not None):
qos = self._set_qos_presets(v_ref)
vref.get('volume_metadata')is not None):
qos = self._set_qos_presets(vref)
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:
qos = self._set_qos_by_volume_type(ctxt, type_id)
@@ -412,8 +410,8 @@ class SolidFireDriver(san.SanISCSIDriver):
# to set any that were provided
params = {'volumeID': sf_volume_id}
create_time = v_ref['created_at'].isoformat()
attributes = {'uuid': v_ref['id'],
create_time = vref['created_at'].isoformat()
attributes = {'uuid': vref['id'],
'is_clone': 'True',
'src_uuid': src_uuid,
'created_at': create_time}
@@ -425,7 +423,7 @@ class SolidFireDriver(san.SanISCSIDriver):
params['attributes'] = attributes
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:
mesg = _('Failed to get model update from clone')
raise exception.SolidFireAPIException(mesg)
@@ -433,27 +431,27 @@ class SolidFireDriver(san.SanISCSIDriver):
# Increment the usage count, just for data collection
# We're only doing this for clones, not create_from snaps
if is_clone:
cloned_count = sf_vol['attributes'].get('cloned_count', 0)
cloned_count += 1
attributes = sf_vol['attributes']
attributes['cloned_count'] = cloned_count
data = self._update_attributes(sf_vol)
return (data, sf_account, model_update)
params = {'volumeID': int(sf_vol['volumeID'])}
params['attributes'] = attributes
data = self._issue_api_request('ModifyVolume', params)
return (data, sfaccount, model_update)
def _update_attributes(self, sf_vol):
cloned_count = sf_vol['attributes'].get('cloned_count', 0)
cloned_count += 1
attributes = sf_vol['attributes']
attributes['cloned_count'] = cloned_count
def _do_volume_create(self, project_id, params):
sfaccount = self._create_sfaccount(project_id)
params['accountID'] = sfaccount['accountID']
params = {'volumeID': int(sf_vol['volumeID'])}
params['attributes'] = attributes
return self._issue_api_request('ModifyVolume', params)
def _do_volume_create(self, sf_account, params):
data = self._issue_api_request('CreateVolume', params)
if (('result' not in data) or ('volumeID' not in data['result'])):
msg = _("Failed volume create: %s") % data
raise exception.SolidFireAPIException(msg)
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):
data = self._issue_api_request('CreateSnapshot', params, version='6.0')
@@ -569,17 +567,19 @@ class SolidFireDriver(san.SanISCSIDriver):
attributes['image_info']['image_created_at'] =\
image_meta['created_at'].isoformat()
attributes['image_info']['image_id'] = image_meta['id']
params = {'name': 'OpenStackIMG-%s' % image_id,
'accountID': None,
'accountID': self.template_account_id,
'sliceCount': 1,
'totalSize': int(min_sz_in_bytes),
'enable512e': self.configuration.sf_emulate_512,
'attributes': attributes,
'qos': {}}
account = self.configuration.sf_template_account_name
template_vol = self._do_volume_create(account, params)
sf_account = self._issue_api_request(
'GetAccountByID',
{'accountID': self.template_account_id})
template_vol = self._do_volume_create(sf_account, params)
tvol = {}
tvol['id'] = image_id
tvol['provider_location'] = template_vol['provider_location']
@@ -588,9 +588,6 @@ class SolidFireDriver(san.SanISCSIDriver):
connector = 'na'
conn = self.initialize_connection(tvol, connector)
attach_info = super(SolidFireDriver, self)._connect_device(conn)
sfaccount = self._get_sfaccount(account)
params = {'accountID': sfaccount['accountID']}
properties = 'na'
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
# Any other case we don't care and just return without doing anything
sfaccount = self._get_sfaccount(
self.configuration.sf_template_account_name)
params = {'accountID': sfaccount['accountID']}
params = {'accountID': self.template_account_id}
sf_vol = self._get_sf_volume(image_meta['id'], params)
if sf_vol is None:
return
@@ -639,7 +633,7 @@ class SolidFireDriver(san.SanISCSIDriver):
return
else:
# Bummer, it's been updated, delete it
params = {'accountID': sfaccount['accountID']}
params = {'accountID': self.template_account_id}
params['volumeID'] = sf_vol['volumeID']
data = self._issue_api_request('DeleteVolume', params)
if 'result' not in data:
@@ -653,6 +647,75 @@ class SolidFireDriver(san.SanISCSIDriver):
msg = _("Failed to create SolidFire Image-Volume")
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,
volume, image_location,
image_meta, image_service):
@@ -739,8 +802,14 @@ class SolidFireDriver(san.SanISCSIDriver):
for k, v in qos.items():
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'],
'accountID': None,
'accountID': sf_account['accountID'],
'sliceCount': slice_count,
'totalSize': int(volume['size'] * units.Gi),
'enable512e': self.configuration.sf_emulate_512,
@@ -755,8 +824,7 @@ class SolidFireDriver(san.SanISCSIDriver):
params['name'] = 'UUID-%s' % v
params['attributes']['migration_uuid'] = volume['id']
params['attributes']['uuid'] = v
return self._do_volume_create(volume['project_id'], params)
return self._do_volume_create(sf_account, params)
def create_cloned_volume(self, volume, src_vref):
"""Create a clone of an existing volume."""
@@ -774,8 +842,8 @@ class SolidFireDriver(san.SanISCSIDriver):
volumeID is what's guaranteed unique.
"""
sfaccount = self._get_sfaccount(volume['project_id'])
if sfaccount is None:
accounts = self._get_sfaccounts_for_tenant(volume['project_id'])
if accounts is None:
LOG.error(_LE("Account for Volume ID %s was not found on "
"the SolidFire Cluster while attempting "
"delete_volume operation!"), volume['id'])
@@ -783,8 +851,11 @@ class SolidFireDriver(san.SanISCSIDriver):
"successfully created."))
return
params = {'accountID': sfaccount['accountID']}
sf_vol = self._get_sf_volume(volume['id'], params)
for acc in accounts:
sf_vol = self._get_volumes_for_account(acc['accountID'],
volume['id'])[0]
if sf_vol:
break
if sf_vol is not None:
params = {'volumeID': sf_vol['volumeID']}
@@ -812,28 +883,27 @@ class SolidFireDriver(san.SanISCSIDriver):
def delete_snapshot(self, snapshot):
"""Delete the specified snapshot from the SolidFire cluster."""
sf_snap_name = 'UUID-%s' % snapshot['id']
sfaccount = self._get_sfaccount(snapshot['project_id'])
params = {'accountID': sfaccount['accountID'],
'name': sf_snap_name}
params = {'accountID': sfaccount['accountID']}
# Get the parent volume of the snapshot
sf_vol = self._get_sf_volume(snapshot['volume_id'], params)
sf_snaps = self._get_sf_snapshots(sf_vol['volumeID'])
snap = next((s for s in sf_snaps if s["name"] == sf_snap_name), None)
if snap:
params = {'snapshotID': snap['snapshotID']}
data = self._issue_api_request('DeleteSnapshot',
params,
version='6.0')
if 'result' not in data:
msg = (_("Failed to delete SolidFire Snapshot: %s") %
data)
raise exception.SolidFireAPIException(msg)
else:
# Make sure it's not "old style" using clones as snaps
LOG.debug("Snapshot not found, checking old style clones.")
self.delete_volume(snapshot)
accounts = self._get_sfaccounts_for_tenant(snapshot['project_id'])
snap = None
for a in accounts:
params = {'accountID': a['accountID']}
sf_vol = self._get_sf_volume(snapshot['volume_id'], params)
sf_snaps = self._get_sf_snapshots(sf_vol['volumeID'])
snap = next((s for s in sf_snaps if s["name"] == sf_snap_name),
None)
if snap:
params = {'snapshotID': snap['snapshotID']}
data = self._issue_api_request('DeleteSnapshot',
params,
version='6.0')
if 'result' not in data:
msg = (_("Failed to delete SolidFire Snapshot: %s") %
data)
raise exception.SolidFireAPIException(msg)
return
# Make sure it's not "old style" using clones as snaps
LOG.debug("Snapshot not found, checking old style clones.")
self.delete_volume(snapshot)
def create_snapshot(self, snapshot):
sfaccount = self._get_sfaccount(snapshot['project_id'])