Merge "Refactoring ITRI DISCO cinder volume driver"

This commit is contained in:
Jenkins 2017-01-05 19:01:46 +00:00 committed by Gerrit Code Review
commit b5eaead90f
12 changed files with 442 additions and 180 deletions

View File

@ -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."""

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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.