'qos': None,
'iqn': test_name}]}}
return result
+ elif method is 'DeleteSnapshot':
+ return {'result': {}}
else:
# Crap, unimplemented API call in Fake
return None
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,
'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)
- testvol = {'project_id': 'testprjid',
- 'name': 'testvol',
- 'size': 1,
- 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
- 'volume_type_id': None,
- 'created_at': timeutils.utcnow()}
+ 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'}
- sfv = solidfire.SolidFireDriver(configuration=self.configuration)
- model_update = sfv.create_volume(testvol)
- self.assertIsNotNone(model_update)
- self.assertIsNone(model_update.get('provider_geometry', None))
+ test_qos_spec = {'id': 'asdfafdasdf',
+ 'specs': {'minIOPS': '1000',
+ 'maxIOPS': '2000',
+ 'burstIOPS': '3000'}}
- def test_create_volume_non_512(self):
- self.stubs.Set(solidfire.SolidFireDriver,
- '_issue_api_request',
- self.fake_issue_api_request)
+ 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)
+ 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]):
+
+ model_update = sfv.create_volume(testvol)
+ self.assertIsNotNone(model_update)
+ self.assertIsNone(model_update.get('provider_geometry', None))
- 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
-
- def test_create_delete_snapshot(self):
+ @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'}]
+
+ 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]):
+
+ 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):
testsnap = {'project_id': 'testprjid',
'name': 'testvol',
'volume_size': 1,
'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,
'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):
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
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,
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',
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,
'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')
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)
'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'))
-
- # 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 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')
+ 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
+ _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.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)
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,
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 = {}
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)
# 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}
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)
# 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']
- data = self._issue_api_request('CreateVolume', params)
+ 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')
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']
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:
# 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
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:
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):
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,
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."""
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'])
"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']}
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'])