]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Update SolidFire volume driver
authorJohn Griffith <john.griffith@solidfire.com>
Tue, 14 Aug 2012 00:35:35 +0000 (18:35 -0600)
committerJohn Griffith <john.griffith@solidfire.com>
Wed, 15 Aug 2012 20:15:29 +0000 (14:15 -0600)
Implements blueprint update-solidfire-driver

* Updates driver to reflect changes in the release version of SF API
* Modify SF naming scheme
* Implement snapshot functionality
* Implement setting qos on create via metadata
* Update/Add tests

Change-Id: I08f7aac31e9d95f971d297a19c285dfa7151b931

cinder/tests/test_solidfire.py [moved from cinder/tests/test_SolidFireSanISCSIDriver.py with 54% similarity]
cinder/volume/san.py
cinder/volume/solidfire.py [new file with mode: 0644]

similarity index 54%
rename from cinder/tests/test_SolidFireSanISCSIDriver.py
rename to cinder/tests/test_solidfire.py
index 0febe787b721bf0604af4aa88480e1c104497f86..d6d0772b6c9dde329df5d4aee5c3d566ecf24f3b 100644 (file)
@@ -17,8 +17,8 @@
 
 from cinder import exception
 from cinder.openstack.common import log as logging
-from cinder.volume import san
 from cinder import test
+from cinder.volume.solidfire import SolidFire
 
 LOG = logging.getLogger(__name__)
 
@@ -31,12 +31,12 @@ class SolidFireVolumeTestCase(test.TestCase):
         if method is 'GetClusterInfo':
             LOG.info('Called Fake GetClusterInfo...')
             results = {'result': {'clusterInfo':
-                          {'name': 'fake-cluster',
-                           'mvip': '1.1.1.1',
-                           'svip': '1.1.1.1',
-                           'uniqueID': 'unqid',
-                           'repCount': 2,
-                           'attributes': {}}}}
+                                  {'name': 'fake-cluster',
+                                   'mvip': '1.1.1.1',
+                                   'svip': '1.1.1.1',
+                                   'uniqueID': 'unqid',
+                                   'repCount': 2,
+                                   'attributes': {}}}}
             return results
 
         elif method is 'AddAccount':
@@ -45,15 +45,15 @@ class SolidFireVolumeTestCase(test.TestCase):
 
         elif method is 'GetAccountByName':
             LOG.info('Called Fake GetAccountByName...')
-            results = {'result': {'account': {
-                       'accountID': 25,
-                       'username': params['username'],
-                       'status': 'active',
-                       'initiatorSecret': '123456789012',
-                       'targetSecret': '123456789012',
-                       'attributes': {},
-                       'volumes': [6, 7, 20]}},
-                   "id": 1}
+            results = {'result': {'account':
+                                  {'accountID': 25,
+                                   'username': params['username'],
+                                   'status': 'active',
+                                   'initiatorSecret': '123456789012',
+                                   'targetSecret': '123456789012',
+                                   'attributes': {},
+                                   'volumes': [6, 7, 20]}},
+                       "id": 1}
             return results
 
         elif method is 'CreateVolume':
@@ -65,46 +65,67 @@ class SolidFireVolumeTestCase(test.TestCase):
             return {'result': {}, 'id': 1}
 
         elif method is 'ListVolumesForAccount':
+            test_name = 'OS-VOLID-a720b3c0-d1f0-11e1-9b23-0800200c9a66'
             LOG.info('Called Fake ListVolumesForAccount...')
-            result = {'result': {'volumes': [{
-                                           'volumeID': '5',
-                                           'name': 'test_volume',
-                                           'accountID': 25,
-                                           'sliceCount': 1,
-                                           'totalSize': 1048576 * 1024,
-                                           'enable512e': False,
-                                           'access': "readWrite",
-                                           'status': "active",
-                                           'attributes':None,
-                                           'qos':None}]}}
+            result = {'result': {
+                'volumes': [{'volumeID': 5,
+                             'name': test_name,
+                             'accountID': 25,
+                             'sliceCount': 1,
+                             'totalSize': 1048576 * 1024,
+                             'enable512e': True,
+                             'access': "readWrite",
+                             'status': "active",
+                             'attributes':None,
+                             'qos': None,
+                             'iqn': test_name}]}}
             return result
 
         else:
             LOG.error('Crap, unimplemented API call in Fake:%s' % method)
 
     def fake_issue_api_request_fails(obj, method, params):
-        return {'error': {
-                    'code': 000,
-                    'name': 'DummyError',
-                    'message': 'This is a fake error response'},
-                    'id': 1}
+        return {'error': {'code': 000,
+                          'name': 'DummyError',
+                          'message': 'This is a fake error response'},
+                'id': 1}
+
+    def fake_volume_get(obj, key, default=None):
+        return {'qos': 'fast'}
 
     def test_create_volume(self):
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        self.stubs.Set(SolidFire, '_issue_api_request',
+                       self.fake_issue_api_request)
+        testvol = {'project_id': 'testprjid',
+                   'name': 'testvol',
+                   'size': 1,
+                   'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
+        sfv = SolidFire()
+        model_update = sfv.create_volume(testvol)
+
+    def test_create_volume_with_qos(self):
+        preset_qos = {}
+        preset_qos['qos'] = 'fast'
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request)
+
         testvol = {'project_id': 'testprjid',
                    'name': 'testvol',
-                   'size': 1}
-        sfv = san.SolidFireSanISCSIDriver()
+                   'size': 1,
+                   'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
+                   'metadata': [preset_qos]}
+
+        sfv = SolidFire()
         model_update = sfv.create_volume(testvol)
 
     def test_create_volume_fails(self):
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request_fails)
         testvol = {'project_id': 'testprjid',
                    'name': 'testvol',
-                   'size': 1}
-        sfv = san.SolidFireSanISCSIDriver()
+                   'size': 1,
+                   'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
+        sfv = SolidFire()
         try:
             sfv.create_volume(testvol)
             self.fail("Should have thrown Error")
@@ -112,49 +133,51 @@ class SolidFireVolumeTestCase(test.TestCase):
             pass
 
     def test_create_sfaccount(self):
-        sfv = san.SolidFireSanISCSIDriver()
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        sfv = SolidFire()
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request)
         account = sfv._create_sfaccount('project-id')
         self.assertNotEqual(account, None)
 
     def test_create_sfaccount_fails(self):
-        sfv = san.SolidFireSanISCSIDriver()
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        sfv = SolidFire()
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request_fails)
         account = sfv._create_sfaccount('project-id')
         self.assertEqual(account, None)
 
     def test_get_sfaccount_by_name(self):
-        sfv = san.SolidFireSanISCSIDriver()
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        sfv = SolidFire()
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request)
         account = sfv._get_sfaccount_by_name('some-name')
         self.assertNotEqual(account, None)
 
     def test_get_sfaccount_by_name_fails(self):
-        sfv = san.SolidFireSanISCSIDriver()
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        sfv = SolidFire()
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request_fails)
         account = sfv._get_sfaccount_by_name('some-name')
         self.assertEqual(account, None)
 
     def test_delete_volume(self):
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request)
         testvol = {'project_id': 'testprjid',
                    'name': 'test_volume',
-                   'size': 1}
-        sfv = san.SolidFireSanISCSIDriver()
+                   'size': 1,
+                   'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
+        sfv = SolidFire()
         model_update = sfv.delete_volume(testvol)
 
     def test_delete_volume_fails_no_volume(self):
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request)
         testvol = {'project_id': 'testprjid',
                    'name': 'no-name',
-                   'size': 1}
-        sfv = san.SolidFireSanISCSIDriver()
+                   'size': 1,
+                   'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
+        sfv = SolidFire()
         try:
             model_update = sfv.delete_volume(testvol)
             self.fail("Should have thrown Error")
@@ -162,25 +185,26 @@ class SolidFireVolumeTestCase(test.TestCase):
             pass
 
     def test_delete_volume_fails_account_lookup(self):
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
-                       self.fake_issue_api_request)
+        self.stubs.Set(SolidFire, '_issue_api_request',
+                       self.fake_issue_api_request_fails)
         testvol = {'project_id': 'testprjid',
                    'name': 'no-name',
-                   'size': 1}
-        sfv = san.SolidFireSanISCSIDriver()
-        self.assertRaises(exception.DuplicateSfVolumeNames,
+                   'size': 1,
+                   'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
+        sfv = SolidFire()
+        self.assertRaises(exception.SfAccountNotFound,
                           sfv.delete_volume,
                           testvol)
 
     def test_get_cluster_info(self):
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request)
-        sfv = san.SolidFireSanISCSIDriver()
+        sfv = SolidFire()
         sfv._get_cluster_info()
 
     def test_get_cluster_info_fail(self):
-        self.stubs.Set(san.SolidFireSanISCSIDriver, '_issue_api_request',
+        self.stubs.Set(SolidFire, '_issue_api_request',
                        self.fake_issue_api_request_fails)
-        sfv = san.SolidFireSanISCSIDriver()
+        sfv = SolidFire()
         self.assertRaises(exception.SolidFireAPIException,
                           sfv._get_cluster_info)
index 0448f8092fbedb3020e5db6c2b04519bf5f08e42..b70c71fa35d912b86ac3d82c89d547dea324a629 100644 (file)
@@ -644,246 +644,3 @@ class HpSanISCSIDriver(SanISCSIDriver):
         cliq_args['volumeName'] = volume['name']
         cliq_args['serverName'] = connector['host']
         self._cliq_run_xml("unassignVolumeToServer", cliq_args)
-
-
-class SolidFireSanISCSIDriver(SanISCSIDriver):
-
-    def _issue_api_request(self, method_name, params):
-        """All API requests to SolidFire device go through this method
-
-        Simple json-rpc web based API calls.
-        each call takes a set of paramaters (dict)
-        and returns results in a dict as well.
-        """
-
-        host = FLAGS.san_ip
-        # For now 443 is the only port our server accepts requests on
-        port = 443
-
-        # NOTE(john-griffith): Probably don't need this, but the idea is
-        # we provide a request_id so we can correlate
-        # responses with requests
-        request_id = int(uuid.uuid4())  # just generate a random number
-
-        cluster_admin = FLAGS.san_login
-        cluster_password = FLAGS.san_password
-
-        command = {'method': method_name,
-                   'id': request_id}
-
-        if params is not None:
-            command['params'] = params
-
-        payload = jsonutils.dumps(command, ensure_ascii=False)
-        payload.encode('utf-8')
-        # we use json-rpc, webserver needs to see json-rpc in header
-        header = {'Content-Type': 'application/json-rpc; charset=utf-8'}
-
-        if cluster_password is not None:
-            # base64.encodestring includes a newline character
-            # in the result, make sure we strip it off
-            auth_key = base64.encodestring('%s:%s' % (cluster_admin,
-                                           cluster_password))[:-1]
-            header['Authorization'] = 'Basic %s' % auth_key
-
-        LOG.debug(_("Payload for SolidFire API call: %s"), payload)
-        connection = httplib.HTTPSConnection(host, port)
-        connection.request('POST', '/json-rpc/1.0', payload, header)
-        response = connection.getresponse()
-        data = {}
-
-        if response.status != 200:
-            connection.close()
-            raise exception.SolidFireAPIException(status=response.status)
-
-        else:
-            data = response.read()
-            try:
-                data = jsonutils.loads(data)
-
-            except (TypeError, ValueError), exc:
-                connection.close()
-                msg = _("Call to json.loads() raised an exception: %s") % exc
-                raise exception.SfJsonEncodeFailure(msg)
-
-            connection.close()
-
-        LOG.debug(_("Results of SolidFire API call: %s"), data)
-        return data
-
-    def _get_volumes_by_sfaccount(self, account_id):
-        params = {'accountID': account_id}
-        data = self._issue_api_request('ListVolumesForAccount', params)
-        if 'result' in data:
-            return data['result']['volumes']
-
-    def _get_sfaccount_by_name(self, sf_account_name):
-        sfaccount = None
-        params = {'username': sf_account_name}
-        data = self._issue_api_request('GetAccountByName', params)
-        if 'result' in data and 'account' in data['result']:
-            LOG.debug(_('Found solidfire account: %s'), sf_account_name)
-            sfaccount = data['result']['account']
-        return sfaccount
-
-    def _create_sfaccount(self, cinder_project_id):
-        """Create account on SolidFire device if it doesn't already exist.
-
-        We're first going to check if the account already exits, if it does
-        just return it.  If not, then create it.
-        """
-
-        sf_account_name = socket.gethostname() + '-' + cinder_project_id
-        sfaccount = self._get_sfaccount_by_name(sf_account_name)
-        if sfaccount is None:
-            LOG.debug(_('solidfire account: %s does not exist, create it...'),
-                      sf_account_name)
-            chap_secret = self._generate_random_string(12)
-            params = {'username': sf_account_name,
-                      'initiatorSecret': chap_secret,
-                      'targetSecret': chap_secret,
-                      'attributes': {}}
-            data = self._issue_api_request('AddAccount', params)
-            if 'result' in data:
-                sfaccount = self._get_sfaccount_by_name(sf_account_name)
-
-        return sfaccount
-
-    def _get_cluster_info(self):
-        params = {}
-        data = self._issue_api_request('GetClusterInfo', params)
-        if 'result' not in data:
-            raise exception.SolidFireAPIDataException(data=data)
-
-        return data['result']
-
-    def _do_export(self, volume):
-        """Gets the associated account, retrieves CHAP info and updates."""
-
-        sfaccount_name = '%s-%s' % (socket.gethostname(), volume['project_id'])
-        sfaccount = self._get_sfaccount_by_name(sfaccount_name)
-
-        model_update = {}
-        model_update['provider_auth'] = ('CHAP %s %s'
-                % (sfaccount['username'], sfaccount['targetSecret']))
-
-        return model_update
-
-    def _generate_random_string(self, length):
-        """Generates random_string to use for CHAP password."""
-
-        char_set = string.ascii_uppercase + string.digits
-        return ''.join(random.sample(char_set, length))
-
-    def create_volume(self, volume):
-        """Create volume on SolidFire device.
-
-        The account is where CHAP settings are derived from, volume is
-        created and exported.  Note that the new volume is immediately ready
-        for use.
-
-        One caveat here is that an existing user account must be specified
-        in the API call to create a new volume.  We use a set algorithm to
-        determine account info based on passed in cinder volume object.  First
-        we check to see if the account already exists (and use it), or if it
-        does not already exist, we'll go ahead and create it.
-
-        For now, we're just using very basic settings, QOS is
-        turned off, 512 byte emulation is off etc.  Will be
-        looking at extensions for these things later, or
-        this module can be hacked to suit needs.
-        """
-
-        LOG.debug(_("Enter SolidFire create_volume..."))
-        GB = 1048576 * 1024
-        slice_count = 1
-        enable_emulation = False
-        attributes = {}
-
-        cluster_info = self._get_cluster_info()
-        iscsi_portal = cluster_info['clusterInfo']['svip'] + ':3260'
-        sfaccount = self._create_sfaccount(volume['project_id'])
-        account_id = sfaccount['accountID']
-        account_name = sfaccount['username']
-        chap_secret = sfaccount['targetSecret']
-
-        params = {'name': volume['name'],
-                  'accountID': account_id,
-                  'sliceCount': slice_count,
-                  'totalSize': volume['size'] * GB,
-                  'enable512e': enable_emulation,
-                  'attributes': attributes}
-
-        data = self._issue_api_request('CreateVolume', params)
-
-        if 'result' not in data or 'volumeID' not in data['result']:
-            raise exception.SolidFireAPIDataException(data=data)
-
-        volume_id = data['result']['volumeID']
-
-        volume_list = self._get_volumes_by_sfaccount(account_id)
-        iqn = None
-        for v in volume_list:
-            if v['volumeID'] == volume_id:
-                iqn = 'iqn.2010-01.com.solidfire:' + v['iqn']
-                break
-
-        model_update = {}
-
-        # NOTE(john-griffith): SF volumes are always at lun 0
-        model_update['provider_location'] = ('%s %s %s'
-                % (iscsi_portal, iqn, 0))
-        model_update['provider_auth'] = ('CHAP %s %s'
-                % (account_name, chap_secret))
-
-        LOG.debug(_("Leaving SolidFire create_volume"))
-        return model_update
-
-    def delete_volume(self, volume):
-        """Delete SolidFire Volume from device.
-
-        SolidFire allows multipe volumes with same name,
-        volumeID is what's guaranteed unique.
-
-        What we'll do here is check volumes based on account. this
-        should work because cinder will increment its volume_id
-        so we should always get the correct volume. This assumes
-        that cinder does not assign duplicate ID's.
-        """
-
-        LOG.debug(_("Enter SolidFire delete_volume..."))
-        sf_account_name = socket.gethostname() + '-' + volume['project_id']
-        sfaccount = self._get_sfaccount_by_name(sf_account_name)
-        if sfaccount is None:
-            raise exception.SfAccountNotFound(account_name=sf_account_name)
-
-        params = {'accountID': sfaccount['accountID']}
-        data = self._issue_api_request('ListVolumesForAccount', params)
-        if 'result' not in data:
-            raise exception.SolidFireAPIDataException(data=data)
-
-        found_count = 0
-        volid = -1
-        for v in data['result']['volumes']:
-            if v['name'] == volume['name']:
-                found_count += 1
-                volid = v['volumeID']
-
-        if found_count != 1:
-            LOG.debug(_("Deleting volumeID: %s"), volid)
-            raise exception.DuplicateSfVolumeNames(vol_name=volume['name'])
-
-        params = {'volumeID': volid}
-        data = self._issue_api_request('DeleteVolume', params)
-        if 'result' not in data:
-            raise exception.SolidFireAPIDataException(data=data)
-
-        LOG.debug(_("Leaving SolidFire delete_volume"))
-
-    def ensure_export(self, context, volume):
-        LOG.debug(_("Executing SolidFire ensure_export..."))
-        return self._do_export(volume)
-
-    def create_export(self, context, volume):
-        LOG.debug(_("Executing SolidFire create_export..."))
-        return self._do_export(volume)
diff --git a/cinder/volume/solidfire.py b/cinder/volume/solidfire.py
new file mode 100644 (file)
index 0000000..c042f5e
--- /dev/null
@@ -0,0 +1,423 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+Drivers for san-stored volumes.
+
+The unique thing about a SAN is that we don't expect that we can run the volume
+controller on the SAN hardware.  We expect to access it over SSH or some API.
+"""
+
+import base64
+import httplib
+import json
+import random
+import socket
+import string
+import uuid
+
+from cinder import exception
+from cinder import flags
+from cinder.openstack.common import cfg
+from cinder.openstack.common import log as logging
+from cinder.volume.san import SanISCSIDriver
+
+
+LOG = logging.getLogger(__name__)
+
+sf_opts = [
+    cfg.BoolOpt('sf_emulate_512',
+                default=True,
+                help='Set 512 byte emulation on volume creation; '),
+
+    cfg.StrOpt('sf_mvip',
+               default='',
+               help='IP address of SolidFire MVIP'),
+
+    cfg.StrOpt('sf_login',
+               default='admin',
+               help='Username for SF Cluster Admin'),
+
+    cfg.StrOpt('sf_password',
+               default='',
+               help='Password for SF Cluster Admin'),
+
+    cfg.StrOpt('sf_allow_tenant_qos',
+               default=True,
+               help='Allow tenants to specify QOS on create'), ]
+
+FLAGS = flags.FLAGS
+FLAGS.register_opts(sf_opts)
+
+
+class SolidFire(SanISCSIDriver):
+
+    sf_qos_dict = {'slow': {'minIOPS': 100,
+                            'maxIOPS': 200,
+                            'burstIOPS': 200},
+                   'medium': {'minIOPS': 200,
+                              'maxIOPS': 400,
+                              'burstIOPS': 400},
+                   'fast': {'minIOPS': 500,
+                            'maxIOPS': 1000,
+                            'burstIOPS': 1000},
+                   'performant': {'minIOPS': 2000,
+                                  'maxIOPS': 4000,
+                                  'burstIOPS': 4000},
+                   'off': None}
+
+    def __init__(self, *args, **kwargs):
+            super(SolidFire, self).__init__(*args, **kwargs)
+
+    def _issue_api_request(self, method_name, params):
+        """All API requests to SolidFire device go through this method
+
+        Simple json-rpc web based API calls.
+        each call takes a set of paramaters (dict)
+        and returns results in a dict as well.
+        """
+
+        host = FLAGS.san_ip
+        # For now 443 is the only port our server accepts requests on
+        port = 443
+
+        # NOTE(john-griffith): Probably don't need this, but the idea is
+        # we provide a request_id so we can correlate
+        # responses with requests
+        request_id = int(uuid.uuid4())  # just generate a random number
+
+        cluster_admin = FLAGS.san_login
+        cluster_password = FLAGS.san_password
+
+        command = {'method': method_name,
+                   'id': request_id}
+
+        if params is not None:
+            command['params'] = params
+
+        payload = json.dumps(command, ensure_ascii=False)
+        payload.encode('utf-8')
+        # we use json-rpc, webserver needs to see json-rpc in header
+        header = {'Content-Type': 'application/json-rpc; charset=utf-8'}
+
+        if cluster_password is not None:
+            # base64.encodestring includes a newline character
+            # in the result, make sure we strip it off
+            auth_key = base64.encodestring('%s:%s' % (cluster_admin,
+                                           cluster_password))[:-1]
+            header['Authorization'] = 'Basic %s' % auth_key
+
+        LOG.debug(_("Payload for SolidFire API call: %s"), payload)
+        connection = httplib.HTTPSConnection(host, port)
+        connection.request('POST', '/json-rpc/1.0', payload, header)
+        response = connection.getresponse()
+        data = {}
+
+        if response.status != 200:
+            connection.close()
+            raise exception.SolidFireAPIException(status=response.status)
+
+        else:
+            data = response.read()
+            try:
+                data = json.loads(data)
+
+            except (TypeError, ValueError), exc:
+                connection.close()
+                msg = _("Call to json.loads() raised an exception: %s") % exc
+                raise exception.SfJsonEncodeFailure(msg)
+
+            connection.close()
+
+        LOG.debug(_("Results of SolidFire API call: %s"), data)
+        return data
+
+    def _get_volumes_by_sfaccount(self, account_id):
+        params = {'accountID': account_id}
+        data = self._issue_api_request('ListVolumesForAccount', params)
+        if 'result' in data:
+            return data['result']['volumes']
+
+    def _get_sfaccount_by_name(self, sf_account_name):
+        sfaccount = None
+        params = {'username': sf_account_name}
+        data = self._issue_api_request('GetAccountByName', params)
+        if 'result' in data and 'account' in data['result']:
+            LOG.debug(_('Found solidfire account: %s'), sf_account_name)
+            sfaccount = data['result']['account']
+        return sfaccount
+
+    def _create_sfaccount(self, cinder_project_id):
+        """Create account on SolidFire device if it doesn't already exist.
+
+        We're first going to check if the account already exits, if it does
+        just return it.  If not, then create it.
+        """
+
+        sf_account_name = socket.gethostname() + '-' + cinder_project_id
+        sfaccount = self._get_sfaccount_by_name(sf_account_name)
+        if sfaccount is None:
+            LOG.debug(_('solidfire account: %s does not exist, create it...'),
+                      sf_account_name)
+            chap_secret = self._generate_random_string(12)
+            params = {'username': sf_account_name,
+                      'initiatorSecret': chap_secret,
+                      'targetSecret': chap_secret,
+                      'attributes': {}}
+            data = self._issue_api_request('AddAccount', params)
+            if 'result' in data:
+                sfaccount = self._get_sfaccount_by_name(sf_account_name)
+
+        return sfaccount
+
+    def _get_cluster_info(self):
+        params = {}
+        data = self._issue_api_request('GetClusterInfo', params)
+        if 'result' not in data:
+            raise exception.SolidFireAPIDataException(data=data)
+
+        return data['result']
+
+    def _do_export(self, volume):
+        """Gets the associated account, retrieves CHAP info and updates."""
+
+        sfaccount_name = '%s-%s' % (socket.gethostname(), volume['project_id'])
+        sfaccount = self._get_sfaccount_by_name(sfaccount_name)
+
+        model_update = {}
+        model_update['provider_auth'] = ('CHAP %s %s'
+                                         % (sfaccount['username'],
+                                            sfaccount['targetSecret']))
+
+        return model_update
+
+    def _generate_random_string(self, length):
+        """Generates random_string to use for CHAP password."""
+
+        char_set = string.ascii_uppercase + string.digits
+        return ''.join(random.sample(char_set, length))
+
+    def _do_volume_create(self, project_id, params):
+        cluster_info = self._get_cluster_info()
+        iscsi_portal = cluster_info['clusterInfo']['svip'] + ':3260'
+        sfaccount = self._create_sfaccount(project_id)
+        chap_secret = sfaccount['targetSecret']
+
+        params['accountID'] = sfaccount['accountID']
+        data = self._issue_api_request('CreateVolume', params)
+
+        if 'result' not in data or 'volumeID' not in data['result']:
+            raise exception.SolidFireAPIDataException(data=data)
+
+        volume_id = data['result']['volumeID']
+
+        volume_list = self._get_volumes_by_sfaccount(sfaccount['accountID'])
+        iqn = None
+        for v in volume_list:
+            if v['volumeID'] == volume_id:
+                iqn = 'iqn.2010-01.com.solidfire:' + v['iqn']
+                break
+
+        model_update = {}
+
+        # NOTE(john-griffith): SF volumes are always at lun 0
+        model_update['provider_location'] = ('%s %s %s'
+                                             % (iscsi_portal, iqn, 0))
+        model_update['provider_auth'] = ('CHAP %s %s'
+                                         % (sfaccount['username'],
+                                         chap_secret))
+
+        return model_update
+
+    def create_volume(self, volume):
+        """Create volume on SolidFire device.
+
+        The account is where CHAP settings are derived from, volume is
+        created and exported.  Note that the new volume is immediately ready
+        for use.
+
+        One caveat here is that an existing user account must be specified
+        in the API call to create a new volume.  We use a set algorithm to
+        determine account info based on passed in cinder volume object.  First
+        we check to see if the account already exists (and use it), or if it
+        does not already exist, we'll go ahead and create it.
+
+        For now, we're just using very basic settings, QOS is
+        turned off, 512 byte emulation is off etc.  Will be
+        looking at extensions for these things later, or
+        this module can be hacked to suit needs.
+        """
+        GB = 1048576 * 1024
+        slice_count = 1
+        attributes = {}
+        qos = {}
+        qos_keys = ['minIOPS', 'maxIOPS', 'burstIOPS']
+        valid_presets = self.sf_qos_dict.keys()
+
+        if FLAGS.sf_allow_tenant_qos and \
+                volume.get('volume_metadata')is not None:
+
+            #First look to see if they included a preset
+            presets = [i.value for i in volume.get('volume_metadata')
+                       if i.key == 'sf-qos' and i.value in valid_presets]
+            if len(presets) > 0:
+                if len(presets) > 1:
+                    LOG.warning(_('More than one valid preset was '
+                                  'detected, using %s' % presets[0]))
+                qos = self.sf_qos_dict[presets[0]]
+            else:
+                #if there was no preset, look for explicit settings
+                for i in volume.get('volume_metadata'):
+                    if i.key in qos_keys:
+                        qos[i.key] = int(i.value)
+
+        params = {'name': 'OS-VOLID-%s' % volume['id'],
+                  'accountID': None,
+                  'sliceCount': slice_count,
+                  'totalSize': volume['size'] * GB,
+                  'enable512e': FLAGS.sf_emulate_512,
+                  'attributes': attributes,
+                  'qos': qos}
+
+        return self._do_volume_create(volume['project_id'], params)
+
+    def delete_volume(self, volume, is_snapshot=False):
+        """Delete SolidFire Volume from device.
+
+        SolidFire allows multipe volumes with same name,
+        volumeID is what's guaranteed unique.
+
+        """
+
+        LOG.debug(_("Enter SolidFire delete_volume..."))
+        sf_account_name = socket.gethostname() + '-' + volume['project_id']
+        sfaccount = self._get_sfaccount_by_name(sf_account_name)
+        if sfaccount is None:
+            raise exception.SfAccountNotFound(account_name=sf_account_name)
+
+        params = {'accountID': sfaccount['accountID']}
+        data = self._issue_api_request('ListVolumesForAccount', params)
+        if 'result' not in data:
+            raise exception.SolidFireAPIDataException(data=data)
+
+        if is_snapshot:
+            seek = 'OS-SNAPID-%s' % (volume['id'])
+        else:
+            seek = 'OS-VOLID-%s' % volume['id']
+            #params = {'name': 'OS-VOLID-:%s' % volume['id'],
+
+        found_count = 0
+        volid = -1
+        for v in data['result']['volumes']:
+            if v['name'] == seek:
+                found_count += 1
+                volid = v['volumeID']
+
+        if found_count == 0:
+            raise exception.VolumeNotFound(volume_id=volume['id'])
+
+        if found_count > 1:
+            LOG.debug(_("Deleting volumeID: %s"), volid)
+            raise exception.DuplicateSfVolumeNames(vol_name=volume['id'])
+
+        params = {'volumeID': volid}
+        data = self._issue_api_request('DeleteVolume', params)
+        if 'result' not in data:
+            raise exception.SolidFireAPIDataException(data=data)
+
+        LOG.debug(_("Leaving SolidFire delete_volume"))
+
+    def ensure_export(self, context, volume):
+        LOG.debug(_("Executing SolidFire ensure_export..."))
+        return self._do_export(volume)
+
+    def create_export(self, context, volume):
+        LOG.debug(_("Executing SolidFire create_export..."))
+        return self._do_export(volume)
+
+    def _do_create_snapshot(self, snapshot, snapshot_name):
+        """Creates a snapshot."""
+        LOG.debug(_("Enter SolidFire create_snapshot..."))
+        sf_account_name = socket.gethostname() + '-' + snapshot['project_id']
+        sfaccount = self._get_sfaccount_by_name(sf_account_name)
+        if sfaccount is None:
+            raise exception.SfAccountNotFound(account_name=sf_account_name)
+
+        params = {'accountID': sfaccount['accountID']}
+        data = self._issue_api_request('ListVolumesForAccount', params)
+        if 'result' not in data:
+            raise exception.SolidFireAPIDataException(data=data)
+
+        found_count = 0
+        volid = -1
+        for v in data['result']['volumes']:
+            if v['name'] == 'OS-VOLID-%s' % snapshot['volume_id']:
+                found_count += 1
+                volid = v['volumeID']
+
+        if found_count == 0:
+            raise exception.VolumeNotFound(volume_id=snapshot['volume_id'])
+        if found_count != 1:
+            raise exception.DuplicateSfVolumeNames(
+                vol_name='OS-VOLID-%s' % snapshot['volume_id'])
+
+        params = {'volumeID': int(volid),
+                  'name': snapshot_name,
+                  'attributes': {'OriginatingVolume': volid}}
+
+        data = self._issue_api_request('CloneVolume', params)
+        if 'result' not in data:
+            raise exception.SolidFireAPIDataException(data=data)
+
+        return (data, sfaccount)
+
+    def delete_snapshot(self, snapshot):
+        self.delete_volume(snapshot, True)
+
+    def create_snapshot(self, snapshot):
+        snapshot_name = 'OS-SNAPID-%s' % (
+                        snapshot['id'])
+        (data, sf_account) = self._do_create_snapshot(snapshot, snapshot_name)
+
+    def create_volume_from_snapshot(self, volume, snapshot):
+        cluster_info = self._get_cluster_info()
+        iscsi_portal = cluster_info['clusterInfo']['svip'] + ':3260'
+        sfaccount = self._create_sfaccount(snapshot['project_id'])
+        chap_secret = sfaccount['targetSecret']
+        snapshot_name = 'OS-VOLID-%s' % volume['id']
+
+        (data, sf_account) = self._do_create_snapshot(snapshot, snapshot_name)
+
+        if 'result' not in data or 'volumeID' not in data['result']:
+            raise exception.SolidFireAPIDataException(data=data)
+
+        volume_id = data['result']['volumeID']
+        volume_list = self._get_volumes_by_sfaccount(sf_account['accountID'])
+        iqn = None
+        for v in volume_list:
+            if v['volumeID'] == volume_id:
+                iqn = 'iqn.2010-01.com.solidfire:' + v['iqn']
+                break
+
+        model_update = {}
+
+        # NOTE(john-griffith): SF volumes are always at lun 0
+        model_update['provider_location'] = ('%s %s %s'
+                                             % (iscsi_portal, iqn, 0))
+        model_update['provider_auth'] = ('CHAP %s %s'
+                                         % (sfaccount['username'],
+                                            chap_secret))
+        return model_update