Merge "Add secondary account capability to SolidFire"
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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'])
|
||||
|
||||
Reference in New Issue
Block a user