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("requests.request")
|
||||||
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
|
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
|
||||||
"PowerStoreClient.clone_volume_or_snapshot")
|
"PowerStoreClient.clone_volume_or_snapshot")
|
||||||
def test_create_volume_from_source_extende_bad_status(
|
def test_create_volume_from_source_extended_bad_status(
|
||||||
self,
|
self,
|
||||||
mock_create_from_source,
|
mock_create_from_source,
|
||||||
mock_extend_request
|
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 import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder.objects import fields
|
from cinder.objects import fields
|
||||||
|
from cinder.objects.group_snapshot import GroupSnapshot
|
||||||
from cinder.objects.snapshot import Snapshot
|
from cinder.objects.snapshot import Snapshot
|
||||||
from cinder.volume.drivers.dell_emc.powerstore import client
|
from cinder.volume.drivers.dell_emc.powerstore import client
|
||||||
from cinder.volume.drivers.dell_emc.powerstore import utils
|
from cinder.volume.drivers.dell_emc.powerstore import utils
|
||||||
from cinder.volume import manager
|
from cinder.volume import manager
|
||||||
|
from cinder.volume import volume_utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -88,6 +90,19 @@ class CommonAdapter(object):
|
||||||
})
|
})
|
||||||
|
|
||||||
def create_volume(self, volume):
|
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():
|
if volume.is_replicated():
|
||||||
pp_name = utils.get_protection_policy_from_volume(volume)
|
pp_name = utils.get_protection_policy_from_volume(volume)
|
||||||
pp_id = self.client.get_protection_policy_id_by_name(pp_name)
|
pp_id = self.client.get_protection_policy_id_by_name(pp_name)
|
||||||
|
@ -98,26 +113,31 @@ class CommonAdapter(object):
|
||||||
replication_status = fields.ReplicationStatus.DISABLED
|
replication_status = fields.ReplicationStatus.DISABLED
|
||||||
LOG.debug("Create PowerStore volume %(volume_name)s of size "
|
LOG.debug("Create PowerStore volume %(volume_name)s of size "
|
||||||
"%(volume_size)s GiB with id %(volume_id)s. "
|
"%(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_name": volume.name,
|
||||||
"volume_size": volume.size,
|
"volume_size": volume.size,
|
||||||
"volume_id": volume.id,
|
"volume_id": volume.id,
|
||||||
"pp_name": pp_name,
|
"pp_name": pp_name,
|
||||||
|
"group_id": volume.group_id,
|
||||||
})
|
})
|
||||||
size_in_bytes = utils.gib_to_bytes(volume.size)
|
size_in_bytes = utils.gib_to_bytes(volume.size)
|
||||||
provider_id = self.client.create_volume(volume.name,
|
provider_id = self.client.create_volume(volume.name,
|
||||||
size_in_bytes,
|
size_in_bytes,
|
||||||
pp_id)
|
pp_id,
|
||||||
|
group_provider_id)
|
||||||
LOG.debug("Successfully created PowerStore volume %(volume_name)s of "
|
LOG.debug("Successfully created PowerStore volume %(volume_name)s of "
|
||||||
"size %(volume_size)s GiB with id %(volume_id)s on "
|
"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.",
|
"PowerStore volume id: %(volume_provider_id)s.",
|
||||||
{
|
{
|
||||||
"volume_name": volume.name,
|
"volume_name": volume.name,
|
||||||
"volume_size": volume.size,
|
"volume_size": volume.size,
|
||||||
"volume_id": volume.id,
|
"volume_id": volume.id,
|
||||||
"pp_name": pp_name,
|
"pp_name": pp_name,
|
||||||
|
"group_id": volume.group_id,
|
||||||
"volume_provider_id": provider_id,
|
"volume_provider_id": provider_id,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
|
@ -273,6 +293,7 @@ class CommonAdapter(object):
|
||||||
"thin_provisioning_support": True,
|
"thin_provisioning_support": True,
|
||||||
"compression_support": True,
|
"compression_support": True,
|
||||||
"multiattach": True,
|
"multiattach": True,
|
||||||
|
"consistent_group_snapshot_enabled": True,
|
||||||
}
|
}
|
||||||
backend_stats = self.client.get_metrics()
|
backend_stats = self.client.get_metrics()
|
||||||
backend_total_capacity = utils.bytes_to_gib(
|
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):
|
class FibreChannelAdapter(CommonAdapter):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
|
|
@ -158,13 +158,14 @@ class PowerStoreClient(object):
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.VolumeBackendAPIException(data=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(
|
r, response = self._send_post_request(
|
||||||
"/volume",
|
"/volume",
|
||||||
payload={
|
payload={
|
||||||
"name": name,
|
"name": name,
|
||||||
"size": size,
|
"size": size,
|
||||||
"protection_policy_id": pp_id,
|
"protection_policy_id": pp_id,
|
||||||
|
"volume_group_id": group_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if r.status_code not in self.ok_codes:
|
if r.status_code not in self.ok_codes:
|
||||||
|
@ -174,6 +175,14 @@ class PowerStoreClient(object):
|
||||||
return response["id"]
|
return response["id"]
|
||||||
|
|
||||||
def delete_volume_or_snapshot(self, entity_id, entity="volume"):
|
def delete_volume_or_snapshot(self, entity_id, entity="volume"):
|
||||||
|
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)
|
r, response = self._send_delete_request("/volume/%s" % entity_id)
|
||||||
if r.status_code not in self.ok_codes:
|
if r.status_code not in self.ok_codes:
|
||||||
if r.status_code == requests.codes.not_found:
|
if r.status_code == requests.codes.not_found:
|
||||||
|
@ -599,3 +608,138 @@ class PowerStoreClient(object):
|
||||||
"with id %s.") % rep_session_id)
|
"with id %s.") % rep_session_id)
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.VolumeBackendAPIException(data=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.0 - Initial version
|
||||||
1.0.1 - Add CHAP support
|
1.0.1 - Add CHAP support
|
||||||
1.1.0 - Add volume replication v2.1 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"
|
VENDOR = "Dell EMC"
|
||||||
|
|
||||||
# ThirdPartySystems wiki page
|
# ThirdPartySystems wiki page
|
||||||
|
@ -187,6 +188,32 @@ class PowerStoreDriver(driver.VolumeDriver):
|
||||||
)
|
)
|
||||||
return secondary_id, volumes_updates, groups_updates
|
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
|
@property
|
||||||
def adapter(self):
|
def adapter(self):
|
||||||
return self.adapters.get(self.active_backend_id)
|
return self.adapters.get(self.active_backend_id)
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
"""Utilities for Dell EMC PowerStore Cinder driver."""
|
"""Utilities for Dell EMC PowerStore Cinder driver."""
|
||||||
|
|
||||||
|
import functools
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from oslo_log import log as logging
|
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)
|
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).
|
- Attach a volume to multiple servers simultaneously (multiattach).
|
||||||
- Revert a volume to a snapshot.
|
- Revert a volume to a snapshot.
|
||||||
- OpenStack replication v2.1 support.
|
- 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
|
Driver configuration
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -161,3 +165,18 @@ failback operation using ``--backend_id default``:
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ cinder failover-host cinder_host@powerstore --backend_id default
|
$ 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.
|
creation of consistent snapshots across a group.
|
||||||
driver.datera=missing
|
driver.datera=missing
|
||||||
driver.dell_emc_powermax=complete
|
driver.dell_emc_powermax=complete
|
||||||
driver.dell_emc_powerstore=missing
|
driver.dell_emc_powerstore=complete
|
||||||
driver.dell_emc_powervault=missing
|
driver.dell_emc_powervault=missing
|
||||||
driver.dell_emc_sc=complete
|
driver.dell_emc_sc=complete
|
||||||
driver.dell_emc_unity=complete
|
driver.dell_emc_unity=complete
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
PowerStore driver: Add Consistency Groups support.
|
Loading…
Reference in New Issue