]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Implement missing Coraid Driver functionality for Havana
authorNikolay Sobolevskiy <nsobolevsky@mirantis.com>
Thu, 1 Aug 2013 12:57:54 +0000 (16:57 +0400)
committerNikolay Sobolevskiy <nsobolevsky@mirantis.com>
Fri, 16 Aug 2013 15:06:12 +0000 (19:06 +0400)
Intorduce a brick connector for AoE protocol.
Refactoring Coraid Driver. Implement missing functionality for Havana:
- Copy Volume To Image
- Copy Image To Volume
- Clone Volume

Fix a bug with resize volume command.

Change-Id: I2af6a41dc44cb8bc4b74da752e0be9ed54a83cb1
Implements: blueprint coraid-driver-refactoring-for-havana

cinder/brick/initiator/connector.py
cinder/exception.py
cinder/tests/brick/test_brick_connector.py
cinder/tests/test_coraid.py
cinder/volume/drivers/coraid.py
etc/cinder/rootwrap.d/volume.filters

index 2560f7e7b8896aaf839d3060f174e419d5f8bf66..35f3775b7d42fa8ed203894769610166034d94f3 100644 (file)
@@ -100,6 +100,10 @@ class InitiatorConnector(executor.Executor):
                                          driver=driver,
                                          root_helper=root_helper,
                                          use_multipath=use_multipath)
+        elif protocol == "AOE":
+            return AoEConnector(execute=execute,
+                                driver=driver,
+                                root_helper=root_helper)
         else:
             msg = (_("Invalid InitiatorConnector protocol "
                      "specified %(protocol)s") %
@@ -650,3 +654,111 @@ class FibreChannelConnector(InitiatorConnector):
                     pci_num = device_path[index - 1]
 
         return pci_num
+
+
+class AoEConnector(InitiatorConnector):
+    """Connector class to attach/detach AoE volumes."""
+    def __init__(self, driver=None, execute=putils.execute,
+                 root_helper="sudo", *args, **kwargs):
+        super(AoEConnector, self).__init__(driver, execute, root_helper,
+                                           *args, **kwargs)
+
+    def _get_aoe_info(self, connection_properties):
+        shelf = connection_properties['target_shelf']
+        lun = connection_properties['target_lun']
+        aoe_device = 'e%(shelf)s.%(lun)s' % {'shelf': shelf,
+                                             'lun': lun}
+        aoe_path = '/dev/etherd/%s' % (aoe_device)
+        return aoe_device, aoe_path
+
+    @lockutils.synchronized('aoe_control', 'aoe-')
+    def connect_volume(self, connection_properties):
+        """Discover and attach the volume.
+
+        connection_properties for AoE must include:
+        target_shelf - shelf id of volume
+        target_lun - lun id of volume
+        """
+        aoe_device, aoe_path = self._get_aoe_info(connection_properties)
+
+        device_info = {
+            'type': 'block',
+            'device': aoe_device,
+            'path': aoe_path,
+        }
+
+        if os.path.exists(aoe_path):
+            self._aoe_revalidate(aoe_device)
+        else:
+            self._aoe_discover()
+
+        waiting_status = {'tries': 0}
+
+        #NOTE(jbr_): Device path is not always present immediately
+        def _wait_for_discovery(aoe_path):
+            if os.path.exists(aoe_path):
+                raise loopingcall.LoopingCallDone
+
+            if waiting_status['tries'] >= CONF.num_volume_device_scan_tries:
+                raise exception.VolumeDeviceNotFound(device=aoe_path)
+
+            LOG.warn(_("AoE volume not yet found at: %(path)s. "
+                       "Try number: %(tries)s"),
+                     {'path': aoe_device,
+                      'tries': waiting_status['tries']})
+
+            self._aoe_discover()
+            waiting_status['tries'] += 1
+
+        timer = loopingcall.FixedIntervalLoopingCall(_wait_for_discovery,
+                                                     aoe_path)
+        timer.start(interval=2).wait()
+
+        if waiting_status['tries']:
+            LOG.debug(_("Found AoE device %(path)s "
+                        "(after %(tries)s rediscover)"),
+                      {'path': aoe_path,
+                       'tries': waiting_status['tries']})
+
+        return device_info
+
+    @lockutils.synchronized('aoe_control', 'aoe-')
+    def disconnect_volume(self, connection_properties, device_info):
+        """Detach and flush the volume.
+
+        connection_properties for AoE must include:
+        target_shelf - shelf id of volume
+        target_lun - lun id of volume
+        """
+        aoe_device, aoe_path = self._get_aoe_info(connection_properties)
+
+        if os.path.exists(aoe_path):
+            self._aoe_flush(aoe_device)
+
+    def _aoe_discover(self):
+        (out, err) = self._execute('aoe-discover',
+                                   run_as_root=True,
+                                   root_helper=self._root_helper,
+                                   check_exit_code=0)
+
+        LOG.debug(_('aoe-discover: stdout=%(out)s stderr%(err)s') %
+                  {'out': out, 'err': err})
+
+    def _aoe_revalidate(self, aoe_device):
+        (out, err) = self._execute('aoe-revalidate',
+                                   aoe_device,
+                                   run_as_root=True,
+                                   root_helper=self._root_helper,
+                                   check_exit_code=0)
+
+        LOG.debug(_('aoe-revalidate %(dev)s: stdout=%(out)s stderr%(err)s') %
+                  {'dev': aoe_device, 'out': out, 'err': err})
+
+    def _aoe_flush(self, aoe_device):
+        (out, err) = self._execute('aoe-flush',
+                                   aoe_device,
+                                   run_as_root=True,
+                                   root_helper=self._root_helper,
+                                   check_exit_code=0)
+        LOG.debug(_('aoe-flush %(dev)s: stdout=%(out)s stderr%(err)s') %
+                  {'dev': aoe_device, 'out': out, 'err': err})
index ef5261fdb6afb2f273856c499327972bd1b1f0b3..9bf902fde0082fc5843f80a01c1e33bb29f9a689 100644 (file)
@@ -618,3 +618,31 @@ class ProtocolNotSupported(CinderException):
 
 class SSHInjectionThreat(CinderException):
     message = _("SSH command injection detected") + ": %(command)s"
+
+
+class CoraidException(CinderException):
+    message = _('Coraid Cinder Driver exception.')
+
+
+class CoraidJsonEncodeFailure(CoraidException):
+    message = _('Failed to encode json data.')
+
+
+class CoraidESMBadCredentials(CoraidException):
+    message = _('Login on ESM failed.')
+
+
+class CoraidESMReloginFailed(CoraidException):
+    message = _('Relogin on ESM failed.')
+
+
+class CoraidESMBadGroup(CoraidException):
+    message = _('Group with name "%(group_name)s" not found.')
+
+
+class CoraidESMConfigureError(CoraidException):
+    message = _('ESM configure request failed: %(message)s.')
+
+
+class CoraidESMNotAvailable(CoraidException):
+    message = _('Coraid ESM not available with reason: %(reason)s.')
index f523d89af1765f663fd447db8dd32a6dee4a403f..5ec9e657eff5c1fcd05adbeb11b1b7a3a45e2eea 100644 (file)
@@ -18,6 +18,8 @@ import os.path
 import string
 import time
 
+import mox
+
 from cinder.brick import exception
 from cinder.brick.initiator import connector
 from cinder.brick.initiator import host_driver
@@ -60,6 +62,10 @@ class ConnectorTestCase(test.TestCase):
         self.assertTrue(obj.__class__.__name__,
                         "FibreChannelConnector")
 
+        obj = connector.InitiatorConnector.factory('aoe')
+        self.assertTrue(obj.__class__.__name__,
+                        "AoEConnector")
+
         self.assertRaises(ValueError,
                           connector.InitiatorConnector.factory,
                           "bogus")
@@ -322,3 +328,100 @@ class FibreChannelConnectorTestCase(ConnectorTestCase):
         self.assertRaises(exception.NoFibreChannelHostsFound,
                           self.connector.connect_volume,
                           connection_info['data'])
+
+
+class AoEConnectorTestCase(ConnectorTestCase):
+    """Test cases for AoE initiator class."""
+    def setUp(self):
+        super(AoEConnectorTestCase, self).setUp()
+        self.mox = mox.Mox()
+        self.connector = connector.AoEConnector()
+        self.connection_properties = {'target_shelf': 'fake_shelf',
+                                      'target_lun': 'fake_lun'}
+
+    def tearDown(self):
+        self.mox.VerifyAll()
+        self.mox.UnsetStubs()
+        super(AoEConnectorTestCase, self).tearDown()
+
+    def _mock_path_exists(self, aoe_path, mock_values=[]):
+        self.mox.StubOutWithMock(os.path, 'exists')
+        for value in mock_values:
+            os.path.exists(aoe_path).AndReturn(value)
+
+    def test_connect_volume(self):
+        """Ensure that if path exist aoe-revaliadte was called."""
+        aoe_device, aoe_path = self.connector._get_aoe_info(
+            self.connection_properties)
+
+        self._mock_path_exists(aoe_path, [True, True])
+
+        self.mox.StubOutWithMock(self.connector, '_execute')
+        self.connector._execute('aoe-revalidate',
+                                aoe_device,
+                                run_as_root=True,
+                                root_helper='sudo',
+                                check_exit_code=0).AndReturn(("", ""))
+        self.mox.ReplayAll()
+
+        self.connector.connect_volume(self.connection_properties)
+
+    def test_connect_volume_without_path(self):
+        """Ensure that if path doesn't exist aoe-discovery was called."""
+
+        aoe_device, aoe_path = self.connector._get_aoe_info(
+            self.connection_properties)
+        expected_info = {
+            'type': 'block',
+            'device': aoe_device,
+            'path': aoe_path,
+        }
+
+        self._mock_path_exists(aoe_path, [False, True])
+
+        self.mox.StubOutWithMock(self.connector, '_execute')
+        self.connector._execute('aoe-discover',
+                                run_as_root=True,
+                                root_helper='sudo',
+                                check_exit_code=0).AndReturn(("", ""))
+        self.mox.ReplayAll()
+
+        volume_info = self.connector.connect_volume(
+            self.connection_properties)
+
+        self.assertDictMatch(volume_info, expected_info)
+
+    def test_connect_volume_could_not_discover_path(self):
+        aoe_device, aoe_path = self.connector._get_aoe_info(
+            self.connection_properties)
+
+        number_of_calls = 4
+        self._mock_path_exists(aoe_path, [False] * (number_of_calls + 1))
+        self.mox.StubOutWithMock(self.connector, '_execute')
+
+        for i in xrange(number_of_calls):
+            self.connector._execute('aoe-discover',
+                                    run_as_root=True,
+                                    root_helper='sudo',
+                                    check_exit_code=0).AndReturn(("", ""))
+        self.mox.ReplayAll()
+        self.assertRaises(exception.VolumeDeviceNotFound,
+                          self.connector.connect_volume,
+                          self.connection_properties)
+
+    def test_disconnect_volume(self):
+        """Ensure that if path exist aoe-revaliadte was called."""
+        aoe_device, aoe_path = self.connector._get_aoe_info(
+            self.connection_properties)
+
+        self._mock_path_exists(aoe_path, [True])
+
+        self.mox.StubOutWithMock(self.connector, '_execute')
+        self.connector._execute('aoe-flush',
+                                aoe_device,
+                                run_as_root=True,
+                                root_helper='sudo',
+                                check_exit_code=0).AndReturn(("", ""))
+        self.mox.ReplayAll()
+
+        self.connector.disconnect_volume(self.connection_properties, {})
index e2cb275c93b4265e7658dd5331354d43a918d790..f31b850847ada20602591ab3fdaaa7aea93112c6 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import math
+
 import mox
 
+from cinder.brick.initiator import connector
 from cinder import exception
+from cinder.image import image_utils
+from cinder.openstack.common import jsonutils
 from cinder.openstack.common import log as logging
 from cinder import test
+from cinder import units
 from cinder.volume import configuration as conf
 from cinder.volume.drivers import coraid
-from cinder.volume.drivers.coraid import CoraidDriver
-from cinder.volume.drivers.coraid import CoraidESMException
-from cinder.volume.drivers.coraid import CoraidRESTClient
+from cinder.volume import volume_types
 
-import cookielib
-import urllib2
 
 LOG = logging.getLogger(__name__)
 
 
+def to_coraid_kb(gb):
+    return math.ceil(float(gb) * units.GiB / 1000)
+
+
+def coraid_volume_size(gb):
+    return '{0}K'.format(to_coraid_kb(gb))
+
+
 fake_esm_ipaddress = "192.168.0.1"
 fake_esm_username = "darmok"
 fake_esm_group = "tanagra"
+fake_esm_group_id = 1
 fake_esm_password = "12345678"
 
+fake_coraid_repository_key = 'repository_key'
+
 fake_volume_name = "volume-12345678-1234-1234-1234-1234567890ab"
-fake_volume_size = "10"
+fake_clone_name = "volume-ffffffff-1234-1234-1234-1234567890ab"
+fake_volume_size = 10
 fake_repository_name = "A-B:C:D"
 fake_pool_name = "FakePool"
 fake_aoetarget = 4081
@@ -48,9 +62,19 @@ fake_lun = 241
 fake_str_aoetarget = str(fake_aoetarget)
 fake_lun_addr = {"shelf": fake_shelf, "lun": fake_lun}
 
+fake_volume_type = {'id': 1}
+
 fake_volume = {"name": fake_volume_name,
                "size": fake_volume_size,
-               "volume_type": {"id": 1}}
+               "volume_type": fake_volume_type}
+
+fake_clone_volume = {"name": fake_clone_name,
+                     "size": fake_volume_size,
+                     "volume_type": fake_volume_type}
+
+fake_big_clone_volume = {"name": fake_clone_name,
+                         "size": fake_volume_size + 1,
+                         "volume_type": fake_volume_type}
 
 fake_volume_info = {"pool": fake_pool_name,
                     "repo": fake_repository_name,
@@ -64,13 +88,15 @@ fake_snapshot_name = "snapshot-12345678-8888-8888-1234-1234567890ab"
 fake_snapshot_id = "12345678-8888-8888-1234-1234567890ab"
 fake_volume_id = "12345678-1234-1234-1234-1234567890ab"
 fake_snapshot = {"id": fake_snapshot_id,
+                 "name": fake_snapshot_name,
                  "volume_id": fake_volume_id,
-                 "volume_size": 10}
+                 "volume_name": fake_volume_name,
+                 "volume_size": int(fake_volume_size) - 1}
 
 fake_configure_data = [{"addr": "cms", "data": "FAKE"}]
 
 fake_esm_fetch = [[
-    {"command": "super_fake_command_of_death"},
+    {"command": "super_fake_command"},
     {"reply": [
         {"lv":
             {"containingPool": fake_pool_name,
@@ -83,6 +109,10 @@ fake_esm_fetch = [[
              },
          "repoName": fake_repository_name}]}]]
 
+fake_esm_fetch_no_volume = [[
+    {"command": "super_fake_command"},
+    {"reply": []}]]
+
 fake_esm_success = {"category": "provider",
                     "tracking": False,
                     "configState": "completedSuccessfully",
@@ -109,12 +139,97 @@ fake_login_reply_group_fail = {"values": [
                                "metaCROp": "noAction"}
 
 
-class TestCoraidDriver(test.TestCase):
+def compare(a, b):
+    if type(a) != type(b):
+        return False
+    if type(a) == list or type(a) == tuple:
+        if len(a) != len(b):
+            return False
+        return all(map(lambda t: compare(t[0], t[1]), zip(a, b)))
+    elif type(a) == dict:
+        if len(a) != len(b):
+            return False
+        for k, v in a.items():
+            if not compare(v, b[k]):
+                return False
+        return True
+    else:
+        return a == b
+
+
+def pack_data(request):
+    request['data'] = jsonutils.dumps(request['data'])
+
+
+class FakeRpcBadRequest(Exception):
+    pass
+
+
+class FakeRpcIsNotCalled(Exception):
+    def __init__(self, handle, url_params, data):
+        self.handle = handle
+        self.url_params = url_params
+        self.data = data
+
+    def __str__(self):
+        return 'Fake Rpc handle for {0}/{1}/{2} not found'.format(
+            self.handle, self.url_params, self.data)
+
+
+class FakeRpcHandle(object):
+    def __init__(self, handle, url_params, data, result):
+        self.handle = handle
+        self.url_params = url_params
+        self.data = data
+        self.result = result
+        self._is_called = False
+
+    def set_called(self):
+        self._is_called = True
+
+    def __call__(self, handle, url_params, data,
+                 allow_empty_response=False):
+        if handle != self.handle:
+            raise FakeRpcBadRequest(
+                'Unexpected handle name {0}. Expected {1}.'
+                .format(handle, self.handle))
+        if not compare(url_params, self.url_params):
+            raise FakeRpcBadRequest('Unexpected url params: {0} / {1}'
+                                    .format(url_params, self.url_params))
+        if not compare(data, self.data):
+            raise FakeRpcBadRequest('Unexpected data: {0}/{1}'
+                                    .format(data, self.data))
+        if callable(self.result):
+            return self.result()
+        else:
+            return self.result
+
+
+class FakeRpc(object):
+    def __init__(self):
+        self._handles = []
+
+    def handle(self, handle, url_params, data, result):
+        self._handles.append(FakeRpcHandle(handle, url_params, data, result))
+
+    def __call__(self, handle_name, url_params, data,
+                 allow_empty_response=False):
+        for handle in self._handles:
+            if (handle.handle == handle_name and
+                compare(handle.url_params, url_params) and
+                    compare(handle.data, handle.data)):
+                handle.set_called()
+                return handle(handle_name, url_params, data,
+                              allow_empty_response)
+        raise FakeRpcIsNotCalled(handle_name, url_params, data)
+
+
+class CoraidDriverTestCase(test.TestCase):
     def setUp(self):
-        super(TestCoraidDriver, self).setUp()
-        self.esm_mock = self.mox.CreateMockAnything()
-        self.stubs.Set(coraid, 'CoraidRESTClient',
-                       lambda *_, **__: self.esm_mock)
+        super(CoraidDriverTestCase, self).setUp()
+
+        self.mox = mox.Mox()
+
         configuration = mox.MockObject(conf.Configuration)
         configuration.append_config_values(mox.IgnoreArg())
         configuration.coraid_esm_address = fake_esm_ipaddress
@@ -123,204 +238,595 @@ class TestCoraidDriver(test.TestCase):
         configuration.coraid_password = fake_esm_password
         configuration.volume_name_template = "volume-%s"
         configuration.snapshot_name_template = "snapshot-%s"
+        configuration.coraid_repository_key = fake_coraid_repository_key
+        configuration.use_multipath_for_image_xfer = False
+        self.fake_rpc = FakeRpc()
+
+        self.mox.StubOutWithMock(coraid.CoraidRESTClient, 'rpc')
+        coraid.CoraidRESTClient.rpc = self.fake_rpc
+
+        self.driver = coraid.CoraidDriver(configuration=configuration)
+        self.driver.do_setup({})
+
+    def tearDown(self):
+        self.mox.UnsetStubs()
+        super(CoraidDriverTestCase, self).tearDown()
+
+    def mock_volume_types(self, repositories=[]):
+        if not repositories:
+            repositories = [fake_repository_name]
+        self.mox.StubOutWithMock(volume_types, 'get_volume_type_extra_specs')
+        for repository in repositories:
+            (volume_types
+             .get_volume_type_extra_specs(fake_volume_type['id'],
+                                          fake_coraid_repository_key)
+             .AndReturn('<in> {0}'.format(repository)))
+
+
+class CoraidDriverLoginSuccessTestCase(CoraidDriverTestCase):
+    def setUp(self):
+        super(CoraidDriverLoginSuccessTestCase, self).setUp()
+
+        login_results = {'state': 'adminSucceed',
+                         'values': [
+                             {'fullPath':
+                              'admin group:{0}'.format(fake_esm_group),
+                              'groupId': fake_esm_group_id
+                              }]}
+
+        self.fake_rpc.handle('admin', {'op': 'login',
+                                       'username': fake_esm_username,
+                                       'password': fake_esm_password},
+                             'Login', login_results)
+
+        self.fake_rpc.handle('admin', {'op': 'setRbacGroup',
+                                       'groupId': fake_esm_group_id},
+                             'Group', {'state': 'adminSucceed'})
+
+
+class CoraidDriverApplianceTestCase(CoraidDriverLoginSuccessTestCase):
+    def test_resize_volume(self):
+        new_volume_size = int(fake_volume_size) + 1
+
+        fetch_request = {'shelf': 'cms',
+                         'orchStrRepo': '',
+                         'lv': fake_volume_name}
+        self.fake_rpc.handle('fetch', fetch_request, None,
+                             fake_esm_fetch)
+
+        reply = {'configState': 'completedSuccessfully'}
 
-        self.drv = CoraidDriver(configuration=configuration)
-        self.drv.do_setup({})
+        resize_volume_request = {'addr': 'cms',
+                                 'data': {
+                                     'lvName': fake_volume_name,
+                                     'newLvName': fake_volume_name + '-resize',
+                                     'size':
+                                         coraid_volume_size(new_volume_size),
+                                     'repoName': fake_repository_name},
+                                 'op': 'orchStrLunMods',
+                                 'args': 'resize'}
+        pack_data(resize_volume_request)
+        self.fake_rpc.handle('configure', {}, [resize_volume_request],
+                             reply)
 
+        real_reply = self.driver._appliance.resize_volume(fake_volume_name,
+                                                          new_volume_size)
+
+        self.assertEqual(reply['configState'], real_reply['configState'])
+
+
+class CoraidDriverIntegrationalTestCase(CoraidDriverLoginSuccessTestCase):
     def test_create_volume(self):
-        setattr(self.esm_mock, 'create_lun', lambda *_: True)
-        self.stubs.Set(CoraidDriver, '_get_repository',
-                       lambda *_: fake_repository_name)
-        self.drv.create_volume(fake_volume)
+        self.mock_volume_types()
+
+        create_volume_request = {'addr': 'cms',
+                                 'data': {
+                                     'servers': [],
+                                     'size':
+                                         coraid_volume_size(fake_volume_size),
+                                     'repoName': fake_repository_name,
+                                     'lvName': fake_volume_name},
+                                 'op': 'orchStrLun',
+                                 'args': 'add'}
+        pack_data(create_volume_request)
+
+        self.fake_rpc.handle('configure', {}, [create_volume_request],
+                             {'configState': 'completedSuccessfully',
+                              'firstParam': 'fake_first_param'})
+
+        self.mox.ReplayAll()
+
+        self.driver.create_volume(fake_volume)
+
+        self.mox.VerifyAll()
 
     def test_delete_volume(self):
-        setattr(self.esm_mock, 'delete_lun',
-                lambda *_: True)
-        self.drv.delete_volume(fake_volume)
+        delete_volume_request = {'addr': 'cms',
+                                 'data': {
+                                     'repoName': fake_repository_name,
+                                     'lvName': fake_volume_name},
+                                 'op': 'orchStrLun/verified',
+                                 'args': 'delete'}
+        pack_data(delete_volume_request)
 
-    def test_initialize_connection(self):
-        setattr(self.esm_mock, '_get_lun_address',
-                lambda *_: fake_lun_addr)
-        self.drv.initialize_connection(fake_volume, '')
+        self.fake_rpc.handle('configure', {}, [delete_volume_request],
+                             {'configState': 'completedSuccessfully'})
+
+        self.fake_rpc.handle('fetch', {'orchStrRepo': '',
+                                       'shelf': 'cms',
+                                       'lv': fake_volume_name},
+                             None,
+                             fake_esm_fetch)
+
+        self.mox.ReplayAll()
+
+        self.driver.delete_volume(fake_volume)
+
+        self.mox.VerifyAll()
+
+    def test_ping_ok(self):
+        self.fake_rpc.handle('fetch', {}, None, '')
+
+        self.mox.ReplayAll()
+
+        self.driver._appliance.ping()
+
+        self.mox.VerifyAll()
+
+    def test_ping_failed(self):
+        self.mox.StubOutWithMock(self.driver._appliance, 'rpc')
+
+        def rpc(handle, url_params, data,
+                allow_empty_response=True):
+            raise Exception("Some exception")
+
+        self.driver._appliance.rpc = rpc
+
+        self.mox.ReplayAll()
+
+        self.assertRaises(exception.CoraidESMNotAvailable,
+                          self.driver._appliance.ping)
+
+        self.mox.VerifyAll()
+
+    def test_delete_not_existing_lun(self):
+        delete_volume_request = {'addr': 'cms',
+                                 'data': {
+                                     'repoName': fake_repository_name,
+                                     'lvName': fake_volume_name},
+                                 'op': 'orchStrLun/verified',
+                                 'args': 'delete'}
+        pack_data(delete_volume_request)
+
+        self.fake_rpc.handle('configure', {}, [delete_volume_request],
+                             {'configState': 'completedSuccessfully'})
+
+        self.fake_rpc.handle('fetch', {'orchStrRepo': '',
+                                       'shelf': 'cms',
+                                       'lv': fake_volume_name},
+                             None,
+                             fake_esm_fetch_no_volume)
+
+        self.mox.ReplayAll()
+
+        self.assertRaises(
+            exception.VolumeNotFound,
+            self.driver._appliance.delete_lun,
+            fake_volume['name'])
+
+        self.mox.VerifyAll()
+
+    def test_delete_not_existing_volume_appliance_is_ok(self):
+        self.mox.StubOutWithMock(self.driver._appliance, 'delete_lun')
+
+        def delete_lun(volume_name):
+            raise exception.VolumeNotFound(volume_id=fake_volume['name'])
+
+        self.driver._appliance.delete_lun = delete_lun
+
+        self.mox.StubOutWithMock(self.driver._appliance, 'ping')
+
+        def ping():
+            pass
+
+        self.driver._appliance.ping = ping
+
+        self.mox.ReplayAll()
+
+        self.driver.delete_volume(fake_volume)
+
+        self.mox.VerifyAll()
+
+    def test_delete_not_existing_volume_sleeping_appliance(self):
+        self.mox.StubOutWithMock(self.driver._appliance, 'delete_lun')
+
+        def delete_lun(volume_name):
+            raise exception.VolumeNotFound(volume_id=fake_volume['name'])
+
+        self.driver._appliance.delete_lun = delete_lun
+
+        self.mox.StubOutWithMock(self.driver._appliance, 'ping')
+
+        def ping():
+            raise exception.CoraidESMNotAvailable(reason="Any reason")
+
+        self.driver._appliance.ping = ping
+
+        self.mox.ReplayAll()
+
+        self.assertRaises(exception.CoraidESMNotAvailable,
+                          self.driver.delete_volume,
+                          fake_volume)
+
+        self.mox.VerifyAll()
 
     def test_create_snapshot(self):
-        setattr(self.esm_mock, 'create_snapshot',
-                lambda *_: True)
-        self.drv.create_snapshot(fake_snapshot)
+        fetch_request = {'shelf': 'cms',
+                         'orchStrRepo': '',
+                         'lv': fake_volume_name}
+        self.fake_rpc.handle('fetch', fetch_request, None,
+                             fake_esm_fetch)
+
+        create_snapshot_request = {'addr': 'cms',
+                                   'data': {
+                                       'repoName': fake_repository_name,
+                                       'lvName': fake_volume_name,
+                                       'newLvName': fake_snapshot_name},
+                                   'op': 'orchStrLunMods',
+                                   'args': 'addClSnap'}
+        pack_data(create_snapshot_request)
+        self.fake_rpc.handle('configure', {}, [create_snapshot_request],
+                             {'configState': 'completedSuccessfully'})
+
+        self.mox.ReplayAll()
+
+        self.driver.create_snapshot(fake_snapshot)
+
+        self.mox.VerifyAll()
 
     def test_delete_snapshot(self):
-        setattr(self.esm_mock, 'delete_snapshot',
-                lambda *_: True)
-        self.drv.delete_snapshot(fake_snapshot)
+        fetch_request = {'shelf': 'cms',
+                         'orchStrRepo': '',
+                         'lv': fake_snapshot_name}
+        self.fake_rpc.handle('fetch', fetch_request, None,
+                             fake_esm_fetch)
+
+        delete_snapshot_request = {'addr': 'cms',
+                                   'data': {
+                                       'repoName': fake_repository_name,
+                                       'lvName': fake_snapshot_name},
+                                   'op': 'orchStrLunMods',
+                                   'args': 'delClSnap'}
+        pack_data(delete_snapshot_request)
+        self.fake_rpc.handle('configure', {}, [delete_snapshot_request],
+                             {'configState': 'completedSuccessfully'})
+
+        self.mox.ReplayAll()
+
+        self.driver.delete_snapshot(fake_snapshot)
+
+        self.mox.VerifyAll()
 
     def test_create_volume_from_snapshot(self):
-        self.esm_mock.create_volume_from_snapshot(
-            fake_volume,
-            fake_snapshot).AndReturn(True)
-        mox.Replay(self.esm_mock)
-        self.esm_mock.create_volume_from_snapshot(fake_volume, fake_snapshot)
-        mox.Verify(self.esm_mock)
-
-    def test_create_volume_from_snapshot_bigger(self):
-        self.esm_mock.create_volume_from_snapshot(
-            fake_volume,
-            fake_snapshot).AndReturn(True)
-        self.esm_mock.resize_volume(fake_volume_name,
-                                    '20').AndReturn(True)
-        mox.Replay(self.esm_mock)
-        self.esm_mock.create_volume_from_snapshot(fake_volume, fake_snapshot)
-        self.esm_mock.resize_volume(fake_volume_name, '20')
-        mox.Verify(self.esm_mock)
+        self.mock_volume_types()
+
+        self.mox.StubOutWithMock(self.driver._appliance, 'resize_volume')
+        self.driver._appliance.resize_volume(fake_volume_name,
+                                             fake_volume['size'])\
+            .AndReturn(None)
+
+        fetch_request = {'shelf': 'cms',
+                         'orchStrRepo': '',
+                         'lv': fake_snapshot_name}
+        self.fake_rpc.handle('fetch', fetch_request, None,
+                             fake_esm_fetch)
+
+        create_clone_request = {'addr': 'cms',
+                                'data': {
+                                    'lvName': fake_snapshot_name,
+                                    'repoName': fake_repository_name,
+                                    'newLvName': fake_volume_name,
+                                    'newRepoName': fake_repository_name},
+                                'op': 'orchStrLunMods',
+                                'args': 'addClone'}
+        pack_data(create_clone_request)
+        self.fake_rpc.handle('configure', {}, [create_clone_request],
+                             {'configState': 'completedSuccessfully'})
+
+        self.mox.ReplayAll()
+
+        self.driver.create_volume_from_snapshot(fake_volume, fake_snapshot)
+
+        self.mox.VerifyAll()
+
+    def test_initialize_connection(self):
+        fetch_request = {'shelf': 'cms',
+                         'orchStrRepo': '',
+                         'lv': fake_volume_name}
+        self.fake_rpc.handle('fetch', fetch_request, None,
+                             fake_esm_fetch)
+
+        self.mox.ReplayAll()
+
+        connection = self.driver.initialize_connection(fake_volume, {})
+
+        self.mox.VerifyAll()
+
+        self.assertEqual(connection['driver_volume_type'], 'aoe')
+        self.assertEqual(connection['data']['target_shelf'], fake_shelf)
+        self.assertEqual(connection['data']['target_lun'], fake_lun)
+
+    def test_get_repository_capabilities(self):
+        reply = [[{}, {'reply': [
+            {'name': 'repo1',
+             'profile':
+                {'fullName': 'Bronze-Bronze:Profile1'}},
+            {'name': 'repo2',
+             'profile':
+                {'fullName': 'Bronze-Bronze:Profile2'}}]}]]
+
+        self.fake_rpc.handle('fetch', {'orchStrRepo': ''}, None,
+                             reply)
+
+        self.mox.ReplayAll()
+
+        capabilities = self.driver.get_volume_stats(refresh=True)
+
+        self.mox.VerifyAll()
+
+        self.assertEqual(
+            capabilities[fake_coraid_repository_key],
+            'Bronze-Bronze:Profile1:repo1 Bronze-Bronze:Profile2:repo2')
+
+    def test_create_cloned_volume(self):
+        self.mock_volume_types([fake_repository_name])
+
+        fetch_request = {'shelf': 'cms',
+                         'orchStrRepo': '',
+                         'lv': fake_volume_name}
+        self.fake_rpc.handle('fetch', fetch_request, None,
+                             fake_esm_fetch)
+
+        shelf_lun = '{0}.{1}'.format(fake_shelf, fake_lun)
+        create_clone_request = {'addr': 'cms',
+                                'data': {
+                                    'shelfLun': shelf_lun,
+                                    'lvName': fake_volume_name,
+                                    'repoName': fake_repository_name,
+                                    'newLvName': fake_clone_name,
+                                    'newRepoName': fake_repository_name},
+                                'op': 'orchStrLunMods',
+                                'args': 'addClone'}
+        pack_data(create_clone_request)
+        self.fake_rpc.handle('configure', {}, [create_clone_request],
+                             {'configState': 'completedSuccessfully'})
+
+        self.mox.ReplayAll()
+
+        self.driver.create_cloned_volume(fake_clone_volume, fake_volume)
+
+        self.mox.VerifyAll()
+
+    def test_create_cloned_volume_with_resize(self):
+        self.mock_volume_types([fake_repository_name])
+
+        self.mox.StubOutWithMock(self.driver._appliance, 'resize_volume')
+        self.driver._appliance.resize_volume(fake_big_clone_volume['name'],
+                                             fake_big_clone_volume['size'])\
+            .AndReturn(None)
+
+        fetch_request = {'shelf': 'cms',
+                         'orchStrRepo': '',
+                         'lv': fake_volume_name}
+        self.fake_rpc.handle('fetch', fetch_request, None,
+                             fake_esm_fetch)
+
+        shelf_lun = '{0}.{1}'.format(fake_shelf, fake_lun)
+        create_clone_request = {'addr': 'cms',
+                                'data': {
+                                    'shelfLun': shelf_lun,
+                                    'lvName': fake_volume_name,
+                                    'repoName': fake_repository_name,
+                                    'newLvName': fake_clone_name,
+                                    'newRepoName': fake_repository_name},
+                                'op': 'orchStrLunMods',
+                                'args': 'addClone'}
+        pack_data(create_clone_request)
+        self.fake_rpc.handle('configure', {}, [create_clone_request],
+                             {'configState': 'completedSuccessfully'})
+
+        self.mox.ReplayAll()
+
+        self.driver.create_cloned_volume(fake_big_clone_volume, fake_volume)
+
+        self.mox.VerifyAll()
+
+    def test_create_cloned_volume_in_different_repository(self):
+        self.mock_volume_types([fake_repository_name + '_another'])
+
+        fetch_request = {'shelf': 'cms',
+                         'orchStrRepo': '',
+                         'lv': fake_volume_name}
+        self.fake_rpc.handle('fetch', fetch_request, None,
+                             fake_esm_fetch)
+
+        self.mox.ReplayAll()
+
+        self.assertRaises(
+            exception.CoraidException,
+            self.driver.create_cloned_volume,
+            fake_clone_volume,
+            fake_volume)
+
+        self.mox.VerifyAll()
 
     def test_extend_volume(self):
-        self.esm_mock.resize_volume(fake_volume_name,
-                                    '20').AndReturn(True)
-        mox.Replay(self.esm_mock)
-        self.esm_mock.resize_volume(fake_volume_name, '20')
-        mox.Verify(self.esm_mock)
+        self.mox.StubOutWithMock(self.driver._appliance, 'resize_volume')
+        self.driver._appliance.resize_volume(fake_volume_name, 10)\
+            .AndReturn(None)
+
+        self.mox.ReplayAll()
+
+        self.driver.extend_volume(fake_volume, 10)
 
+        self.mox.VerifyAll()
 
-class TestCoraidRESTClient(test.TestCase):
+
+class AutoReloginCoraidTestCase(test.TestCase):
     def setUp(self):
-        super(TestCoraidRESTClient, self).setUp()
-        self.stubs.Set(cookielib, 'CookieJar', lambda *_: True)
-        self.stubs.Set(urllib2, 'build_opener', lambda *_: True)
-        self.stubs.Set(urllib2, 'HTTPCookieProcessor', lambda *_: True)
-        self.stubs.Set(CoraidRESTClient, '_login', lambda *_: True)
-        self.rest_mock = self.mox.CreateMockAnything()
-        self.stubs.Set(coraid, 'CoraidRESTClient',
-                       lambda *_, **__: self.rest_mock)
-        self.drv = CoraidRESTClient(fake_esm_ipaddress,
-                                    fake_esm_username,
-                                    fake_esm_group,
-                                    fake_esm_password)
-
-    def test__get_group_id(self):
-        setattr(self.rest_mock, '_get_group_id',
-                lambda *_: True)
-        self.assertEquals(self.drv._get_group_id(fake_esm_group,
-                                                 fake_login_reply),
-                          fake_group_id)
-
-    def test__set_group(self):
-        setattr(self.rest_mock, '_set_group',
-                lambda *_: fake_group_id)
-        self.stubs.Set(CoraidRESTClient, '_admin_esm_cmd',
-                       lambda *_: fake_login_reply)
-        self.drv._set_group(fake_login_reply)
-
-    def test__set_group_fails_no_group(self):
-        setattr(self.rest_mock, '_set_group',
-                lambda *_: False)
-        self.stubs.Set(CoraidRESTClient, '_admin_esm_cmd',
-                       lambda *_: fake_login_reply_group_fail)
-        self.assertRaises(CoraidESMException,
-                          self.drv._set_group,
-                          fake_login_reply_group_fail)
-
-    def test__configure(self):
-        setattr(self.rest_mock, '_configure',
-                lambda *_: True)
-        self.stubs.Set(CoraidRESTClient, '_esm_cmd',
-                       lambda *_: fake_esm_success)
-        self.drv._configure(fake_configure_data)
-
-    def test__get_volume_info(self):
-        setattr(self.rest_mock, '_get_volume_info',
-                lambda *_: fake_volume_info)
-        self.stubs.Set(CoraidRESTClient, '_esm_cmd',
-                       lambda *_: fake_esm_fetch)
-        self.drv._get_volume_info(fake_volume_name)
-
-    def test__get_lun_address(self):
-        setattr(self.rest_mock, '_get_lun_address',
-                lambda *_: fake_lun_info)
-        self.stubs.Set(CoraidRESTClient, '_get_volume_info',
-                       lambda *_: fake_volume_info)
-        self.drv._get_lun_address(fake_volume_name)
-
-    def test_create_lun(self):
-        setattr(self.rest_mock, 'create_lun',
-                lambda *_: True)
-        self.stubs.Set(CoraidRESTClient, '_configure',
-                       lambda *_: fake_esm_success)
-        self.rest_mock.create_lun(fake_volume_name, '10',
-                                  fake_repository_name)
-        self.drv.create_lun(fake_volume_name, '10',
-                            fake_repository_name)
-
-    def test_delete_lun_ok(self):
-        """Test Delete Volume classic case."""
-        setattr(self.rest_mock, 'delete_lun',
-                lambda *_: self.mox.CreateMockAnything())
-        self.stubs.Set(CoraidRESTClient, '_get_volume_info',
-                       lambda *_: fake_volume_info)
-        self.stubs.Set(CoraidRESTClient, '_configure',
-                       lambda *_: fake_esm_success)
-        self.rest_mock.delete_lun(fake_volume_name)
-        result = self.drv.delete_lun(fake_volume_name)
-        self.assertTrue(result)
-
-    def test_delete_lun_in_error(self):
-        """Test Delete Volume in Error State."""
-        setattr(self.rest_mock, 'delete_lun',
-                lambda *_: self.mox.CreateMockAnything())
-        self.stubs.Set(CoraidRESTClient, '_get_volume_info',
-                       lambda *_: Exception)
-        self.stubs.Set(CoraidRESTClient, '_check_esm_alive',
-                       lambda *_: True)
-        self.rest_mock.delete_lun(fake_volume_name)
-        result = self.drv.delete_lun(fake_volume_name)
-        self.assertTrue(result)
-
-    def test_delete_lun_esm_unavailable(self):
-        """Test Delete Volume with ESM Unavailable."""
-        setattr(self.rest_mock, 'delete_lun',
-                lambda *_: self.mox.CreateMockAnything())
-        self.stubs.Set(CoraidRESTClient, '_get_volume_info',
-                       lambda *_: Exception)
-        self.stubs.Set(CoraidRESTClient, '_check_esm_alive',
-                       lambda *_: False)
-        self.rest_mock.delete_lun(fake_volume_name)
-        result = self.drv.delete_lun(fake_volume_name)
-        self.assertRaises(Exception, result)
+        super(AutoReloginCoraidTestCase, self).setUp()
+        self.mox = mox.Mox()
 
-    def test_create_snapshot(self):
-        setattr(self.rest_mock, 'create_snapshot',
-                lambda *_: True)
-        self.stubs.Set(CoraidRESTClient, '_get_volume_info',
-                       lambda *_: fake_volume_info)
-        self.stubs.Set(CoraidRESTClient, '_configure',
-                       lambda *_: fake_esm_success)
-        self.drv.create_snapshot(fake_volume_name,
-                                 fake_volume_name)
+        self.rest_client = coraid.CoraidRESTClient('https://fake')
+        self.appliance = coraid.CoraidAppliance(self.rest_client,
+                                                'fake_username',
+                                                'fake_password',
+                                                'fake_group')
 
-    def test_delete_snapshot(self):
-        setattr(self.rest_mock, 'delete_snapshot',
-                lambda *_: True)
-        self.stubs.Set(CoraidRESTClient, '_get_volume_info',
-                       lambda *_: fake_volume_info)
-        self.stubs.Set(CoraidRESTClient, '_configure',
-                       lambda *_: fake_esm_success)
-        self.drv.delete_snapshot(fake_volume_name)
+    def tearDown(self):
+        self.mox.UnsetStubs()
+        super(AutoReloginCoraidTestCase, self).tearDown()
 
-    def test_create_volume_from_snapshot(self):
-        setattr(self.rest_mock, 'create_volume_from_snapshot',
-                lambda *_: True)
-        self.stubs.Set(CoraidRESTClient, '_get_volume_info',
-                       lambda *_: fake_volume_info)
-        self.stubs.Set(CoraidRESTClient, '_configure',
-                       lambda *_: fake_esm_success)
-        self.drv.create_volume_from_snapshot(fake_volume_name,
-                                             fake_volume_name,
-                                             fake_repository_name)
+    def _test_auto_relogin_fail(self, state):
+        self.mox.StubOutWithMock(self.rest_client, 'rpc')
 
-    def test_resize_volume(self):
-        setattr(self.rest_mock, 'resize_volume',
-                lambda *_: True)
-        self.stubs.Set(CoraidRESTClient, '_get_volume_info',
-                       lambda *_: fake_volume_info)
-        self.stubs.Set(CoraidRESTClient, '_configure',
-                       lambda *_: fake_esm_success)
-        self.drv.resize_volume(fake_volume_name,
-                               '20')
+        self.rest_client.rpc('fake_handle', {}, None, False).\
+            AndReturn({'state': state,
+                       'metaCROp': 'reboot'})
+
+        self.rest_client.rpc('fake_handle', {}, None, False).\
+            AndReturn({'state': state,
+                       'metaCROp': 'reboot'})
+
+        self.rest_client.rpc('fake_handle', {}, None, False).\
+            AndReturn({'state': state,
+                       'metaCROp': 'reboot'})
+
+        self.mox.StubOutWithMock(self.appliance, '_ensure_session')
+        self.appliance._ensure_session().AndReturn(None)
+
+        self.mox.StubOutWithMock(self.appliance, '_relogin')
+        self.appliance._relogin().AndReturn(None)
+        self.appliance._relogin().AndReturn(None)
+
+        self.mox.ReplayAll()
+
+        self.assertRaises(exception.CoraidESMReloginFailed,
+                          self.appliance.rpc,
+                          'fake_handle', {}, None, False)
+
+        self.mox.VerifyAll()
+
+    def test_auto_relogin_fail_admin(self):
+        self._test_auto_relogin_fail('GeneralAdminFailure')
+
+    def test_auto_relogin_fail_inactivity(self):
+        self._test_auto_relogin_fail('passwordInactivityTimeout')
+
+    def test_auto_relogin_fail_absolute(self):
+        self._test_auto_relogin_fail('passwordAbsoluteTimeout')
+
+    def test_auto_relogin_success(self):
+        self.mox.StubOutWithMock(self.rest_client, 'rpc')
+
+        self.rest_client.rpc('fake_handle', {}, None, False).\
+            AndReturn({'state': 'GeneralAdminFailure',
+                       'metaCROp': 'reboot'})
+
+        self.rest_client.rpc('fake_handle', {}, None, False).\
+            AndReturn({'state': 'ok'})
+
+        self.mox.StubOutWithMock(self.appliance, '_ensure_session')
+        self.appliance._ensure_session().AndReturn(None)
+
+        self.mox.StubOutWithMock(self.appliance, '_relogin')
+        self.appliance._relogin().AndReturn(None)
+
+        self.mox.ReplayAll()
+
+        reply = self.appliance.rpc('fake_handle', {}, None, False)
+
+        self.mox.VerifyAll()
+
+        self.assertEqual(reply['state'], 'ok')
+
+
+class CoraidDriverImageTestCases(CoraidDriverTestCase):
+    def setUp(self):
+        super(CoraidDriverImageTestCases, self).setUp()
+
+        self.fake_dev_path = '/dev/ether/fake_dev'
+
+        self.fake_connection = {'driver_volume_type': 'aoe',
+                                'data': {'target_shelf': fake_shelf,
+                                         'target_lun': fake_lun}}
+
+        self.fake_volume_info = {
+            'shelf': self.fake_connection['data']['target_shelf'],
+            'lun': self.fake_connection['data']['target_lun']}
+
+        self.mox.StubOutWithMock(self.driver, 'initialize_connection')
+        self.driver.initialize_connection(fake_volume, {})\
+            .AndReturn(self.fake_connection)
+
+        self.mox.StubOutWithMock(self.driver, 'terminate_connection')
+        self.driver.terminate_connection(fake_volume, mox.IgnoreArg())\
+            .AndReturn(None)
+
+        self.mox.StubOutWithMock(connector, 'get_connector_properties')
+        connector.get_connector_properties().AndReturn({})
+
+        self.mox.StubOutWithMock(connector.InitiatorConnector, 'factory')
+
+        aoe_initiator = self.mox.CreateMockAnything()
+
+        connector.InitiatorConnector.factory('aoe', use_multipath=False)\
+            .AndReturn(aoe_initiator)
+
+        aoe_initiator\
+            .connect_volume(self.fake_connection['data'])\
+            .AndReturn({'path': self.fake_dev_path})
+
+        aoe_initiator.check_valid_device(self.fake_dev_path)\
+            .AndReturn(True)
+
+        aoe_initiator.disconnect_volume(
+            {'target_shelf': self.fake_volume_info['shelf'],
+             'target_lun': self.fake_volume_info['lun']}, mox.IgnoreArg())
+
+    def test_copy_volume_to_image(self):
+        fake_image_service = 'fake-image-service'
+        fake_image_meta = 'fake-image-meta'
+
+        self.mox.StubOutWithMock(image_utils, 'upload_volume')
+        image_utils.upload_volume({},
+                                  fake_image_service,
+                                  fake_image_meta,
+                                  self.fake_dev_path)
+
+        self.mox.ReplayAll()
+        self.driver.copy_volume_to_image({},
+                                         fake_volume,
+                                         fake_image_service,
+                                         fake_image_meta)
+
+        self.mox.VerifyAll()
+
+    def test_copy_image_to_volume(self):
+        fake_image_service = 'fake-image-service'
+        fake_image_id = 'fake-image-id;'
+
+        self.mox.StubOutWithMock(image_utils, 'fetch_to_raw')
+        image_utils.fetch_to_raw({},
+                                 fake_image_service,
+                                 fake_image_id,
+                                 self.fake_dev_path)
+
+        self.mox.ReplayAll()
+
+        self.driver.copy_image_to_volume({},
+                                         fake_volume,
+                                         fake_image_service,
+                                         fake_image_id)
+
+        self.mox.VerifyAll()
index 23a656a6e38c950a2ca0bc066563906fe481605e..354dd885ac05c35d1f03cf26a41ae2753a31b8bb 100644 (file)
 Desc    : Driver to store volumes on Coraid Appliances.
 Require : Coraid EtherCloud ESM, Coraid VSX and Coraid SRX.
 Author  : Jean-Baptiste RANSY <openstack@alyseo.com>
+Author  : Alex Zasimov <azasimov@mirantis.com>
+Author  : Nikolay Sobolevsky <nsobolevsky@mirantis.com>
 Contrib : Larry Matter <support@coraid.com>
 """
 
 import cookielib
-import time
+import math
+import urllib
 import urllib2
+import urlparse
 
 from oslo.config import cfg
 
+from cinder import exception
 from cinder.openstack.common import jsonutils
+from cinder.openstack.common import lockutils
 from cinder.openstack.common import log as logging
+from cinder import units
 from cinder.volume import driver
 from cinder.volume import volume_types
 
@@ -57,391 +64,473 @@ CONF = cfg.CONF
 CONF.register_opts(coraid_opts)
 
 
-class CoraidException(Exception):
-    def __init__(self, message=None, error=None):
-        super(CoraidException, self).__init__(message, error)
+ESM_SESSION_EXPIRED_STATES = ['GeneralAdminFailure',
+                              'passwordInactivityTimeout',
+                              'passwordAbsoluteTimeout']
 
-    def __str__(self):
-        return '%s: %s' % self.args
 
+class CoraidRESTClient(object):
+    """Executes REST RPC requests on Coraid ESM EtherCloud Appliance."""
+
+    def __init__(self, esm_url):
+        self._check_esm_url(esm_url)
+        self._esm_url = esm_url
+        self._cookie_jar = cookielib.CookieJar()
+        self._url_opener = urllib2.build_opener(
+            urllib2.HTTPCookieProcessor(self._cookie_jar))
+
+    def _check_esm_url(self, esm_url):
+        splitted = urlparse.urlsplit(esm_url)
+        if splitted.scheme != 'https':
+            raise ValueError(
+                _('Invalid ESM url scheme "%s". Supported https only.') %
+                splitted.scheme)
+
+    @lockutils.synchronized('coraid_rpc', 'cinder-', False)
+    def rpc(self, handle, url_params, data, allow_empty_response=False):
+        return self._rpc(handle, url_params, data, allow_empty_response)
+
+    def _rpc(self, handle, url_params, data, allow_empty_response):
+        """Execute REST RPC using url <esm_url>/handle?url_params.
+
+        Send JSON encoded data in body of POST request.
+
+        Exceptions:
+            urllib2.URLError
+              1. Name or service not found (e.reason is socket.gaierror)
+              2. Socket blocking operation timeout (e.reason is
+                 socket.timeout)
+              3. Network IO error (e.reason is socket.error)
+
+            urllib2.HTTPError
+              1. HTTP 404, HTTP 500 etc.
+
+            CoraidJsonEncodeFailure - bad REST response
+        """
+        # Handle must be simple path, for example:
+        #    /configure
+        if '?' in handle or '&' in handle:
+            raise ValueError(_('Invalid REST handle name. Expected path.'))
+
+        # Request url includes base ESM url, handle path and optional
+        # URL params.
+        rest_url = urlparse.urljoin(self._esm_url, handle)
+        encoded_url_params = urllib.urlencode(url_params)
+        if encoded_url_params:
+            rest_url += '?' + encoded_url_params
+
+        if data is None:
+            json_request = None
+        else:
+            json_request = jsonutils.dumps(data)
+
+        request = urllib2.Request(rest_url, json_request)
+        response = self._url_opener.open(request).read()
 
-class CoraidRESTException(CoraidException):
-    pass
+        try:
+            if not response and allow_empty_response:
+                reply = {}
+            else:
+                reply = jsonutils.loads(response)
+        except (TypeError, ValueError) as exc:
+            msg = (_('Call to json.loads() failed: %(ex)s.'
+                     ' Response: %(resp)s') %
+                   {'ex': exc, 'resp': response})
+            raise exception.CoraidJsonEncodeFailure(msg)
 
+        return reply
 
-class CoraidESMException(CoraidException):
-    pass
 
+def to_coraid_kb(gb):
+    return math.ceil(float(gb) * units.GiB / 1000)
 
-class CoraidRESTClient(object):
-    """Executes volume driver commands on Coraid ESM EtherCloud Appliance."""
-
-    def __init__(self, ipaddress, user, group, password):
-        self.url = "https://%s:8443/" % ipaddress
-        self.user = user
-        self.group = group
-        self.password = password
-        self.session = False
-        self.cookiejar = cookielib.CookieJar()
-        self.urlOpener = urllib2.build_opener(
-            urllib2.HTTPCookieProcessor(self.cookiejar))
-        LOG.debug(_('Running with CoraidDriver for ESM EtherCLoud'))
+
+def coraid_volume_size(gb):
+    return '{0}K'.format(to_coraid_kb(gb))
+
+
+class CoraidAppliance(object):
+    def __init__(self, rest_client, username, password, group):
+        self._rest_client = rest_client
+        self._username = username
+        self._password = password
+        self._group = group
+        self._logined = False
 
     def _login(self):
-        """Login and Session Handler."""
-        if not self.session or self.session < time.time():
-            url = ('admin?op=login&username=%s&password=%s' %
-                   (self.user, self.password))
-            data = 'Login'
-            reply = self._admin_esm_cmd(url, data)
-            if reply.get('state') == 'adminSucceed':
-                self.session = time.time() + 1100
-                msg = _('Update session cookie %(session)s')
-                LOG.debug(msg % dict(session=self.session))
-                self._set_group(reply)
-                return True
-            else:
-                errmsg = reply.get('message', '')
-                msg = _('Message : %(message)s')
-                raise CoraidESMException(msg % dict(message=errmsg))
-        return True
-
-    def _set_group(self, reply):
-        """Set effective group."""
-        if self.group:
-            group = self.group
-            groupId = self._get_group_id(group, reply)
-            if groupId:
-                url = ('admin?op=setRbacGroup&groupId=%s' % (groupId))
-                data = 'Group'
-                reply = self._admin_esm_cmd(url, data)
-                if reply.get('state') == 'adminSucceed':
-                    return True
-                else:
-                    errmsg = reply.get('message', '')
-                    msg = _('Error while trying to set group: %(message)s')
-                    raise CoraidRESTException(msg % dict(message=errmsg))
-            else:
-                msg = _('Unable to find group: %(group)s')
-                raise CoraidESMException(msg % dict(group=group))
-        return True
-
-    def _get_group_id(self, groupName, loginResult):
-        """Map group name to group ID."""
-        # NOTE(lmatter): All other groups are under the admin group
-        fullName = "admin group:%s" % groupName
-        groupId = False
-        for kid in loginResult['values']:
-            fullPath = kid['fullPath']
-            if fullPath == fullName:
-                return kid['groupId']
-        return False
-
-    def _esm_cmd(self, url=False, data=None):
-        self._login()
-        return self._admin_esm_cmd(url, data)
-
-    def _admin_esm_cmd(self, url=False, data=None):
-        """
-        _admin_esm_cmd represent the entry point to send requests to ESM
-        Appliance.  Send the HTTPS call, get response in JSON
-        convert response into Python Object and return it.
+        """Login into ESM.
+
+        Perform login request and return available groups.
+
+        :returns: dict -- map with group_name to group_id
         """
-        if url:
-            url = self.url + url
+        ADMIN_GROUP_PREFIX = 'admin group:'
 
-            req = urllib2.Request(url, data)
+        url_params = {'op': 'login',
+                      'username': self._username,
+                      'password': self._password}
+        reply = self._rest_client.rpc('admin', url_params, 'Login')
+        if reply['state'] != 'adminSucceed':
+            raise exception.CoraidESMBadCredentials()
 
-            try:
-                res = self.urlOpener.open(req).read()
-            except Exception:
-                raise CoraidRESTException(_('ESM urlOpen error'))
+        # Read groups map from login reply.
+        groups_map = {}
+        for group_info in reply.get('values', []):
+            full_group_name = group_info['fullPath']
+            if full_group_name.startswith(ADMIN_GROUP_PREFIX):
+                group_name = full_group_name[len(ADMIN_GROUP_PREFIX):]
+                groups_map[group_name] = group_info['groupId']
 
-            try:
-                res_json = jsonutils.loads(res)
-            except Exception:
-                raise CoraidRESTException(_('JSON Error'))
+        return groups_map
 
-            return res_json
-        else:
-            raise CoraidRESTException(_('Request without URL'))
+    def _set_effective_group(self, groups_map, group):
+        """Set effective group.
 
-    def _check_esm_alive(self):
+        Use groups_map returned from _login method.
+        """
         try:
-            url = self.url + 'fetch'
-            req = urllib2.Request(url)
-            code = self.urlOpener.open(req).getcode()
-            if code == '200':
-                return True
-            return False
-        except Exception:
-            return False
-
-    def _configure(self, data):
-        """In charge of all commands into 'configure'."""
-        url = 'configure'
-        LOG.debug(_('Configure data : %s'), data)
-        response = self._esm_cmd(url, data)
-        LOG.debug(_("Configure response : %s"), response)
-        if response:
-            if response.get('configState') == 'completedSuccessfully':
-                return True
+            group_id = groups_map[group]
+        except KeyError:
+            raise exception.CoraidESMBadGroup(group_name=group)
+
+        url_params = {'op': 'setRbacGroup',
+                      'groupId': group_id}
+        reply = self._rest_client.rpc('admin', url_params, 'Group')
+        if reply['state'] != 'adminSucceed':
+            raise exception.CoraidESMBadCredentials()
+
+        self._logined = True
+
+    def _ensure_session(self):
+        if not self._logined:
+            groups_map = self._login()
+            self._set_effective_group(groups_map, self._group)
+
+    def _relogin(self):
+        self._logined = False
+        self._ensure_session()
+
+    def rpc(self, handle, url_params, data, allow_empty_response=False):
+        self._ensure_session()
+
+        relogin_attempts = 3
+        # Do action, relogin if needed and repeat action.
+        while True:
+            reply = self._rest_client.rpc(handle, url_params, data,
+                                          allow_empty_response)
+
+            if ('state' in reply and
+                reply['state'] in ESM_SESSION_EXPIRED_STATES and
+                    reply['metaCROp'] == 'reboot'):
+                relogin_attempts -= 1
+                if relogin_attempts <= 0:
+                    raise exception.CoraidESMReloginFailed()
+                LOG.debug(_('Session is expired. Relogin on ESM.'))
+                self._relogin()
             else:
-                errmsg = response.get('message', '')
-                msg = _('Message : %(message)s')
-                raise CoraidESMException(msg % dict(message=errmsg))
-        return False
-
-    def _get_volume_info(self, volume_name):
-        """Retrive volume informations for a given volume name."""
-        url = 'fetch?shelf=cms&orchStrRepo&lv=%s' % (volume_name)
+                return reply
+
+    def _is_bad_config_state(self, reply):
+        return (not reply or
+                'configState' not in reply or
+                reply['configState'] != 'completedSuccessfully')
+
+    def configure(self, json_request):
+        reply = self.rpc('configure', {}, json_request)
+        if self._is_bad_config_state(reply):
+            # Calculate error message
+            if not reply:
+                message = _('Reply is empty.')
+            else:
+                message = reply.get('message', _('Error message is empty.'))
+            raise exception.CoraidESMConfigureError(message=message)
+        return reply
+
+    def esm_command(self, request):
+        request['data'] = jsonutils.dumps(request['data'])
+        return self.configure([request])
+
+    def get_volume_info(self, volume_name):
+        """Retrieve volume information for a given volume name."""
+        url_params = {'shelf': 'cms',
+                      'orchStrRepo': '',
+                      'lv': volume_name}
+        reply = self.rpc('fetch', url_params, None)
         try:
-            response = self._esm_cmd(url)
-            info = response[0][1]['reply'][0]
-            return {"pool": info['lv']['containingPool'],
-                    "repo": info['repoName'],
-                    "vsxidx": info['lv']['lunIndex'],
-                    "index": info['lv']['lvStatus']['exportedLun']['lun'],
-                    "shelf": info['lv']['lvStatus']['exportedLun']['shelf']}
-        except Exception:
-            msg = _('Unable to retrive volume infos for volume %(volname)s')
-            raise CoraidESMException(msg % dict(volname=volume_name))
-
-    def _get_lun_address(self, volume_name):
-        """Return AoE Address for a given Volume."""
-        volume_info = self._get_volume_info(volume_name)
-        shelf = volume_info['shelf']
-        lun = volume_info['index']
-        return {'shelf': shelf, 'lun': lun}
-
-    def create_lun(self, volume_name, volume_size, repository):
-        """Create LUN on Coraid Backend Storage."""
-        data = '[{"addr":"cms","data":"{' \
-               '\\"servers\\":[\\"\\"],' \
-               '\\"repoName\\":\\"%s\\",' \
-               '\\"size\\":\\"%sG\\",' \
-               '\\"lvName\\":\\"%s\\"}",' \
-               '"op":"orchStrLun",' \
-               '"args":"add"}]' % (repository, volume_size,
-                                   volume_name)
-        return self._configure(data)
+            volume_info = reply[0][1]['reply'][0]
+        except (IndexError, KeyError):
+            raise exception.VolumeNotFound(volume_id=volume_name)
+        return {'pool': volume_info['lv']['containingPool'],
+                'repo': volume_info['repoName'],
+                'lun': volume_info['lv']['lvStatus']['exportedLun']['lun'],
+                'shelf': volume_info['lv']['lvStatus']['exportedLun']['shelf']}
+
+    def get_volume_repository(self, volume_name):
+        volume_info = self.get_volume_info(volume_name)
+        return volume_info['repo']
+
+    def get_all_repos(self):
+        reply = self.rpc('fetch', {'orchStrRepo': ''}, None)
+        try:
+            return reply[0][1]['reply']
+        except (IndexError, KeyError):
+            return []
 
-    def delete_lun(self, volume_name):
-        """Delete LUN."""
+    def ping(self):
         try:
-            volume_info = self._get_volume_info(volume_name)
-            repository = volume_info['repo']
-            data = '[{"addr":"cms","data":"{' \
-                   '\\"repoName\\":\\"%(repo)s\\",' \
-                   '\\"lvName\\":\\"%(volname)s\\"}",' \
-                   '"op":"orchStrLun/verified",' \
-                   '"args":"delete"}]' % dict(repo=repository,
-                                              volname=volume_name)
-            return self._configure(data)
-        except Exception:
-            if self._check_esm_alive():
-                return True
-            else:
-                return False
+            self.rpc('fetch', {}, None, allow_empty_response=True)
+        except Exception as e:
+            LOG.debug(_('Coraid Appliance ping failed: %s'), str(e))
+            raise exception.CoraidESMNotAvailable(reason=str(e))
+
+    def create_lun(self, repository_name, volume_name, volume_size_in_gb):
+        request = {'addr': 'cms',
+                   'data': {
+                       'servers': [],
+                       'repoName': repository_name,
+                       'lvName': volume_name,
+                       'size': coraid_volume_size(volume_size_in_gb)},
+                   'op': 'orchStrLun',
+                   'args': 'add'}
+        esm_result = self.esm_command(request)
+        LOG.debug(_('Volume "%(name)s" created with VSX LUN "%(lun)s"') %
+                  {'name': volume_name,
+                   'lun': esm_result['firstParam']})
+        return esm_result
+
+    def delete_lun(self, volume_name):
+        repository_name = self.get_volume_repository(volume_name)
+        request = {'addr': 'cms',
+                   'data': {
+                       'repoName': repository_name,
+                       'lvName': volume_name},
+                   'op': 'orchStrLun/verified',
+                   'args': 'delete'}
+        esm_result = self.esm_command(request)
+        LOG.debug(_('Volume "%s" deleted.'), volume_name)
+        return esm_result
+
+    def resize_volume(self, volume_name, new_volume_size_in_gb):
+        LOG.debug(_('Resize volume "%(name)s" to %(size)s') %
+                  {'name': volume_name,
+                   'size': new_volume_size_in_gb})
+        repository = self.get_volume_repository(volume_name)
+        LOG.debug(_('Repository for volume "%(name)s" found: "%(repo)s"') %
+                  {'name': volume_name,
+                   'repo': repository})
+
+        request = {'addr': 'cms',
+                   'data': {
+                       'lvName': volume_name,
+                       'newLvName': volume_name + '-resize',
+                       'size': coraid_volume_size(new_volume_size_in_gb),
+                       'repoName': repository},
+                   'op': 'orchStrLunMods',
+                   'args': 'resize'}
+        esm_result = self.esm_command(request)
+
+        LOG.debug(_('Volume "%(name)s" resized. New size is %(size)s') %
+                  {'name': volume_name,
+                   'size': new_volume_size_in_gb})
+        return esm_result
 
     def create_snapshot(self, volume_name, snapshot_name):
-        """Create Snapshot."""
-        volume_info = self._get_volume_info(volume_name)
-        repository = volume_info['repo']
-        data = '[{"addr":"cms","data":"{' \
-               '\\"repoName\\":\\"%s\\",' \
-               '\\"lvName\\":\\"%s\\",' \
-               '\\"newLvName\\":\\"%s\\"}",' \
-               '"op":"orchStrLunMods",' \
-               '"args":"addClSnap"}]' % (repository, volume_name,
-                                         snapshot_name)
-        return self._configure(data)
+        volume_repository = self.get_volume_repository(volume_name)
+        request = {'addr': 'cms',
+                   'data': {
+                       'repoName': volume_repository,
+                       'lvName': volume_name,
+                       'newLvName': snapshot_name},
+                   'op': 'orchStrLunMods',
+                   'args': 'addClSnap'}
+        esm_result = self.esm_command(request)
+        return esm_result
 
     def delete_snapshot(self, snapshot_name):
-        """Delete Snapshot."""
-        snapshot_info = self._get_volume_info(snapshot_name)
-        repository = snapshot_info['repo']
-        data = '[{"addr":"cms","data":"{' \
-               '\\"repoName\\":\\"%s\\",' \
-               '\\"lvName\\":\\"%s\\"}",' \
-               '"op":"orchStrLunMods",' \
-               '"args":"delClSnap"}]' % (repository, snapshot_name)
-        return self._configure(data)
-
-    def create_volume_from_snapshot(self, snapshot_name,
-                                    volume_name, repository):
-        """Create a LUN from a Snapshot."""
-        snapshot_info = self._get_volume_info(snapshot_name)
-        snapshot_repo = snapshot_info['repo']
-        data = '[{"addr":"cms","data":"{' \
-               '\\"lvName\\":\\"%s\\",' \
-               '\\"repoName\\":\\"%s\\",' \
-               '\\"newLvName\\":\\"%s\\",' \
-               '\\"newRepoName\\":\\"%s\\"}",' \
-               '"op":"orchStrLunMods",' \
-               '"args":"addClone"}]' % (snapshot_name, snapshot_repo,
-                                        volume_name, repository)
-        return self._configure(data)
-
-    def resize_volume(self, volume_name, volume_size):
-        volume_info = self._get_volume_info(volume_name)
-        repository = volume_info['repo']
-        data = '[{"addr":"cms","data":"{' \
-               '\\"lvName\\":\\"%s\\",' \
-               '\\"newLvSize\\":\\"%s\\"}",' \
-               '\\"repoName\\":\\"%s\\"}",' \
-               '"op":"orchStrLunMods",' \
-               '"args":"resizeVolume"}]' % (volume_name,
-                                            volume_size,
-                                            repository)
-        return self._configure(data)
+        repository_name = self.get_volume_repository(snapshot_name)
+        request = {'addr': 'cms',
+                   'data': {
+                       'repoName': repository_name,
+                       'lvName': snapshot_name},
+                   'op': 'orchStrLunMods',
+                   'args': 'delClSnap'}
+        esm_result = self.esm_command(request)
+        return esm_result
+
+    def create_volume_from_snapshot(self,
+                                    snapshot_name,
+                                    volume_name,
+                                    dest_repository_name):
+        snapshot_repo = self.get_volume_repository(snapshot_name)
+        request = {'addr': 'cms',
+                   'data': {
+                       'lvName': snapshot_name,
+                       'repoName': snapshot_repo,
+                       'newLvName': volume_name,
+                       'newRepoName': dest_repository_name},
+                   'op': 'orchStrLunMods',
+                   'args': 'addClone'}
+        esm_result = self.esm_command(request)
+        return esm_result
+
+    def clone_volume(self,
+                     src_volume_name,
+                     dst_volume_name,
+                     dst_repository_name):
+        src_volume_info = self.get_volume_info(src_volume_name)
+
+        if src_volume_info['repo'] != dst_repository_name:
+            raise exception.CoraidException(
+                _('Cannot create clone volume in different repository.'))
+
+        request = {'addr': 'cms',
+                   'data': {
+                       'shelfLun': '{0}.{1}'.format(src_volume_info['shelf'],
+                                                    src_volume_info['lun']),
+                       'lvName': src_volume_name,
+                       'repoName': src_volume_info['repo'],
+                       'newLvName': dst_volume_name,
+                       'newRepoName': dst_repository_name},
+                   'op': 'orchStrLunMods',
+                   'args': 'addClone'}
+        return self.esm_command(request)
 
 
 class CoraidDriver(driver.VolumeDriver):
     """This is the Class to set in cinder.conf (volume_driver)."""
 
+    VERSION = '1.0.0'
+
     def __init__(self, *args, **kwargs):
         super(CoraidDriver, self).__init__(*args, **kwargs)
         self.configuration.append_config_values(coraid_opts)
+        self._appliance = None
+
+        self._stats = {'driver_version': self.VERSION,
+                       'free_capacity_gb': 'unknown',
+                       'reserved_percentage': 0,
+                       'storage_protocol': 'aoe',
+                       'total_capacity_gb': 'unknown',
+                       'vendor_name': 'Coraid'}
+        backend_name = self.configuration.safe_get('volume_backend_name')
+        self._stats['volume_backend_name'] = backend_name or 'EtherCloud ESM'
 
     def do_setup(self, context):
         """Initialize the volume driver."""
-        self.esm = CoraidRESTClient(self.configuration.coraid_esm_address,
-                                    self.configuration.coraid_user,
-                                    self.configuration.coraid_group,
-                                    self.configuration.coraid_password)
+        esm_url = "https://{0}:8443".format(
+            self.configuration.coraid_esm_address)
+
+        rest_client = CoraidRESTClient(esm_url)
+        self._appliance = CoraidAppliance(rest_client,
+                                          self.configuration.coraid_user,
+                                          self.configuration.coraid_password,
+                                          self.configuration.coraid_group)
 
     def check_for_setup_error(self):
         """Return an error if prerequisites aren't met."""
-        if not self.esm._login():
-            raise LookupError(_("Cannot login on Coraid ESM"))
+        self._appliance.ping()
 
     def _get_repository(self, volume_type):
-        """
-        Return the ESM Repository from the Volume Type.
+        """Get the ESM Repository from the Volume Type.
+
         The ESM Repository is stored into a volume_type_extra_specs key.
         """
         volume_type_id = volume_type['id']
         repository_key_name = self.configuration.coraid_repository_key
         repository = volume_types.get_volume_type_extra_specs(
             volume_type_id, repository_key_name)
-        return repository
+        # Remove <in> keyword from repository name if needed
+        if repository.startswith('<in> '):
+            return repository[len('<in> '):]
+        else:
+            return repository
 
     def create_volume(self, volume):
         """Create a Volume."""
-        try:
-            repository = self._get_repository(volume['volume_type'])
-            self.esm.create_lun(volume['name'], volume['size'], repository)
-        except Exception:
-            msg = _('Fail to create volume %(volname)s')
-            LOG.debug(msg % dict(volname=volume['name']))
-            raise
+        repository = self._get_repository(volume['volume_type'])
+        self._appliance.create_lun(repository, volume['name'], volume['size'])
         # NOTE(jbr_): The manager currently interprets any return as
         # being the model_update for provider location.
         # return None to not break it (thank to jgriffith and DuncanT)
         return
 
+    def create_cloned_volume(self, volume, src_vref):
+        dst_volume_repository = self._get_repository(volume['volume_type'])
+
+        self._appliance.clone_volume(src_vref['name'],
+                                     volume['name'],
+                                     dst_volume_repository)
+
+        if volume['size'] != src_vref['size']:
+            self._appliance.resize_volume(volume['name'], volume['size'])
+
+        return
+
     def delete_volume(self, volume):
         """Delete a Volume."""
         try:
-            self.esm.delete_lun(volume['name'])
-        except Exception:
-            msg = _('Failed to delete volume %(volname)s')
-            LOG.debug(msg % dict(volname=volume['name']))
-            raise
-        return
+            self._appliance.delete_lun(volume['name'])
+        except exception.VolumeNotFound:
+            self._appliance.ping()
 
     def create_snapshot(self, snapshot):
         """Create a Snapshot."""
-        volume_name = (self.configuration.volume_name_template
-                       % snapshot['volume_id'])
-        snapshot_name = (self.configuration.snapshot_name_template
-                         % snapshot['id'])
-        try:
-            self.esm.create_snapshot(volume_name, snapshot_name)
-        except Exception as e:
-            msg = _('Failed to Create Snapshot %(snapname)s')
-            LOG.debug(msg % dict(snapname=snapshot_name))
-            raise
-        return
+        volume_name = snapshot['volume_name']
+        snapshot_name = snapshot['name']
+        self._appliance.create_snapshot(volume_name, snapshot_name)
 
     def delete_snapshot(self, snapshot):
         """Delete a Snapshot."""
-        snapshot_name = (self.configuration.snapshot_name_template
-                         % snapshot['id'])
-        try:
-            self.esm.delete_snapshot(snapshot_name)
-        except Exception:
-            msg = _('Failed to Delete Snapshot %(snapname)s')
-            LOG.debug(msg % dict(snapname=snapshot_name))
-            raise
-        return
+        snapshot_name = snapshot['name']
+        self._appliance.delete_snapshot(snapshot_name)
 
     def create_volume_from_snapshot(self, volume, snapshot):
         """Create a Volume from a Snapshot."""
-        snapshot_name = (self.configuration.snapshot_name_template
-                         % snapshot['id'])
+        snapshot_name = snapshot['name']
         repository = self._get_repository(volume['volume_type'])
-        try:
-            self.esm.create_volume_from_snapshot(snapshot_name,
-                                                 volume['name'],
-                                                 repository)
-            resize = volume['size'] > snapshot['volume_size']
-            if resize:
-                self.esm.resize_volume(volume['name'], volume['size'])
-        except Exception:
-            msg = _('Failed to Create Volume from Snapshot %(snapname)s')
-            LOG.debug(msg % dict(snapname=snapshot_name))
-            raise
-        return
+        self._appliance.create_volume_from_snapshot(snapshot_name,
+                                                    volume['name'],
+                                                    repository)
+        if volume['size'] > snapshot['volume_size']:
+            self._appliance.resize_volume(volume['name'], volume['size'])
 
     def extend_volume(self, volume, new_size):
-        """Extend an Existing Volume."""
-        try:
-            self.esm.resize_volume(volume['name'], new_size)
-        except Exception:
-            msg = _('Failed to Extend Volume %(volname)s')
-            LOG.debug(msg % dict(volname=volume['name']))
-            raise
+        """Extend an existing volume."""
+        self._appliance.resize_volume(volume['name'], new_size)
         return
 
     def initialize_connection(self, volume, connector):
         """Return connection information."""
-        try:
-            infos = self.esm._get_lun_address(volume['name'])
-            shelf = infos['shelf']
-            lun = infos['lun']
-
-            aoe_properties = {
-                'target_shelf': shelf,
-                'target_lun': lun,
-            }
-            return {
-                'driver_volume_type': 'aoe',
-                'data': aoe_properties,
-            }
-        except Exception:
-            msg = _('Failed to Initialize Connection. '
-                    'Volume Name: %(volname)s '
-                    'Shelf: %(shelf)s, '
-                    'Lun: %(lun)s')
-            LOG.debug(msg % dict(volname=volume['name'],
-                                 shelf=shelf,
-                                 lun=lun))
-            raise
-        return
+        volume_info = self._appliance.get_volume_info(volume['name'])
+
+        shelf = volume_info['shelf']
+        lun = volume_info['lun']
+
+        LOG.debug(_('Initialize connection %(shelf)s/%(lun)s for %(name)s') %
+                  {'shelf': shelf,
+                   'lun': lun,
+                   'name': volume['name']})
+
+        aoe_properties = {'target_shelf': shelf,
+                          'target_lun': lun}
+
+        return {'driver_volume_type': 'aoe',
+                'data': aoe_properties}
+
+    def _get_repository_capabilities(self):
+        repos_list = map(lambda i: i['profile']['fullName'] + ':' + i['name'],
+                         self._appliance.get_all_repos())
+        return ' '.join(repos_list)
+
+    def update_volume_stats(self):
+        capabilities = self._get_repository_capabilities()
+        self._stats[self.configuration.coraid_repository_key] = capabilities
 
     def get_volume_stats(self, refresh=False):
         """Return Volume Stats."""
-        data = {'driver_version': self.VERSION,
-                'free_capacity_gb': 'unknown',
-                'reserved_percentage': 0,
-                'storage_protocol': 'aoe',
-                'total_capacity_gb': 'unknown',
-                'vendor_name': 'Coraid'}
-        backend_name = self.configuration.safe_get('volume_backend_name')
-        data['volume_backend_name'] = backend_name or 'EtherCloud ESM'
-        return data
+        if refresh:
+            self.update_volume_stats()
+        return self._stats
 
     def local_path(self, volume):
         pass
@@ -457,6 +546,3 @@ class CoraidDriver(driver.VolumeDriver):
 
     def ensure_export(self, context, volume):
         pass
-
-    def detach_volume(self, context, volume):
-        pass
index b095475b9b12cb217e6dea95b148ca53c3fc2514..0a870996979d8c76b7dec84a1c3e7061aaf25309 100644 (file)
@@ -77,3 +77,8 @@ mmlsconfig: CommandFilter, /usr/lpp/mmfs/bin/mmlsconfig, root
 mmlsfs: CommandFilter, /usr/lpp/mmfs/bin/mmlsfs, root
 find: CommandFilter, find, root
 mkfs: CommandFilter, mkfs, root
+
+# cinder/brick/initiator/connector.py:
+aoe-revalidate: CommandFilter, aoe-revalidate, root
+aoe-discover: CommandFilter, aoe-discover, root
+aoe-flush: CommandFilter, aoe-flush, root