Browse Source

Add OpenStack volume replication v2.1 support in PowerStore driver

Cinder driver for PowerStore supports volumes/snapshots with
replication enabled according to OpenStack volume replication specification.

Implements: blueprint powerstore-replication-support
Change-Id: I94d089374dee76d401dc6cf83a9c594779e7eb3e
changes/54/773854/8
Ivan Pchelintsev 8 months ago
parent
commit
f328341ed0
  1. 4
      cinder/tests/unit/volume/drivers/dell_emc/powerstore/__init__.py
  2. 91
      cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_base.py
  3. 121
      cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_replication.py
  4. 16
      cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_snapshot_create_delete_revert.py
  5. 23
      cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py
  6. 9
      cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py
  7. 24
      cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_from_source.py
  8. 478
      cinder/volume/drivers/dell_emc/powerstore/adapter.py
  9. 269
      cinder/volume/drivers/dell_emc/powerstore/client.py
  10. 140
      cinder/volume/drivers/dell_emc/powerstore/driver.py
  11. 7
      cinder/volume/drivers/dell_emc/powerstore/options.py
  12. 11
      cinder/volume/drivers/dell_emc/powerstore/utils.py
  13. 72
      doc/source/configuration/block-storage/drivers/dell-emc-powerstore-driver.rst
  14. 4
      doc/source/reference/support-matrix.ini
  15. 10
      releasenotes/notes/bp-powerstore-replication-support-700016b83437602e.yaml

4
cinder/tests/unit/volume/drivers/dell_emc/powerstore/__init__.py

@ -18,6 +18,7 @@ from unittest import mock
import requests
from cinder import context
from cinder.tests.unit import test
from cinder.volume import configuration
from cinder.volume.drivers.dell_emc.powerstore import driver
@ -51,6 +52,7 @@ class MockResponse(requests.Response):
class TestPowerStoreDriver(test.TestCase):
def setUp(self):
super(TestPowerStoreDriver, self).setUp()
self.context = context.RequestContext('fake', 'fake', auth_token=True)
self.configuration = configuration.Configuration(
options.POWERSTORE_OPTS,
configuration.SHARED_CONF_GROUP
@ -76,5 +78,3 @@ class TestPowerStoreDriver(test.TestCase):
self._override_shared_conf("san_ip", override="127.0.0.1")
self._override_shared_conf("san_login", override="test")
self._override_shared_conf("san_password", override="test")
self._override_shared_conf("powerstore_appliances",
override="test-appliance")

91
cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_base.py

@ -22,47 +22,21 @@ from cinder.tests.unit.volume.drivers.dell_emc import powerstore
class TestBase(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_appliance_id_by_name")
def test_configuration(self, mock_appliance, mock_chap):
mock_appliance.return_value = "A1"
def test_configuration(self, mock_chap):
self.driver.check_for_setup_error()
def test_configuration_rest_parameters_not_set(self):
self.driver.adapter.client.rest_ip = None
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.check_for_setup_error)
def test_configuration_appliances_not_set(self):
self.driver.adapter.appliances = {}
self.assertRaises(exception.VolumeBackendAPIException,
self.assertRaises(exception.InvalidInput,
self.driver.check_for_setup_error)
@mock.patch("requests.request")
def test_configuration_appliance_not_found(self, mock_get_request):
mock_get_request.return_value = powerstore.MockResponse()
error = self.assertRaises(exception.VolumeBackendAPIException,
self.driver.check_for_setup_error)
self.assertIn("not found", error.msg)
@mock.patch("requests.request")
def test_configuration_appliance_bad_status(self, mock_get_request):
mock_get_request.return_value = powerstore.MockResponse(rc=400)
error = self.assertRaises(exception.VolumeBackendAPIException,
self.driver.check_for_setup_error)
self.assertIn("Failed to query PowerStore appliances.", error.msg)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_appliance_id_by_name")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_appliance_metrics")
"PowerStoreClient.get_metrics")
def test_update_volume_stats(self,
mock_metrics,
mock_appliance,
mock_chap):
mock_appliance.return_value = "A1"
mock_metrics.return_value = {
"physical_total": 2147483648,
"physical_used": 1073741824,
@ -72,16 +46,65 @@ class TestBase(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_appliance_id_by_name")
@mock.patch("requests.request")
def test_update_volume_stats_bad_status(self,
mock_metrics,
mock_appliance,
mock_chap):
mock_appliance.return_value = "A1"
mock_metrics.return_value = powerstore.MockResponse(rc=400)
self.driver.check_for_setup_error()
error = self.assertRaises(exception.VolumeBackendAPIException,
self.driver._update_volume_stats)
self.assertIn("Failed to query metrics", error.msg)
self.assertIn("Failed to query PowerStore metrics", error.msg)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
def test_configuration_with_replication(self, mock_chap):
replication_device = [
{
"backend_id": "repl_1",
"san_ip": "127.0.0.2",
"san_login": "test_1",
"san_password": "test_2"
}
]
self._override_shared_conf("replication_device",
override=replication_device)
self.driver.do_setup({})
self.driver.check_for_setup_error()
self.assertEqual(2, len(self.driver.adapters))
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
def test_configuration_with_replication_2_rep_devices(self, mock_chap):
device = {
"backend_id": "repl_1",
"san_ip": "127.0.0.2",
"san_login": "test_1",
"san_password": "test_2"
}
replication_device = [device] * 2
self._override_shared_conf("replication_device",
override=replication_device)
self.driver.do_setup({})
error = self.assertRaises(exception.InvalidInput,
self.driver.check_for_setup_error)
self.assertIn("PowerStore driver does not support more than one "
"replication device.", error.msg)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
def test_configuration_with_replication_failed_over(self, mock_chap):
replication_device = [
{
"backend_id": "repl_1",
"san_ip": "127.0.0.2",
"san_login": "test_1",
"san_password": "test_2"
}
]
self._override_shared_conf("replication_device",
override=replication_device)
self.driver.do_setup({})
self.driver.check_for_setup_error()
self.driver.active_backend_id = "repl_1"
self.assertFalse(self.driver.replication_enabled)

121
cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_replication.py

@ -0,0 +1,121 @@
# 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.objects import fields
from cinder.tests.unit import fake_volume
from cinder.tests.unit.volume.drivers.dell_emc import powerstore
from cinder.volume.drivers.dell_emc.powerstore import client
class TestReplication(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
def setUp(self, mock_chap):
super(TestReplication, self).setUp()
self.replication_backend_id = "repl_1"
replication_device = [
{
"backend_id": self.replication_backend_id,
"san_ip": "127.0.0.2",
"san_login": "test_1",
"san_password": "test_2"
}
]
self._override_shared_conf("replication_device",
override=replication_device)
self.driver.do_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,
replication_status="enabled"
)
def test_failover_host_no_volumes(self):
self.driver.failover_host({}, [], self.replication_backend_id)
self.assertEqual(self.replication_backend_id,
self.driver.active_backend_id)
def test_failover_host_invalid_secondary_id(self):
error = self.assertRaises(exception.InvalidReplicationTarget,
self.driver.failover_host,
{}, [], "invalid_id")
self.assertIn("is not a valid choice", error.msg)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.wait_for_failover_completion")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.failover_volume_replication_session")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_volume_replication_session_id")
def test_failover_volume(self,
mock_rep_session,
mock_failover,
mock_wait_failover):
updates = self.driver.adapter.failover_volume(self.volume,
is_failback=False)
self.assertIsNone(updates)
@mock.patch("requests.request")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.failover_volume_replication_session")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_volume_replication_session_id")
def test_failover_volume_already_failed_over(self,
mock_rep_session,
mock_failover,
mock_wait_failover):
mock_wait_failover.return_value = powerstore.MockResponse(
content={
"response_body": {
"messages": [
{
"code": client.SESSION_ALREADY_FAILED_OVER_ERROR,
},
],
},
},
rc=200
)
updates = self.driver.adapter.failover_volume(self.volume,
is_failback=False)
self.assertIsNone(updates)
@mock.patch("requests.request")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.failover_volume_replication_session")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_volume_replication_session_id")
def test_failover_volume_failover_error(self,
mock_rep_session,
mock_failover,
mock_wait_failover):
mock_wait_failover.return_value = powerstore.MockResponse(
content={
"state": "FAILED",
"response_body": None,
},
rc=200
)
updates = self.driver.adapter.failover_volume(self.volume,
is_failback=False)
self.assertEqual(self.volume.id, updates["volume_id"])
self.assertEqual(fields.ReplicationStatus.FAILOVER_ERROR,
updates["updates"]["replication_status"])

16
cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_snapshot_create_delete_revert.py

@ -24,28 +24,26 @@ from cinder.tests.unit.volume.drivers.dell_emc import powerstore
class TestSnapshotCreateDelete(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_appliance_id_by_name")
def setUp(self, mock_appliance, mock_chap):
def setUp(self, mock_chap):
super(TestSnapshotCreateDelete, self).setUp()
mock_appliance.return_value = "A1"
self.driver.check_for_setup_error()
self.volume = fake_volume.fake_volume_obj(
{},
host="host@backend#test-appliance",
self.context,
host="host@backend",
provider_id="fake_id",
size=8
)
self.snapshot = fake_snapshot.fake_snapshot_obj(
{},
provider_id="fake_id_1",
self.context,
volume=self.volume
)
self.mock_object(self.driver.adapter.client,
"get_snapshot_id_by_name",
return_value="fake_id_1")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.create_snapshot")
def test_create_snapshot(self, mock_create):
mock_create.return_value = self.snapshot.provider_id
self.driver.create_snapshot(self.snapshot)
@mock.patch("requests.request")

23
cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py

@ -26,17 +26,14 @@ from cinder.volume.drivers.dell_emc.powerstore import utils
class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_appliance_id_by_name")
def setUp(self, mock_appliance, mock_chap):
def setUp(self, mock_chap):
super(TestVolumeAttachDetach, self).setUp()
mock_appliance.return_value = "A1"
mock_chap.return_value = {"mode": "Single"}
self.iscsi_driver.check_for_setup_error()
self.fc_driver.check_for_setup_error()
self.volume = fake_volume.fake_volume_obj(
{},
host="host@backend#test-appliance",
self.context,
host="host@backend",
provider_id="fake_id",
size=8
)
@ -124,12 +121,12 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
self.assertNotIn("auth_password", connection_properties["data"])
def test_get_fc_targets(self):
wwns = self.fc_driver.adapter._get_fc_targets("A1")
wwns = self.fc_driver.adapter._get_fc_targets()
self.assertEqual(2, len(wwns))
def test_get_fc_targets_filtered(self):
self.fc_driver.adapter.allowed_ports = ["58:cc:f0:98:49:23:07:02"]
wwns = self.fc_driver.adapter._get_fc_targets("A1")
wwns = self.fc_driver.adapter._get_fc_targets()
self.assertEqual(1, len(wwns))
self.assertFalse(
utils.fc_wwn_to_string("58:cc:f0:98:49:21:07:02") in wwns
@ -138,19 +135,18 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
def test_get_fc_targets_filtered_no_matched_ports(self):
self.fc_driver.adapter.allowed_ports = ["fc_wwn_1", "fc_wwn_2"]
error = self.assertRaises(exception.VolumeBackendAPIException,
self.fc_driver.adapter._get_fc_targets,
"A1")
self.fc_driver.adapter._get_fc_targets)
self.assertIn("There are no accessible Fibre Channel targets on the "
"system.", error.msg)
def test_get_iscsi_targets(self):
iqns, portals = self.iscsi_driver.adapter._get_iscsi_targets("A1")
iqns, portals = self.iscsi_driver.adapter._get_iscsi_targets()
self.assertTrue(len(iqns) == len(portals))
self.assertEqual(2, len(portals))
def test_get_iscsi_targets_filtered(self):
self.iscsi_driver.adapter.allowed_ports = ["1.2.3.4"]
iqns, portals = self.iscsi_driver.adapter._get_iscsi_targets("A1")
iqns, portals = self.iscsi_driver.adapter._get_iscsi_targets()
self.assertTrue(len(iqns) == len(portals))
self.assertEqual(1, len(portals))
self.assertFalse(
@ -160,8 +156,7 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
def test_get_iscsi_targets_filtered_no_matched_ports(self):
self.iscsi_driver.adapter.allowed_ports = ["1.1.1.1", "2.2.2.2"]
error = self.assertRaises(exception.VolumeBackendAPIException,
self.iscsi_driver.adapter._get_iscsi_targets,
"A1")
self.iscsi_driver.adapter._get_iscsi_targets)
self.assertIn("There are no accessible iSCSI targets on the system.",
error.msg)

9
cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py

@ -24,15 +24,12 @@ from cinder.volume.drivers.dell_emc.powerstore import client
class TestVolumeCreateDeleteExtend(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_appliance_id_by_name")
def setUp(self, mock_appliance, mock_chap):
def setUp(self, mock_chap):
super(TestVolumeCreateDeleteExtend, self).setUp()
mock_appliance.return_value = "A1"
self.driver.check_for_setup_error()
self.volume = fake_volume.fake_volume_obj(
{},
host="host@backend#test-appliance",
self.context,
host="host@backend",
provider_id="fake_id",
size=8
)

24
cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_from_source.py

@ -24,29 +24,29 @@ from cinder.tests.unit.volume.drivers.dell_emc import powerstore
class TestVolumeCreateFromSource(powerstore.TestPowerStoreDriver):
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_appliance_id_by_name")
def setUp(self, mock_appliance, mock_chap):
def setUp(self, mock_chap):
super(TestVolumeCreateFromSource, self).setUp()
mock_appliance.return_value = "A1"
self.driver.check_for_setup_error()
self.volume = fake_volume.fake_volume_obj(
{},
host="host@backend#test-appliance",
self.context,
host="host@backend",
provider_id="fake_id",
size=8
)
self.source_volume = fake_volume.fake_volume_obj(
{},
host="host@backend#test-appliance",
self.context,
host="host@backend",
provider_id="fake_id_1",
size=8
)
self.source_snapshot = fake_snapshot.fake_snapshot_obj(
{},
provider_id="fake_id_2",
self.context,
volume=self.source_volume,
volume_size=8
)
self.mock_object(self.driver.adapter.client,
"get_snapshot_id_by_name",
return_value="fake_id_1")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.clone_volume_or_snapshot")
@ -91,7 +91,7 @@ class TestVolumeCreateFromSource(powerstore.TestPowerStoreDriver):
mock_create_request.return_value = powerstore.MockResponse(rc=400)
error = self.assertRaises(
exception.VolumeBackendAPIException,
self.driver.adapter._create_volume_from_source,
self.driver.adapter.create_volume_from_source,
self.volume,
self.source_volume
)
@ -109,7 +109,7 @@ class TestVolumeCreateFromSource(powerstore.TestPowerStoreDriver):
self.volume.size = 16
error = self.assertRaises(
exception.VolumeBackendAPIException,
self.driver.adapter._create_volume_from_source,
self.driver.adapter.create_volume_from_source,
self.volume,
self.source_volume
)

478
cinder/volume/drivers/dell_emc/powerstore/adapter.py

@ -21,11 +21,11 @@ from oslo_utils import strutils
from cinder import coordination
from cinder import exception
from cinder.i18n import _
from cinder.objects import fields
from cinder.objects.snapshot import Snapshot
from cinder.volume.drivers.dell_emc.powerstore import client
from cinder.volume.drivers.dell_emc.powerstore import options
from cinder.volume.drivers.dell_emc.powerstore import utils
from cinder.volume import volume_utils
from cinder.volume import manager
LOG = logging.getLogger(__name__)
@ -35,15 +35,19 @@ CHAP_MODE_SINGLE = "Single"
class CommonAdapter(object):
def __init__(self, active_backend_id, configuration):
self.active_backend_id = active_backend_id
self.appliances = None
self.appliances_to_ids_map = {}
self.client = None
self.configuration = configuration
def __init__(self,
backend_id,
backend_name,
ports,
**client_config):
if isinstance(ports, str):
ports = ports.split(",")
self.allowed_ports = [port.strip().lower() for port in ports]
self.backend_id = backend_id
self.backend_name = backend_name
self.client = client.PowerStoreClient(**client_config)
self.storage_protocol = None
self.allowed_ports = None
self.use_chap_auth = None
self.use_chap_auth = False
@staticmethod
def initiators(connector):
@ -62,79 +66,71 @@ class CommonAdapter(object):
return True
return port.lower() in self.allowed_ports
def _get_connection_properties(self, appliance_id, volume_lun):
def _get_connection_properties(self, volume_lun):
raise NotImplementedError
def do_setup(self):
self.appliances = (
self.configuration.safe_get(options.POWERSTORE_APPLIANCES)
)
self.allowed_ports = [
port.strip().lower() for port in
self.configuration.safe_get(options.POWERSTORE_PORTS)
]
self.client = client.PowerStoreClient(configuration=self.configuration)
self.client.do_setup()
def check_for_setup_error(self):
self.client.check_for_setup_error()
if not self.appliances:
msg = _("PowerStore appliances must be set.")
raise exception.VolumeBackendAPIException(data=msg)
self.appliances_to_ids_map = {}
for appliance_name in self.appliances:
self.appliances_to_ids_map[appliance_name] = (
self.client.get_appliance_id_by_name(appliance_name)
)
self.use_chap_auth = False
if self.storage_protocol == PROTOCOL_ISCSI:
chap_config = self.client.get_chap_config()
if chap_config.get("mode") == CHAP_MODE_SINGLE:
self.use_chap_auth = True
LOG.debug("Successfully initialized PowerStore %(protocol)s adapter. "
"PowerStore appliances: %(appliances)s. "
LOG.debug("Successfully initialized PowerStore %(protocol)s adapter "
"for %(backend_id)s %(backend_name)s backend. "
"Allowed ports: %(allowed_ports)s. "
"Use CHAP authentication: %(use_chap_auth)s.",
{
"protocol": self.storage_protocol,
"appliances": self.appliances,
"backend_id": self.backend_id,
"backend_name": self.backend_name,
"allowed_ports": self.allowed_ports,
"use_chap_auth": self.use_chap_auth,
})
def create_volume(self, volume):
appliance_name = volume_utils.extract_host(volume.host, "pool")
appliance_id = self.appliances_to_ids_map[appliance_name]
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)
replication_status = fields.ReplicationStatus.ENABLED
else:
pp_name = None
pp_id = None
replication_status = fields.ReplicationStatus.DISABLED
LOG.debug("Create PowerStore volume %(volume_name)s of size "
"%(volume_size)s GiB with id %(volume_id)s on appliance "
"%(appliance_name)s.",
"%(volume_size)s GiB with id %(volume_id)s. "
"Protection policy: %(pp_name)s.",
{
"volume_name": volume.name,
"volume_size": volume.size,
"volume_id": volume.id,
"appliance_name": appliance_name,
"pp_name": pp_name,
})
size_in_bytes = utils.gib_to_bytes(volume.size)
provider_id = self.client.create_volume(appliance_id,
volume.name,
size_in_bytes)
provider_id = self.client.create_volume(volume.name,
size_in_bytes,
pp_id)
LOG.debug("Successfully created PowerStore volume %(volume_name)s of "
"size %(volume_size)s GiB with id %(volume_id)s on "
"appliance %(appliance_name)s. "
"Protection policy: %(pp_name)s."
"PowerStore volume id: %(volume_provider_id)s.",
{
"volume_name": volume.name,
"volume_size": volume.size,
"volume_id": volume.id,
"appliance_name": appliance_name,
"pp_name": pp_name,
"volume_provider_id": provider_id,
})
return {
"provider_id": provider_id,
"replication_status": replication_status,
}
def delete_volume(self, volume):
if not volume.provider_id:
try:
provider_id = self._get_volume_provider_id(volume)
except exception.VolumeBackendAPIException:
provider_id = None
if not provider_id:
LOG.warning("Volume %(volume_name)s with id %(volume_id)s "
"does not have provider_id thus does not "
"map to PowerStore volume.",
@ -149,20 +145,21 @@ class CommonAdapter(object):
{
"volume_name": volume.name,
"volume_id": volume.id,
"volume_provider_id": volume.provider_id,
"volume_provider_id": provider_id,
})
self._detach_volume_from_hosts(volume)
self.client.delete_volume_or_snapshot(volume.provider_id)
self.client.delete_volume_or_snapshot(provider_id)
LOG.debug("Successfully deleted PowerStore volume %(volume_name)s "
"with id %(volume_id)s. PowerStore volume id: "
"%(volume_provider_id)s.",
{
"volume_name": volume.name,
"volume_id": volume.id,
"volume_provider_id": volume.provider_id,
"volume_provider_id": provider_id,
})
def extend_volume(self, volume, new_size):
provider_id = self._get_volume_provider_id(volume)
LOG.debug("Extend PowerStore volume %(volume_name)s of size "
"%(volume_size)s GiB with id %(volume_id)s to "
"%(volume_new_size)s GiB. "
@ -172,10 +169,10 @@ class CommonAdapter(object):
"volume_size": volume.size,
"volume_id": volume.id,
"volume_new_size": new_size,
"volume_provider_id": volume.provider_id,
"volume_provider_id": provider_id,
})
size_in_bytes = utils.gib_to_bytes(new_size)
self.client.extend_volume(volume.provider_id, size_in_bytes)
self.client.extend_volume(provider_id, size_in_bytes)
LOG.debug("Successfully extended PowerStore volume %(volume_name)s "
"of size %(volume_size)s GiB with id "
"%(volume_id)s to %(volume_new_size)s GiB. "
@ -185,10 +182,11 @@ class CommonAdapter(object):
"volume_size": volume.size,
"volume_id": volume.id,
"volume_new_size": new_size,
"volume_provider_id": volume.provider_id,
"volume_provider_id": provider_id,
})
def create_snapshot(self, snapshot):
volume_provider_id = self._get_volume_provider_id(snapshot.volume)
LOG.debug("Create PowerStore snapshot %(snapshot_name)s with id "
"%(snapshot_id)s of volume %(volume_name)s with id "
"%(volume_id)s. PowerStore volume id: "
@ -198,124 +196,57 @@ class CommonAdapter(object):
"snapshot_id": snapshot.id,
"volume_name": snapshot.volume.name,
"volume_id": snapshot.volume.id,
"volume_provider_id": snapshot.volume.provider_id,
"volume_provider_id": volume_provider_id,
})
snapshot_provider_id = self.client.create_snapshot(
snapshot.volume.provider_id,
snapshot.name)
self.client.create_snapshot(volume_provider_id, snapshot.name)
LOG.debug("Successfully created PowerStore snapshot %(snapshot_name)s "
"with id %(snapshot_id)s of volume %(volume_name)s with "
"id %(volume_id)s. PowerStore snapshot id: "
"%(snapshot_provider_id)s, volume id: "
"id %(volume_id)s. PowerStore volume id: "
"%(volume_provider_id)s.",
{
"snapshot_name": snapshot.name,
"snapshot_id": snapshot.id,
"volume_name": snapshot.volume.name,
"volume_id": snapshot.volume.id,
"snapshot_provider_id": snapshot_provider_id,
"volume_provider_id": snapshot.volume.provider_id,
"volume_provider_id": volume_provider_id,
})
return {
"provider_id": snapshot_provider_id,
}
def delete_snapshot(self, snapshot):
try:
volume_provider_id = self._get_volume_provider_id(snapshot.volume)
except exception.VolumeBackendAPIException:
return
LOG.debug("Delete PowerStore snapshot %(snapshot_name)s with id "
"%(snapshot_id)s of volume %(volume_name)s with "
"id %(volume_id)s. PowerStore snapshot id: "
"%(snapshot_provider_id)s, volume id: "
"id %(volume_id)s. PowerStore volume id: "
"%(volume_provider_id)s.",
{
"snapshot_name": snapshot.name,
"snapshot_id": snapshot.id,
"volume_name": snapshot.volume.name,
"volume_id": snapshot.volume.id,
"snapshot_provider_id": snapshot.provider_id,
"volume_provider_id": snapshot.volume.provider_id,
"volume_provider_id": volume_provider_id,
})
self.client.delete_volume_or_snapshot(snapshot.provider_id,
try:
snapshot_provider_id = self.client.get_snapshot_id_by_name(
volume_provider_id,
snapshot.name
)
except exception.VolumeBackendAPIException:
return
self.client.delete_volume_or_snapshot(snapshot_provider_id,
entity="snapshot")
LOG.debug("Successfully deleted PowerStore snapshot %(snapshot_name)s "
"with id %(snapshot_id)s of volume %(volume_name)s with "
"id %(volume_id)s. PowerStore snapshot id: "
"%(snapshot_provider_id)s, volume id: "
"id %(volume_id)s. PowerStore volume id: "
"%(volume_provider_id)s.",
{
"snapshot_name": snapshot.name,
"snapshot_id": snapshot.id,
"volume_name": snapshot.volume.name,
"volume_id": snapshot.volume.id,
"snapshot_provider_id": snapshot.provider_id,
"volume_provider_id": snapshot.volume.provider_id,
})
def create_cloned_volume(self, volume, src_vref):
LOG.debug("Clone PowerStore volume %(source_volume_name)s with id "
"%(source_volume_id)s to volume %(cloned_volume_name)s of "
"size %(cloned_volume_size)s GiB with id "
"%(cloned_volume_id)s. PowerStore source volume id: "
"%(source_volume_provider_id)s.",
{
"source_volume_name": src_vref.name,
"source_volume_id": src_vref.id,
"cloned_volume_name": volume.name,
"cloned_volume_size": volume.size,
"cloned_volume_id": volume.id,
"source_volume_provider_id": src_vref.provider_id,
})
cloned_provider_id = self._create_volume_from_source(volume, src_vref)
LOG.debug("Successfully cloned PowerStore volume "
"%(source_volume_name)s with id %(source_volume_id)s to "
"volume %(cloned_volume_name)s of size "
"%(cloned_volume_size)s GiB with id %(cloned_volume_id)s. "
"PowerStore source volume id: "
"%(source_volume_provider_id)s, "
"cloned volume id: %(cloned_volume_provider_id)s.",
{
"source_volume_name": src_vref.name,
"source_volume_id": src_vref.id,
"cloned_volume_name": volume.name,
"cloned_volume_size": volume.size,
"cloned_volume_id": volume.id,
"source_volume_provider_id": src_vref.provider_id,
"cloned_volume_provider_id": cloned_provider_id,
})
return {
"provider_id": cloned_provider_id,
}
def create_volume_from_snapshot(self, volume, snapshot):
LOG.debug("Create PowerStore volume %(volume_name)s of size "
"%(volume_size)s GiB with id %(volume_id)s from snapshot "
"%(snapshot_name)s with id %(snapshot_id)s. PowerStore "
"snapshot id: %(snapshot_provider_id)s.",
{
"volume_name": volume.name,
"volume_id": volume.id,
"volume_size": volume.size,
"snapshot_name": snapshot.name,
"snapshot_id": snapshot.id,
"snapshot_provider_id": snapshot.provider_id,
})
volume_provider_id = self._create_volume_from_source(volume, snapshot)
LOG.debug("Successfully created PowerStore volume %(volume_name)s "
"of size %(volume_size)s GiB with id %(volume_id)s from "
"snapshot %(snapshot_name)s with id %(snapshot_id)s. "
"PowerStore volume id: %(volume_provider_id)s, "
"snapshot id: %(snapshot_provider_id)s.",
{
"volume_name": volume.name,
"volume_id": volume.id,
"volume_size": volume.size,
"snapshot_name": snapshot.name,
"snapshot_id": snapshot.id,
"volume_provider_id": volume_provider_id,
"snapshot_provider_id": snapshot.provider_id,
})
return {
"provider_id": volume_provider_id,
}
def initialize_connection(self, volume, connector, **kwargs):
connection_properties = self._connect_volume(volume, connector)
@ -336,76 +267,96 @@ class CommonAdapter(object):
def update_volume_stats(self):
stats = {
"volume_backend_name": (
self.configuration.safe_get("volume_backend_name") or
"powerstore"
),
"volume_backend_name": self.backend_name,
"storage_protocol": self.storage_protocol,
"thick_provisioning_support": False,
"thin_provisioning_support": True,
"compression_support": True,
"multiattach": True,
"pools": [],
}
backend_total_capacity = 0
backend_free_capacity = 0
for appliance_name in self.appliances:
appliance_stats = self.client.get_appliance_metrics(
self.appliances_to_ids_map[appliance_name]
)
appliance_total_capacity = utils.bytes_to_gib(
appliance_stats["physical_total"]
)
appliance_free_capacity = (
appliance_total_capacity -
utils.bytes_to_gib(appliance_stats["physical_used"])
)
pool = {
"pool_name": appliance_name,
"total_capacity_gb": appliance_total_capacity,
"free_capacity_gb": appliance_free_capacity,
"thick_provisioning_support": False,
"thin_provisioning_support": True,
"compression_support": True,
"multiattach": True,
}
backend_total_capacity += appliance_total_capacity
backend_free_capacity += appliance_free_capacity
stats["pools"].append(pool)
backend_stats = self.client.get_metrics()
backend_total_capacity = utils.bytes_to_gib(
backend_stats["physical_total"]
)
backend_free_capacity = (
backend_total_capacity -
utils.bytes_to_gib(backend_stats["physical_used"])
)
stats["total_capacity_gb"] = backend_total_capacity
stats["free_capacity_gb"] = backend_free_capacity
LOG.debug("Free capacity for backend '%(backend)s': "
"%(free)s GiB, total capacity: %(total)s GiB.",
{
"backend": stats["volume_backend_name"],
"backend": self.backend_name,
"free": backend_free_capacity,
"total": backend_total_capacity,
})
return stats
def _create_volume_from_source(self, volume, source):
"""Create PowerStore volume from source (snapshot or another volume).
:param volume: OpenStack volume object
:param source: OpenStack source snapshot or volume
:return: newly created PowerStore volume id
"""
def create_volume_from_source(self, volume, source):
if isinstance(source, Snapshot):
entity = "snapshot"
source_size = source.volume_size
source_volume_provider_id = self._get_volume_provider_id(
source.volume
)
source_provider_id = self.client.get_snapshot_id_by_name(
source_volume_provider_id,
source.name
)
else:
entity = "volume"
source_size = source.size
source_provider_id = self._get_volume_provider_id(source)
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)
replication_status = fields.ReplicationStatus.ENABLED
else:
pp_name = None
pp_id = None
replication_status = fields.ReplicationStatus.DISABLED
LOG.debug("Create PowerStore volume %(volume_name)s of size "
"%(volume_size)s GiB with id %(volume_id)s from %(entity)s "
"%(entity_name)s with id %(entity_id)s. "
"Protection policy: %(pp_name)s.",
{
"volume_name": volume.name,
"volume_id": volume.id,
"volume_size": volume.size,
"entity": entity,
"entity_name": source.name,
"entity_id": source.id,
"pp_name": pp_name,
})
volume_provider_id = self.client.clone_volume_or_snapshot(
volume.name,
source.provider_id,
source_provider_id,
pp_id,
entity
)
if volume.size > source_size:
size_in_bytes = utils.gib_to_bytes(volume.size)
self.client.extend_volume(volume_provider_id, size_in_bytes)
return volume_provider_id
LOG.debug("Successfully created PowerStore volume %(volume_name)s "
"of size %(volume_size)s GiB with id %(volume_id)s from "
"%(entity)s %(entity_name)s with id %(entity_id)s. "
"Protection policy %(pp_name)s. "
"PowerStore volume id: %(volume_provider_id)s.",
{
"volume_name": volume.name,
"volume_id": volume.id,
"volume_size": volume.size,
"entity": entity,
"entity_name": source.name,
"entity_id": source.id,
"pp_name": pp_name,
"volume_provider_id": volume_provider_id,
})
return {
"provider_id": volume_provider_id,
"replication_status": replication_status,
}
def _filter_hosts_by_initiators(self, initiators):
"""Filter hosts by given list of initiators.
@ -590,6 +541,7 @@ class CommonAdapter(object):
:return: attached volume logical number
"""
provider_id = self._get_volume_provider_id(volume)
LOG.debug("Attach PowerStore volume %(volume_name)s with id "
"%(volume_id)s to host %(host_name)s. PowerStore volume id: "
"%(volume_provider_id)s, host id: %(host_provider_id)s.",
@ -597,13 +549,11 @@ class CommonAdapter(object):
"volume_name": volume.name,
"volume_id": volume.id,
"host_name": host["name"],
"volume_provider_id": volume.provider_id,
"volume_provider_id": provider_id,
"host_provider_id": host["id"],
})
self.client.attach_volume_to_host(host["id"], volume.provider_id)
volume_lun = self.client.get_volume_lun(
host["id"], volume.provider_id
)
self.client.attach_volume_to_host(host["id"], provider_id)
volume_lun = self.client.get_volume_lun(host["id"], provider_id)
LOG.debug("Successfully attached PowerStore volume %(volume_name)s "
"with id %(volume_id)s to host %(host_name)s. "
"PowerStore volume id: %(volume_provider_id)s, "
@ -613,7 +563,7 @@ class CommonAdapter(object):
"volume_name": volume.name,
"volume_id": volume.id,
"host_name": host["name"],
"volume_provider_id": volume.provider_id,
"volume_provider_id": provider_id,
"host_provider_id": host["id"],
"volume_lun": volume_lun,
})
@ -638,14 +588,11 @@ class CommonAdapter(object):
:return: volume connection properties
"""
appliance_name = volume_utils.extract_host(volume.host, "pool")
appliance_id = self.appliances_to_ids_map[appliance_name]
chap_credentials, volume_lun = self._create_host_and_attach(
connector,
volume
)
connection_properties = self._get_connection_properties(appliance_id,
volume_lun)
connection_properties = self._get_connection_properties(volume_lun)
if self.use_chap_auth:
connection_properties["data"]["auth_method"] = "CHAP"
connection_properties["data"]["auth_username"] = (
@ -666,11 +613,10 @@ class CommonAdapter(object):
:return: None
"""
provider_id = self._get_volume_provider_id(volume)
if hosts_to_detach is None:
# Force detach. Get all mapped hosts and detach.
hosts_to_detach = self.client.get_volume_mapped_hosts(
volume.provider_id
)
hosts_to_detach = self.client.get_volume_mapped_hosts(provider_id)
if not hosts_to_detach:
# Volume is not attached to any host.
return
@ -680,11 +626,11 @@ class CommonAdapter(object):
{
"volume_name": volume.name,
"volume_id": volume.id,
"volume_provider_id": volume.provider_id,
"volume_provider_id": provider_id,
"hosts_provider_ids": hosts_to_detach,
})
for host_id in hosts_to_detach:
self.client.detach_volume_from_host(host_id, volume.provider_id)
self.client.detach_volume_from_host(host_id, provider_id)
LOG.debug("Successfully detached PowerStore volume "
"%(volume_name)s with id %(volume_id)s from hosts. "
"PowerStore volume id: %(volume_provider_id)s, "
@ -692,7 +638,7 @@ class CommonAdapter(object):
{
"volume_name": volume.name,
"volume_id": volume.id,
"volume_provider_id": volume.provider_id,
"volume_provider_id": provider_id,
"hosts_provider_ids": hosts_to_detach,
})
@ -721,40 +667,130 @@ class CommonAdapter(object):
self._detach_volume_from_hosts(volume, [host["id"]])
def revert_to_snapshot(self, volume, snapshot):
volume_provider_id = self._get_volume_provider_id(volume)
snapshot_volume_provider_id = self._get_volume_provider_id(
snapshot.volume
)
LOG.debug("Restore PowerStore volume %(volume_name)s with id "
"%(volume_id)s from snapshot %(snapshot_name)s with id "
"%(snapshot_id)s. PowerStore volume id: "
"%(volume_provider_id)s, snapshot id: "
"%(snapshot_provider_id)s.",
"%(volume_provider_id)s.",
{
"volume_name": volume.name,
"volume_id": volume.id,
"snapshot_name": snapshot.name,
"snapshot_id": snapshot.id,
"volume_provider_id": volume.provider_id,
"snapshot_provider_id": snapshot.provider_id,
"volume_provider_id": volume_provider_id,
})
self.client.restore_from_snapshot(volume.provider_id,
snapshot.provider_id)
snapshot_provider_id = self.client.get_snapshot_id_by_name(
snapshot_volume_provider_id,
snapshot.name
)
self.client.restore_from_snapshot(volume_provider_id,
snapshot_provider_id)
LOG.debug("Successfully restored PowerStore volume %(volume_name)s "
"with id %(volume_id)s from snapshot %(snapshot_name)s "
"with id %(snapshot_id)s. PowerStore volume id: "
"%(volume_provider_id)s, snapshot id: "
"%(snapshot_provider_id)s.",
"%(volume_provider_id)s.",
{
"volume_name": volume.name,
"volume_id": volume.id,
"snapshot_name": snapshot.name,
"snapshot_id": snapshot.id,
"volume_provider_id": volume.provider_id,
"snapshot_provider_id": snapshot.provider_id,
"volume_provider_id": volume_provider_id,
})
def _get_volume_provider_id(self, volume):
"""Get provider_id for volume.
If the secondary backend is used after failover operation try to get
volume provider_id from PowerStore API.
:param volume: OpenStack volume object
:return: volume provider_id
"""
if (
self.backend_id == manager.VolumeManager.FAILBACK_SENTINEL or
not volume.is_replicated()
):
return volume.provider_id
else:
return self.client.get_volume_id_by_name(volume.name)
def teardown_volume_replication(self, volume):
"""Teardown replication for volume so it can be deleted.
:param volume: OpenStack volume object
:return: None
"""
LOG.debug("Teardown replication for volume %(volume_name)s "
"with id %(volume_id)s.",
{
"volume_name": volume.name,
"volume_id": volume.id,
})
try:
provider_id = self._get_volume_provider_id(volume)
rep_session_id = self.client.get_volume_replication_session_id(
provider_id
)
except exception.VolumeBackendAPIException:
LOG.warning("Replication session for volume %(volume_name)s with "
"id %(volume_id)s is not found. Replication for "
"volume was not configured or was modified from "
"storage side.",
{
"volume_name": volume.name,
"volume_id": volume.id,
})
return
self.client.unassign_volume_protection_policy(provider_id)
self.client.wait_for_replication_session_deletion(rep_session_id)
def failover_host(self, volumes, groups, is_failback):
volumes_updates = []
groups_updates = []
for volume in volumes:
updates = self.failover_volume(volume, is_failback)
if updates:
volumes_updates.append(updates)
return volumes_updates, groups_updates
def failover_volume(self, volume, is_failback):
error_status = (fields.ReplicationStatus.ERROR if is_failback else
fields.ReplicationStatus.FAILOVER_ERROR)
try:
provider_id = self._get_volume_provider_id(volume)
rep_session_id = self.client.get_volume_replication_session_id(
provider_id
)
failover_job_id = self.client.failover_volume_replication_session(
rep_session_id,
is_failback
)
failover_success = self.client.wait_for_failover_completion(
failover_job_id
)
if is_failback:
self.client.reprotect_volume_replication_session(
rep_session_id
)
except exception.VolumeBackendAPIException:
failover_success = False
if not failover_success:
return {
"volume_id": volume.id,
"updates": {
"replication_status": error_status,
},
}
class FibreChannelAdapter(CommonAdapter):
def __init__(self, active_backend_id, configuration):
super(FibreChannelAdapter, self).__init__(active_backend_id,
configuration)
def __init__(self, **kwargs):
super(FibreChannelAdapter, self).__init__(**kwargs)
self.storage_protocol = PROTOCOL_FC
self.driver_volume_type = "fibre_channel"
@ -762,15 +798,14 @@ class FibreChannelAdapter(CommonAdapter):
def initiators(connector):
return utils.extract_fc_wwpns(connector)
def _get_fc_targets(self, appliance_id):
"""Get available FC WWNs for PowerStore appliance.
def _get_fc_targets(self):
"""Get available FC WWNs.
:param appliance_id: PowerStore appliance id
:return: list of FC WWNs
"""
wwns = []
fc_ports = self.client.get_fc_port(appliance_id)
fc_ports = self.client.get_fc_port()
for port in fc_ports:
if self._port_is_allowed(port["wwn"]):
wwns.append(utils.fc_wwn_to_string(port["wwn"]))
@ -780,15 +815,14 @@ class FibreChannelAdapter(CommonAdapter):
raise exception.VolumeBackendAPIException(data=msg)
return wwns
def _get_connection_properties(self, appliance_id, volume_lun):
def _get_connection_properties(self, volume_lun):
"""Fill connection properties dict with data to attach volume.
:param appliance_id: PowerStore appliance id
:param volume_lun: attached volume logical unit number
:return: connection properties
"""
target_wwns = self._get_fc_targets(appliance_id)
target_wwns = self._get_fc_targets()
return {
"driver_volume_type": self.driver_volume_type,
"data": {
@ -800,8 +834,8 @@ class FibreChannelAdapter(CommonAdapter):
class iSCSIAdapter(CommonAdapter):
def __init__(self, active_backend_id, configuration):
super(iSCSIAdapter, self).__init__(active_backend_id, configuration)
def __init__(self, **kwargs):
super(iSCSIAdapter, self).__init__(**kwargs)
self.storage_protocol = PROTOCOL_ISCSI
self.driver_volume_type = "iscsi"
@ -809,16 +843,15 @@ class iSCSIAdapter(CommonAdapter):
def initiators(connector):
return [connector["initiator"]]
def _get_iscsi_targets(self, appliance_id):
"""Get available iSCSI portals and IQNs for PowerStore appliance.
def _get_iscsi_targets(self):
"""Get available iSCSI portals and IQNs.
:param appliance_id: PowerStore appliance id
:return: iSCSI portals and IQNs
"""
iqns = []
portals = []
ip_pool_addresses = self.client.get_ip_pool_address(appliance_id)
ip_pool_addresses = self.client.get_ip_pool_address()
for address in ip_pool_addresses:
if self._port_is_allowed(address["address"]):
portals.append(
@ -831,15 +864,14 @@ class iSCSIAdapter(CommonAdapter):
raise exception.VolumeBackendAPIException(data=msg)
return iqns, portals
def _get_connection_properties(self, appliance_id, volume_lun):
def _get_connection_properties(self, volume_lun):
"""Fill connection properties dict with data to attach volume.
:param appliance_id: PowerStore appliance id
:param volume_lun: attached volume logical unit number
:return: connection properties
"""
iqns, portals = self._get_iscsi_targets(appliance_id)
iqns, portals = self._get_iscsi_targets()
return {
"driver_volume_type": self.driver_volume_type,
"data": {

269
cinder/volume/drivers/dell_emc/powerstore/client.py

@ -24,24 +24,31 @@ import requests
from cinder import exception
from cinder.i18n import _
from cinder import utils as cinder_utils
LOG = logging.getLogger(__name__)
VOLUME_NOT_MAPPED_ERROR = "0xE0A08001000F"
SESSION_ALREADY_FAILED_OVER_ERROR = "0xE0201005000C"
class PowerStoreClient(object):
def __init__(self, configuration):
self.configuration = configuration
self.rest_ip = None
self.rest_username = None
self.rest_password = None
self.verify_certificate = None
self.certificate_path = None
self.base_url = None
def __init__(self,
rest_ip,
rest_username,
rest_password,
verify_certificate,
certificate_path):
self.rest_ip = rest_ip
self.rest_username = rest_username
self.rest_password = rest_password
self.verify_certificate = verify_certificate
self.certificate_path = certificate_path
self.base_url = "https://%s:/api/rest" % self.rest_ip
self.ok_codes = [
requests.codes.ok,
requests.codes.created,
requests.codes.accepted,
requests.codes.no_content,
requests.codes.partial_content
]
@ -53,28 +60,16 @@ class PowerStoreClient(object):
verify_cert = self.certificate_path
return verify_cert
def do_setup(self):
self.rest_ip = self.configuration.safe_get("san_ip")
self.rest_username = self.configuration.safe_get("san_login")
self.rest_password = self.configuration.safe_get("san_password")
self.base_url = "https://%s:/api/rest" % self.rest_ip
self.verify_certificate = self.configuration.safe_get(
"driver_ssl_cert_verify"
)
if self.verify_certificate:
self.certificate_path = (
self.configuration.safe_get("driver_ssl_cert_path")
)
def check_for_setup_error(self):
if not all([self.rest_ip, self.rest_username, self.rest_password]):
msg = _("REST server IP, username and password must be set.")
raise exception.VolumeBackendAPIException(data=msg)
raise exception.InvalidInput(reason=msg)
# log warning if not using certificates
if not self.verify_certificate:
LOG.warning("Verify certificate is not set, using default of "
"False.")
self.verify_certificate = False
LOG.debug("Successfully initialized PowerStore REST client. "
"Server IP: %(ip)s, username: %(username)s. "
"Verify server's certificate: %(verify_cert)s.",
@ -97,10 +92,9 @@ class PowerStoreClient(object):
request_params = {
"auth": (self.rest_username, self.rest_password),
"verify": self._verify_cert,
"params": params
}
if method == "GET":
request_params["params"] = params
else:
if method != "GET":
request_params["data"] = json.dumps(payload)
request_url = self.base_url + url
r = requests.request(method, request_url, **request_params)
@ -143,55 +137,34 @@ class PowerStoreClient(object):
raise exception.VolumeBackendAPIException(data=msg)
return response
def get_appliance_id_by_name(self, appliance_name):
r, response = self._send_get_request(
"/appliance",
params={
"name": "eq.%s" % appliance_name,
}
)
if r.status_code not in self.ok_codes:
msg = _("Failed to query PowerStore appliances.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
try:
appliance_id = response[0].get("id")
return appliance_id
except IndexError:
msg = _("PowerStore appliance %s is not found.") % appliance_name
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def get_appliance_metrics(self, appliance_id):
def get_metrics(self):
r, response = self._send_post_request(
"/metrics/generate",
payload={
"entity": "space_metrics_by_appliance",
"entity_id": appliance_id,
"entity": "space_metrics_by_cluster",
"entity_id": "0",
},
log_response_data=False
)
if r.status_code not in self.ok_codes:
msg = (_("Failed to query metrics for "
"PowerStore appliance with id %s.") % appliance_id)
msg = _("Failed to query PowerStore metrics.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
try:
latest_metrics = response[-1]
return latest_metrics
except IndexError:
msg = (_("Failed to query metrics for "
"PowerStore appliance with id %s.") % appliance_id)
msg = _("Failed to query PowerStore metrics.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def create_volume(self, appliance_id, name, size):
def create_volume(self, name, size, pp_id):
r, response = self._send_post_request(
"/volume",
payload={
"appliance_id": appliance_id,
"name": name,
"size": size,
"protection_policy_id": pp_id,
}
)
if r.status_code not in self.ok_codes:
@ -247,14 +220,40 @@ class PowerStoreClient(object):
raise exception.VolumeBackendAPIException(data=msg)