Merge "Add Consistency Groups support in PowerStore driver"

This commit is contained in:
Zuul 2021-03-13 09:10:14 +00:00 committed by Gerrit Code Review
commit f1f736335f
11 changed files with 802 additions and 8 deletions

View File

@ -100,7 +100,7 @@ class TestVolumeCreateFromSource(powerstore.TestPowerStoreDriver):
@mock.patch("requests.request")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.clone_volume_or_snapshot")
def test_create_volume_from_source_extende_bad_status(
def test_create_volume_from_source_extended_bad_status(
self,
mock_create_from_source,
mock_extend_request

View File

@ -0,0 +1,144 @@
# Copyright (c) 2021 Dell Inc. or its subsidiaries.
# 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.
from unittest import mock
from cinder import exception
from cinder.tests.unit import fake_group
from cinder.tests.unit import fake_volume
from cinder.tests.unit.volume.drivers.dell_emc import powerstore
class TestVolumeGroupCreateDeleteUpdate(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
def setUp(self, mock_chap):
super(TestVolumeGroupCreateDeleteUpdate, self).setUp()
self.driver.check_for_setup_error()
self.volume1 = fake_volume.fake_volume_obj(
self.context,
host="host@backend",
provider_id="fake_id",
size=8
)
self.volume2 = fake_volume.fake_volume_obj(
self.context,
host="host@backend",
provider_id="fake_id",
size=8
)
self.group = fake_group.fake_group_obj(
self.context,
)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.create_vg")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_create_volume_group(self, mock_is_cg, mock_create):
mock_create.return_value = "fake_id"
mock_is_cg.return_value = True
self.driver.create_group(self.context, self.group)
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_create_volume_group_fallback_to_generic(self, mock_is_cg):
mock_is_cg.return_value = False
self.assertRaises(NotImplementedError,
self.driver.create_group,
self.context,
self.group)
@mock.patch("requests.request")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_create_volume_group_bad_status(self,
mock_is_cg,
mock_create_request):
mock_create_request.return_value = powerstore.MockResponse(rc=400)
error = self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_group,
self.context,
self.group)
self.assertIn("Failed to create PowerStore volume group", error.msg)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.delete_volume_or_snapshot")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_delete_volume_group(self, mock_is_cg, mock_get_id, mock_delete):
self.driver.delete_group(self.context, self.group, [])
@mock.patch("requests.request")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_delete_volume_group_bad_status(self,
mock_is_cg,
mock_get_id,
mock_delete):
mock_delete.return_value = powerstore.MockResponse(rc=400)
error = self.assertRaises(
exception.VolumeBackendAPIException,
self.driver.delete_group,
self.context,
self.group,
[]
)
self.assertIn("Failed to delete PowerStore volume group", error.msg)
@mock.patch("cinder.objects.volume.Volume.is_replicated")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_update_volume_group_add_replicated_volumes(self,
mock_is_cg,
mock_replicated):
mock_replicated.return_value = True
self.assertRaises(exception.InvalidVolume,
self.driver.update_group,
self.context,
self.group,
[self.volume1],
[])
@mock.patch("requests.request")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_update_volume_group_add_volumes_bad_status(self,
mock_is_cg,
mock_get_vg_id,
mock_add_volumes):
mock_add_volumes.return_value = powerstore.MockResponse(rc=400)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.update_group,
self.context,
self.group,
[self.volume1],
[])
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.remove_volumes_from_vg")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.add_volumes_to_vg")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_update_volume_group_add_remove_volumes(self,
mock_is_cg,
mock_get_vg_id,
mock_add_volumes,
mock_remove_volumes):
self.driver.update_group(self.context,
self.group,
add_volumes=[self.volume1],
remove_volumes=[self.volume2])

View File

@ -0,0 +1,112 @@
# Copyright (c) 2021 Dell Inc. or its subsidiaries.
# 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.
from unittest import mock
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.volume.drivers.dell_emc import powerstore
class TestVolumeGroupCreateFromSource(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
def setUp(self, mock_chap):
super(TestVolumeGroupCreateFromSource, self).setUp()
self.driver.check_for_setup_error()
self.volume = fake_volume.fake_volume_obj(
self.context,
host="host@backend",
provider_id="fake_id",
size=8
)
self.source_volume = fake_volume.fake_volume_obj(
self.context,
host="host@backend",
provider_id="fake_id",
size=8
)
self.source_volume_snap = fake_snapshot.fake_snapshot_obj(
self.context,
volume=self.source_volume,
volume_size=8
)
self.group = fake_group.fake_group_obj(
self.context,
)
self.source_group = fake_group.fake_group_obj(
self.context,
)
self.source_group_snap = fake_group_snapshot.fake_group_snapshot_obj(
self.context
)
self.source_group_snap.group = self.source_group
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.rename_volume")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_volume_id_by_name")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.clone_vg_or_vg_snapshot")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_create_volume_group_clone(self,
mock_is_cg,
mock_get_group_id,
mock_clone,
mock_get_volume_id,
mock_rename):
mock_get_volume_id.return_value = "fake_id"
group_updates, volume_updates = self.driver.create_group_from_src(
self.context,
self.group,
volumes=[self.volume],
source_group=self.source_group,
source_vols=[self.source_volume]
)
self.assertEqual(1, len(volume_updates))
self.assertEqual("fake_id", volume_updates[0]["provider_id"])
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.rename_volume")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_volume_id_by_name")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.clone_vg_or_vg_snapshot")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_snapshot_id_by_name")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_create_volume_group_from_snapshot(self,
mock_is_cg,
mock_get_group_id,
mock_get_snapshot_id,
mock_clone,
mock_get_volume_id,
mock_rename):
mock_get_volume_id.return_value = "fake_id"
group_updates, volume_updates = self.driver.create_group_from_src(
self.context,
self.group,
volumes=[self.volume],
snapshots=[self.source_volume_snap],
group_snapshot=self.source_group_snap
)
self.assertEqual(1, len(volume_updates))
self.assertEqual("fake_id", volume_updates[0]["provider_id"])

View File

@ -0,0 +1,101 @@
# Copyright (c) 2021 Dell Inc. or its subsidiaries.
# 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.
from unittest import mock
from cinder import exception
from cinder.tests.unit import fake_group
from cinder.tests.unit import fake_group_snapshot
from cinder.tests.unit.volume.drivers.dell_emc import powerstore
class TestVolumeGroupSnapshotCreateDelete(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
def setUp(self, mock_chap):
super(TestVolumeGroupSnapshotCreateDelete, self).setUp()
self.driver.check_for_setup_error()
self.group = fake_group.fake_group_obj(
self.context,
)
self.group_snapshot = fake_group_snapshot.fake_group_snapshot_obj(
self.context
)
self.group_snapshot.group = self.group
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.create_vg_snapshot")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_create_volume_group_snapshot(self,
mock_is_cg,
mock_get_id,
mock_create):
self.driver.create_group_snapshot(self.context,
self.group_snapshot,
[])
@mock.patch("requests.request")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_create_volume_group_snapshot_bad_status(self,
mock_is_cg,
mock_get_id,
mock_create):
mock_create.return_value = powerstore.MockResponse(rc=400)
error = self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_group_snapshot,
self.context,
self.group_snapshot,
[])
self.assertIn("Failed to create snapshot", error.msg)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.delete_volume_or_snapshot")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_snapshot_id_by_name")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_delete_volume_group_snapshot(self,
mock_is_cg,
mock_get_group_id,
mock_get_snapshot_id,
mock_delete):
self.driver.delete_group_snapshot(self.context,
self.group_snapshot,
[])
@mock.patch("requests.request")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_snapshot_id_by_name")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_vg_id_by_name")
@mock.patch("cinder.volume.volume_utils.is_group_a_cg_snapshot_type")
def test_delete_volume_group_snapshot_bad_status(self,
mock_is_cg,
mock_get_group_id,
mock_get_snapshot_id,
mock_delete):
mock_delete.return_value = powerstore.MockResponse(rc=400)
error = self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_group_snapshot,
self.context,
self.group_snapshot,
[])
self.assertIn("Failed to delete PowerStore volume group snapshot",
error.msg)

View File

@ -22,10 +22,12 @@ from cinder import coordination
from cinder import exception
from cinder.i18n import _
from cinder.objects import fields
from cinder.objects.group_snapshot import GroupSnapshot
from cinder.objects.snapshot import Snapshot
from cinder.volume.drivers.dell_emc.powerstore import client
from cinder.volume.drivers.dell_emc.powerstore import utils
from cinder.volume import manager
from cinder.volume import volume_utils
LOG = logging.getLogger(__name__)
@ -88,6 +90,19 @@ class CommonAdapter(object):
})
def create_volume(self, volume):
group_provider_id = None
if (
volume.group_id and
volume_utils.is_group_a_cg_snapshot_type(volume.group)
):
if volume.is_replicated():
msg = _("Volume with enabled replication can not be added to "
"PowerStore volume group.")
LOG.error(msg)
raise exception.InvalidVolume(reason=msg)
group_provider_id = self.client.get_vg_id_by_name(
volume.group_id
)
if volume.is_replicated():
pp_name = utils.get_protection_policy_from_volume(volume)
pp_id = self.client.get_protection_policy_id_by_name(pp_name)
@ -98,26 +113,31 @@ class CommonAdapter(object):
replication_status = fields.ReplicationStatus.DISABLED
LOG.debug("Create PowerStore volume %(volume_name)s of size "
"%(volume_size)s GiB with id %(volume_id)s. "
"Protection policy: %(pp_name)s.",
"Protection policy: %(pp_name)s. "
"Volume group id: %(group_id)s. ",
{
"volume_name": volume.name,
"volume_size": volume.size,
"volume_id": volume.id,
"pp_name": pp_name,
"group_id": volume.group_id,
})
size_in_bytes = utils.gib_to_bytes(volume.size)
provider_id = self.client.create_volume(volume.name,
size_in_bytes,
pp_id)
pp_id,
group_provider_id)
LOG.debug("Successfully created PowerStore volume %(volume_name)s of "
"size %(volume_size)s GiB with id %(volume_id)s on "
"Protection policy: %(pp_name)s."
"Protection policy: %(pp_name)s. "
"Volume group id: %(group_id)s. "
"PowerStore volume id: %(volume_provider_id)s.",
{
"volume_name": volume.name,
"volume_size": volume.size,
"volume_id": volume.id,
"pp_name": pp_name,
"group_id": volume.group_id,
"volume_provider_id": provider_id,
})
return {
@ -273,6 +293,7 @@ class CommonAdapter(object):
"thin_provisioning_support": True,
"compression_support": True,
"multiattach": True,
"consistent_group_snapshot_enabled": True,
}
backend_stats = self.client.get_metrics()
backend_total_capacity = utils.bytes_to_gib(
@ -787,6 +808,212 @@ class CommonAdapter(object):
},
}
@utils.is_group_a_cg_snapshot_type
def create_group(self, group):
LOG.debug("Create PowerStore volume group %(group_name)s with id "
"%(group_id)s.",
{
"group_name": group.name,
"group_id": group.id,
})
self.client.create_vg(group.id)
LOG.debug("Successfully created PowerStore volume group "
"%(group_name)s with id %(group_id)s.",
{
"group_name": group.name,
"group_id": group.id,
})
@utils.is_group_a_cg_snapshot_type
def delete_group(self, group):
LOG.debug("Delete PowerStore volume group %(group_name)s with id "
"%(group_id)s.",
{
"group_name": group.name,
"group_id": group.id,
})
try:
group_provider_id = self.client.get_vg_id_by_name(
group.id
)
except exception.VolumeBackendAPIException:
return None, None
self.client.delete_volume_or_snapshot(group_provider_id,
entity="volume group")
LOG.debug("Successfully deleted PowerStore volume group "
"%(group_name)s with id %(group_id)s.",
{
"group_name": group.name,
"group_id": group.id,
})
return None, None
@utils.is_group_a_cg_snapshot_type
def update_group(self, group, add_volumes, remove_volumes):
volumes_to_add = []
for volume in add_volumes:
if volume.is_replicated():
msg = _("Volume with enabled replication can not be added to "
"PowerStore volume group.")
LOG.error(msg)
raise exception.InvalidVolume(reason=msg)
volumes_to_add.append(self._get_volume_provider_id(volume))
volumes_to_remove = [
self._get_volume_provider_id(volume) for volume in remove_volumes
]
LOG.debug("Update PowerStore volume group %(group_name)s with id "
"%(group_id)s. Add PowerStore volumes with ids: "
"%(volumes_to_add)s, remove PowerStore volumes with ids: "
"%(volumes_to_remove)s.",
{
"group_name": group.name,
"group_id": group.id,
"volumes_to_add": volumes_to_add,
"volumes_to_remove": volumes_to_remove,
})
group_provider_id = self.client.get_vg_id_by_name(group.id)
if volumes_to_add:
self.client.add_volumes_to_vg(group_provider_id,
volumes_to_add)
if volumes_to_remove:
self.client.remove_volumes_from_vg(group_provider_id,
volumes_to_remove)
LOG.debug("Successfully updated PowerStore volume group "
"%(group_name)s with id %(group_id)s. "
"Add PowerStore volumes with ids: %(volumes_to_add)s, "
"remove PowerStore volumes with ids: %(volumes_to_remove)s.",
{
"group_name": group.name,
"group_id": group.id,
"volumes_to_add": volumes_to_add,
"volumes_to_remove": volumes_to_remove,
})
return None, None, None
@utils.is_group_a_cg_snapshot_type
def create_group_snapshot(self, group_snapshot):
LOG.debug("Create PowerStore snapshot %(snapshot_name)s with id "
"%(snapshot_id)s of volume group %(group_name)s with id "
"%(group_id)s.",
{
"snapshot_name": group_snapshot.name,
"snapshot_id": group_snapshot.id,
"group_name": group_snapshot.group.name,
"group_id": group_snapshot.group.id,
})
group_provider_id = self.client.get_vg_id_by_name(
group_snapshot.group.id
)
self.client.create_vg_snapshot(
group_provider_id,
group_snapshot.id
)
LOG.debug("Successfully created PowerStore snapshot %(snapshot_name)s "
"with id %(snapshot_id)s of volume group %(group_name)s "
"with id %(group_id)s.",
{
"snapshot_name": group_snapshot.name,
"snapshot_id": group_snapshot.id,
"group_name": group_snapshot.group.name,
"group_id": group_snapshot.group.id,
})
return None, None
@utils.is_group_a_cg_snapshot_type
def delete_group_snapshot(self, group_snapshot):
LOG.debug("Delete PowerStore snapshot %(snapshot_name)s with id "
"%(snapshot_id)s of volume group %(group_name)s with "
"id %(group_id)s.",
{
"snapshot_name": group_snapshot.name,
"snapshot_id": group_snapshot.id,
"group_name": group_snapshot.group.name,
"group_id": group_snapshot.group.id,
})
try:
group_provider_id = self.client.get_vg_id_by_name(
group_snapshot.group.id
)
group_snapshot_provider_id = (
self.client.get_vg_snapshot_id_by_name(
group_provider_id,
group_snapshot.id
))
except exception.VolumeBackendAPIException:
return None, None
self.client.delete_volume_or_snapshot(group_snapshot_provider_id,
entity="volume group snapshot")
LOG.debug("Successfully deleted PowerStore snapshot %(snapshot_name)s "
"with id %(snapshot_id)s of volume group %(group_name)s "
"with id %(group_id)s.",
{
"snapshot_name": group_snapshot.name,
"snapshot_id": group_snapshot.id,
"group_name": group_snapshot.group.name,
"group_id": group_snapshot.group.id,
})
return None, None
@utils.is_group_a_cg_snapshot_type
def create_group_from_source(self,
group,
volumes,
source,
snapshots,
source_vols):
if isinstance(source, GroupSnapshot):
entity = "volume group snapshot"
group_provider_id = self.client.get_vg_id_by_name(
source.group.id
)
source_provider_id = self.client.get_vg_snapshot_id_by_name(
group_provider_id,
source.id
)
source_vols = [snapshot.volume for snapshot in snapshots]
base_clone_name = "%s.%s" % (group.id, source.id)
else:
entity = "volume group"
source_provider_id = self.client.get_vg_id_by_name(source.id)
base_clone_name = group.id
LOG.debug("Create PowerStore volume group %(group_name)s with id "
"%(group_id)s from %(entity)s %(entity_name)s with id "
"%(entity_id)s.",
{
"group_name": group.name,
"group_id": group.id,
"entity": entity,
"entity_name": source.name,
"entity_id": source.id,
})
self.client.clone_vg_or_vg_snapshot(
group.id,
source_provider_id,
entity
)
LOG.debug("Successfully created PowerStore volume group "
"%(group_name)s with id %(group_id)s from %(entity)s "
"%(entity_name)s with id %(entity_id)s.",
{
"group_name": group.name,
"group_id": group.id,
"entity": entity,
"entity_name": source.name,
"entity_id": source.id,
})
updates = []
for volume, source_vol in zip(volumes, source_vols):
volume_name = "%s.%s" % (base_clone_name, source_vol.name)
volume_provider_id = self.client.get_volume_id_by_name(volume_name)
self.client.rename_volume(volume_provider_id, volume.name)
volume_updates = {
"id": volume.id,
"provider_id": volume_provider_id,
"replication_status": group.replication_status,
}
updates.append(volume_updates)
return None, updates
class FibreChannelAdapter(CommonAdapter):
def __init__(self, **kwargs):

View File

@ -158,13 +158,14 @@ class PowerStoreClient(object):
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def create_volume(self, name, size, pp_id):
def create_volume(self, name, size, pp_id, group_id):
r, response = self._send_post_request(
"/volume",
payload={
"name": name,
"size": size,
"protection_policy_id": pp_id,
"volume_group_id": group_id,
}
)
if r.status_code not in self.ok_codes:
@ -174,7 +175,15 @@ class PowerStoreClient(object):
return response["id"]
def delete_volume_or_snapshot(self, entity_id, entity="volume"):
r, response = self._send_delete_request("/volume/%s" % entity_id)
if entity in ["volume group", "volume group snapshot"]:
r, response = self._send_delete_request(
"/volume_group/%s" % entity_id,
payload={
"delete_members": True,
},
)
else:
r, response = self._send_delete_request("/volume/%s" % entity_id)
if r.status_code not in self.ok_codes:
if r.status_code == requests.codes.not_found:
LOG.warning("PowerStore %(entity)s with id %(entity_id)s is "
@ -599,3 +608,138 @@ class PowerStoreClient(object):
"with id %s.") % rep_session_id)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def create_vg(self, name):
r, response = self._send_post_request(
"/volume_group",
payload={
"name": name,
}
)
if r.status_code not in self.ok_codes:
msg = _("Failed to create PowerStore volume group %s.") % name
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return response["id"]
def get_vg_id_by_name(self, name):
r, response = self._send_get_request(
"/volume_group",
params={
"name": "eq.%s" % name,
}
)
if r.status_code not in self.ok_codes:
msg = _("Failed to query PowerStore volume groups.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
try:
group_id = response[0].get("id")
return group_id
except IndexError:
msg = _("PowerStore volume group %s is not found.") % name
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def add_volumes_to_vg(self, group_id, volume_ids):
r, response = self._send_post_request(
"/volume_group/%s/add_members" % group_id,
payload={
"volume_ids": volume_ids,
}
)
if r.status_code not in self.ok_codes:
msg = (_("Failed to add volumes to PowerStore volume group "
"with id %(group_id)s. Volumes: %(volume_ids)s.")
% {"group_id": group_id,
"volume_ids": volume_ids, })
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def remove_volumes_from_vg(self, group_id, volume_ids):
r, response = self._send_post_request(
"/volume_group/%s/remove_members" % group_id,
payload={
"volume_ids": volume_ids,
}
)
if r.status_code not in self.ok_codes:
msg = (_("Failed to remove volumes from PowerStore volume group "
"with id %(group_id)s. Volumes: %(volume_ids)s.")
% {"group_id": group_id,
"volume_ids": volume_ids, })
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def create_vg_snapshot(self, group_id, name):
r, response = self._send_post_request(
"/volume_group/%s/snapshot" % group_id,
payload={
"name": name,
}
)
if r.status_code not in self.ok_codes:
msg = (_("Failed to create snapshot %(snapshot_name)s for "
"PowerStore volume group with id %(group_id)s.")
% {"snapshot_name": name,
"group_id": group_id, })
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return response["id"]
def get_vg_snapshot_id_by_name(self, group_id, name):
r, response = self._send_get_request(
"/volume_group",
params={
"name": "eq.%s" % name,
"protection_data->>source_id": "eq.%s" % group_id,
"type": "eq.Snapshot",
}
)
if r.status_code not in self.ok_codes:
msg = _("Failed to query PowerStore volume groups snapshots.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
try:
vg_snap_id = response[0].get("id")
return vg_snap_id
except IndexError:
msg = (_("PowerStore snapshot %(snapshot_name)s for volume group"
"with id %(group_id)s is not found.")
% {"snapshot_name": name,
"group_id": group_id, })
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def clone_vg_or_vg_snapshot(self,
name,
entity_id,
entity="volume group"):
r, response = self._send_post_request(
"/volume_group/%s/clone" % entity_id,
payload={
"name": name,
}
)
if r.status_code not in self.ok_codes:
msg = (_("Failed to create clone %(clone_name)s for "
"PowerStore %(entity)s with id %(entity_id)s.")
% {"clone_name": name,
"entity": entity,
"entity_id": entity_id, })
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return response["id"]
def rename_volume(self, volume_id, name):
r, response = self._send_patch_request(
"/volume/%s" % volume_id,
payload={
"name": name,
}
)
if r.status_code not in self.ok_codes:
msg = (_("Failed to rename PowerStore volume with id %s.")
% volume_id)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)

View File

@ -46,9 +46,10 @@ class PowerStoreDriver(driver.VolumeDriver):
1.0.0 - Initial version
1.0.1 - Add CHAP support
1.1.0 - Add volume replication v2.1 support
1.1.1 - Add Consistency Groups support
"""
VERSION = "1.1.0"
VERSION = "1.1.1"
VENDOR = "Dell EMC"
# ThirdPartySystems wiki page
@ -187,6 +188,32 @@ class PowerStoreDriver(driver.VolumeDriver):
)
return secondary_id, volumes_updates, groups_updates
def create_group(self, context, group):
return self.adapter.create_group(group)
def delete_group(self, context, group, volumes):
return self.adapter.delete_group(group)
def update_group(self, context, group,
add_volumes=None, remove_volumes=None):
return self.adapter.update_group(group, add_volumes, remove_volumes)
def create_group_snapshot(self, context, group_snapshot, snapshots):
return self.adapter.create_group_snapshot(group_snapshot)
def delete_group_snapshot(self, context, group_snapshot, snapshots):
return self.adapter.delete_group_snapshot(group_snapshot)
def create_group_from_src(self, context, group, volumes,
group_snapshot=None, snapshots=None,
source_group=None, source_vols=None):
source = group_snapshot or source_group
return self.adapter.create_group_from_source(group,
volumes,
source,
snapshots,
source_vols)
@property
def adapter(self):
return self.adapters.get(self.active_backend_id)

View File

@ -15,6 +15,7 @@
"""Utilities for Dell EMC PowerStore Cinder driver."""
import functools
import re
from oslo_log import log as logging
@ -162,3 +163,18 @@ def get_protection_policy_from_volume(volume):
"""
return volume.volume_type.extra_specs.get(driver.POWERSTORE_PP_KEY)
def is_group_a_cg_snapshot_type(func):
"""Check if group is a consistent snapshot group.
Fallback to generic volume group implementation if consistent group
snapshot is not enabled.
"""
@functools.wraps(func)
def inner(self, *args, **kwargs):
if not volume_utils.is_group_a_cg_snapshot_type(args[0]):
raise NotImplementedError
return func(self, *args, **kwargs)
return inner

View File

@ -19,6 +19,10 @@ Supported operations
- Attach a volume to multiple servers simultaneously (multiattach).
- Revert a volume to a snapshot.
- OpenStack replication v2.1 support.
- Create, delete, update Consistency Groups.
- Create, delete Consistency Groups snapshots.
- Clone a Consistency Group.
- Create a Consistency Group from a Consistency Group snapshot.
Driver configuration
~~~~~~~~~~~~~~~~~~~~
@ -161,3 +165,18 @@ failback operation using ``--backend_id default``:
.. code-block:: console
$ cinder failover-host cinder_host@powerstore --backend_id default
Consistency Groups support
~~~~~~~~~~~~~~~~~~~~~~~~~~
To use PowerStore Volume Groups create Group Type with consistent group
snapshot enabled.
.. code-block:: console
$ cinder --os-volume-api-version 3.11 group-type-create powerstore_vg
$ cinder --os-volume-api-version 3.11 group-type-key powerstore_vg set consistent_group_snapshot_enabled="<is> True"
.. note:: Currently driver does not support Consistency Groups replication.
Adding volume to Consistency Group and creating volume in Consistency Group
will fail if volume is replicated.

View File

@ -569,7 +569,7 @@ notes=Vendor drivers that support consistency groups are able to
creation of consistent snapshots across a group.
driver.datera=missing
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=missing
driver.dell_emc_powerstore=complete
driver.dell_emc_powervault=missing
driver.dell_emc_sc=complete
driver.dell_emc_unity=complete

View File

@ -0,0 +1,4 @@
---
features:
- |
PowerStore driver: Add Consistency Groups support.