Remove the ITRI DISCO driver
The ITRI DISCO driver was marked unsupported in the Rocky release. It hasn't reported on a patch in 74 days and hasn't successfully executed CI in 231 days. This falls well outside our CI requirements and therefore the driver is being removed. Change-Id: Iab4288983e8e13a3b9d59b30887d1934a2eb66c8
This commit is contained in:
parent
a2d4a9569e
commit
c1007ed827
@ -91,8 +91,6 @@ from cinder.volume.drivers.dell_emc.vnx import common as \
|
||||
cinder_volume_drivers_dell_emc_vnx_common
|
||||
from cinder.volume.drivers.dell_emc import xtremio as \
|
||||
cinder_volume_drivers_dell_emc_xtremio
|
||||
from cinder.volume.drivers.disco import disco as \
|
||||
cinder_volume_drivers_disco_disco
|
||||
from cinder.volume.drivers import drbdmanagedrv as \
|
||||
cinder_volume_drivers_drbdmanagedrv
|
||||
from cinder.volume.drivers.fujitsu import eternus_dx_common as \
|
||||
@ -287,7 +285,6 @@ def list_opts():
|
||||
cinder_volume_drivers_dell_emc_vmax_common.vmax_opts,
|
||||
cinder_volume_drivers_dell_emc_vnx_common.VNX_OPTS,
|
||||
cinder_volume_drivers_dell_emc_xtremio.XTREMIO_OPTS,
|
||||
cinder_volume_drivers_disco_disco.disco_opts,
|
||||
cinder_volume_drivers_drbdmanagedrv.drbd_opts,
|
||||
cinder_volume_drivers_fujitsu_eternusdxcommon.
|
||||
FJ_ETERNUS_DX_OPT_opts,
|
||||
|
@ -1,163 +0,0 @@
|
||||
# Copyright (c) 2015 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.
|
||||
|
||||
"""Parent class for the DISCO driver unit test."""
|
||||
|
||||
import mock
|
||||
from suds import client
|
||||
|
||||
from os_brick.initiator import connector
|
||||
|
||||
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):
|
||||
"""Generic class for the DISCO test case."""
|
||||
|
||||
DETAIL_OPTIONS = {
|
||||
'success': 1,
|
||||
'pending': 2,
|
||||
'failure': 3
|
||||
}
|
||||
|
||||
ERROR_STATUS = 1
|
||||
|
||||
def setUp(self):
|
||||
"""Initialise variable common to all the test cases."""
|
||||
super(TestDISCODriver, self).setUp()
|
||||
|
||||
mock_exec = mock.Mock()
|
||||
mock_exec.return_value = ('', '')
|
||||
self.cfg = mock.Mock(spec=conf.Configuration)
|
||||
self.cfg.disco_client = '127.0.0.1'
|
||||
self.cfg.disco_client_port = '9898'
|
||||
self.cfg.disco_wsdl_path = 'somewhere'
|
||||
self.cfg.disco_volume_name_prefix = 'openstack-'
|
||||
self.cfg.disco_snapshot_check_timeout = 3600
|
||||
self.cfg.disco_restore_check_timeout = 3600
|
||||
self.cfg.disco_clone_check_timeout = 3600
|
||||
self.cfg.disco_retry_interval = 2
|
||||
self.cfg.num_volume_device_scan_tries = 3
|
||||
self.cfg.disco_choice_client = 'SOAP'
|
||||
self.cfg.disco_rest_ip = '127.0.0.1'
|
||||
self.cfg.san_ip = '127.0.0.1'
|
||||
self.cfg.san_api_port = '8080'
|
||||
|
||||
self.FAKE_RESPONSE = {
|
||||
'standard': {
|
||||
'success': {'status': 0, 'result': 'a normal message'},
|
||||
'fail': {'status': 1, 'result': 'an error message'}}
|
||||
}
|
||||
|
||||
mock.patch.object(client,
|
||||
'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()
|
||||
|
||||
self.driver = driver.DiscoDriver(execute=mock_exec,
|
||||
configuration=self.cfg)
|
||||
self.driver.do_setup(None)
|
||||
|
||||
self.attach_detach = attach_detach.AttachDetachDiscoVolume(self.cfg)
|
||||
|
||||
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
|
||||
|
||||
def create_client(self, *cmd, **kwargs):
|
||||
"""Mock the client's methods."""
|
||||
return FakeClient()
|
||||
|
||||
def get_mock_connector(self, *cmd, **kwargs):
|
||||
"""Mock the os_brick connector."""
|
||||
return None
|
||||
|
||||
def get_mock_attribute(self, *cmd, **kwargs):
|
||||
"""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 client."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Create a fake service attribute."""
|
||||
self.service = FakeMethod()
|
||||
|
||||
|
||||
class FakeMethod(object):
|
||||
"""Fake class recensing some of the method of the rest client."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Fake class to mock the client."""
|
||||
|
||||
def volumeCreate(self, *args, **kwargs):
|
||||
""""Mock function to create a volume."""
|
||||
|
||||
def volumeDelete(self, *args, **kwargs):
|
||||
""""Mock function to delete a volume."""
|
||||
|
||||
def snapshotCreate(self, *args, **kwargs):
|
||||
""""Mock function to create a snapshot."""
|
||||
|
||||
def snapshotDetail(self, *args, **kwargs):
|
||||
""""Mock function to get the snapshot detail."""
|
||||
|
||||
def snapshotDelete(self, *args, **kwargs):
|
||||
""""Mock function to delete snapshot."""
|
||||
|
||||
def restoreFromSnapshot(self, *args, **kwargs):
|
||||
""""Mock function to create a volume from a snapshot."""
|
||||
|
||||
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."""
|
||||
|
||||
def volumeClone(self, *args, **kwargs):
|
||||
""""Mock function to clone a volume."""
|
||||
|
||||
def cloneDetail(self, *args, **kwargs):
|
||||
"""Mock function to get the clone detail."""
|
||||
|
||||
def volumeExtend(self, *args, **kwargs):
|
||||
"""Mock function to extend a volume."""
|
||||
|
||||
def systemInformationList(self, *args, **kwargs):
|
||||
"""Mock function to get the backend properties."""
|
@ -1,158 +0,0 @@
|
||||
# (c) Copyright 2015 Industrial Technology Research Institute.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test cases for create cloned volume."""
|
||||
|
||||
import copy
|
||||
import mock
|
||||
import six
|
||||
import time
|
||||
|
||||
|
||||
from cinder import exception
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit import utils
|
||||
from cinder.tests.unit.volume.drivers import disco
|
||||
|
||||
|
||||
class CreateCloneVolumeTestCase(disco.TestDISCODriver):
|
||||
"""Test cases for DISCO connector."""
|
||||
|
||||
def setUp(self):
|
||||
"""Initialise variables and mock functions."""
|
||||
super(CreateCloneVolumeTestCase, self).setUp()
|
||||
|
||||
self.dest_volume = fake_volume.fake_volume_obj(self.ctx)
|
||||
# Create mock functions for all the call done by the driver."""
|
||||
mock.patch.object(self.requester,
|
||||
'volumeClone',
|
||||
self.clone_request).start()
|
||||
|
||||
mock.patch.object(self.requester,
|
||||
'cloneDetail',
|
||||
self.clone_detail_request).start()
|
||||
|
||||
mock.patch.object(self.requester,
|
||||
'volumeDetailByName',
|
||||
self.volume_detail_request).start()
|
||||
|
||||
self.volume_detail_response = {
|
||||
'status': 0,
|
||||
'volumeInfoResult':
|
||||
{'volumeId': 1234567}
|
||||
}
|
||||
|
||||
clone_success = (
|
||||
copy.deepcopy(self.FAKE_RESPONSE['standard']['success']))
|
||||
clone_pending = (
|
||||
copy.deepcopy(self.FAKE_RESPONSE['standard']['success']))
|
||||
clone_fail = (
|
||||
copy.deepcopy(self.FAKE_RESPONSE['standard']['success']))
|
||||
clone_response_fail = (
|
||||
copy.deepcopy(self.FAKE_RESPONSE['standard']['success']))
|
||||
|
||||
clone_success['result'] = (
|
||||
six.text_type(self.DETAIL_OPTIONS['success']))
|
||||
clone_pending['result'] = (
|
||||
six.text_type(self.DETAIL_OPTIONS['pending']))
|
||||
clone_fail['result'] = (
|
||||
six.text_type(self.DETAIL_OPTIONS['failure']))
|
||||
clone_response_fail['status'] = 1
|
||||
|
||||
self.FAKE_RESPONSE['clone_detail'] = {
|
||||
'success': clone_success,
|
||||
'fail': clone_fail,
|
||||
'pending': clone_pending,
|
||||
'request_fail': clone_response_fail
|
||||
}
|
||||
|
||||
self.response = self.FAKE_RESPONSE['standard']['success']
|
||||
self.response['result'] = '1234'
|
||||
|
||||
self.response_detail = (
|
||||
self.FAKE_RESPONSE['clone_detail']['success'])
|
||||
self.test_pending = False
|
||||
self.test_pending_count = 0
|
||||
|
||||
def clone_request(self, *cmd, **kwargs):
|
||||
"""Mock function for the createVolumeFromSnapshot function."""
|
||||
return self.response
|
||||
|
||||
def clone_detail_request(self, *cmd, **kwargs):
|
||||
"""Mock function for the restoreDetail function."""
|
||||
if self.test_pending:
|
||||
if self.test_pending_count == 0:
|
||||
self.test_pending_count += 1
|
||||
return self.FAKE_RESPONSE['clone_detail']['pending']
|
||||
else:
|
||||
return self.FAKE_RESPONSE['clone_detail']['success']
|
||||
else:
|
||||
return self.response_detail
|
||||
|
||||
def volume_detail_request(self, *cmd, **kwargs):
|
||||
"""Mock function for the volumeDetail function."""
|
||||
return self.volume_detail_response
|
||||
|
||||
def test_create_cloned_volume(self):
|
||||
"""Normal case."""
|
||||
expected = 1234567
|
||||
actual = self.driver.create_cloned_volume(self.dest_volume,
|
||||
self.volume)
|
||||
self.assertEqual(expected, actual['provider_location'])
|
||||
|
||||
def test_create_clone_volume_fail(self):
|
||||
"""Clone volume request to DISCO fails."""
|
||||
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_RESPONSE['standard']['success']
|
||||
self.response_detail = (
|
||||
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_RESPONSE['standard']['success']
|
||||
self.response_detail = (
|
||||
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_RESPONSE['standard']['success']
|
||||
self.test_pending = True
|
||||
self.test_create_cloned_volume()
|
||||
|
||||
@mock.patch.object(time, 'time')
|
||||
def test_create_cloned_volume_timeout(self, mock_time):
|
||||
"""Clone request timeout."""
|
||||
timeout = 3
|
||||
mock_time.side_effect = utils.generate_timeout_series(timeout)
|
||||
self.driver.configuration.disco_clone_check_timeout = timeout
|
||||
self.response = self.FAKE_RESPONSE['standard']['success']
|
||||
self.response_detail = (
|
||||
self.FAKE_RESPONSE['clone_detail']['pending'])
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_create_cloned_volume)
|
||||
|
||||
def test_create_cloned_volume_volume_detail_fail(self):
|
||||
"""Get volume detail request to DISCO fails."""
|
||||
self.volume_detail_response['status'] = 1
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_create_cloned_volume)
|
@ -1,153 +0,0 @@
|
||||
# (c) Copyright 2015 Industrial Technology Research Institute.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test case for the function create snapshot."""
|
||||
|
||||
|
||||
import copy
|
||||
import mock
|
||||
import time
|
||||
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import utils
|
||||
from cinder.tests.unit.volume.drivers import disco
|
||||
|
||||
|
||||
class CreateSnapshotTestCase(disco.TestDISCODriver):
|
||||
"""Test cases for DISCO connector."""
|
||||
|
||||
def get_fake_volume(self, ctx, id):
|
||||
"""Return fake volume from db calls."""
|
||||
return self.volume
|
||||
|
||||
def setUp(self):
|
||||
"""Initialise variables and mock functions."""
|
||||
super(CreateSnapshotTestCase, self).setUp()
|
||||
|
||||
self.snapshot = fake_snapshot.fake_snapshot_obj(
|
||||
self.ctx, **{'volume': self.volume})
|
||||
|
||||
# Mock db call in the cinder driver
|
||||
self.mock_object(db.sqlalchemy.api, 'volume_get',
|
||||
self.get_fake_volume)
|
||||
|
||||
mock.patch.object(self.requester,
|
||||
'snapshotCreate',
|
||||
self.snapshot_request).start()
|
||||
|
||||
mock.patch.object(self.requester,
|
||||
'snapshotDetail',
|
||||
self.snapshot_detail_request).start()
|
||||
|
||||
snapshot_detail_response = {
|
||||
'status': 0,
|
||||
'snapshotInfoResult':
|
||||
{'snapshotId': 1234,
|
||||
'description': 'a description',
|
||||
'createTime': '',
|
||||
'expireTime': '',
|
||||
'isDeleted': False,
|
||||
'status': 0}
|
||||
}
|
||||
|
||||
snap_success = copy.deepcopy(snapshot_detail_response)
|
||||
snap_pending = copy.deepcopy(snapshot_detail_response)
|
||||
snap_fail = copy.deepcopy(snapshot_detail_response)
|
||||
snap_response_fail = copy.deepcopy(snapshot_detail_response)
|
||||
snap_success['snapshotInfoResult']['status'] = (
|
||||
self.DETAIL_OPTIONS['success'])
|
||||
snap_pending['snapshotInfoResult']['status'] = (
|
||||
self.DETAIL_OPTIONS['pending'])
|
||||
snap_fail['snapshotInfoResult']['status'] = (
|
||||
self.DETAIL_OPTIONS['failure'])
|
||||
snap_response_fail['status'] = 1
|
||||
|
||||
self.FAKE_RESPONSE['snapshot_detail'] = {
|
||||
'success': snap_success,
|
||||
'fail': snap_fail,
|
||||
'pending': snap_pending,
|
||||
'request_fail': snap_response_fail}
|
||||
|
||||
self.response = (
|
||||
self.FAKE_RESPONSE['standard']['success'])
|
||||
self.response['result'] = 1234
|
||||
|
||||
self.response_detail = (
|
||||
self.FAKE_RESPONSE['snapshot_detail']['success'])
|
||||
self.test_pending = False
|
||||
|
||||
self.test_pending_count = 0
|
||||
|
||||
def snapshot_request(self, *cmd, **kwargs):
|
||||
"""Mock function for the createSnapshot call."""
|
||||
return self.response
|
||||
|
||||
def snapshot_detail_request(self, *cmd, **kwargs):
|
||||
"""Mock function for the snapshotDetail call."""
|
||||
if self.test_pending:
|
||||
if self.test_pending_count == 0:
|
||||
self.test_pending_count += 1
|
||||
return self.FAKE_RESPONSE['snapshot_detail']['pending']
|
||||
else:
|
||||
return self.FAKE_RESPONSE['snapshot_detail']['success']
|
||||
else:
|
||||
return self.response_detail
|
||||
|
||||
def test_create_snapshot(self):
|
||||
"""Normal test case."""
|
||||
expected = 1234
|
||||
actual = self.driver.create_snapshot(self.volume)
|
||||
self.assertEqual(expected, actual['provider_location'])
|
||||
|
||||
def test_create_snapshot_fail(self):
|
||||
"""Request to DISCO failed."""
|
||||
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_RESPONSE['standard']['success']
|
||||
self.response_detail = (
|
||||
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_RESPONSE['standard']['success']
|
||||
self.response_detail = (
|
||||
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_RESPONSE['standard']['success']
|
||||
self.test_pending = True
|
||||
self.test_create_snapshot()
|
||||
|
||||
@mock.patch.object(time, 'time')
|
||||
def test_create_snapshot_timeout(self, mock_time):
|
||||
"""Snapshot request timeout."""
|
||||
timeout = 3
|
||||
mock_time.side_effect = utils.generate_timeout_series(timeout)
|
||||
self.driver.configuration.disco_snapshot_check_timeout = timeout
|
||||
self.response = self.FAKE_RESPONSE['standard']['success']
|
||||
self.response_detail = (
|
||||
self.FAKE_RESPONSE['snapshot_detail']['pending'])
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_create_snapshot)
|
@ -1,53 +0,0 @@
|
||||
# (c) Copyright 2015 Industrial Technology Research Institute.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test case for the create volume function."""
|
||||
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder.tests.unit.volume.drivers import disco
|
||||
|
||||
|
||||
class CreateVolumeTestCase(disco.TestDISCODriver):
|
||||
"""Test cases for DISCO connector."""
|
||||
|
||||
def setUp(self):
|
||||
"""Prepare variables and mock functions."""
|
||||
super(CreateVolumeTestCase, self).setUp()
|
||||
|
||||
# Mock the method volumeCreate.
|
||||
mock.patch.object(self.requester,
|
||||
'volumeCreate',
|
||||
self.perform_disco_request).start()
|
||||
|
||||
self.response = self.FAKE_RESPONSE['standard']['success']
|
||||
|
||||
def perform_disco_request(self, *cmd, **kwargs):
|
||||
"""Mock function for the suds client."""
|
||||
return self.response
|
||||
|
||||
def test_create_volume(self):
|
||||
"""Normal case."""
|
||||
expected = '1234567'
|
||||
self.response['result'] = expected
|
||||
ret = self.driver.create_volume(self.volume)
|
||||
actual = ret['provider_location']
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_create_volume_fail(self):
|
||||
"""Request to DISCO failed."""
|
||||
self.response = self.FAKE_RESPONSE['standard']['fail']
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_create_volume)
|
@ -1,166 +0,0 @@
|
||||
# (c) Copyright 2015 Industrial Technology Research Institute.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test case for create volume from snapshot."""
|
||||
|
||||
import copy
|
||||
import mock
|
||||
import time
|
||||
|
||||
from cinder import exception
|
||||
from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import utils
|
||||
from cinder.tests.unit.volume.drivers import disco
|
||||
|
||||
|
||||
class CreateVolumeFromSnapshotTestCase(disco.TestDISCODriver):
|
||||
"""Test cases for the create volume from snapshot of DISCO connector."""
|
||||
|
||||
def setUp(self):
|
||||
"""Initialise variables and mock functions."""
|
||||
super(CreateVolumeFromSnapshotTestCase, self).setUp()
|
||||
|
||||
self.snapshot = fake_snapshot.fake_snapshot_obj(
|
||||
self.ctx, **{'volume': self.volume})
|
||||
|
||||
# Mock restoreFromSnapshot, restoreDetail
|
||||
# and volume detail since they are in the function path
|
||||
mock.patch.object(self.requester,
|
||||
'restoreFromSnapshot',
|
||||
self.restore_request).start()
|
||||
|
||||
mock.patch.object(self.requester,
|
||||
'restoreDetail',
|
||||
self.restore_detail_request).start()
|
||||
|
||||
mock.patch.object(self.requester,
|
||||
'volumeDetailByName',
|
||||
self.volume_detail_request).start()
|
||||
|
||||
restore_detail_response = {
|
||||
'status': 0,
|
||||
'restoreInfoResult':
|
||||
{'restoreId': 1234,
|
||||
'startTime': '',
|
||||
'statusPercent': '',
|
||||
'volumeName': 'aVolumeName',
|
||||
'snapshotId': 1234,
|
||||
'status': 0}
|
||||
}
|
||||
|
||||
self.volume_detail_response = {
|
||||
'status': 0,
|
||||
'volumeInfoResult':
|
||||
{'volumeId': 1234567}
|
||||
}
|
||||
|
||||
rest_success = copy.deepcopy(restore_detail_response)
|
||||
rest_pending = copy.deepcopy(restore_detail_response)
|
||||
rest_fail = copy.deepcopy(restore_detail_response)
|
||||
rest_response_fail = copy.deepcopy(restore_detail_response)
|
||||
rest_success['restoreInfoResult']['status'] = (
|
||||
self.DETAIL_OPTIONS['success'])
|
||||
rest_pending['restoreInfoResult']['status'] = (
|
||||
self.DETAIL_OPTIONS['pending'])
|
||||
rest_fail['restoreInfoResult']['status'] = (
|
||||
self.DETAIL_OPTIONS['failure'])
|
||||
rest_response_fail['status'] = 1
|
||||
|
||||
self.FAKE_RESPONSE['restore_detail'] = {
|
||||
'success': rest_success,
|
||||
'fail': rest_fail,
|
||||
'pending': rest_pending,
|
||||
'request_fail': rest_response_fail
|
||||
}
|
||||
|
||||
self.response = self.FAKE_RESPONSE['standard']['success']
|
||||
self.response['result'] = '1234'
|
||||
|
||||
self.response_detail = (
|
||||
self.FAKE_RESPONSE['restore_detail']['success'])
|
||||
self.test_pending = False
|
||||
|
||||
self.test_pending_count = 0
|
||||
|
||||
def restore_request(self, *cmd, **kwargs):
|
||||
"""Mock function for the createVolumeFromSnapshot function."""
|
||||
return self.response
|
||||
|
||||
def restore_detail_request(self, *cmd, **kwargs):
|
||||
"""Mock function for the restoreDetail function."""
|
||||
if self.test_pending:
|
||||
if self.test_pending_count == 0:
|
||||
self.test_pending_count += 1
|
||||
return self.FAKE_RESPONSE['restore_detail']['pending']
|
||||
else:
|
||||
return self.FAKE_RESPONSE['restore_detail']['success']
|
||||
else:
|
||||
return self.response_detail
|
||||
|
||||
def volume_detail_request(self, *cmd, **kwargs):
|
||||
"""Mock function for the volumeDetail function."""
|
||||
return self.volume_detail_response
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
"""Normal case."""
|
||||
expected = 1234567
|
||||
actual = self.driver.create_volume_from_snapshot(self.volume,
|
||||
self.snapshot)
|
||||
self.assertEqual(expected, actual['provider_location'])
|
||||
|
||||
def test_create_volume_from_snapshot_fail(self):
|
||||
"""Create volume from snapshot request fails."""
|
||||
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_RESPONSE['standard']['success']
|
||||
self.response_detail = (
|
||||
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_RESPONSE['standard']['success']
|
||||
self.response_detail = (
|
||||
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_RESPONSE['standard']['success']
|
||||
self.test_pending = True
|
||||
self.test_create_volume_from_snapshot()
|
||||
|
||||
@mock.patch.object(time, 'time')
|
||||
def test_create_volume_from_snapshot_timeout(self, mock_time):
|
||||
"""Create volume from snapshot task timeout."""
|
||||
timeout = 3
|
||||
mock_time.side_effect = utils.generate_timeout_series(timeout)
|
||||
self.driver.configuration.disco_restore_check_timeout = timeout
|
||||
self.response = self.FAKE_RESPONSE['standard']['success']
|
||||
self.response_detail = (
|
||||
self.FAKE_RESPONSE['restore_detail']['pending'])
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_create_volume_from_snapshot)
|
||||
|
||||
def test_create_volume_from_snapshot_volume_detail_fail(self):
|
||||
"""Cannot get the newly created volume information."""
|
||||
self.volume_detail_response['status'] = 1
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_create_volume_from_snapshot)
|
@ -1,51 +0,0 @@
|
||||
# (c) Copyright 2015 Industrial Technology Research Institute.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test case for the delete snapshot function."""
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit.volume.drivers import disco
|
||||
|
||||
|
||||
class DeleteSnapshotTestCase(disco.TestDISCODriver):
|
||||
"""Test cases to delete DISCO volumes."""
|
||||
|
||||
def setUp(self):
|
||||
"""Initialise variables and mock functions."""
|
||||
super(DeleteSnapshotTestCase, self).setUp()
|
||||
|
||||
# Mock snapshotDelete function.
|
||||
mock.patch.object(self.requester,
|
||||
'snapshotDelete',
|
||||
self.perform_disco_request).start()
|
||||
|
||||
self.response = self.FAKE_RESPONSE['standard']['success']
|
||||
self.snapshot = fake_snapshot.fake_snapshot_obj(
|
||||
self.ctx, **{'volume': self.volume})
|
||||
|
||||
def perform_disco_request(self, *cmd, **kwargs):
|
||||
"""Mock function to delete a snapshot."""
|
||||
return self.response
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
"""Delete a snapshot."""
|
||||
self.driver.delete_snapshot(self.snapshot)
|
||||
|
||||
def test_delete_snapshot_fail(self):
|
||||
"""Make the API returns an error while deleting."""
|
||||
self.response = self.FAKE_RESPONSE['standard']['fail']
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_delete_snapshot)
|
@ -1,49 +0,0 @@
|
||||
# (c) Copyright 2015 Industrial Technology Research Institute.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test case for the delete volume function."""
|
||||
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder.tests.unit.volume.drivers import disco
|
||||
|
||||
|
||||
class DeleteVolumeTestCase(disco.TestDISCODriver):
|
||||
"""Test cases to delete DISCO volumes."""
|
||||
|
||||
def setUp(self):
|
||||
"""Initialise variables and mock functions."""
|
||||
super(DeleteVolumeTestCase, self).setUp()
|
||||
|
||||
# Mock volumeDelete function.
|
||||
mock.patch.object(self.requester,
|
||||
'volumeDelete',
|
||||
self.perform_disco_request).start()
|
||||
|
||||
self.response = self.FAKE_RESPONSE['standard']['success']
|
||||
|
||||
def perform_disco_request(self, *cmd, **kwargs):
|
||||
"""Mock function to delete a volume."""
|
||||
return self.response
|
||||
|
||||
def test_delete_volume(self):
|
||||
"""Delete a volume."""
|
||||
self.driver.delete_volume(self.volume)
|
||||
|
||||
def test_delete_volume_fail(self):
|
||||
"""Make the API returns an error while deleting."""
|
||||
self.response = self.FAKE_RESPONSE['standard']['fail']
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_delete_volume)
|
@ -1,50 +0,0 @@
|
||||
# (c) Copyright 2015 Industrial Technology Research Institute.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test cases for the extend volume feature."""
|
||||
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder.tests.unit.volume.drivers import disco
|
||||
|
||||
|
||||
class VolumeExtendTestCase(disco.TestDISCODriver):
|
||||
"""Test cases for DISCO connector."""
|
||||
|
||||
def setUp(self):
|
||||
"""Initialise variables and mock functions."""
|
||||
super(VolumeExtendTestCase, self).setUp()
|
||||
|
||||
# Mock function to extend a volume.
|
||||
mock.patch.object(self.requester,
|
||||
'volumeExtend',
|
||||
self.perform_disco_request).start()
|
||||
|
||||
self.response = self.FAKE_RESPONSE['standard']['success']
|
||||
self.new_size = 5
|
||||
|
||||
def perform_disco_request(self, *cmd, **kwargs):
|
||||
"""Mock volumExtend function from suds client."""
|
||||
return self.response
|
||||
|
||||
def test_extend_volume(self):
|
||||
"""Extend a volume, normal case."""
|
||||
self.driver.extend_volume(self.volume, self.new_size)
|
||||
|
||||
def test_extend_volume_fail(self):
|
||||
"""Request to DISCO failed."""
|
||||
self.response = self.FAKE_RESPONSE['standard']['fail']
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_extend_volume)
|
@ -1,133 +0,0 @@
|
||||
# (c) Copyright 2016 Industrial Technology Research Institute.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test case for the function manage_existing."""
|
||||
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder.tests.unit.volume.drivers import disco
|
||||
|
||||
|
||||
class ManageExistingTestCase(disco.TestDISCODriver):
|
||||
"""Test cases for Disco connector."""
|
||||
|
||||
def setUp(self):
|
||||
"""Initialize variables and mock functions."""
|
||||
super(ManageExistingTestCase, self).setUp()
|
||||
|
||||
# Mock function to extract volume information by its ID
|
||||
mock.patch.object(self.requester,
|
||||
'volumeDetail',
|
||||
self.perform_disco_request).start()
|
||||
|
||||
# Mock function to extract volume information by its Name
|
||||
mock.patch.object(self.requester,
|
||||
'volumeDetailByName',
|
||||
self.perform_disco_request).start()
|
||||
|
||||
self.response = {'volumeInfoResult': {
|
||||
'volumeName': 'abcdefg',
|
||||
'volumeId': 1234567,
|
||||
'volSizeMb': 2
|
||||
},
|
||||
'status': 0
|
||||
}
|
||||
|
||||
self.existing_ref_no_identification = {}
|
||||
self.existing_ref_with_id = {'source-id': 1234567}
|
||||
self.existing_ref_with_name = {'source-name': 'abcdefg'}
|
||||
self.existing_ref_no_identification = self.existing_ref_with_id
|
||||
|
||||
def perform_disco_request(self, *args, **kwargs):
|
||||
"""Mock volumeDetail/volumeDetailByName function from rest client."""
|
||||
return self.response
|
||||
|
||||
def call_manage_existing(self):
|
||||
"""Manage an existing volume."""
|
||||
self.driver.manage_existing(
|
||||
self.volume,
|
||||
self.existing_ref_no_identification)
|
||||
|
||||
def test_manage_existing_no_identification(self):
|
||||
"""Manage an existing volume, no id/name."""
|
||||
self.existing_ref_no_identification = {}
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.call_manage_existing)
|
||||
|
||||
def test_manage_existing_case_id(self):
|
||||
"""Manage an existing volume, by its id."""
|
||||
expected = {'display_name': 'abcdefg'}
|
||||
ret = self.driver.manage_existing(self.volume,
|
||||
self.existing_ref_with_id)
|
||||
actual = {'display_name': ret['display_name']}
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_manage_existing_case_name(self):
|
||||
"""Manage an existing volume, by its name."""
|
||||
expected = {'provider_location': 1234567}
|
||||
ret = self.driver.manage_existing(self.volume,
|
||||
self.existing_ref_with_name)
|
||||
actual = {'provider_location': ret['provider_location']}
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_manage_existing_get_size(self):
|
||||
"""Get size of an existing volume."""
|
||||
self.driver.manage_existing_get_size(
|
||||
self.volume,
|
||||
self.existing_ref_no_identification)
|
||||
|
||||
def test_manage_existing_get_size_no_identification(self):
|
||||
"""Error while getting size of an existing volume, no id/name."""
|
||||
self.existing_ref_no_identification = {}
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_manage_existing_get_size)
|
||||
|
||||
def test_manage_existing_get_size_case_id(self):
|
||||
"""Get size of an existing volume, by its id."""
|
||||
expected = 2
|
||||
ret = self.driver.manage_existing_get_size(self.volume,
|
||||
self.existing_ref_with_id)
|
||||
self.assertEqual(expected, ret)
|
||||
|
||||
def test_manage_existing_get_size_case_name(self):
|
||||
"""Get size of an existing volume, by its name."""
|
||||
expected = 2
|
||||
ret = self.driver.manage_existing_get_size(self.volume,
|
||||
self.existing_ref_with_name)
|
||||
self.assertEqual(expected, ret)
|
||||
|
||||
def test_manage_existing_case_id_fail(self):
|
||||
"""Request to DISCO failed."""
|
||||
self.response['status'] = 1
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_manage_existing_case_id)
|
||||
|
||||
def test_manage_existing_case_name_fail(self):
|
||||
"""Request to DISCO failed."""
|
||||
self.response['status'] = 1
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_manage_existing_case_name)
|
||||
|
||||
def test_manage_existing_get_size_case_id_fail(self):
|
||||
"""Request to DISCO failed."""
|
||||
self.response['status'] = 1
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_manage_existing_get_size_case_id)
|
||||
|
||||
def test_manage_existing_get_size_case_name_fail(self):
|
||||
"""Request to DISCO failed."""
|
||||
self.response['status'] = 1
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.test_manage_existing_get_size_case_name)
|
@ -1,650 +0,0 @@
|
||||
# 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 Block device Driver."""
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
from oslo_utils import units
|
||||
import six
|
||||
from suds import client
|
||||
|
||||
from cinder import context
|
||||
from cinder.db.sqlalchemy import api
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.image import image_utils
|
||||
from cinder import interface
|
||||
from cinder.volume import configuration
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.disco import disco_api
|
||||
from cinder.volume.drivers.disco import disco_attach_detach
|
||||
from cinder.volume.drivers.san import san
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
disco_opts = [
|
||||
cfg.IPOpt('disco_client',
|
||||
default='127.0.0.1',
|
||||
help='The IP of DMS client socket server',
|
||||
deprecated_group='DEFAULT'),
|
||||
cfg.PortOpt('disco_client_port',
|
||||
default=9898,
|
||||
help='The port to connect DMS client socket server',
|
||||
deprecated_group='DEFAULT'),
|
||||
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('disco_rest_ip',
|
||||
help='The IP address of the REST server',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason='Using san_ip later',
|
||||
deprecated_name='rest_ip', deprecated_group='DEFAULT'),
|
||||
cfg.StrOpt('disco_choice_client',
|
||||
help='Use soap client or rest client for communicating '
|
||||
'with DISCO. Possible values are "soap" or '
|
||||
'"rest".', choices=['soap', 'rest'],
|
||||
deprecated_name='choice_client', deprecated_group='DEFAULT'),
|
||||
cfg.PortOpt('disco_src_api_port',
|
||||
default=8080,
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason='Using san_api_port later',
|
||||
help='The port of DISCO source API',
|
||||
deprecated_group='DEFAULT'),
|
||||
cfg.StrOpt('disco_volume_name_prefix',
|
||||
default='openstack-',
|
||||
help='Prefix before volume name to differentiate '
|
||||
'DISCO volume created through openstack '
|
||||
'and the other ones',
|
||||
deprecated_name='volume_name_prefix'),
|
||||
cfg.IntOpt('disco_snapshot_check_timeout',
|
||||
default=3600,
|
||||
help='How long we check whether a snapshot '
|
||||
'is finished before we give up',
|
||||
deprecated_name='snapshot_check_timeout'),
|
||||
cfg.IntOpt('disco_restore_check_timeout',
|
||||
default=3600,
|
||||
help='How long we check whether a restore '
|
||||
'is finished before we give up',
|
||||
deprecated_name='restore_check_timeout'),
|
||||
cfg.IntOpt('disco_clone_check_timeout',
|
||||
default=3600,
|
||||
help='How long we check whether a clone '
|
||||
'is finished before we give up',
|
||||
deprecated_name='clone_check_timeout'),
|
||||
cfg.IntOpt('disco_retry_interval',
|
||||
default=1,
|
||||
help='How long we wait before retrying to '
|
||||
'get an item detail',
|
||||
deprecated_name='retry_interval')
|
||||
]
|
||||
|
||||
DISCO_CODE_MAPPING = {
|
||||
'request.success': 1,
|
||||
'request.ongoing': 2,
|
||||
'request.failure': 3,
|
||||
}
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(disco_opts, group=configuration.SHARED_CONF_GROUP)
|
||||
|
||||
|
||||
# Driver to communicate with DISCO storage solution
|
||||
@interface.volumedriver
|
||||
class DiscoDriver(driver.VolumeDriver):
|
||||
"""Execute commands related to DISCO Volumes.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
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"
|
||||
|
||||
# TODO(jsbryant) Remove driver in Stein if CI is not fixed
|
||||
SUPPORTED = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Init Disco driver : get configuration, create client."""
|
||||
super(DiscoDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration.append_config_values(disco_opts)
|
||||
self.configuration.append_config_values(san.san_opts)
|
||||
self.ctxt = context.get_admin_context()
|
||||
self.attach_detach_volume = (
|
||||
disco_attach_detach.AttachDetachDiscoVolume(self.configuration))
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Create client for DISCO request manager."""
|
||||
LOG.debug("Enter in DiscoDriver do_setup.")
|
||||
if (self.configuration.disco_choice_client.lower() == "rest" and
|
||||
self.configuration.san_ip):
|
||||
self.client = disco_api.DiscoApi(
|
||||
self.configuration.san_ip,
|
||||
self.configuration.san_api_port)
|
||||
elif (self.configuration.disco_choice_client.lower() == "rest" and
|
||||
self.configuration.disco_rest_ip):
|
||||
self.client = disco_api.DiscoApi(
|
||||
self.configuration.disco_rest_ip,
|
||||
self.configuration.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."""
|
||||
if self.configuration.disco_choice_client.lower() == "soap":
|
||||
path = self.configuration.disco_wsdl_path
|
||||
if not os.path.exists(path):
|
||||
msg = _("Could not find DISCO wsdl file.")
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
else:
|
||||
if not (self.configuration.disco_rest_ip or
|
||||
self.configuration.san_ip):
|
||||
msg = _("Could not find the IP address of the REST server.")
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Create a disco volume."""
|
||||
name = self.configuration.disco_volume_name_prefix, volume["id"]
|
||||
vol_name = ''.join(name)
|
||||
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.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:
|
||||
msg = (_("Error while creating volume "
|
||||
"[status] %(stat)s - [result] %(res)s.") %
|
||||
{'stat': six.text_type(status), 'res': result})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
LOG.debug("Volume %s created.", volume["name"])
|
||||
return {'provider_location': result}
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Delete a logical volume."""
|
||||
disco_vol_id = volume['provider_location']
|
||||
LOG.debug("Delete disco volume : %s.", 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:
|
||||
msg = (_("Error while deleting volume "
|
||||
"[status] %(stat)s - [result] %(res)s.") %
|
||||
{'stat': six.text_type(status), 'res': result})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
LOG.debug("Volume %s deleted.", volume['name'])
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Create a disco snapshot."""
|
||||
volume = api.volume_get(self.ctxt, snapshot['volume_id'])
|
||||
description = snapshot['display_description']
|
||||
vol_id = volume['provider_location']
|
||||
LOG.debug("Create snapshot of volume : %(id)s, "
|
||||
"description : %(desc)s.",
|
||||
{'id': vol_id, 'desc': description})
|
||||
|
||||
# Trigger an asynchronous local snapshot
|
||||
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:
|
||||
msg = (_("Error while creating snapshot "
|
||||
"[status] %(stat)s - [result] %(res)s.") %
|
||||
{'stat': six.text_type(status), 'res': result})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
# Monitor the status until it becomes either success or fail
|
||||
params = {'snapshot_id': int(result)}
|
||||
start_time = int(time.time())
|
||||
snapshot_request = DISCOCheck(self.client,
|
||||
params,
|
||||
start_time,
|
||||
"snapshot_detail",
|
||||
self.configuration)
|
||||
timeout = self.configuration.disco_snapshot_check_timeout
|
||||
snapshot_request._monitor_request(timeout)
|
||||
|
||||
snapshot['provider_location'] = result
|
||||
LOG.debug("snapshot taken successfully on volume : %(volume)s.",
|
||||
{'volume': volume['name']})
|
||||
return {'provider_location': result}
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Delete a disco snapshot."""
|
||||
LOG.debug("Enter in delete a disco snapshot.")
|
||||
|
||||
snap_id = snapshot['provider_location']
|
||||
LOG.debug("[start] Delete snapshot : %s.", 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:
|
||||
msg = (_("Error while deleting snapshot "
|
||||
"[status] %(stat)s - [result] %(res)s") %
|
||||
{'stat': six.text_type(status), 'res': result})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Create a volume from a snapshot."""
|
||||
name = self.configuration.disco_volume_name_prefix, volume['id']
|
||||
snap_id = snapshot['provider_location']
|
||||
vol_name = ''.join(name)
|
||||
# Trigger an asynchronous restore operation
|
||||
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.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:
|
||||
msg = (_("Error[%(stat)s - %(res)s] while restoring snapshot "
|
||||
"[%(snap_id)s] into volume [%(vol)s].") %
|
||||
{'stat': six.text_type(status), 'res': result,
|
||||
'snap_id': snap_id, 'vol': vol_name})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
# Monitor the status until it becomes
|
||||
# either success, fail or timeout
|
||||
params = {'restore_id': int(result)}
|
||||
start_time = int(time.time())
|
||||
restore_request = DISCOCheck(self.client,
|
||||
params,
|
||||
start_time,
|
||||
"restore_detail",
|
||||
self.configuration)
|
||||
timeout = self.configuration.disco_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:
|
||||
msg = (_("Error[status] %(stat)s - [result] %(res)s] "
|
||||
"while getting volume id.") %
|
||||
{'stat': six.text_type(status), 'res': result})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
LOG.debug("Restore done [status] %(stat)s - "
|
||||
"[volume id] %(vol_id)s.",
|
||||
{'stat': status, 'vol_id': six.text_type(new_vol_id)})
|
||||
return {'provider_location': new_vol_id}
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Create a clone of the specified volume."""
|
||||
LOG.debug("Creating clone of volume: %s.", src_vref['id'])
|
||||
name = self.configuration.disco_volume_name_prefix, volume['id']
|
||||
vol_name = ''.join(name)
|
||||
vol_size = volume['size'] * units.Ki
|
||||
src_vol_id = src_vref['provider_location']
|
||||
LOG.debug("Clone volume : "
|
||||
"[name] %(name)s - [source] %(source)s - [size] %(size)s.",
|
||||
{'name': vol_name,
|
||||
'source': src_vol_id,
|
||||
'size': six.text_type(vol_size)})
|
||||
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:
|
||||
msg = (_("Error while creating volume "
|
||||
"[status] %(stat)s - [result] %(res)s.") %
|
||||
{'stat': six.text_type(status), 'res': result})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
# Monitor the status until it becomes
|
||||
# either success, fail or timeout
|
||||
params = {'clone_id': int(result),
|
||||
'vol_name': vol_name}
|
||||
start_time = int(time.time())
|
||||
clone_request = DISCOCheck(self.client,
|
||||
params,
|
||||
start_time,
|
||||
"clone_detail",
|
||||
self.configuration)
|
||||
clone_request._monitor_request(
|
||||
self.configuration.disco_clone_check_timeout)
|
||||
reply = self.client.volumeDetailByName(vol_name)
|
||||
status = reply['status']
|
||||
new_vol_id = reply['volumeInfoResult']['volumeId']
|
||||
|
||||
if status:
|
||||
msg = (_("Error[%(stat)s - %(res)s] "
|
||||
"while getting volume id."),
|
||||
{'stat': six.text_type(status), 'res': result})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
LOG.debug("clone done : "
|
||||
"[status] %(stat)s - [volume id] %(vol_id)s.",
|
||||
{'stat': status, 'vol_id': six.text_type(new_vol_id)})
|
||||
return {'provider_location': new_vol_id}
|
||||
|
||||
def copy_image_to_volume(self, context, volume, image_service, image_id):
|
||||
"""Fetch the image from image_service and write it to the volume."""
|
||||
LOG.debug("Enter in copy image to volume for disco.")
|
||||
|
||||
try:
|
||||
attach_detach_volume = (
|
||||
disco_attach_detach.AttachDetachDiscoVolume(
|
||||
self.configuration))
|
||||
device_info = attach_detach_volume._attach_volume(volume)
|
||||
image_utils.fetch_to_raw(context,
|
||||
image_service,
|
||||
image_id,
|
||||
device_info['path'],
|
||||
self.configuration.volume_dd_blocksize,
|
||||
size=volume['size'])
|
||||
finally:
|
||||
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:
|
||||
attach_detach_volume = (
|
||||
disco_attach_detach.AttachDetachDiscoVolume(
|
||||
self.configuration))
|
||||
device_info = attach_detach_volume._attach_volume(volume)
|
||||
image_utils.upload_volume(context,
|
||||
image_service,
|
||||
image_meta,
|
||||
device_info['path'])
|
||||
finally:
|
||||
attach_detach_volume._detach_volume(volume)
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend an existing volume's size."""
|
||||
vol_id = volume['provider_location']
|
||||
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.volumeExtend(vol_id, new_size_mb)
|
||||
status = reply['status']
|
||||
result = reply['result']
|
||||
if status:
|
||||
msg = (_("Error while extending volume "
|
||||
"[status] %(stat)s - [result] %(res)s."),
|
||||
{'stat': six.text_type(status), 'res': result})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
LOG.debug("Volume extended : [id] %(vid)s - "
|
||||
"[status] %(stat)s - [result] %(res)s.",
|
||||
{'vid': vol_id,
|
||||
'stat': six.text_type(status),
|
||||
'res': result})
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""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': cp
|
||||
}
|
||||
LOG.debug("Initialize connection [data]: %s.", data)
|
||||
return data
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Function called after attaching a volume."""
|
||||
LOG.debug("Enter in terminate connection with disco.")
|
||||
|
||||
def _update_volume_stats(self):
|
||||
LOG.debug("Enter in update volume stats.")
|
||||
stats = {}
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
stats['volume_backend_name'] = backend_name or 'disco'
|
||||
stats['storage_protocol'] = 'disco'
|
||||
stats['driver_version'] = self.VERSION
|
||||
stats['reserved_percentage'] = 0
|
||||
stats['vendor_name'] = 'ITRI'
|
||||
stats['QoS_support'] = False
|
||||
|
||||
try:
|
||||
reply = self.client.systemInformationList()
|
||||
status = reply['status']
|
||||
|
||||
if status:
|
||||
msg = (_("Error while getting "
|
||||
"disco information [%s].") %
|
||||
six.text_type(status))
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
info_list = reply['propertyListResult']['PropertyInfoList']
|
||||
for info in info_list:
|
||||
if info['name'] == 'freeCapacityGB':
|
||||
stats['free_capacity_gb'] = float(info['value'])
|
||||
elif info['name'] == 'totalCapacityGB':
|
||||
stats['total_capacity_gb'] = float(info['value'])
|
||||
except Exception:
|
||||
stats['total_capacity_gb'] = 'unknown'
|
||||
stats['free_capacity_gb'] = 'unknown'
|
||||
|
||||
self._stats = stats
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get backend information."""
|
||||
if refresh:
|
||||
self._update_volume_stats()
|
||||
return self._stats
|
||||
|
||||
def local_path(self, volume):
|
||||
"""Return the path to the DISCO volume."""
|
||||
return "/dev/dms%s" % volume['name']
|
||||
|
||||
def manage_existing(self, volume, existing_ref):
|
||||
"""Manage an existing volume."""
|
||||
if 'source-name' not in existing_ref and'source-id'not in existing_ref:
|
||||
msg = _("No source-id/source-name in existing_ref")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
elif 'source-name' not in existing_ref:
|
||||
src_vol = self.client.volumeDetail(
|
||||
existing_ref['source-id'])
|
||||
if src_vol['status']:
|
||||
vol_id = existing_ref['source-id']
|
||||
msg = (_("Error while getting volume details, "
|
||||
"[status] %(stat)s - [volume id] %(vol_id)s") %
|
||||
{'stat': six.text_type(src_vol['status']),
|
||||
'vol_id': vol_id})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return {'display_name': src_vol['volumeInfoResult']['volumeName'],
|
||||
'provider_location': existing_ref['source-id']}
|
||||
else:
|
||||
src_vol = self.client.volumeDetailByName(
|
||||
existing_ref['source-name'])
|
||||
if src_vol['status']:
|
||||
vol_name = existing_ref['source-name']
|
||||
msg = (_("Error while getting volume details with the name "
|
||||
"%(name)s: [status] %(stat)s") % {'name': vol_name,
|
||||
'stat': src_vol['status']})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return {
|
||||
'provider_location': src_vol['volumeInfoResult']['volumeId']}
|
||||
|
||||
def unmanage(self, volume):
|
||||
"""Unmanage an existing volume."""
|
||||
LOG.debug("unmanage is called", resource=volume)
|
||||
|
||||
def manage_existing_get_size(self, volume, existing_ref):
|
||||
"""Return size of an existing volume."""
|
||||
if 'source-name' not in existing_ref and'source-id'not in existing_ref:
|
||||
msg = _("No source-id/source-name in existing_ref")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
elif 'source-name' not in existing_ref:
|
||||
src_vol = self.client.volumeDetail(
|
||||
existing_ref['source-id'])
|
||||
if src_vol['status']:
|
||||
vol_id = existing_ref['source-id']
|
||||
msg = (_("Error while getting volume details, "
|
||||
"[status] %(stat)s - [volume id] %(vol_id)s") %
|
||||
{'stat': six.text_type(src_vol['status']),
|
||||
'vol_id': vol_id})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return src_vol['volumeInfoResult']['volSizeMb']
|
||||
else:
|
||||
src_vol = self.client.volumeDetailByName(
|
||||
existing_ref['source-name'])
|
||||
if src_vol['status']:
|
||||
vol_name = existing_ref['source-name']
|
||||
msg = (_("Error while getting volume details with the name "
|
||||
"%(name)s: [status] %(stat)s") % {'name': vol_name,
|
||||
'stat': src_vol['status']})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return src_vol['volumeInfoResult']['volSizeMb']
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Ensure an export."""
|
||||
pass
|
||||
|
||||
def create_export(self, context, volume, connector):
|
||||
"""Export the volume."""
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
"""Remove an export for a logical volume."""
|
||||
pass
|
||||
|
||||
|
||||
class DISCOCheck(object):
|
||||
"""Used to monitor DISCO operations."""
|
||||
|
||||
def __init__(self, client, param, start_time, function, configuration):
|
||||
"""Init some variables for checking some requests done in DISCO."""
|
||||
self.start_time = start_time
|
||||
self.function = function
|
||||
self.client = client
|
||||
self.param = param
|
||||
self.configuration = configuration
|
||||
|
||||
def is_timeout(self, start_time, timeout):
|
||||
"""Check whether we reach the timeout."""
|
||||
current_time = int(time.time())
|
||||
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:
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
item_status = self._get_item_status(operation, reply)
|
||||
if item_status == DISCO_CODE_MAPPING['request.failure']:
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
elif item_status == DISCO_CODE_MAPPING['request.success']:
|
||||
raise loopingcall.LoopingCallDone(retvalue=reply)
|
||||
elif self.is_timeout(start_time, timeout):
|
||||
msg = (_("Timeout while calling %s ") % operation)
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def _call_api(self, operation, params):
|
||||
"""Make the call to the SOAP api."""
|
||||
if operation == 'snapshot_detail':
|
||||
return self.client.snapshotDetail(params['snapshot_id'])
|
||||
if operation == 'restore_detail':
|
||||
return self.client.restoreDetail(params['restore_id'])
|
||||
if operation == 'clone_detail':
|
||||
return self.client.cloneDetail(params['clone_id'],
|
||||
params['vol_name'])
|
||||
else:
|
||||
msg = (_("Unknown operation %s."), operation)
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def _get_item_status(self, operation, reply):
|
||||
"""Make the call to the SOAP api."""
|
||||
if reply is None:
|
||||
msg = (_("Call returned a None object"))
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
elif operation == 'snapshot_detail':
|
||||
return reply['snapshotInfoResult']['status']
|
||||
elif operation == 'restore_detail':
|
||||
return reply['restoreInfoResult']['status']
|
||||
elif operation == 'clone_detail':
|
||||
return int(reply['result'])
|
||||
else:
|
||||
msg = (_("Unknown operation "
|
||||
"%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=self.configuration.disco_retry_interval).wait()
|
@ -1,165 +0,0 @@
|
||||
# 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
|
||||
|
||||
import requests
|
||||
import six
|
||||
|
||||
|
||||
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()
|
@ -1,69 +0,0 @@
|
||||
# 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 import initiator
|
||||
from os_brick.initiator import connector
|
||||
from oslo_log import log as logging
|
||||
|
||||
from cinder import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AttachDetachDiscoVolume(object):
|
||||
"""Class for attach and detach a DISCO volume."""
|
||||
|
||||
def __init__(self, configuration):
|
||||
"""Init volume attachment class."""
|
||||
self.configuration = configuration
|
||||
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
|
||||
|
||||
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 initiator.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)
|
@ -1,27 +0,0 @@
|
||||
========================
|
||||
ITRI DISCO volume driver
|
||||
========================
|
||||
|
||||
Supported operations
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The DISCO driver supports the following features:
|
||||
|
||||
* Volume create and delete
|
||||
* Volume attach and detach
|
||||
* Snapshot create and delete
|
||||
* Create volume from snapshot
|
||||
* Get volume stats
|
||||
* Copy image to volume
|
||||
* Copy volume to image
|
||||
* Clone volume
|
||||
* Extend volume
|
||||
* Manage and unmanage volume
|
||||
|
||||
Configuration options
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. config-table::
|
||||
:config-target: Disco
|
||||
|
||||
cinder.volume.drivers.disco.disco
|
@ -52,7 +52,6 @@ Driver Configuration Reference
|
||||
drivers/ibm-storwize-svc-driver
|
||||
drivers/infinidat-volume-driver
|
||||
drivers/inspur-instorage-driver
|
||||
drivers/itri-disco-driver
|
||||
drivers/kaminario-driver
|
||||
drivers/lenovo-driver
|
||||
drivers/nec-storage-m-series-driver
|
||||
|
@ -105,9 +105,6 @@ title=Infinidat Storage Driver (iSCSI, FC)
|
||||
[driver.inspur]
|
||||
title=Inspur G2 Storage Driver (iSCSI, FC)
|
||||
|
||||
[driver.itri_disco]
|
||||
title=ITRI DISCO Driver (DISCO)
|
||||
|
||||
[driver.kaminario]
|
||||
title=Kaminario Storage Driver (iSCSI, FC)
|
||||
|
||||
@ -235,7 +232,6 @@ driver.ibm_gpfs=complete
|
||||
driver.ibm_storwize=complete
|
||||
driver.ibm_xiv=complete
|
||||
driver.inspur=complete
|
||||
driver.itri_disco=missing
|
||||
driver.kaminario=complete
|
||||
driver.lenovo=complete
|
||||
driver.linbit_drbd=complete
|
||||
@ -301,7 +297,6 @@ driver.ibm_gpfs=complete
|
||||
driver.ibm_storwize=complete
|
||||
driver.ibm_xiv=complete
|
||||
driver.inspur=complete
|
||||
driver.itri_disco=complete
|
||||
driver.kaminario=complete
|
||||
driver.lenovo=complete
|
||||
driver.linbit_drbd=complete
|
||||
@ -367,7 +362,6 @@ driver.ibm_gpfs=missing
|
||||
driver.ibm_storwize=complete
|
||||
driver.ibm_xiv=missing
|
||||
driver.inspur=complete
|
||||
driver.itri_disco=missing
|
||||
driver.kaminario=missing
|
||||
driver.lenovo=missing
|
||||
driver.linbit_drbd=missing
|
||||
@ -434,7 +428,6 @@ driver.ibm_gpfs=missing
|
||||
driver.ibm_storwize=complete
|
||||
driver.ibm_xiv=missing
|
||||
driver.inspur=complete
|
||||
driver.itri_disco=missing
|
||||
driver.kaminario=missing
|
||||
driver.lenovo=missing
|
||||
driver.linbit_drbd=missing
|
||||
@ -502,7 +495,6 @@ driver.ibm_gpfs=missing
|
||||
driver.ibm_storwize=complete
|
||||
driver.ibm_xiv=complete
|
||||
driver.inspur=complete
|
||||
driver.itri_disco=missing
|
||||
driver.kaminario=complete
|
||||
driver.lenovo=missing
|
||||
driver.linbit_drbd=missing
|
||||
@ -571,7 +563,6 @@ driver.ibm_gpfs=missing
|
||||
driver.ibm_storwize=complete
|
||||
driver.ibm_xiv=complete
|
||||
driver.inspur=complete
|
||||
driver.itri_disco=missing
|
||||
driver.kaminario=missing
|
||||
driver.lenovo=missing
|
||||
driver.linbit_drbd=missing
|
||||
@ -639,7 +630,6 @@ driver.ibm_gpfs=missing
|
||||
driver.ibm_storwize=missing
|
||||
driver.ibm_xiv=missing
|
||||
driver.inspur=missing
|
||||
driver.itri_disco=missing
|
||||
driver.kaminario=complete
|
||||
driver.lenovo=missing
|
||||
driver.linbit_drbd=missing
|
||||
@ -708,7 +698,6 @@ driver.ibm_gpfs=missing
|
||||
driver.ibm_storwize=missing
|
||||
driver.ibm_xiv=missing
|
||||
driver.inspur=missing
|
||||
driver.itri_disco=missing
|
||||
driver.kaminario=missing
|
||||
driver.lenovo=missing
|
||||
driver.linbit_drbd=missing
|
||||
@ -777,7 +766,6 @@ driver.ibm_gpfs=missing
|
||||
driver.ibm_storwize=complete
|
||||
driver.ibm_xiv=complete
|
||||
driver.inspur=missing
|
||||
driver.itri_disco=missing
|
||||
driver.kaminario=missing
|
||||
driver.lenovo=missing
|
||||
driver.linbit_drbd=missing
|
||||
|
@ -68,3 +68,4 @@ release.
|
||||
|
||||
* Stein
|
||||
* HGST Flash Storage Suite Driver (vgc)
|
||||
* ITRI DISCO Driver
|
||||
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
The ITRI DISCO storage driver has been removed after completion of its
|
||||
deprecation period without a reliable 3rd Party CI system being
|
||||
supported. Customers using the ITRI DISCO driver should not upgrade
|
||||
Cinder without first migrating all volumes from their DISCO backend
|
||||
to a supported storage backend. Failure to migrate volumes will
|
||||
result in no longer being able to access volumes back by the ITRI DISCO
|
||||
storage backend.
|
||||
other:
|
||||
- |
|
||||
The ITRI DISCO storage driver was marked unsupported in Rocky due to
|
||||
3rd Party CI not meeting Cinder's requirements. As a result the
|
||||
driver is removed starting from the Stein release.
|
Loading…
Reference in New Issue
Block a user