Refactoring ITRI DISCO cinder volume driver
This commit is the refactored version of DISCO volume driver of ITRI. We change the SOAP API to REST API. It adds also two different classes: * Class for the different calls using REST API * Class for attach/detach a DISCO volume It also includes the unit tests. DocImpact Implements: blueprint refactor-disco-volume-driver Change-Id: Id0e2d2ef4a873e86e514d5784a957ea4f593a027
This commit is contained in:
parent
0ed3a80d6c
commit
5c841f2d7b
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
|
@ -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)
|
|
@ -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.
|
Loading…
Reference in New Issue