Add Consistency Groups support in PowerStore driver
Implements: blueprint powerstore-cg-support Change-Id: Ibd5f57ec472ed95a85fd608fce9a1a0090f31afe
This commit is contained in:
parent
f328341ed0
commit
ae00a173bd
@ -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
|
||||
|
@ -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])
|
@ -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"])
|
@ -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)
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -553,7 +553,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
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
PowerStore driver: Add Consistency Groups support.
|
Loading…
Reference in New Issue
Block a user