diff --git a/cinder/tests/unit/volume/drivers/disco/__init__.py b/cinder/tests/unit/volume/drivers/disco/__init__.py index 7229723d0..303a19bc7 100644 --- a/cinder/tests/unit/volume/drivers/disco/__init__.py +++ b/cinder/tests/unit/volume/drivers/disco/__init__.py @@ -20,12 +20,15 @@ import mock from suds import client from os_brick.initiator import connector +from oslo_config import cfg from cinder import context from cinder import test from cinder.tests.unit import fake_volume from cinder.volume import configuration as conf import cinder.volume.drivers.disco.disco as driver +import cinder.volume.drivers.disco.disco_api as disco_api +import cinder.volume.drivers.disco.disco_attach_detach as attach_detach class TestDISCODriver(test.TestCase): @@ -55,9 +58,14 @@ class TestDISCODriver(test.TestCase): self.cfg.restore_check_timeout = 3600 self.cfg.clone_check_timeout = 3600 self.cfg.snapshot_reserve_days = -1 - self.cfg.retry_interval = 1 + self.cfg.retry_interval = 2 + self.cfg.num_volume_device_scan_tries = 3 - self.FAKE_SOAP_RESPONSE = { + CONF = cfg.CONF + CONF.choice_client = 'SOAP' + CONF.rest_ip = '127.0.0.1' + + self.FAKE_RESPONSE = { 'standard': { 'success': {'status': 0, 'result': 'a normal message'}, 'fail': {'status': 1, 'result': 'an error message'}} @@ -67,26 +75,28 @@ class TestDISCODriver(test.TestCase): 'Client', self.create_client).start() + mock.patch.object(disco_api, + 'DiscoApi', + self.create_client).start() + mock.patch.object(connector.InitiatorConnector, 'factory', self.get_mock_connector).start() - mock.patch.object(driver.DiscoDriver, - '_get_connector_identifier', - self.get_mock_attribute).start() - self.driver = driver.DiscoDriver(execute=mock_exec, configuration=self.cfg) self.driver.do_setup(None) + self.attach_detach = attach_detach.AttachDetachDiscoVolume() + self.ctx = context.RequestContext('fake', 'fake', auth_token=True) self.volume = fake_volume.fake_volume_obj(self.ctx) self.volume['volume_id'] = '1234567' - self.requester = self.driver.client.service + self.requester = self.driver.client def create_client(self, *cmd, **kwargs): - """Mock the suds client.""" + """Mock the client's methods.""" return FakeClient() def get_mock_connector(self, *cmd, **kwargs): @@ -97,9 +107,13 @@ class TestDISCODriver(test.TestCase): """Mock the os_brick connector.""" return 'DISCO' + def get_fake_volume(self, *cmd, **kwards): + """Return a volume object for the tests.""" + return self.volume + class FakeClient(object): - """Fake class to mock suds.Client.""" + """Fake class to mock client.""" def __init__(self, *args, **kwargs): """Create a fake service attribute.""" @@ -107,10 +121,10 @@ class FakeClient(object): class FakeMethod(object): - """Fake class recensing some of the method of the suds client.""" + """Fake class recensing some of the method of the rest client.""" def __init__(self, *args, **kwargs): - """Fake class to mock the suds client.""" + """Fake class to mock the client.""" def volumeCreate(self, *args, **kwargs): """"Mock function to create a volume.""" @@ -133,6 +147,9 @@ class FakeMethod(object): def restoreDetail(self, *args, **kwargs): """"Mock function to detail the restore operation.""" + def volumeDetail(self, *args, **kwargs): + """Mock function to get the volume detail from its id.""" + def volumeDetailByName(self, *args, **kwargs): """"Mock function to get the volume detail from its name.""" diff --git a/cinder/tests/unit/volume/drivers/disco/test_create_cloned_volume.py b/cinder/tests/unit/volume/drivers/disco/test_create_cloned_volume.py index c41aa3965..bf3637d15 100644 --- a/cinder/tests/unit/volume/drivers/disco/test_create_cloned_volume.py +++ b/cinder/tests/unit/volume/drivers/disco/test_create_cloned_volume.py @@ -34,7 +34,7 @@ class CreateCloneVolumeTestCase(disco.TestDISCODriver): super(CreateCloneVolumeTestCase, self).setUp() self.dest_volume = fake_volume.fake_volume_obj(self.ctx) - # Create mock functions for all the suds call done by the driver.""" + # Create mock functions for all the call done by the driver.""" mock.patch.object(self.requester, 'volumeClone', self.clone_request).start() @@ -54,13 +54,13 @@ class CreateCloneVolumeTestCase(disco.TestDISCODriver): } clone_success = ( - copy.deepcopy(self.FAKE_SOAP_RESPONSE['standard']['success'])) + copy.deepcopy(self.FAKE_RESPONSE['standard']['success'])) clone_pending = ( - copy.deepcopy(self.FAKE_SOAP_RESPONSE['standard']['success'])) + copy.deepcopy(self.FAKE_RESPONSE['standard']['success'])) clone_fail = ( - copy.deepcopy(self.FAKE_SOAP_RESPONSE['standard']['success'])) + copy.deepcopy(self.FAKE_RESPONSE['standard']['success'])) clone_response_fail = ( - copy.deepcopy(self.FAKE_SOAP_RESPONSE['standard']['success'])) + copy.deepcopy(self.FAKE_RESPONSE['standard']['success'])) clone_success['result'] = ( six.text_type(self.DETAIL_OPTIONS['success'])) @@ -70,18 +70,18 @@ class CreateCloneVolumeTestCase(disco.TestDISCODriver): six.text_type(self.DETAIL_OPTIONS['failure'])) clone_response_fail['status'] = 1 - self.FAKE_SOAP_RESPONSE['clone_detail'] = { + self.FAKE_RESPONSE['clone_detail'] = { 'success': clone_success, 'fail': clone_fail, 'pending': clone_pending, 'request_fail': clone_response_fail } - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response['result'] = '1234' self.response_detail = ( - self.FAKE_SOAP_RESPONSE['clone_detail']['success']) + self.FAKE_RESPONSE['clone_detail']['success']) self.test_pending = False self.test_pending_count = 0 @@ -94,9 +94,9 @@ class CreateCloneVolumeTestCase(disco.TestDISCODriver): if self.test_pending: if self.test_pending_count == 0: self.test_pending_count += 1 - return self.FAKE_SOAP_RESPONSE['clone_detail']['pending'] + return self.FAKE_RESPONSE['clone_detail']['pending'] else: - return self.FAKE_SOAP_RESPONSE['clone_detail']['success'] + return self.FAKE_RESPONSE['clone_detail']['success'] else: return self.response_detail @@ -113,29 +113,29 @@ class CreateCloneVolumeTestCase(disco.TestDISCODriver): def test_create_clone_volume_fail(self): """Clone volume request to DISCO fails.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['fail'] + self.response = self.FAKE_RESPONSE['standard']['fail'] self.assertRaises(exception.VolumeBackendAPIException, self.test_create_cloned_volume) def test_create_cloned_volume_fail_not_immediate(self): """Get clone detail returns that the clone fails.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response_detail = ( - self.FAKE_SOAP_RESPONSE['clone_detail']['fail']) + self.FAKE_RESPONSE['clone_detail']['fail']) self.assertRaises(exception.VolumeBackendAPIException, self.test_create_cloned_volume) def test_create_cloned_volume_fail_not_immediate_response_fail(self): """Get clone detail request to DISCO fails.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response_detail = ( - self.FAKE_SOAP_RESPONSE['clone_detail']['request_fail']) + self.FAKE_RESPONSE['clone_detail']['request_fail']) self.assertRaises(exception.VolumeBackendAPIException, self.test_create_cloned_volume) def test_create_cloned_volume_fail_not_immediate_request_fail(self): """Get clone detail returns the task is pending then complete.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.test_pending = True self.test_create_cloned_volume() @@ -145,9 +145,9 @@ class CreateCloneVolumeTestCase(disco.TestDISCODriver): timeout = 3 mock_time.side_effect = utils.generate_timeout_series(timeout) self.driver.configuration.clone_check_timeout = timeout - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response_detail = ( - self.FAKE_SOAP_RESPONSE['clone_detail']['pending']) + self.FAKE_RESPONSE['clone_detail']['pending']) self.assertRaises(exception.VolumeBackendAPIException, self.test_create_cloned_volume) diff --git a/cinder/tests/unit/volume/drivers/disco/test_create_snapshot.py b/cinder/tests/unit/volume/drivers/disco/test_create_snapshot.py index 898ecd6e4..627e9d3af 100644 --- a/cinder/tests/unit/volume/drivers/disco/test_create_snapshot.py +++ b/cinder/tests/unit/volume/drivers/disco/test_create_snapshot.py @@ -75,18 +75,18 @@ class CreateSnapshotTestCase(disco.TestDISCODriver): self.DETAIL_OPTIONS['failure']) snap_response_fail['status'] = 1 - self.FAKE_SOAP_RESPONSE['snapshot_detail'] = { + self.FAKE_RESPONSE['snapshot_detail'] = { 'success': snap_success, 'fail': snap_fail, 'pending': snap_pending, 'request_fail': snap_response_fail} self.response = ( - self.FAKE_SOAP_RESPONSE['standard']['success']) + self.FAKE_RESPONSE['standard']['success']) self.response['result'] = 1234 self.response_detail = ( - self.FAKE_SOAP_RESPONSE['snapshot_detail']['success']) + self.FAKE_RESPONSE['snapshot_detail']['success']) self.test_pending = False self.test_pending_count = 0 @@ -100,9 +100,9 @@ class CreateSnapshotTestCase(disco.TestDISCODriver): if self.test_pending: if self.test_pending_count == 0: self.test_pending_count += 1 - return self.FAKE_SOAP_RESPONSE['snapshot_detail']['pending'] + return self.FAKE_RESPONSE['snapshot_detail']['pending'] else: - return self.FAKE_SOAP_RESPONSE['snapshot_detail']['success'] + return self.FAKE_RESPONSE['snapshot_detail']['success'] else: return self.response_detail @@ -114,29 +114,29 @@ class CreateSnapshotTestCase(disco.TestDISCODriver): def test_create_snapshot_fail(self): """Request to DISCO failed.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['fail'] + self.response = self.FAKE_RESPONSE['standard']['fail'] self.assertRaises(exception.VolumeBackendAPIException, self.test_create_snapshot) def test_create_snapshot_fail_not_immediate(self): """Request to DISCO failed when monitoring the snapshot details.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response_detail = ( - self.FAKE_SOAP_RESPONSE['snapshot_detail']['fail']) + self.FAKE_RESPONSE['snapshot_detail']['fail']) self.assertRaises(exception.VolumeBackendAPIException, self.test_create_snapshot) def test_create_snapshot_fail_not_immediate_response_fail(self): """Request to get the snapshot details returns a failure.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response_detail = ( - self.FAKE_SOAP_RESPONSE['snapshot_detail']['request_fail']) + self.FAKE_RESPONSE['snapshot_detail']['request_fail']) self.assertRaises(exception.VolumeBackendAPIException, self.test_create_snapshot) def test_create_snapshot_detail_pending(self): """Request to get the snapshot detail return pending then success.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.test_pending = True self.test_create_snapshot() @@ -146,8 +146,8 @@ class CreateSnapshotTestCase(disco.TestDISCODriver): timeout = 3 mock_time.side_effect = utils.generate_timeout_series(timeout) self.driver.configuration.snapshot_check_timeout = timeout - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response_detail = ( - self.FAKE_SOAP_RESPONSE['snapshot_detail']['pending']) + self.FAKE_RESPONSE['snapshot_detail']['pending']) self.assertRaises(exception.VolumeBackendAPIException, self.test_create_snapshot) diff --git a/cinder/tests/unit/volume/drivers/disco/test_create_volume.py b/cinder/tests/unit/volume/drivers/disco/test_create_volume.py index 65d91c589..d4006d289 100644 --- a/cinder/tests/unit/volume/drivers/disco/test_create_volume.py +++ b/cinder/tests/unit/volume/drivers/disco/test_create_volume.py @@ -27,12 +27,12 @@ class CreateVolumeTestCase(disco.TestDISCODriver): """Prepare variables and mock functions.""" super(CreateVolumeTestCase, self).setUp() - # Mock the suds cliebt. + # Mock the method volumeCreate. mock.patch.object(self.requester, 'volumeCreate', self.perform_disco_request).start() - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] def perform_disco_request(self, *cmd, **kwargs): """Mock function for the suds client.""" @@ -48,6 +48,6 @@ class CreateVolumeTestCase(disco.TestDISCODriver): def test_create_volume_fail(self): """Request to DISCO failed.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['fail'] + self.response = self.FAKE_RESPONSE['standard']['fail'] self.assertRaises(exception.VolumeBackendAPIException, self.test_create_volume) diff --git a/cinder/tests/unit/volume/drivers/disco/test_create_volume_from_snapshot.py b/cinder/tests/unit/volume/drivers/disco/test_create_volume_from_snapshot.py index 8895ab89c..9d5abcff8 100644 --- a/cinder/tests/unit/volume/drivers/disco/test_create_volume_from_snapshot.py +++ b/cinder/tests/unit/volume/drivers/disco/test_create_volume_from_snapshot.py @@ -77,18 +77,18 @@ class CreateVolumeFromSnapshotTestCase(disco.TestDISCODriver): self.DETAIL_OPTIONS['failure']) rest_response_fail['status'] = 1 - self.FAKE_SOAP_RESPONSE['restore_detail'] = { + self.FAKE_RESPONSE['restore_detail'] = { 'success': rest_success, 'fail': rest_fail, 'pending': rest_pending, 'request_fail': rest_response_fail } - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response['result'] = '1234' self.response_detail = ( - self.FAKE_SOAP_RESPONSE['restore_detail']['success']) + self.FAKE_RESPONSE['restore_detail']['success']) self.test_pending = False self.test_pending_count = 0 @@ -102,9 +102,9 @@ class CreateVolumeFromSnapshotTestCase(disco.TestDISCODriver): if self.test_pending: if self.test_pending_count == 0: self.test_pending_count += 1 - return self.FAKE_SOAP_RESPONSE['restore_detail']['pending'] + return self.FAKE_RESPONSE['restore_detail']['pending'] else: - return self.FAKE_SOAP_RESPONSE['restore_detail']['success'] + return self.FAKE_RESPONSE['restore_detail']['success'] else: return self.response_detail @@ -121,29 +121,29 @@ class CreateVolumeFromSnapshotTestCase(disco.TestDISCODriver): def test_create_volume_from_snapshot_fail(self): """Create volume from snapshot request fails.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['fail'] + self.response = self.FAKE_RESPONSE['standard']['fail'] self.assertRaises(exception.VolumeBackendAPIException, self.test_create_volume_from_snapshot) def test_create_volume_from_snapshot_fail_not_immediate(self): """Get restore details request fails.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response_detail = ( - self.FAKE_SOAP_RESPONSE['restore_detail']['fail']) + self.FAKE_RESPONSE['restore_detail']['fail']) self.assertRaises(exception.VolumeBackendAPIException, self.test_create_volume_from_snapshot) def test_create_volume_from_snapshot_fail_detail_response_fail(self): """Get restore details reports that restore operation fails.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response_detail = ( - self.FAKE_SOAP_RESPONSE['restore_detail']['request_fail']) + self.FAKE_RESPONSE['restore_detail']['request_fail']) self.assertRaises(exception.VolumeBackendAPIException, self.test_create_volume_from_snapshot) def test_create_volume_from_snapshot_fail_not_immediate_resp_fail(self): """Get restore details reports that the task is pending, then done.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.test_pending = True self.test_create_volume_from_snapshot() @@ -153,9 +153,9 @@ class CreateVolumeFromSnapshotTestCase(disco.TestDISCODriver): timeout = 3 mock_time.side_effect = utils.generate_timeout_series(timeout) self.driver.configuration.restore_check_timeout = timeout - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.response_detail = ( - self.FAKE_SOAP_RESPONSE['restore_detail']['pending']) + self.FAKE_RESPONSE['restore_detail']['pending']) self.assertRaises(exception.VolumeBackendAPIException, self.test_create_volume_from_snapshot) diff --git a/cinder/tests/unit/volume/drivers/disco/test_delete_snapshot.py b/cinder/tests/unit/volume/drivers/disco/test_delete_snapshot.py index 88cbeaab1..e102957c6 100644 --- a/cinder/tests/unit/volume/drivers/disco/test_delete_snapshot.py +++ b/cinder/tests/unit/volume/drivers/disco/test_delete_snapshot.py @@ -27,12 +27,12 @@ class DeleteSnapshotTestCase(disco.TestDISCODriver): """Initialise variables and mock functions.""" super(DeleteSnapshotTestCase, self).setUp() - # Mock snapshotDelete function from suds client. + # Mock snapshotDelete function. mock.patch.object(self.requester, 'snapshotDelete', self.perform_disco_request).start() - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.snapshot = fake_snapshot.fake_snapshot_obj( self.ctx, **{'volume': self.volume}) @@ -46,6 +46,6 @@ class DeleteSnapshotTestCase(disco.TestDISCODriver): def test_delete_snapshot_fail(self): """Make the API returns an error while deleting.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['fail'] + self.response = self.FAKE_RESPONSE['standard']['fail'] self.assertRaises(exception.VolumeBackendAPIException, self.test_delete_snapshot) diff --git a/cinder/tests/unit/volume/drivers/disco/test_delete_volume.py b/cinder/tests/unit/volume/drivers/disco/test_delete_volume.py index 79675036e..8bbf16802 100644 --- a/cinder/tests/unit/volume/drivers/disco/test_delete_volume.py +++ b/cinder/tests/unit/volume/drivers/disco/test_delete_volume.py @@ -27,12 +27,12 @@ class DeleteVolumeTestCase(disco.TestDISCODriver): """Initialise variables and mock functions.""" super(DeleteVolumeTestCase, self).setUp() - # Mock volumeDelete function from suds client. + # Mock volumeDelete function. mock.patch.object(self.requester, 'volumeDelete', self.perform_disco_request).start() - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] def perform_disco_request(self, *cmd, **kwargs): """Mock function to delete a volume.""" @@ -44,6 +44,6 @@ class DeleteVolumeTestCase(disco.TestDISCODriver): def test_delete_volume_fail(self): """Make the API returns an error while deleting.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['fail'] + self.response = self.FAKE_RESPONSE['standard']['fail'] self.assertRaises(exception.VolumeBackendAPIException, self.test_delete_volume) diff --git a/cinder/tests/unit/volume/drivers/disco/test_extend_volume.py b/cinder/tests/unit/volume/drivers/disco/test_extend_volume.py index 8c4ad28ba..c1924ac3d 100644 --- a/cinder/tests/unit/volume/drivers/disco/test_extend_volume.py +++ b/cinder/tests/unit/volume/drivers/disco/test_extend_volume.py @@ -32,7 +32,7 @@ class VolumeExtendTestCase(disco.TestDISCODriver): 'volumeExtend', self.perform_disco_request).start() - self.response = self.FAKE_SOAP_RESPONSE['standard']['success'] + self.response = self.FAKE_RESPONSE['standard']['success'] self.new_size = 5 def perform_disco_request(self, *cmd, **kwargs): @@ -45,6 +45,6 @@ class VolumeExtendTestCase(disco.TestDISCODriver): def test_extend_volume_fail(self): """Request to DISCO failed.""" - self.response = self.FAKE_SOAP_RESPONSE['standard']['fail'] + self.response = self.FAKE_RESPONSE['standard']['fail'] self.assertRaises(exception.VolumeBackendAPIException, self.test_extend_volume) diff --git a/cinder/volume/drivers/disco/disco.py b/cinder/volume/drivers/disco/disco.py index a9d352a77..f89030b5c 100644 --- a/cinder/volume/drivers/disco/disco.py +++ b/cinder/volume/drivers/disco/disco.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 Industrial Technology Research Institute. +# copyright (c) 2016 Industrial Technology Research Institute. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -18,7 +18,6 @@ import os import time -from os_brick.initiator import connector from oslo_config import cfg from oslo_log import log as logging from oslo_service import loopingcall @@ -32,8 +31,9 @@ from cinder import exception from cinder.i18n import _ from cinder.image import image_utils from cinder import interface -from cinder import utils from cinder.volume import driver +from cinder.volume.drivers.disco import disco_api +from cinder.volume.drivers.disco import disco_attach_detach LOG = logging.getLogger(__name__) @@ -47,8 +47,18 @@ disco_opts = [ help='The port to connect DMS client socket server'), cfg.StrOpt('disco_wsdl_path', default='/etc/cinder/DISCOService.wsdl', + deprecated_for_removal=True, help='Path to the wsdl file ' 'to communicate with DISCO request manager'), + cfg.IPOpt('rest_ip', + help='The IP address of the REST server'), + cfg.StrOpt('choice_client', + help='Use soap client or rest client for communicating ' + 'with DISCO. Possible values are "soap" or ' + '"rest".'), + cfg.PortOpt('disco_src_api_port', + default='8080', + help='The port of DISCO source API'), cfg.StrOpt('volume_name_prefix', default='openstack-', help='Prefix before volume name to differentiate ' @@ -85,9 +95,16 @@ CONF.register_opts(disco_opts) # Driver to communicate with DISCO storage solution @interface.volumedriver class DiscoDriver(driver.VolumeDriver): - """Execute commands related to DISCO Volumes.""" + """Execute commands related to DISCO Volumes. - VERSION = "1.0" + Version history: + 1.0 - disco volume driver using SOAP + 1.1 - disco volume driver using REST and only compatible + with version greater than disco-1.6.4 + + """ + + VERSION = "1.1" CI_WIKI_NAME = "ITRI_DISCO_CI" def __init__(self, *args, **kwargs): @@ -95,40 +112,30 @@ class DiscoDriver(driver.VolumeDriver): super(DiscoDriver, self).__init__(*args, **kwargs) self.configuration.append_config_values(disco_opts) self.ctxt = context.get_admin_context() - - self.connector = connector.InitiatorConnector.factory( - self._get_connector_identifier(), utils.get_root_helper(), - device_scan_attempts=( - self.configuration.num_volume_device_scan_tries) - ) - - self.connection_conf = {} - self.connection_conf['server_ip'] = self.configuration.disco_client - self.connection_conf['server_port'] = ( - self.configuration.disco_client_port) - - self.connection_properties = {} - self.connection_properties['name'] = None - self.connection_properties['disco_id'] = None - self.connection_properties['conf'] = self.connection_conf + self.attach_detach_volume = ( + disco_attach_detach.AttachDetachDiscoVolume()) def do_setup(self, context): """Create client for DISCO request manager.""" LOG.debug("Enter in DiscoDriver do_setup.") - path = ''.join(['file:', self.configuration.disco_wsdl_path]) - self.client = client.Client(path, cache=None) + if CONF.choice_client.lower() == "rest": + self.client = disco_api.DiscoApi( + CONF.rest_ip, CONF.disco_src_api_port) + else: + path = ''.join(['file:', self.configuration.disco_wsdl_path]) + init_client = client.Client(path, cache=None) + self.client = init_client.service def check_for_setup_error(self): """Make sure we have the pre-requisites.""" - LOG.debug("Enter in DiscoDriver check_for_setup_error.") - path = self.configuration.disco_wsdl_path - if not os.path.exists(path): - msg = _("Could not find DISCO wsdl file.") + if not CONF.rest_ip and CONF.choice_client.lower() == "rest": + msg = _("Could not find the IP address of the REST server.") raise exception.VolumeBackendAPIException(data=msg) - - def _get_connector_identifier(self): - """Return connector identifier, put here to mock it in unit tests.""" - return connector.DISCO + else: + path = self.configuration.disco_wsdl_path + if not os.path.exists(path): + msg = _("Could not find DISCO wsdl file.") + raise exception.VolumeBackendAPIException(data=msg) def create_volume(self, volume): """Create a disco volume.""" @@ -137,13 +144,13 @@ class DiscoDriver(driver.VolumeDriver): vol_size = volume['size'] * units.Ki LOG.debug("Create volume : [name] %(vname)s - [size] %(vsize)s.", {'vname': vol_name, 'vsize': six.text_type(vol_size)}) - reply = self.client.service.volumeCreate(vol_name, vol_size) + reply = self.client.volumeCreate(vol_name, vol_size) status = reply['status'] result = reply['result'] LOG.debug("Create volume : [status] %(stat)s - [result] %(res)s.", {'stat': six.text_type(status), 'res': result}) - if status != 0: + if status: msg = (_("Error while creating volume " "[status] %(stat)s - [result] %(res)s.") % {'stat': six.text_type(status), 'res': result}) @@ -156,14 +163,14 @@ class DiscoDriver(driver.VolumeDriver): """Delete a logical volume.""" disco_vol_id = volume['provider_location'] LOG.debug("Delete disco volume : %s.", disco_vol_id) - reply = self.client.service.volumeDelete(disco_vol_id) + reply = self.client.volumeDelete(disco_vol_id) status = reply['status'] result = reply['result'] LOG.debug("Delete volume [status] %(stat)s - [result] %(res)s.", {'stat': six.text_type(status), 'res': result}) - if status != 0: + if status: msg = (_("Error while deleting volume " "[status] %(stat)s - [result] %(res)s.") % {'stat': six.text_type(status), 'res': result}) @@ -182,15 +189,15 @@ class DiscoDriver(driver.VolumeDriver): {'id': vol_id, 'desc': description}) # Trigger an asynchronous local snapshot - reply = self.client.service.snapshotCreate(vol_id, - -1, -1, - description) + reply = self.client.snapshotCreate(vol_id, + -1, -1, + description) status = reply['status'] result = reply['result'] LOG.debug("Create snapshot : [status] %(stat)s - [result] %(res)s.", {'stat': six.text_type(status), 'res': result}) - if status != 0: + if status: msg = (_("Error while creating snapshot " "[status] %(stat)s - [result] %(res)s.") % {'stat': six.text_type(status), 'res': result}) @@ -200,14 +207,12 @@ class DiscoDriver(driver.VolumeDriver): # Monitor the status until it becomes either success or fail params = {'snapshot_id': int(result)} start_time = int(time.time()) - - timer = loopingcall.FixedIntervalLoopingCall( - self._retry_get_detail, - start_time, - self.configuration.snapshot_check_timeout, - 'snapshot_detail', - params) - reply = timer.start(interval=self.configuration.retry_interval).wait() + snapshot_request = DISCOCheck(self.client, + params, + start_time, + "snapshot_detail") + timeout = self.configuration.snapshot_check_timeout + snapshot_request._monitor_request(timeout) snapshot['provider_location'] = result LOG.debug("snapshot taken successfully on volume : %(volume)s.", @@ -220,14 +225,14 @@ class DiscoDriver(driver.VolumeDriver): snap_id = snapshot['provider_location'] LOG.debug("[start] Delete snapshot : %s.", snap_id) - reply = self.client.service.snapshotDelete(snap_id) + reply = self.client.snapshotDelete(snap_id) status = reply['status'] result = reply['result'] LOG.debug("[End] Delete snapshot : " "[status] %(stat)s - [result] %(res)s.", {'stat': six.text_type(status), 'res': result}) - if status != 0: + if status: msg = (_("Error while deleting snapshot " "[status] %(stat)s - [result] %(res)s") % {'stat': six.text_type(status), 'res': result}) @@ -243,14 +248,15 @@ class DiscoDriver(driver.VolumeDriver): LOG.debug("[start] Create volume from snapshot : " "%(snap_id)s - name : %(vol_name)s.", {'snap_id': snap_id, 'vol_name': vol_name}) - reply = self.client.service.restoreFromSnapshot(snap_id, vol_name) + reply = self.client.restoreFromSnapshot(snap_id, vol_name, -1, None, + -1) status = reply['status'] result = reply['result'] LOG.debug("Restore volume from snapshot " "[status] %(stat)s - [result] %(res)s.", {'stat': six.text_type(status), 'res': result}) - if status != 0: + if status: msg = (_("Error[%(stat)s - %(res)s] while restoring snapshot " "[%(snap_id)s] into volume [%(vol)s].") % {'stat': six.text_type(status), 'res': result, @@ -262,20 +268,17 @@ class DiscoDriver(driver.VolumeDriver): # either success, fail or timeout params = {'restore_id': int(result)} start_time = int(time.time()) - - timer = loopingcall.FixedIntervalLoopingCall( - self._retry_get_detail, - start_time, - self.configuration.restore_check_timeout, - 'restore_detail', - params) - reply = timer.start(interval=self.configuration.retry_interval).wait() - - reply = self.client.service.volumeDetailByName(vol_name) + restore_request = DISCOCheck(self.client, + params, + start_time, + "restore_detail") + timeout = self.configuration.restore_check_timeout + restore_request._monitor_request(timeout) + reply = self.client.volumeDetailByName(vol_name) status = reply['status'] new_vol_id = reply['volumeInfoResult']['volumeId'] - if status != 0: + if status: msg = (_("Error[status] %(stat)s - [result] %(res)s] " "while getting volume id.") % {'stat': six.text_type(status), 'res': result}) @@ -298,13 +301,13 @@ class DiscoDriver(driver.VolumeDriver): {'name': vol_name, 'source': src_vol_id, 'size': six.text_type(vol_size)}) - reply = self.client.service.volumeClone(src_vol_id, vol_name) + reply = self.client.volumeClone(src_vol_id, vol_name) status = reply['status'] result = reply['result'] LOG.debug("Clone volume : [status] %(stat)s - [result] %(res)s.", {'stat': six.text_type(status), 'res': result}) - if status != 0: + if status: msg = (_("Error while creating volume " "[status] %(stat)s - [result] %(res)s.") % {'stat': six.text_type(status), 'res': result}) @@ -316,20 +319,16 @@ class DiscoDriver(driver.VolumeDriver): params = {'clone_id': int(result), 'vol_name': vol_name} start_time = int(time.time()) - - timer = loopingcall.FixedIntervalLoopingCall( - self._retry_get_detail, - start_time, - self.configuration.clone_check_timeout, - 'clone_detail', - params) - reply = timer.start(interval=self.configuration.retry_interval).wait() - - reply = self.client.service.volumeDetailByName(vol_name) + clone_request = DISCOCheck(self.client, + params, + start_time, + "clone_detail") + clone_request._monitor_request(self.configuration.clone_check_timeout) + reply = self.client.volumeDetailByName(vol_name) status = reply['status'] new_vol_id = reply['volumeInfoResult']['volumeId'] - if status != 0: + if status: msg = (_("Error[%(stat)s - %(res)s] " "while getting volume id."), {'stat': six.text_type(status), 'res': result}) @@ -346,7 +345,9 @@ class DiscoDriver(driver.VolumeDriver): LOG.debug("Enter in copy image to volume for disco.") try: - device_info = self._attach_volume(volume) + attach_detach_volume = ( + disco_attach_detach.AttachDetachDiscoVolume()) + device_info = attach_detach_volume._attach_volume(volume) image_utils.fetch_to_raw(context, image_service, image_id, @@ -354,30 +355,21 @@ class DiscoDriver(driver.VolumeDriver): self.configuration.volume_dd_blocksize, size=volume['size']) finally: - self._detach_volume(volume) - - def _attach_volume(self, volume): - """Call the connector.connect_volume().""" - connection_properties = self._get_connection_properties(volume) - device_info = self.connector.connect_volume(connection_properties) - return device_info - - def _detach_volume(self, volume): - """Call the connector.disconnect_volume().""" - connection_properties = self._get_connection_properties(volume) - self.connector.disconnect_volume(connection_properties, volume) + attach_detach_volume._detach_volume(volume) def copy_volume_to_image(self, context, volume, image_service, image_meta): """Copy a volume to a new image.""" LOG.debug("Enter in copy image to volume for disco.") try: - device_info = self._attach_volume(volume) + attach_detach_volume = ( + disco_attach_detach.AttachDetachDiscoVolume()) + device_info = attach_detach_volume._attach_volume(volume) image_utils.upload_volume(context, image_service, image_meta, device_info['path']) finally: - self._detach_volume(volume) + attach_detach_volume._detach_volume(volume) def extend_volume(self, volume, new_size): """Extend an existing volume's size.""" @@ -385,11 +377,10 @@ class DiscoDriver(driver.VolumeDriver): LOG.debug("Extends volume : %(id)s, new size : %(size)s.", {'id': vol_id, 'size': new_size}) new_size_mb = new_size * units.Ki - reply = self.client.service.volumeExtend(vol_id, new_size_mb) + reply = self.client.volumeExtend(vol_id, new_size_mb) status = reply['status'] result = reply['result'] - - if status != 0: + if status: msg = (_("Error while extending volume " "[status] %(stat)s - [result] %(res)s."), {'stat': six.text_type(status), 'res': result}) @@ -405,20 +396,14 @@ class DiscoDriver(driver.VolumeDriver): """Function called before attaching a volume.""" LOG.debug("Enter in initialize connection with disco, " "connector is %s.", connector) + cp = self.attach_detach_volume._get_connection_properties(volume) data = { 'driver_volume_type': 'disco', - 'data': self._get_connection_properties(volume) + 'data': cp } LOG.debug("Initialize connection [data]: %s.", data) return data - def _get_connection_properties(self, volume): - """Return a dictionnary with the connection properties.""" - connection_properties = dict(self.connection_properties) - connection_properties['name'] = volume['name'] - connection_properties['disco_id'] = volume['provider_location'] - return connection_properties - def terminate_connection(self, volume, connector, **kwargs): """Function called after attaching a volume.""" LOG.debug("Enter in terminate connection with disco.") @@ -435,10 +420,10 @@ class DiscoDriver(driver.VolumeDriver): stats['QoS_support'] = False try: - reply = self.client.service.systemInformationList() + reply = self.client.systemInformationList() status = reply['status'] - if status != 0: + if status: msg = (_("Error while getting " "disco information [%s].") % six.text_type(status)) @@ -479,24 +464,30 @@ class DiscoDriver(driver.VolumeDriver): """Remove an export for a logical volume.""" pass + +class DISCOCheck(object): + """Used to monitor DISCO operations.""" + + def __init__(self, client, param, start_time, function): + """Init some variables for checking some requests done in DISCO.""" + self.start_time = start_time + self.function = function + self.client = client + self.param = param + def is_timeout(self, start_time, timeout): """Check whether we reach the timeout.""" current_time = int(time.time()) - if current_time - start_time > timeout: - return True - else: - return False + return current_time - start_time > timeout def _retry_get_detail(self, start_time, timeout, operation, params): """Keep trying to query an item detail unless we reach the timeout.""" reply = self._call_api(operation, params) status = reply['status'] - msg = (_("Error while getting %(op)s details, " "returned code: %(status)s.") % {'op': operation, 'status': six.text_type(status)}) - - if status != 0: + if status: LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) @@ -515,12 +506,12 @@ class DiscoDriver(driver.VolumeDriver): def _call_api(self, operation, params): """Make the call to the SOAP api.""" if operation == 'snapshot_detail': - return self.client.service.snapshotDetail(params['snapshot_id']) + return self.client.snapshotDetail(params['snapshot_id']) if operation == 'restore_detail': - return self.client.service.restoreDetail(params['restore_id']) + return self.client.restoreDetail(params['restore_id']) if operation == 'clone_detail': - return self.client.service.cloneDetail(params['clone_id'], - params['vol_name']) + return self.client.cloneDetail(params['clone_id'], + params['vol_name']) else: msg = (_("Unknown operation %s."), operation) LOG.error(msg) @@ -543,3 +534,13 @@ class DiscoDriver(driver.VolumeDriver): "%s."), operation) LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) + + def _monitor_request(self, timeout): + """Monitor the request.""" + timer = loopingcall.FixedIntervalLoopingCall( + self._retry_get_detail, + self.start_time, + timeout, + self.function, + self.param) + timer.start(interval=CONF.retry_interval).wait() diff --git a/cinder/volume/drivers/disco/disco_api.py b/cinder/volume/drivers/disco/disco_api.py new file mode 100644 index 000000000..2eb348287 --- /dev/null +++ b/cinder/volume/drivers/disco/disco_api.py @@ -0,0 +1,169 @@ +# copyright (c) 2016 Industrial Technology Research Institute. +# 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. + +"""DISCO Backup Service Implementation.""" + +import json + +from oslo_log import log as logging +import requests +import six + + +LOG = logging.getLogger(__name__) + + +class DiscoApi(object): + """Class for all the requests to Disco API.""" + + def __init__(self, ip, port): + """Init client.""" + # Rest related variables + self.req_headers = {'Content-type': 'application/json'} + prefix_vars = {'server_ip': ip, + 'server_port': port, + 'api_prefix': 'RM-REST-Server/disco'} + self.request_prefix = ("http://%(server_ip)s:%(server_port)s" + "/%(api_prefix)s") % prefix_vars + self.prefix_var = {'req_prefix': self.request_prefix} + + def volumeCreate(self, volume_name, size): + """Create a DISCO volume.""" + params = {'volumeName': volume_name, 'volumeSize': size, + 'backupPolicyId': -1} + data = json.dumps(params, + sort_keys=True, + indent=4, + separators=(',', ': ')) + request = ("%(req_prefix)s/volume" % self.prefix_var) + r = requests.post(request, data, headers=self.req_headers) + return r.json() + + def volumeDelete(self, volume_id): + """Delete the temporary volume.""" + request_vars = {'req_prefix': self.request_prefix, + 'volume_id': six.text_type(volume_id)} + request = ("%(req_prefix)s/volume/%(volume_id)s") % request_vars + r = requests.delete(request) + return r.json() + + def volumeExtend(self, vol_id, size): + """Extend DISCO volume.""" + params = {'volumeSize': six.text_type(size), + 'volumeId': six.text_type(vol_id)} + data = json.dumps(params, + sort_keys=True, + indent=4, + separators=(',', ': ')) + request = ("%(req_prefix)s/volume/extend" % self.prefix_var) + r = requests.put(request, data, headers=self.req_headers) + return r.json() + + def volumeDetail(self, volume_id): + """Get volume information of the destination DISCO volume.""" + request_vars = {'req_prefix': self.request_prefix, + 'vol_id': six.text_type(volume_id)} + request = ("%(req_prefix)s/volume/%(vol_id)s") % request_vars + r = requests.get(request) + volume_info = r.json() + return volume_info + + def volumeDetailByName(self, volume_name): + """Get volume information of the DISCO volume.""" + request_vars = {'req_prefix': self.request_prefix, + 'volume_name': six.text_type(volume_name)} + request = ("%(req_prefix)s/volume?name=%(volume_name)s") % request_vars + r = requests.get(request) + return r.json() + + def volumeClone(self, volume_id, volume_name): + """Clone a DISCO volume.""" + params = {'volumeName': volume_name, 'volumeId': volume_id} + data = json.dumps(params, + sort_keys=True, + indent=4, + separators=(',', ': ')) + request = ("%(req_prefix)s/clone" % self.prefix_var) + r = requests.post(request, data, headers=self.req_headers) + return r.json() + + def cloneDetail(self, clone_id, clone_name): + """Get detail of the clone.""" + request_vars = {'req_prefix': self.request_prefix, + 'clone_name': clone_name, + 'clone_id': six.text_type(clone_id)} + request = ("%(req_prefix)s/clone?cloneId=%(clone_id)s&" + "name=%(clone_name)s") % request_vars + r = requests.get(request) + return r.json() + + def snapshotCreate(self, disco_volume_id, reserve_days, zone_id=None, + description=None): + """Take a snapshot of the volume.""" + params = {'volumeId': disco_volume_id, + 'reserveDays': reserve_days, + 'description': description} + data = json.dumps(params, sort_keys=True, indent=4, + separators=(',', ': ')) + + request = ("%(req_prefix)s/snapshot" % self.prefix_var) + r = requests.post(request, data, headers=self.req_headers) + return r.json() + + def snapshotDelete(self, snapshot_id): + """Delete a snapshot.""" + request_vars = {'req_prefix': self.request_prefix, + 'snapshot_id': six.text_type(snapshot_id)} + request = ("%(req_prefix)s/snapshot/%(snapshot_id)s") % request_vars + r = requests.delete(request) + return r.json() + + def snapshotDetail(self, snapshot_id): + """Monitor end of the snapshot.""" + request_vars = {'req_prefix': self.request_prefix, + 'snapshot_id': snapshot_id} + request = ("%(req_prefix)s/snapshot/%(snapshot_id)s") % request_vars + r = requests.get(request) + return r.json() + + def restoreFromSnapshot(self, snapshot_id, volume_name, zone_id, + description, volume_id): + """restore a snapshot of into a volume.""" + params = {'snapshotId': snapshot_id, + 'volumeName': volume_name, + 'zone_id': zone_id, + 'description': "local restore snapshot", + 'volumeId': volume_id} + data = json.dumps(params, + sort_keys=True, + indent=4, + separators=(',', ': ')) + request = ("%(req_prefix)s/restore" % self.prefix_var) + r = requests.post(request, data, headers=self.req_headers) + return r.json() + + def restoreDetail(self, restore_id): + """Monitor end of the restore.""" + request_vars = {'req_prefix': self.request_prefix, + 'restore_id': restore_id} + request = ("%(req_prefix)s/restore/%(restore_id)s") % request_vars + r = requests.get(request) + return r.json() + + def systemInformationList(self): + """Get the list of the system information.""" + request = ("%(req_prefix)s/systemInformationList") % self.prefix_var + r = requests.get(request) + return r.json() diff --git a/cinder/volume/drivers/disco/disco_attach_detach.py b/cinder/volume/drivers/disco/disco_attach_detach.py new file mode 100644 index 000000000..d84751693 --- /dev/null +++ b/cinder/volume/drivers/disco/disco_attach_detach.py @@ -0,0 +1,70 @@ +# copyright (c) 2016 Industrial Technology Research Institute. +# 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. + +"""Class for DISCO to attach and detach volume.""" + +from os_brick.initiator import connector +from oslo_config import cfg +from oslo_log import log as logging + +from cinder import utils + + +LOG = logging.getLogger(__name__) + +CONF = cfg.CONF + + +class AttachDetachDiscoVolume(object): + """Class for attach and detach a DISCO volume.""" + + def __init__(self): + """Init volume attachment class.""" + self.connector = connector.InitiatorConnector.factory( + self._get_connector_identifier(), utils.get_root_helper(), + device_scan_attempts=( + CONF.num_volume_device_scan_tries) + ) + self.connection_conf = {} + self.connection_conf['server_ip'] = CONF.disco_client + self.connection_conf['server_port'] = ( + CONF.disco_client_port) + + self.connection_properties = {} + self.connection_properties['name'] = None + self.connection_properties['disco_id'] = None + self.connection_properties['conf'] = self.connection_conf + + def _get_connection_properties(self, volume): + """Return a dictionnary with the connection properties.""" + connection_properties = dict(self.connection_properties) + connection_properties['name'] = volume['name'] + connection_properties['disco_id'] = volume['provider_location'] + return connection_properties + + def _get_connector_identifier(self): + """Return connector identifier, put here to mock it in unit tests.""" + return connector.DISCO + + def _attach_volume(self, volume): + """Call the connector.connect_volume().""" + connection_properties = self._get_connection_properties(volume) + device_info = self.connector.connect_volume(connection_properties) + return device_info + + def _detach_volume(self, volume): + """Call the connector.disconnect_volume().""" + connection_properties = self._get_connection_properties(volume) + self.connector.disconnect_volume(connection_properties, volume) diff --git a/releasenotes/notes/refactor-disco-volume-driver-3ff0145707ec0f3e.yaml b/releasenotes/notes/refactor-disco-volume-driver-3ff0145707ec0f3e.yaml new file mode 100644 index 000000000..d5857b887 --- /dev/null +++ b/releasenotes/notes/refactor-disco-volume-driver-3ff0145707ec0f3e.yaml @@ -0,0 +1,5 @@ +--- +Deprecations: + - Marked the ITRI DISCO driver option ``disco_wsdl_path`` as deprecated. + The new preferred protocol for array communication is REST and SOAP + support will be removed.