]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add secondary account capability to SolidFire
authorJohn Griffith <john.griffith8@gmail.com>
Mon, 1 Jun 2015 20:04:15 +0000 (20:04 +0000)
committerJohn Griffith <john.griffith8@gmail.com>
Wed, 10 Jun 2015 19:24:26 +0000 (13:24 -0600)
The SolidFire Cluster has a limit of 2K volumes per SolidFire
account. Given that the SolidFire account maps directly to the
OpenStack tenant this can be a limitation for some large scale
deployments that may have tenants that wish to create more than
2K volumes in their OpenStack cloud.

This patch introduces the concept of a secondary account for
an OpenStack tenant on the SolidFire backend.

If an OpenStack tenant has more than 2K volumes, and wishes to
create additional volumes, we can create a secondary/supplemental
SolidFire account that is owned by the same OpenStack tenant.

If they wish to go beyond 4K volumes... well, SolidFire V3
driver will deal with that, for now 4K is a line in the sand.

While working on unit tests, we again move to using mock
opportunistically.  In addition, a few tests that either
weren't actually testing anything, or were just a fake-loop
that didn't actually work have been removed:

NOTE:
This patch is a temporary for backporting purposes, we are
still planning a refresh in Liberty of the driver.

Change-Id: I1c8a14e6bea1b6f47d10777c56c5af42b63e2dae

cinder/tests/unit/test_solidfire.py
cinder/volume/drivers/solidfire.py

index a7bfc1312bdf2771a7250efd7b27afe667fc395a..f788ee72f33c2bad008268229fd247a1e81dc285 100644 (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)
-        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,
@@ -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'))
-
-        # 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)
 
index dde7d5c761c445ce35ec4aff02ffc3cd21ee11ef..02c9d799ee13fc5ebd980ee8e32217b06c0d18f5 100644 (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 = {}
@@ -346,64 +349,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)
 
@@ -411,8 +409,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}
@@ -424,7 +422,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)
@@ -432,27 +430,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']
-        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')
@@ -568,17 +566,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']
@@ -587,9 +587,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:
@@ -624,10 +621,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
@@ -638,7 +632,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:
@@ -652,6 +646,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):
@@ -738,8 +801,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,
@@ -754,8 +823,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."""
@@ -773,8 +841,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'])
@@ -782,8 +850,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']}
@@ -811,28 +882,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'])