Hitachi: Add generic volume groups
This patch adds consistency group capability as generic volume groups for the Hitachi driver. DocImpact Implements: blueprint hitachi-vsp-add-consistency-groups Change-Id: I101d6899c8e7d4911c64cded2c10da68f5bceed2
This commit is contained in:
parent
dd00352c35
commit
36e0087577
@ -24,11 +24,13 @@ from requests import models
|
||||
from cinder import context as cinder_context
|
||||
from cinder import db
|
||||
from cinder.db.sqlalchemy import api as sqlalchemy_api
|
||||
from cinder.objects import group_snapshot as obj_group_snap
|
||||
from cinder.objects import snapshot as obj_snap
|
||||
from cinder.tests.unit import fake_group
|
||||
from cinder.tests.unit import fake_group_snapshot
|
||||
from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit import test
|
||||
from cinder import utils
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.hitachi import hbsd_common
|
||||
@ -37,6 +39,7 @@ from cinder.volume.drivers.hitachi import hbsd_rest
|
||||
from cinder.volume.drivers.hitachi import hbsd_rest_api
|
||||
from cinder.volume.drivers.hitachi import hbsd_utils
|
||||
from cinder.volume import volume_types
|
||||
from cinder.volume import volume_utils
|
||||
from cinder.zonemanager import utils as fczm_utils
|
||||
|
||||
# Configuration parameter values
|
||||
@ -73,11 +76,14 @@ DEFAULT_CONNECTOR = {
|
||||
CTXT = cinder_context.get_admin_context()
|
||||
|
||||
TEST_VOLUME = []
|
||||
for i in range(3):
|
||||
for i in range(4):
|
||||
volume = {}
|
||||
volume['id'] = '00000000-0000-0000-0000-{0:012d}'.format(i)
|
||||
volume['name'] = 'test-volume{0:d}'.format(i)
|
||||
volume['provider_location'] = '{0:d}'.format(i)
|
||||
if i == 3:
|
||||
volume['provider_location'] = None
|
||||
else:
|
||||
volume['provider_location'] = '{0:d}'.format(i)
|
||||
volume['size'] = 128
|
||||
if i == 2:
|
||||
volume['status'] = 'in-use'
|
||||
@ -107,6 +113,23 @@ snapshot = obj_snap.Snapshot._from_db_object(
|
||||
fake_snapshot.fake_db_snapshot(**snapshot))
|
||||
TEST_SNAPSHOT.append(snapshot)
|
||||
|
||||
TEST_GROUP = []
|
||||
for i in range(2):
|
||||
group = {}
|
||||
group['id'] = '20000000-0000-0000-0000-{0:012d}'.format(i)
|
||||
group['status'] = 'available'
|
||||
group = fake_group.fake_group_obj(CTXT, **group)
|
||||
TEST_GROUP.append(group)
|
||||
|
||||
TEST_GROUP_SNAP = []
|
||||
group_snapshot = {}
|
||||
group_snapshot['id'] = '30000000-0000-0000-0000-{0:012d}'.format(0)
|
||||
group_snapshot['status'] = 'available'
|
||||
group_snapshot = obj_group_snap.GroupSnapshot._from_db_object(
|
||||
CTXT, obj_group_snap.GroupSnapshot(),
|
||||
fake_group_snapshot.fake_db_group_snapshot(**group_snapshot))
|
||||
TEST_GROUP_SNAP.append(group_snapshot)
|
||||
|
||||
# Dummy response for REST API
|
||||
POST_SESSIONS_RESULT = {
|
||||
"token": "b74777a3-f9f0-4ea8-bd8f-09847fac48d3",
|
||||
@ -205,6 +228,18 @@ GET_SNAPSHOTS_RESULT = {
|
||||
],
|
||||
}
|
||||
|
||||
GET_SNAPSHOTS_RESULT_PAIR = {
|
||||
"data": [
|
||||
{
|
||||
"primaryOrSecondary": "S-VOL",
|
||||
"status": "PAIR",
|
||||
"pvolLdevId": 0,
|
||||
"muNumber": 1,
|
||||
"svolLdevId": 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
GET_SNAPSHOTS_RESULT_BUSY = {
|
||||
"data": [
|
||||
{
|
||||
@ -403,6 +438,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
'rest_server_ip_port']
|
||||
self.configuration.hitachi_rest_tcp_keepalive = True
|
||||
self.configuration.hitachi_discard_zero_page = True
|
||||
self.configuration.hitachi_rest_number = "0"
|
||||
|
||||
self.configuration.hitachi_zoning_request = False
|
||||
|
||||
@ -434,7 +470,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(
|
||||
utils, 'brick_get_connector_properties',
|
||||
volume_utils, 'brick_get_connector_properties',
|
||||
side_effect=_brick_get_connector_properties)
|
||||
def _setup_driver(
|
||||
self, brick_get_connector_properties=None, request=None):
|
||||
@ -462,7 +498,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
# API test cases
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(
|
||||
utils, 'brick_get_connector_properties',
|
||||
volume_utils, 'brick_get_connector_properties',
|
||||
side_effect=_brick_get_connector_properties)
|
||||
def test_do_setup(self, brick_get_connector_properties, request):
|
||||
drv = hbsd_fc.HBSDFCDriver(
|
||||
@ -483,7 +519,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(
|
||||
utils, 'brick_get_connector_properties',
|
||||
volume_utils, 'brick_get_connector_properties',
|
||||
side_effect=_brick_get_connector_properties)
|
||||
def test_do_setup_create_hg(self, brick_get_connector_properties, request):
|
||||
"""Normal case: The host group not exists."""
|
||||
@ -510,7 +546,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(
|
||||
utils, 'brick_get_connector_properties',
|
||||
volume_utils, 'brick_get_connector_properties',
|
||||
side_effect=_brick_get_connector_properties)
|
||||
def test_do_setup_pool_name(self, brick_get_connector_properties, request):
|
||||
"""Normal case: Specify a pool name instead of pool id"""
|
||||
@ -896,3 +932,143 @@ class HBSDRESTFCDriverTest(test.TestCase):
|
||||
req = models.Response()
|
||||
ret = session.__call__(req)
|
||||
self.assertEqual('Session token', ret.headers['Authorization'])
|
||||
|
||||
def test_create_group(self):
|
||||
ret = self.driver.create_group(self.ctxt, TEST_GROUP[0])
|
||||
self.assertIsNone(ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_group(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
ret = self.driver.delete_group(
|
||||
self.ctxt, TEST_GROUP[0], [TEST_VOLUME[0]])
|
||||
self.assertEqual(4, request.call_count)
|
||||
actual = (
|
||||
{'status': TEST_GROUP[0]['status']},
|
||||
[{'id': TEST_VOLUME[0]['id'], 'status': 'deleted'}]
|
||||
)
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_create_group_from_src_volume(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
ret = self.driver.create_group_from_src(
|
||||
self.ctxt, TEST_GROUP[1], [TEST_VOLUME[1]],
|
||||
source_group=TEST_GROUP[0], source_vols=[TEST_VOLUME[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
None, [{'id': TEST_VOLUME[1]['id'], 'provider_location': '1'}])
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_create_group_from_src_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
ret = self.driver.create_group_from_src(
|
||||
self.ctxt, TEST_GROUP[0], [TEST_VOLUME[0]],
|
||||
group_snapshot=TEST_GROUP_SNAP[0], snapshots=[TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
None, [{'id': TEST_VOLUME[0]['id'], 'provider_location': '1'}])
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
def test_create_group_from_src_volume_error(self):
|
||||
self.assertRaises(
|
||||
hbsd_utils.HBSDError, self.driver.create_group_from_src,
|
||||
self.ctxt, TEST_GROUP[1], [TEST_VOLUME[1]],
|
||||
source_group=TEST_GROUP[0], source_vols=[TEST_VOLUME[3]]
|
||||
)
|
||||
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type')
|
||||
def test_update_group(self, is_group_a_cg_snapshot_type):
|
||||
is_group_a_cg_snapshot_type.return_value = False
|
||||
ret = self.driver.update_group(
|
||||
self.ctxt, TEST_GROUP[0], add_volumes=[TEST_VOLUME[0]])
|
||||
self.assertTupleEqual((None, None, None), ret)
|
||||
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type')
|
||||
def test_update_group_error(self, is_group_a_cg_snapshot_type):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
self.assertRaises(
|
||||
hbsd_utils.HBSDError, self.driver.update_group,
|
||||
self.ctxt, TEST_GROUP[0], add_volumes=[TEST_VOLUME[3]],
|
||||
remove_volumes=[TEST_VOLUME[0]]
|
||||
)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(sqlalchemy_api, 'volume_get', side_effect=_volume_get)
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type')
|
||||
def test_create_group_snapshot_non_cg(
|
||||
self, is_group_a_cg_snapshot_type, volume_get, request):
|
||||
is_group_a_cg_snapshot_type.return_value = False
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(4, request.call_count)
|
||||
actual = (
|
||||
{'status': 'available'},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
'provider_location': '1',
|
||||
'status': 'available'}]
|
||||
)
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(sqlalchemy_api, 'volume_get', side_effect=_volume_get)
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type')
|
||||
def test_create_group_snapshot_cg(
|
||||
self, is_group_a_cg_snapshot_type, volume_get, request):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
request.side_effect = [FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT_PAIR),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
None,
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
'provider_location': '1',
|
||||
'status': 'available'}]
|
||||
)
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_group_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR),
|
||||
FakeResponse(200, NOTFOUND_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
ret = self.driver.delete_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]])
|
||||
self.assertEqual(10, request.call_count)
|
||||
actual = (
|
||||
{'status': TEST_GROUP_SNAP[0]['status']},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'], 'status': 'deleted'}]
|
||||
)
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
@ -22,17 +22,21 @@ import requests
|
||||
from cinder import context as cinder_context
|
||||
from cinder import db
|
||||
from cinder.db.sqlalchemy import api as sqlalchemy_api
|
||||
from cinder.objects import group_snapshot as obj_group_snap
|
||||
from cinder.objects import snapshot as obj_snap
|
||||
from cinder.tests.unit import fake_group
|
||||
from cinder.tests.unit import fake_group_snapshot
|
||||
from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit import test
|
||||
from cinder import utils
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.hitachi import hbsd_common
|
||||
from cinder.volume.drivers.hitachi import hbsd_iscsi
|
||||
from cinder.volume.drivers.hitachi import hbsd_rest
|
||||
from cinder.volume.drivers.hitachi import hbsd_utils
|
||||
from cinder.volume import volume_types
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
# Configuration parameter values
|
||||
CONFIG_MAP = {
|
||||
@ -63,11 +67,14 @@ DEFAULT_CONNECTOR = {
|
||||
CTXT = cinder_context.get_admin_context()
|
||||
|
||||
TEST_VOLUME = []
|
||||
for i in range(3):
|
||||
for i in range(4):
|
||||
volume = {}
|
||||
volume['id'] = '00000000-0000-0000-0000-{0:012d}'.format(i)
|
||||
volume['name'] = 'test-volume{0:d}'.format(i)
|
||||
volume['provider_location'] = '{0:d}'.format(i)
|
||||
if i == 3:
|
||||
volume['provider_location'] = None
|
||||
else:
|
||||
volume['provider_location'] = '{0:d}'.format(i)
|
||||
volume['size'] = 128
|
||||
if i == 2:
|
||||
volume['status'] = 'in-use'
|
||||
@ -97,6 +104,23 @@ snapshot = obj_snap.Snapshot._from_db_object(
|
||||
fake_snapshot.fake_db_snapshot(**snapshot))
|
||||
TEST_SNAPSHOT.append(snapshot)
|
||||
|
||||
TEST_GROUP = []
|
||||
for i in range(2):
|
||||
group = {}
|
||||
group['id'] = '20000000-0000-0000-0000-{0:012d}'.format(i)
|
||||
group['status'] = 'available'
|
||||
group = fake_group.fake_group_obj(CTXT, **group)
|
||||
TEST_GROUP.append(group)
|
||||
|
||||
TEST_GROUP_SNAP = []
|
||||
group_snapshot = {}
|
||||
group_snapshot['id'] = '30000000-0000-0000-0000-{0:012d}'.format(0)
|
||||
group_snapshot['status'] = 'available'
|
||||
group_snapshot = obj_group_snap.GroupSnapshot._from_db_object(
|
||||
CTXT, obj_group_snap.GroupSnapshot(),
|
||||
fake_group_snapshot.fake_db_group_snapshot(**group_snapshot))
|
||||
TEST_GROUP_SNAP.append(group_snapshot)
|
||||
|
||||
# Dummy response for REST API
|
||||
POST_SESSIONS_RESULT = {
|
||||
"token": "b74777a3-f9f0-4ea8-bd8f-09847fac48d3",
|
||||
@ -205,6 +229,18 @@ GET_SNAPSHOTS_RESULT = {
|
||||
],
|
||||
}
|
||||
|
||||
GET_SNAPSHOTS_RESULT_PAIR = {
|
||||
"data": [
|
||||
{
|
||||
"primaryOrSecondary": "S-VOL",
|
||||
"status": "PAIR",
|
||||
"pvolLdevId": 0,
|
||||
"muNumber": 1,
|
||||
"svolLdevId": 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
GET_LDEVS_RESULT = {
|
||||
"data": [
|
||||
{
|
||||
@ -301,6 +337,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
'rest_server_ip_port']
|
||||
self.configuration.hitachi_rest_tcp_keepalive = True
|
||||
self.configuration.hitachi_discard_zero_page = True
|
||||
self.configuration.hitachi_rest_number = "0"
|
||||
|
||||
self.configuration.use_chap_auth = True
|
||||
self.configuration.chap_username = CONFIG_MAP['auth_user']
|
||||
@ -330,7 +367,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(
|
||||
utils, 'brick_get_connector_properties',
|
||||
volume_utils, 'brick_get_connector_properties',
|
||||
side_effect=_brick_get_connector_properties)
|
||||
def _setup_driver(
|
||||
self, brick_get_connector_properties=None, request=None):
|
||||
@ -360,7 +397,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
# API test cases
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(
|
||||
utils, 'brick_get_connector_properties',
|
||||
volume_utils, 'brick_get_connector_properties',
|
||||
side_effect=_brick_get_connector_properties)
|
||||
def test_do_setup(self, brick_get_connector_properties, request):
|
||||
drv = hbsd_iscsi.HBSDISCSIDriver(
|
||||
@ -386,7 +423,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(
|
||||
utils, 'brick_get_connector_properties',
|
||||
volume_utils, 'brick_get_connector_properties',
|
||||
side_effect=_brick_get_connector_properties)
|
||||
def test_do_setup_create_hg(self, brick_get_connector_properties, request):
|
||||
"""Normal case: The host group not exists."""
|
||||
@ -700,3 +737,143 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
|
||||
self.driver.revert_to_snapshot(
|
||||
self.ctxt, TEST_VOLUME[0], TEST_SNAPSHOT[0])
|
||||
self.assertEqual(5, request.call_count)
|
||||
|
||||
def test_create_group(self):
|
||||
ret = self.driver.create_group(self.ctxt, TEST_GROUP[0])
|
||||
self.assertIsNone(ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_group(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
ret = self.driver.delete_group(
|
||||
self.ctxt, TEST_GROUP[0], [TEST_VOLUME[0]])
|
||||
self.assertEqual(4, request.call_count)
|
||||
actual = (
|
||||
{'status': TEST_GROUP[0]['status']},
|
||||
[{'id': TEST_VOLUME[0]['id'], 'status': 'deleted'}]
|
||||
)
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_create_group_from_src_volume(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
ret = self.driver.create_group_from_src(
|
||||
self.ctxt, TEST_GROUP[1], [TEST_VOLUME[1]],
|
||||
source_group=TEST_GROUP[0], source_vols=[TEST_VOLUME[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
None, [{'id': TEST_VOLUME[1]['id'], 'provider_location': '1'}])
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_create_group_from_src_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
ret = self.driver.create_group_from_src(
|
||||
self.ctxt, TEST_GROUP[0], [TEST_VOLUME[0]],
|
||||
group_snapshot=TEST_GROUP_SNAP[0], snapshots=[TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
None, [{'id': TEST_VOLUME[0]['id'], 'provider_location': '1'}])
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
def test_create_group_from_src_volume_error(self):
|
||||
self.assertRaises(
|
||||
hbsd_utils.HBSDError, self.driver.create_group_from_src,
|
||||
self.ctxt, TEST_GROUP[1], [TEST_VOLUME[1]],
|
||||
source_group=TEST_GROUP[0], source_vols=[TEST_VOLUME[3]]
|
||||
)
|
||||
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type')
|
||||
def test_update_group(self, is_group_a_cg_snapshot_type):
|
||||
is_group_a_cg_snapshot_type.return_value = False
|
||||
ret = self.driver.update_group(
|
||||
self.ctxt, TEST_GROUP[0], add_volumes=[TEST_VOLUME[0]])
|
||||
self.assertTupleEqual((None, None, None), ret)
|
||||
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type')
|
||||
def test_update_group_error(self, is_group_a_cg_snapshot_type):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
self.assertRaises(
|
||||
hbsd_utils.HBSDError, self.driver.update_group,
|
||||
self.ctxt, TEST_GROUP[0], add_volumes=[TEST_VOLUME[3]],
|
||||
remove_volumes=[TEST_VOLUME[0]]
|
||||
)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(sqlalchemy_api, 'volume_get', side_effect=_volume_get)
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type')
|
||||
def test_create_group_snapshot_non_cg(
|
||||
self, is_group_a_cg_snapshot_type, volume_get, request):
|
||||
is_group_a_cg_snapshot_type.return_value = False
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(4, request.call_count)
|
||||
actual = (
|
||||
{'status': 'available'},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
'provider_location': '1',
|
||||
'status': 'available'}]
|
||||
)
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
@mock.patch.object(sqlalchemy_api, 'volume_get', side_effect=_volume_get)
|
||||
@mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type')
|
||||
def test_create_group_snapshot_cg(
|
||||
self, is_group_a_cg_snapshot_type, volume_get, request):
|
||||
is_group_a_cg_snapshot_type.return_value = True
|
||||
request.side_effect = [FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT_PAIR),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT)]
|
||||
ret = self.driver.create_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]]
|
||||
)
|
||||
self.assertEqual(5, request.call_count)
|
||||
actual = (
|
||||
None,
|
||||
[{'id': TEST_SNAPSHOT[0]['id'],
|
||||
'provider_location': '1',
|
||||
'status': 'available'}]
|
||||
)
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
||||
@mock.patch.object(requests.Session, "request")
|
||||
def test_delete_group_snapshot(self, request):
|
||||
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR),
|
||||
FakeResponse(200, NOTFOUND_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(200, GET_SNAPSHOTS_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(200, GET_LDEV_RESULT),
|
||||
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
|
||||
ret = self.driver.delete_group_snapshot(
|
||||
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]])
|
||||
self.assertEqual(10, request.call_count)
|
||||
actual = (
|
||||
{'status': TEST_GROUP_SNAP[0]['status']},
|
||||
[{'id': TEST_SNAPSHOT[0]['id'], 'status': 'deleted'}]
|
||||
)
|
||||
self.assertTupleEqual(actual, ret)
|
||||
|
0
cinder/volume/drivers/hitachi/__init__.py
Normal file
0
cinder/volume/drivers/hitachi/__init__.py
Normal file
@ -26,7 +26,7 @@ from cinder.volume import configuration
|
||||
from cinder.volume.drivers.hitachi import hbsd_utils as utils
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
VERSION = '2.0.0'
|
||||
VERSION = '2.1.0'
|
||||
|
||||
_STR_VOLUME = 'volume'
|
||||
_STR_SNAPSHOT = 'snapshot'
|
||||
@ -318,7 +318,9 @@ class HBSDCommon():
|
||||
reserved_percentage=self.conf.safe_get('reserved_percentage'),
|
||||
QoS_support=False,
|
||||
thick_provisioning_support=False,
|
||||
multiattach=True
|
||||
multiattach=True,
|
||||
consistencygroup_support=True,
|
||||
consistent_group_snapshot_enabled=True
|
||||
))
|
||||
try:
|
||||
(total_capacity, free_capacity,
|
||||
@ -779,3 +781,22 @@ class HBSDCommon():
|
||||
self.restore_ldev(pvol, svol)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_group(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete_group(self, group, volumes):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_group_from_src(
|
||||
self, context, group, volumes, snapshots=None, source_vols=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
def update_group(self, group, add_volumes=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_group_snapshot(self, context, group_snapshot, snapshots):
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete_group_snapshot(self, group_snapshot, snapshots):
|
||||
raise NotImplementedError()
|
||||
|
@ -15,6 +15,7 @@
|
||||
"""Fibre channel module for Hitachi HBSD Driver."""
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import excutils
|
||||
|
||||
from cinder import interface
|
||||
from cinder.volume import configuration
|
||||
@ -64,6 +65,7 @@ class HBSDFCDriver(driver.FibreChannelDriver):
|
||||
1.1.0 - Add manage_existing/manage_existing_get_size/unmanage methods
|
||||
2.0.0 - Major redesign of the driver. This version requires the REST
|
||||
API for communication with the storage backend.
|
||||
2.1.0 - Add Cinder generic volume groups.
|
||||
|
||||
"""
|
||||
|
||||
@ -228,3 +230,37 @@ class HBSDFCDriver(driver.FibreChannelDriver):
|
||||
def revert_to_snapshot(self, context, volume, snapshot):
|
||||
"""Rollback the specified snapshot"""
|
||||
return self.common.revert_to_snapshot(volume, snapshot)
|
||||
|
||||
@volume_utils.trace
|
||||
def create_group(self, context, group):
|
||||
return self.common.create_group()
|
||||
|
||||
@volume_utils.trace
|
||||
def delete_group(self, context, group, volumes):
|
||||
return self.common.delete_group(group, volumes)
|
||||
|
||||
@volume_utils.trace
|
||||
def create_group_from_src(
|
||||
self, context, group, volumes, group_snapshot=None, snapshots=None,
|
||||
source_group=None, source_vols=None):
|
||||
return self.common.create_group_from_src(
|
||||
context, group, volumes, snapshots, source_vols)
|
||||
|
||||
@volume_utils.trace
|
||||
def update_group(
|
||||
self, context, group, add_volumes=None, remove_volumes=None):
|
||||
try:
|
||||
return self.common.update_group(group, add_volumes)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
for remove_volume in remove_volumes:
|
||||
utils.cleanup_cg_in_volume(remove_volume)
|
||||
|
||||
@volume_utils.trace
|
||||
def create_group_snapshot(self, context, group_snapshot, snapshots):
|
||||
return self.common.create_group_snapshot(
|
||||
context, group_snapshot, snapshots)
|
||||
|
||||
@volume_utils.trace
|
||||
def delete_group_snapshot(self, context, group_snapshot, snapshots):
|
||||
return self.common.delete_group_snapshot(group_snapshot, snapshots)
|
||||
|
@ -14,6 +14,8 @@
|
||||
#
|
||||
"""iSCSI module for Hitachi HBSD Driver."""
|
||||
|
||||
from oslo_utils import excutils
|
||||
|
||||
from cinder import interface
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.hitachi import hbsd_common as common
|
||||
@ -49,6 +51,7 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
|
||||
1.1.0 - Add manage_existing/manage_existing_get_size/unmanage methods
|
||||
2.0.0 - Major redesign of the driver. This version requires the REST
|
||||
API for communication with the storage backend.
|
||||
2.1.0 - Add Cinder generic volume groups.
|
||||
|
||||
"""
|
||||
|
||||
@ -212,3 +215,37 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
|
||||
def revert_to_snapshot(self, context, volume, snapshot):
|
||||
"""Rollback the specified snapshot"""
|
||||
return self.common.revert_to_snapshot(volume, snapshot)
|
||||
|
||||
@volume_utils.trace
|
||||
def create_group(self, context, group):
|
||||
return self.common.create_group()
|
||||
|
||||
@volume_utils.trace
|
||||
def delete_group(self, context, group, volumes):
|
||||
return self.common.delete_group(group, volumes)
|
||||
|
||||
@volume_utils.trace
|
||||
def create_group_from_src(
|
||||
self, context, group, volumes, group_snapshot=None, snapshots=None,
|
||||
source_group=None, source_vols=None):
|
||||
return self.common.create_group_from_src(
|
||||
context, group, volumes, snapshots, source_vols)
|
||||
|
||||
@volume_utils.trace
|
||||
def update_group(
|
||||
self, context, group, add_volumes=None, remove_volumes=None):
|
||||
try:
|
||||
return self.common.update_group(group, add_volumes)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
for remove_volume in remove_volumes:
|
||||
utils.cleanup_cg_in_volume(remove_volume)
|
||||
|
||||
@volume_utils.trace
|
||||
def create_group_snapshot(self, context, group_snapshot, snapshots):
|
||||
return self.common.create_group_snapshot(
|
||||
context, group_snapshot, snapshots)
|
||||
|
||||
@volume_utils.trace
|
||||
def delete_group_snapshot(self, context, group_snapshot, snapshots):
|
||||
return self.common.delete_group_snapshot(group_snapshot, snapshots)
|
||||
|
@ -24,11 +24,13 @@ from oslo_utils import timeutils
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import exception
|
||||
from cinder.objects import fields
|
||||
from cinder.volume import configuration
|
||||
from cinder.volume.drivers.hitachi import hbsd_common as common
|
||||
from cinder.volume.drivers.hitachi import hbsd_rest_api as rest_api
|
||||
from cinder.volume.drivers.hitachi import hbsd_utils as utils
|
||||
from cinder.volume.drivers.san import san
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
_LU_PATH_DEFINED = ('B958', '015A')
|
||||
NORMAL_STS = 'NML'
|
||||
@ -86,6 +88,9 @@ EX_ENLDEV = 'EX_ENLDEV'
|
||||
EX_INVARG = 'EX_INVARG'
|
||||
_INVALID_RANGE = [EX_ENLDEV, EX_INVARG]
|
||||
|
||||
_MAX_COPY_GROUP_NAME = 29
|
||||
_MAX_CTG_COUNT_EXCEEDED_ADD_SNAPSHOT = ('2E10', '2302')
|
||||
_MAX_PAIR_COUNT_IN_CTG_EXCEEDED_ADD_SNAPSHOT = ('2E13', '9900')
|
||||
|
||||
REST_VOLUME_OPTS = [
|
||||
cfg.BoolOpt(
|
||||
@ -789,3 +794,277 @@ class HBSDREST(common.HBSDCommon):
|
||||
return False
|
||||
return (result[0]['primaryOrSecondary'] == "S-VOL" and
|
||||
int(result[0]['pvolLdevId']) == pvol)
|
||||
|
||||
def create_group(self):
|
||||
return None
|
||||
|
||||
def _delete_group(self, group, objs, is_snapshot):
|
||||
model_update = {'status': group.status}
|
||||
objs_model_update = []
|
||||
events = []
|
||||
|
||||
def _delete_group_obj(group, obj, is_snapshot):
|
||||
obj_update = {'id': obj.id}
|
||||
try:
|
||||
if is_snapshot:
|
||||
self.delete_snapshot(obj)
|
||||
else:
|
||||
self.delete_volume(obj)
|
||||
obj_update['status'] = 'deleted'
|
||||
except (utils.HBSDError, exception.VolumeIsBusy,
|
||||
exception.SnapshotIsBusy) as exc:
|
||||
obj_update['status'] = 'available' if isinstance(
|
||||
exc, (exception.VolumeIsBusy,
|
||||
exception.SnapshotIsBusy)) else 'error'
|
||||
utils.output_log(
|
||||
MSG.GROUP_OBJECT_DELETE_FAILED,
|
||||
obj='snapshot' if is_snapshot else 'volume',
|
||||
group='group snapshot' if is_snapshot else 'group',
|
||||
group_id=group.id, obj_id=obj.id, ldev=utils.get_ldev(obj),
|
||||
reason=exc.msg)
|
||||
raise loopingcall.LoopingCallDone(obj_update)
|
||||
|
||||
for obj in objs:
|
||||
loop = loopingcall.FixedIntervalLoopingCall(
|
||||
_delete_group_obj, group, obj, is_snapshot)
|
||||
event = loop.start(interval=0)
|
||||
events.append(event)
|
||||
for e in events:
|
||||
obj_update = e.wait()
|
||||
if obj_update['status'] != 'deleted':
|
||||
model_update['status'] = 'error'
|
||||
objs_model_update.append(obj_update)
|
||||
return model_update, objs_model_update
|
||||
|
||||
def delete_group(self, group, volumes):
|
||||
return self._delete_group(group, volumes, False)
|
||||
|
||||
def delete_group_snapshot(self, group_snapshot, snapshots):
|
||||
return self._delete_group(group_snapshot, snapshots, True)
|
||||
|
||||
def create_group_from_src(
|
||||
self, context, group, volumes, snapshots=None, source_vols=None):
|
||||
volumes_model_update = []
|
||||
new_ldevs = []
|
||||
events = []
|
||||
|
||||
def _create_group_volume_from_src(context, volume, src, from_snapshot):
|
||||
volume_model_update = {'id': volume.id}
|
||||
try:
|
||||
ldev = utils.get_ldev(src)
|
||||
if ldev is None:
|
||||
msg = utils.output_log(
|
||||
MSG.INVALID_LDEV_FOR_VOLUME_COPY,
|
||||
type='snapshot' if from_snapshot else 'volume',
|
||||
id=src.id)
|
||||
raise utils.HBSDError(msg)
|
||||
volume_model_update.update(
|
||||
self.create_volume_from_snapshot(volume, src) if
|
||||
from_snapshot else self.create_cloned_volume(volume,
|
||||
src))
|
||||
except Exception as exc:
|
||||
volume_model_update['msg'] = utils.get_exception_msg(exc)
|
||||
raise loopingcall.LoopingCallDone(volume_model_update)
|
||||
|
||||
try:
|
||||
from_snapshot = True if snapshots else False
|
||||
for volume, src in zip(volumes,
|
||||
snapshots if snapshots else source_vols):
|
||||
loop = loopingcall.FixedIntervalLoopingCall(
|
||||
_create_group_volume_from_src, context, volume, src,
|
||||
from_snapshot)
|
||||
event = loop.start(interval=0)
|
||||
events.append(event)
|
||||
is_success = True
|
||||
for e in events:
|
||||
volume_model_update = e.wait()
|
||||
if 'msg' in volume_model_update:
|
||||
is_success = False
|
||||
msg = volume_model_update['msg']
|
||||
else:
|
||||
volumes_model_update.append(volume_model_update)
|
||||
ldev = utils.get_ldev(volume_model_update)
|
||||
if ldev is not None:
|
||||
new_ldevs.append(ldev)
|
||||
if not is_success:
|
||||
raise utils.HBSDError(msg)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
for new_ldev in new_ldevs:
|
||||
try:
|
||||
self.delete_ldev(new_ldev)
|
||||
except utils.HBSDError:
|
||||
utils.output_log(MSG.DELETE_LDEV_FAILED, ldev=new_ldev)
|
||||
return None, volumes_model_update
|
||||
|
||||
def update_group(self, group, add_volumes=None):
|
||||
if add_volumes and volume_utils.is_group_a_cg_snapshot_type(group):
|
||||
for volume in add_volumes:
|
||||
ldev = utils.get_ldev(volume)
|
||||
if ldev is None:
|
||||
msg = utils.output_log(MSG.LDEV_NOT_EXIST_FOR_ADD_GROUP,
|
||||
volume_id=volume.id,
|
||||
group='consistency group',
|
||||
group_id=group.id)
|
||||
raise utils.HBSDError(msg)
|
||||
return None, None, None
|
||||
|
||||
def _create_non_cgsnapshot(self, group_snapshot, snapshots):
|
||||
model_update = {'status': fields.GroupSnapshotStatus.AVAILABLE}
|
||||
snapshots_model_update = []
|
||||
events = []
|
||||
|
||||
def _create_non_cgsnapshot_snapshot(group_snapshot, snapshot):
|
||||
snapshot_model_update = {'id': snapshot.id}
|
||||
try:
|
||||
snapshot_model_update.update(self.create_snapshot(snapshot))
|
||||
snapshot_model_update['status'] = (
|
||||
fields.SnapshotStatus.AVAILABLE)
|
||||
except Exception:
|
||||
snapshot_model_update['status'] = fields.SnapshotStatus.ERROR
|
||||
utils.output_log(
|
||||
MSG.GROUP_SNAPSHOT_CREATE_FAILED,
|
||||
group=group_snapshot.group_id,
|
||||
group_snapshot=group_snapshot.id,
|
||||
group_type=group_snapshot.group_type_id,
|
||||
volume=snapshot.volume_id, snapshot=snapshot.id)
|
||||
raise loopingcall.LoopingCallDone(snapshot_model_update)
|
||||
|
||||
for snapshot in snapshots:
|
||||
loop = loopingcall.FixedIntervalLoopingCall(
|
||||
_create_non_cgsnapshot_snapshot, group_snapshot, snapshot)
|
||||
event = loop.start(interval=0)
|
||||
events.append(event)
|
||||
for e in events:
|
||||
snapshot_model_update = e.wait()
|
||||
if (snapshot_model_update['status'] ==
|
||||
fields.SnapshotStatus.ERROR):
|
||||
model_update['status'] = fields.GroupSnapshotStatus.ERROR
|
||||
snapshots_model_update.append(snapshot_model_update)
|
||||
return model_update, snapshots_model_update
|
||||
|
||||
def _create_ctg_snapshot_group_name(self, ldev):
|
||||
now = timeutils.utcnow()
|
||||
strnow = now.strftime("%y%m%d%H%M%S%f")
|
||||
ctg_name = '%(prefix)sC%(ldev)s%(time)s' % {
|
||||
'prefix': utils.DRIVER_PREFIX,
|
||||
'ldev': "{0:06X}".format(ldev),
|
||||
'time': strnow[:len(strnow) - 3],
|
||||
}
|
||||
return ctg_name[:_MAX_COPY_GROUP_NAME]
|
||||
|
||||
def _delete_pairs_from_storage(self, pairs):
|
||||
for pair in pairs:
|
||||
try:
|
||||
self._delete_pair_from_storage(pair['pvol'], pair['svol'])
|
||||
except utils.HBSDError:
|
||||
utils.output_log(MSG.DELETE_PAIR_FAILED, pvol=pair['pvol'],
|
||||
svol=pair['svol'])
|
||||
|
||||
def _create_ctg_snap_pair(self, pairs):
|
||||
snapshotgroup_name = self._create_ctg_snapshot_group_name(
|
||||
pairs[0]['pvol'])
|
||||
try:
|
||||
for pair in pairs:
|
||||
try:
|
||||
body = {"snapshotGroupName": snapshotgroup_name,
|
||||
"snapshotPoolId":
|
||||
self.storage_info['snap_pool_id'],
|
||||
"pvolLdevId": pair['pvol'],
|
||||
"svolLdevId": pair['svol'],
|
||||
"isConsistencyGroup": True,
|
||||
"canCascade": True,
|
||||
"isDataReductionForceCopy": True}
|
||||
self.client.add_snapshot(body)
|
||||
except utils.HBSDError as ex:
|
||||
if ((utils.safe_get_err_code(ex.kwargs.get('errobj')) ==
|
||||
_MAX_CTG_COUNT_EXCEEDED_ADD_SNAPSHOT) or
|
||||
(utils.safe_get_err_code(ex.kwargs.get('errobj')) ==
|
||||
_MAX_PAIR_COUNT_IN_CTG_EXCEEDED_ADD_SNAPSHOT)):
|
||||
msg = utils.output_log(MSG.FAILED_CREATE_CTG_SNAPSHOT)
|
||||
raise utils.HBSDError(msg)
|
||||
elif (utils.safe_get_err_code(ex.kwargs.get('errobj')) ==
|
||||
rest_api.INVALID_SNAPSHOT_POOL and
|
||||
not self.conf.hitachi_snap_pool):
|
||||
msg = utils.output_log(
|
||||
MSG.INVALID_PARAMETER, param='hitachi_snap_pool')
|
||||
raise utils.HBSDError(msg)
|
||||
raise
|
||||
self._wait_copy_pair_status(pair['svol'], PAIR)
|
||||
self.client.split_snapshotgroup(snapshotgroup_name)
|
||||
for pair in pairs:
|
||||
self._wait_copy_pair_status(pair['svol'], PSUS)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self._delete_pairs_from_storage(pairs)
|
||||
|
||||
def _create_cgsnapshot(self, context, cgsnapshot, snapshots):
|
||||
pairs = []
|
||||
events = []
|
||||
snapshots_model_update = []
|
||||
|
||||
def _create_cgsnapshot_volume(snapshot):
|
||||
pair = {'snapshot': snapshot}
|
||||
try:
|
||||
pair['pvol'] = utils.get_ldev(snapshot.volume)
|
||||
if pair['pvol'] is None:
|
||||
msg = utils.output_log(
|
||||
MSG.INVALID_LDEV_FOR_VOLUME_COPY,
|
||||
type='volume', id=snapshot.volume_id)
|
||||
raise utils.HBSDError(msg)
|
||||
size = snapshot.volume_size
|
||||
pair['svol'] = self.create_ldev(size)
|
||||
except Exception as exc:
|
||||
pair['msg'] = utils.get_exception_msg(exc)
|
||||
raise loopingcall.LoopingCallDone(pair)
|
||||
|
||||
try:
|
||||
for snapshot in snapshots:
|
||||
ldev = utils.get_ldev(snapshot.volume)
|
||||
if ldev is None:
|
||||
msg = utils.output_log(
|
||||
MSG.INVALID_LDEV_FOR_VOLUME_COPY, type='volume',
|
||||
id=snapshot.volume_id)
|
||||
raise utils.HBSDError(msg)
|
||||
for snapshot in snapshots:
|
||||
loop = loopingcall.FixedIntervalLoopingCall(
|
||||
_create_cgsnapshot_volume, snapshot)
|
||||
event = loop.start(interval=0)
|
||||
events.append(event)
|
||||
is_success = True
|
||||
for e in events:
|
||||
pair = e.wait()
|
||||
if 'msg' in pair:
|
||||
is_success = False
|
||||
msg = pair['msg']
|
||||
pairs.append(pair)
|
||||
if not is_success:
|
||||
raise utils.HBSDError(msg)
|
||||
self._create_ctg_snap_pair(pairs)
|
||||
except Exception:
|
||||
for pair in pairs:
|
||||
if 'svol' in pair and pair['svol'] is not None:
|
||||
try:
|
||||
self.delete_ldev(pair['svol'])
|
||||
except utils.HBSDError:
|
||||
utils.output_log(
|
||||
MSG.DELETE_LDEV_FAILED, ldev=pair['svol'])
|
||||
model_update = {'status': fields.GroupSnapshotStatus.ERROR}
|
||||
for snapshot in snapshots:
|
||||
snapshot_model_update = {'id': snapshot.id,
|
||||
'status': fields.SnapshotStatus.ERROR}
|
||||
snapshots_model_update.append(snapshot_model_update)
|
||||
return model_update, snapshots_model_update
|
||||
for pair in pairs:
|
||||
snapshot_model_update = {
|
||||
'id': pair['snapshot'].id,
|
||||
'status': fields.SnapshotStatus.AVAILABLE,
|
||||
'provider_location': str(pair['svol'])}
|
||||
snapshots_model_update.append(snapshot_model_update)
|
||||
return None, snapshots_model_update
|
||||
|
||||
def create_group_snapshot(self, context, group_snapshot, snapshots):
|
||||
if volume_utils.is_group_a_cg_snapshot_type(group_snapshot):
|
||||
return self._create_cgsnapshot(context, group_snapshot, snapshots)
|
||||
else:
|
||||
return self._create_non_cgsnapshot(group_snapshot, snapshots)
|
||||
|
@ -750,6 +750,14 @@ class RestApiClient():
|
||||
}
|
||||
self._invoke(url, body=body)
|
||||
|
||||
def split_snapshotgroup(self, snapshot_group_id):
|
||||
url = '%(url)s/snapshot-groups/%(id)s/actions/%(action)s/invoke' % {
|
||||
'url': self.object_url,
|
||||
'id': snapshot_group_id,
|
||||
'action': 'split',
|
||||
}
|
||||
self._invoke(url)
|
||||
|
||||
def discard_zero_page(self, ldev_id):
|
||||
"""Return the ldev's no-data pages to the storage pool."""
|
||||
url = '%(url)s/ldevs/%(id)s/actions/%(action)s/invoke' % {
|
||||
|
@ -344,6 +344,21 @@ class HBSDMsg(enum.Enum):
|
||||
'to manage the volume.',
|
||||
'suffix': ERROR_SUFFIX,
|
||||
}
|
||||
FAILED_CREATE_CTG_SNAPSHOT = {
|
||||
'msg_id': 712,
|
||||
'loglevel': base_logging.ERROR,
|
||||
'msg': 'Failed to create a consistency group snapshot. '
|
||||
'The number of pairs in the consistency group or the number of '
|
||||
'consistency group snapshots has reached the limit.',
|
||||
'suffix': ERROR_SUFFIX,
|
||||
}
|
||||
LDEV_NOT_EXIST_FOR_ADD_GROUP = {
|
||||
'msg_id': 716,
|
||||
'loglevel': base_logging.ERROR,
|
||||
'msg': 'No logical device exists in the storage system for the volume '
|
||||
'%(volume_id)s to be added to the %(group)s %(group_id)s.',
|
||||
'suffix': ERROR_SUFFIX,
|
||||
}
|
||||
SNAPSHOT_UNMANAGE_FAILED = {
|
||||
'msg_id': 722,
|
||||
'loglevel': base_logging.ERROR,
|
||||
@ -395,6 +410,23 @@ class HBSDMsg(enum.Enum):
|
||||
'body: %(body)s)',
|
||||
'suffix': ERROR_SUFFIX,
|
||||
}
|
||||
GROUP_OBJECT_DELETE_FAILED = {
|
||||
'msg_id': 736,
|
||||
'loglevel': base_logging.ERROR,
|
||||
'msg': 'Failed to delete a %(obj)s in a %(group)s. (%(group)s: '
|
||||
'%(group_id)s, %(obj)s: %(obj_id)s, LDEV: %(ldev)s, reason: '
|
||||
'%(reason)s)',
|
||||
'suffix': ERROR_SUFFIX,
|
||||
}
|
||||
GROUP_SNAPSHOT_CREATE_FAILED = {
|
||||
'msg_id': 737,
|
||||
'loglevel': base_logging.ERROR,
|
||||
'msg': 'Failed to create a volume snapshot in a group snapshot that '
|
||||
'does not guarantee consistency. (group: %(group)s, '
|
||||
'group snapshot: %(group_snapshot)s, group type: '
|
||||
'%(group_type)s, volume: %(volume)s, snapshot: %(snapshot)s)',
|
||||
'suffix': ERROR_SUFFIX,
|
||||
}
|
||||
|
||||
def __init__(self, error_info):
|
||||
"""Initialize Enum attributes."""
|
||||
@ -526,3 +558,20 @@ def is_shared_connection(volume, connector):
|
||||
if attachment.attached_host == host:
|
||||
connection_count += 1
|
||||
return connection_count > 1
|
||||
|
||||
|
||||
def cleanup_cg_in_volume(volume):
|
||||
if ('group_id' in volume and volume.group_id and
|
||||
'consistencygroup_id' in volume and
|
||||
volume.consistencygroup_id):
|
||||
volume.consistencygroup_id = None
|
||||
if 'consistencygroup' in volume:
|
||||
volume.consistencygroup = None
|
||||
|
||||
|
||||
def get_exception_msg(exc):
|
||||
if exc.args:
|
||||
return exc.msg if isinstance(
|
||||
exc, exception.CinderException) else exc.args[0]
|
||||
else:
|
||||
return ""
|
||||
|
@ -63,6 +63,8 @@ Supported operations
|
||||
* Create, delete, attach, and detach volumes.
|
||||
* Create, list, and delete volume snapshots.
|
||||
* Create a volume from a snapshot.
|
||||
* Create, list, update, and delete consistency groups.
|
||||
* Create, list, and delete consistency group snapshots.
|
||||
* Copy a volume to an image.
|
||||
* Copy an image to a volume.
|
||||
* Clone a volume.
|
||||
|
@ -587,7 +587,7 @@ driver.dell_emc_vnx=complete
|
||||
driver.dell_emc_powerflex=complete
|
||||
driver.dell_emc_xtremio=complete
|
||||
driver.fujitsu_eternus=missing
|
||||
driver.hitachi_vsp=missing
|
||||
driver.hitachi_vsp=complete
|
||||
driver.hpe_3par=complete
|
||||
driver.hpe_msa=missing
|
||||
driver.huawei_t_v1=missing
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Hitachi driver: Add Cinder generic volume groups.
|
Loading…
Reference in New Issue
Block a user