cinder/cinder/tests/unit/test_pure.py

2557 lines
109 KiB
Python

# Copyright (c) 2014 Pure Storage, Inc.
# 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 copy import deepcopy
import sys
import ddt
import mock
from oslo_utils import units
from cinder import exception
from cinder import test
from cinder.tests.unit import fake_volume
def fake_retry(exceptions, interval=1, retries=3, backoff_rate=2):
def _decorator(f):
return f
return _decorator
patch_retry = mock.patch('cinder.utils.retry', fake_retry)
patch_retry.start()
sys.modules['purestorage'] = mock.Mock()
from cinder.volume.drivers import pure
# Only mock utils.retry for cinder.volume.drivers.pure import
patch_retry.stop()
DRIVER_PATH = "cinder.volume.drivers.pure"
BASE_DRIVER_OBJ = DRIVER_PATH + ".PureBaseVolumeDriver"
ISCSI_DRIVER_OBJ = DRIVER_PATH + ".PureISCSIDriver"
FC_DRIVER_OBJ = DRIVER_PATH + ".PureFCDriver"
ARRAY_OBJ = DRIVER_PATH + ".FlashArray"
GET_ARRAY_PRIMARY = {"version": "99.9.9",
"revision": "201411230504+8a400f7",
"array_name": "pure_target1",
"id": "primary_array_id"}
GET_ARRAY_SECONDARY = {"version": "99.9.9",
"revision": "201411230504+8a400f7",
"array_name": "pure_target2",
"id": "secondary_array_id"}
REPLICATION_TARGET_TOKEN = "12345678-abcd-1234-abcd-1234567890ab"
REPLICATION_PROTECTION_GROUP = "cinder-group"
REPLICATION_INTERVAL_IN_SEC = 900
REPLICATION_RETENTION_SHORT_TERM = 14400
REPLICATION_RETENTION_LONG_TERM = 6
REPLICATION_RETENTION_LONG_TERM_PER_DAY = 3
PRIMARY_MANAGEMENT_IP = GET_ARRAY_PRIMARY["array_name"]
API_TOKEN = "12345678-abcd-1234-abcd-1234567890ab"
VOLUME_BACKEND_NAME = "Pure_iSCSI"
ISCSI_PORT_NAMES = ["ct0.eth2", "ct0.eth3", "ct1.eth2", "ct1.eth3"]
FC_PORT_NAMES = ["ct0.fc2", "ct0.fc3", "ct1.fc2", "ct1.fc3"]
ISCSI_IPS = ["10.0.0." + str(i + 1) for i in range(len(ISCSI_PORT_NAMES))]
FC_WWNS = ["21000024ff59fe9" + str(i + 1) for i in range(len(FC_PORT_NAMES))]
HOSTNAME = "computenode1"
PURE_HOST_NAME = pure.PureBaseVolumeDriver._generate_purity_host_name(HOSTNAME)
PURE_HOST = {
"name": PURE_HOST_NAME,
"hgroup": None,
"iqn": [],
"wwn": [],
}
REST_VERSION = "1.2"
VOLUME_ID = "abcdabcd-1234-abcd-1234-abcdeffedcba"
VOLUME_TYPE_ID = "357aa1f1-4f9c-4f10-acec-626af66425ba"
VOLUME = {
"name": "volume-" + VOLUME_ID,
"id": VOLUME_ID,
"display_name": "fake_volume",
"size": 2,
"host": "irrelevant",
"volume_type": None,
"volume_type_id": VOLUME_TYPE_ID,
"replication_status": None,
"consistencygroup_id": None,
"provider_location": GET_ARRAY_PRIMARY["id"]
}
VOLUME_PURITY_NAME = VOLUME['name'] + '-cinder'
VOLUME_WITH_CGROUP = VOLUME.copy()
VOLUME_WITH_CGROUP['consistencygroup_id'] = \
"4a2f7e3a-312a-40c5-96a8-536b8a0fe074"
SRC_VOL_ID = "dc7a294d-5964-4379-a15f-ce5554734efc"
SRC_VOL = {
"name": "volume-" + SRC_VOL_ID,
"id": SRC_VOL_ID,
"display_name": 'fake_src',
"size": 2,
"host": "irrelevant",
"volume_type": None,
"volume_type_id": None,
"consistencygroup_id": None,
}
SNAPSHOT_ID = "04fe2f9a-d0c4-4564-a30d-693cc3657b47"
SNAPSHOT = {
"name": "snapshot-" + SNAPSHOT_ID,
"id": SNAPSHOT_ID,
"volume_id": SRC_VOL_ID,
"volume_name": "volume-" + SRC_VOL_ID,
"volume_size": 2,
"display_name": "fake_snapshot",
"cgsnapshot_id": None,
}
SNAPSHOT_PURITY_NAME = SRC_VOL["name"] + '-cinder.' + SNAPSHOT["name"]
SNAPSHOT_WITH_CGROUP = SNAPSHOT.copy()
SNAPSHOT_WITH_CGROUP['cgsnapshot_id'] = \
"4a2f7e3a-312a-40c5-96a8-536b8a0fe075"
INITIATOR_IQN = "iqn.1993-08.org.debian:01:222"
INITIATOR_WWN = "5001500150015081"
ISCSI_CONNECTOR = {"initiator": INITIATOR_IQN, "host": HOSTNAME}
FC_CONNECTOR = {"wwpns": {INITIATOR_WWN}, "host": HOSTNAME}
TARGET_IQN = "iqn.2010-06.com.purestorage:flasharray.12345abc"
TARGET_WWN = "21000024ff59fe94"
TARGET_PORT = "3260"
INITIATOR_TARGET_MAP =\
{
# _build_initiator_target_map() calls list(set()) on the list,
# we must also call list(set()) to get the exact same order
'5001500150015081': list(set(FC_WWNS)),
}
DEVICE_MAPPING =\
{
"fabric": {'initiator_port_wwn_list': {INITIATOR_WWN},
'target_port_wwn_list': FC_WWNS
},
}
ISCSI_PORTS = [{"name": name,
"iqn": TARGET_IQN,
"portal": ip + ":" + TARGET_PORT,
"wwn": None,
} for name, ip in zip(ISCSI_PORT_NAMES, ISCSI_IPS)]
FC_PORTS = [{"name": name,
"iqn": None,
"portal": None,
"wwn": wwn,
} for name, wwn in zip(FC_PORT_NAMES, FC_WWNS)]
NON_ISCSI_PORT = {
"name": "ct0.fc1",
"iqn": None,
"portal": None,
"wwn": "5001500150015081",
}
PORTS_WITH = ISCSI_PORTS + [NON_ISCSI_PORT]
PORTS_WITHOUT = [NON_ISCSI_PORT]
VOLUME_CONNECTIONS = [
{"host": "h1", "name": VOLUME["name"] + "-cinder"},
{"host": "h2", "name": VOLUME["name"] + "-cinder"},
]
TOTAL_CAPACITY = 50.0
USED_SPACE = 32.1
PROVISIONED_CAPACITY = 70.0
DEFAULT_OVER_SUBSCRIPTION = 20
SPACE_INFO = {
"capacity": TOTAL_CAPACITY * units.Gi,
"total": USED_SPACE * units.Gi,
}
SPACE_INFO_EMPTY = {
"capacity": TOTAL_CAPACITY * units.Gi,
"total": 0,
}
PERF_INFO = {
'writes_per_sec': 318,
'usec_per_write_op': 255,
'output_per_sec': 234240,
'reads_per_sec': 15,
'input_per_sec': 2827943,
'time': '2015-12-17T21:50:55Z',
'usec_per_read_op': 192,
'queue_depth': 4,
}
PERF_INFO_RAW = [PERF_INFO]
ISCSI_CONNECTION_INFO = {
"driver_volume_type": "iscsi",
"data": {
"target_discovered": False,
"discard": True,
"target_luns": [1, 1, 1, 1],
"target_iqns": [TARGET_IQN, TARGET_IQN, TARGET_IQN, TARGET_IQN],
"target_portals": [ISCSI_IPS[0] + ":" + TARGET_PORT,
ISCSI_IPS[1] + ":" + TARGET_PORT,
ISCSI_IPS[2] + ":" + TARGET_PORT,
ISCSI_IPS[3] + ":" + TARGET_PORT],
},
}
FC_CONNECTION_INFO = {
"driver_volume_type": "fibre_channel",
"data": {
"target_wwn": FC_WWNS,
"target_lun": 1,
"target_discovered": True,
"initiator_target_map": INITIATOR_TARGET_MAP,
"discard": True,
},
}
PURE_SNAPSHOT = {
"created": "2015-05-27T17:34:33Z",
"name": "vol1.snap1",
"serial": "8343DFDE2DAFBE40000115E4",
"size": 3221225472,
"source": "vol1"
}
PURE_PGROUP = {
"hgroups": None,
"hosts": None,
"name": "pg1",
"source": "pure01",
"targets": None,
"volumes": ["v1"]
}
PGROUP_ON_TARGET_NOT_ALLOWED = {
"name": "array1:replicated_pgroup",
"hgroups": None,
"source": "array1",
"hosts": None,
"volumes": ["array1:replicated_volume"],
"time_remaining": None,
"targets": [{"name": "array2",
"allowed": False}]}
PGROUP_ON_TARGET_ALLOWED = {
"name": "array1:replicated_pgroup",
"hgroups": None,
"source": "array1",
"hosts": None,
"volumes": ["array1:replicated_volume"],
"time_remaining": None,
"targets": [{"name": "array2",
"allowed": True}]}
CONNECTED_ARRAY = {
"id": "6b1a7ce3-da61-0d86-65a7-9772cd259fef",
"version": "99.9.9",
"connected": True,
"management_address": "10.42.10.229",
"replication_address": "192.168.10.229",
"type": ["replication"],
"array_name": "3rd-pure-generic2"}
REPLICATED_PGSNAPS = [
{
"name": "array1:cinder-repl-pg.3",
"created": "2014-12-04T22:59:38Z",
"started": "2014-12-04T22:59:38Z",
"completed": "2014-12-04T22:59:39Z",
"source": "array1:cinder-repl-pg",
"logical_data_transferred": 0,
"progress": 1.0,
"data_transferred": 318
},
{
"name": "array1:cinder-repl-pg.2",
"created": "2014-12-04T21:59:38Z",
"started": "2014-12-04T21:59:38Z",
"completed": "2014-12-04T21:59:39Z",
"source": "array1:cinder-repl-pg",
"logical_data_transferred": 0,
"progress": 1.0,
"data_transferred": 318
},
{
"name": "array1:cinder-repl-pg.1",
"created": "2014-12-04T20:59:38Z",
"started": "2014-12-04T20:59:38Z",
"completed": "2014-12-04T20:59:39Z",
"source": "array1:cinder-repl-pg",
"logical_data_transferred": 0,
"progress": 1.0,
"data_transferred": 318
}]
REPLICATED_VOLUME_SNAPS = [
{
"source": "array1:replicated_volume1",
"serial": "BBA481C01639104E0001D5F7",
"created": "2014-12-04T22:59:38Z",
"name": "array1:cinder-repl-pg.2.replicated_volume1",
"size": 1048576
},
{
"source": "array1:replicated_volume2",
"serial": "BBA481C01639104E0001D5F8",
"created": "2014-12-04T22:59:38Z",
"name": "array1:cinder-repl-pg.2.replicated_volume2",
"size": 1048576
},
{
"source": "array1:replicated_volume3",
"serial": "BBA481C01639104E0001D5F9",
"created": "2014-12-04T22:59:38Z",
"name": "array1:PureSRAEndToEndPGroup1.2.replicated_volume3",
"size": 1048576
}
]
NON_REPLICATED_VOL_TYPE = {"is_public": True,
"extra_specs": {},
"name": "volume_type_1",
"id": VOLUME_TYPE_ID}
REPLICATED_VOL_TYPE = {"is_public": True,
"extra_specs":
{pure.EXTRA_SPECS_REPL_ENABLED:
"<is> True"},
"name": "volume_type_2",
"id": VOLUME_TYPE_ID}
class FakePureStorageHTTPError(Exception):
def __init__(self, target=None, rest_version=None, code=None,
headers=None, text=None):
self.target = target
self.rest_version = rest_version
self.code = code
self.headers = headers
self.text = text
class PureDriverTestCase(test.TestCase):
def setUp(self):
super(PureDriverTestCase, self).setUp()
self.mock_config = mock.Mock()
self.mock_config.san_ip = PRIMARY_MANAGEMENT_IP
self.mock_config.pure_api_token = API_TOKEN
self.mock_config.volume_backend_name = VOLUME_BACKEND_NAME
self.mock_config.safe_get.return_value = None
self.mock_config.pure_eradicate_on_delete = False
self.array = mock.Mock()
self.array.get.return_value = GET_ARRAY_PRIMARY
self.array.array_name = GET_ARRAY_PRIMARY["array_name"]
self.array.array_id = GET_ARRAY_PRIMARY["id"]
self.array2 = mock.Mock()
self.array2.array_name = GET_ARRAY_SECONDARY["array_name"]
self.array2.array_id = GET_ARRAY_SECONDARY["id"]
self.array2.get.return_value = GET_ARRAY_SECONDARY
self.purestorage_module = pure.purestorage
self.purestorage_module.PureHTTPError = FakePureStorageHTTPError
def fake_get_array(*args, **kwargs):
if 'action' in kwargs and kwargs['action'] is 'monitor':
return PERF_INFO_RAW
if 'space' in kwargs and kwargs['space'] is True:
return SPACE_INFO
def assert_error_propagates(self, mocks, func, *args, **kwargs):
"""Assert that errors from mocks propagate to func.
Fail if exceptions raised by mocks are not seen when calling
func(*args, **kwargs). Ensure that we are really seeing exceptions
from the mocks by failing if just running func(*args, **kargs) raises
an exception itself.
"""
func(*args, **kwargs)
for mock_func in mocks:
original_side_effect = mock_func.side_effect
mock_func.side_effect = [exception.PureDriverException(
reason='reason')]
self.assertRaises(exception.PureDriverException,
func, *args, **kwargs)
mock_func.side_effect = original_side_effect
class PureBaseSharedDriverTestCase(PureDriverTestCase):
def setUp(self):
super(PureBaseSharedDriverTestCase, self).setUp()
self.driver = pure.PureBaseVolumeDriver(configuration=self.mock_config)
self.driver._array = self.array
self.array.get_rest_version.return_value = '1.4'
self.purestorage_module.FlashArray.side_effect = None
self.array2.get_rest_version.return_value = '1.4'
def tearDown(self):
super(PureBaseSharedDriverTestCase, self).tearDown()
class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
def setUp(self):
super(PureBaseVolumeDriverTestCase, self).setUp()
def _setup_mocks_for_replication(self):
# Mock config values
self.mock_config.pure_replica_interval_default = (
REPLICATION_INTERVAL_IN_SEC)
self.mock_config.pure_replica_retention_short_term_default = (
REPLICATION_RETENTION_SHORT_TERM)
self.mock_config.pure_replica_retention_long_term_default = (
REPLICATION_RETENTION_LONG_TERM)
self.mock_config.pure_replica_retention_long_term_default = (
REPLICATION_RETENTION_LONG_TERM_PER_DAY)
self.mock_config.safe_get.return_value = [
{"target_device_id": self.driver._array.array_id,
"managed_backend_name": None,
"san_ip": "1.2.3.4",
"api_token": "abc123"}]
@mock.patch(BASE_DRIVER_OBJ + '._generate_replication_retention')
@mock.patch(BASE_DRIVER_OBJ + '._setup_replicated_pgroups')
def test_parse_replication_configs_single_target(
self,
mock_setup_repl_pgroups,
mock_generate_replication_retention):
retention = mock.MagicMock()
mock_generate_replication_retention.return_value = retention
mock_setup_repl_pgroups.return_value = None
# Test single array configured
self.mock_config.safe_get.return_value = [
{"target_device_id": self.driver._array.id,
"managed_backend_name": None,
"san_ip": "1.2.3.4",
"api_token": "abc123"}]
self.purestorage_module.FlashArray.return_value = self.array
self.driver.parse_replication_configs()
self.assertEqual(1, len(self.driver._replication_target_arrays))
self.assertEqual(self.array, self.driver._replication_target_arrays[0])
only_target_array = self.driver._replication_target_arrays[0]
self.assertEqual(self.driver._array.id,
only_target_array._target_device_id)
@mock.patch(BASE_DRIVER_OBJ + '._generate_replication_retention')
@mock.patch(BASE_DRIVER_OBJ + '._setup_replicated_pgroups')
def test_parse_replication_configs_multiple_target(
self,
mock_setup_repl_pgroups,
mock_generate_replication_retention):
retention = mock.MagicMock()
mock_generate_replication_retention.return_value = retention
mock_setup_repl_pgroups.return_value = None
# Test multiple arrays configured
self.mock_config.safe_get.return_value = [
{"target_device_id": GET_ARRAY_PRIMARY["id"],
"managed_backend_name": None,
"san_ip": "1.2.3.4",
"api_token": "abc123"},
{"target_device_id": GET_ARRAY_SECONDARY["id"],
"managed_backend_name": None,
"san_ip": "1.2.3.5",
"api_token": "abc124"}]
self.purestorage_module.FlashArray.side_effect = \
[self.array, self.array2]
self.driver.parse_replication_configs()
self.assertEqual(2, len(self.driver._replication_target_arrays))
self.assertEqual(self.array, self.driver._replication_target_arrays[0])
first_target_array = self.driver._replication_target_arrays[0]
self.assertEqual(GET_ARRAY_PRIMARY["id"],
first_target_array._target_device_id)
self.assertEqual(
self.array2, self.driver._replication_target_arrays[1])
second_target_array = self.driver._replication_target_arrays[1]
self.assertEqual(GET_ARRAY_SECONDARY["id"],
second_target_array._target_device_id)
@mock.patch(BASE_DRIVER_OBJ + '._generate_replication_retention')
@mock.patch(BASE_DRIVER_OBJ + '._setup_replicated_pgroups')
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_do_setup_replicated(self, mock_get_volume_type,
mock_setup_repl_pgroups,
mock_generate_replication_retention):
retention = mock.MagicMock()
mock_generate_replication_retention.return_value = retention
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
self._setup_mocks_for_replication()
self.array2.get.return_value = GET_ARRAY_SECONDARY
self.array.get.return_value = GET_ARRAY_PRIMARY
self.purestorage_module.FlashArray.side_effect = [self.array,
self.array2]
self.driver.do_setup(None)
self.assertEqual(self.array, self.driver._array)
self.assertEqual(1, len(self.driver._replication_target_arrays))
self.assertEqual(self.array2,
self.driver._replication_target_arrays[0])
calls = [
mock.call(self.array2, [self.array], 'cinder-group',
REPLICATION_INTERVAL_IN_SEC, retention),
mock.call(self.array, [self.array2], 'cinder-group',
REPLICATION_INTERVAL_IN_SEC, retention)
]
mock_setup_repl_pgroups.assert_has_calls(calls)
def test_generate_purity_host_name(self):
result = self.driver._generate_purity_host_name(
"really-long-string-thats-a-bit-too-long")
self.assertTrue(result.startswith("really-long-string-that-"))
self.assertTrue(result.endswith("-cinder"))
self.assertEqual(63, len(result))
self.assertTrue(pure.GENERATED_NAME.match(result))
result = self.driver._generate_purity_host_name("!@#$%^-invalid&*")
self.assertTrue(result.startswith("invalid---"))
self.assertTrue(result.endswith("-cinder"))
self.assertEqual(49, len(result))
self.assertTrue(pure.GENERATED_NAME.match(result))
@mock.patch(BASE_DRIVER_OBJ + "._is_volume_replicated_type", autospec=True)
def test_create_volume(self, mock_is_replicated_type):
mock_is_replicated_type.return_value = False
self.driver.create_volume(VOLUME)
self.array.create_volume.assert_called_with(
VOLUME["name"] + "-cinder", 2 * units.Gi)
self.assert_error_propagates([self.array.create_volume],
self.driver.create_volume, VOLUME)
@mock.patch(BASE_DRIVER_OBJ + "._add_volume_to_consistency_group",
autospec=True)
@mock.patch(BASE_DRIVER_OBJ + "._is_volume_replicated_type", autospec=True)
def test_create_volume_with_cgroup(self, mock_is_replicated_type,
mock_add_to_cgroup):
vol_name = VOLUME_WITH_CGROUP["name"] + "-cinder"
mock_is_replicated_type.return_value = False
self.driver.create_volume(VOLUME_WITH_CGROUP)
mock_add_to_cgroup\
.assert_called_with(self.driver,
VOLUME_WITH_CGROUP['consistencygroup_id'],
vol_name)
@mock.patch(BASE_DRIVER_OBJ + "._is_volume_replicated_type", autospec=True)
def test_create_volume_from_snapshot(self, mock_is_replicated_type):
vol_name = VOLUME["name"] + "-cinder"
snap_name = SNAPSHOT["volume_name"] + "-cinder." + SNAPSHOT["name"]
mock_is_replicated_type.return_value = False
# Branch where extend unneeded
self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT)
self.array.copy_volume.assert_called_with(snap_name, vol_name)
self.assertFalse(self.array.extend_volume.called)
self.assert_error_propagates(
[self.array.copy_volume],
self.driver.create_volume_from_snapshot, VOLUME, SNAPSHOT)
self.assertFalse(self.array.extend_volume.called)
# Branch where extend needed
SNAPSHOT["volume_size"] = 1 # resize so smaller than VOLUME
self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT)
expected = [mock.call.copy_volume(snap_name, vol_name),
mock.call.extend_volume(vol_name, 2 * units.Gi)]
self.array.assert_has_calls(expected)
self.assert_error_propagates(
[self.array.copy_volume, self.array.extend_volume],
self.driver.create_volume_from_snapshot, VOLUME, SNAPSHOT)
SNAPSHOT["volume_size"] = 2 # reset size
@mock.patch(BASE_DRIVER_OBJ + "._get_snap_name")
def test_create_volume_from_snapshot_cant_get_name(self, mock_get_name):
mock_get_name.return_value = None
self.assertRaises(exception.PureDriverException,
self.driver.create_volume_from_snapshot,
VOLUME, SNAPSHOT)
@mock.patch(BASE_DRIVER_OBJ + "._get_pgroup_snap_name_from_snapshot")
def test_create_volume_from_cgsnapshot_cant_get_name(self, mock_get_name):
mock_get_name.return_value = None
self.assertRaises(exception.PureDriverException,
self.driver.create_volume_from_snapshot,
VOLUME, SNAPSHOT_WITH_CGROUP)
@mock.patch(BASE_DRIVER_OBJ + "._add_volume_to_consistency_group",
autospec=True)
@mock.patch(BASE_DRIVER_OBJ + "._extend_if_needed", autospec=True)
@mock.patch(BASE_DRIVER_OBJ + "._get_pgroup_snap_name_from_snapshot")
@mock.patch(BASE_DRIVER_OBJ + "._is_volume_replicated_type", autospec=True)
def test_create_volume_from_cgsnapshot(self, mock_is_replicated_type,
mock_get_snap_name,
mock_extend_if_needed,
mock_add_to_cgroup):
vol_name = VOLUME_WITH_CGROUP["name"] + "-cinder"
snap_name = "consisgroup-4a2f7e3a-312a-40c5-96a8-536b8a0f" \
"e074-cinder.4a2f7e3a-312a-40c5-96a8-536b8a0fe075."\
+ vol_name
mock_get_snap_name.return_value = snap_name
mock_is_replicated_type.return_value = False
self.driver.create_volume_from_snapshot(VOLUME_WITH_CGROUP,
SNAPSHOT_WITH_CGROUP)
self.array.copy_volume.assert_called_with(snap_name, vol_name)
self.assertTrue(mock_get_snap_name.called)
self.assertTrue(mock_extend_if_needed.called)
self.driver.create_volume_from_snapshot(VOLUME_WITH_CGROUP,
SNAPSHOT_WITH_CGROUP)
mock_add_to_cgroup\
.assert_called_with(self.driver,
VOLUME_WITH_CGROUP['consistencygroup_id'],
vol_name)
# Tests cloning a volume that is not replicated type
@mock.patch(BASE_DRIVER_OBJ + "._is_volume_replicated_type", autospec=True)
def test_create_cloned_volume(self, mock_is_replicated_type):
vol_name = VOLUME["name"] + "-cinder"
src_name = SRC_VOL["name"] + "-cinder"
mock_is_replicated_type.return_value = False
# Branch where extend unneeded
self.driver.create_cloned_volume(VOLUME, SRC_VOL)
self.array.copy_volume.assert_called_with(src_name, vol_name)
self.assertFalse(self.array.extend_volume.called)
self.assert_error_propagates(
[self.array.copy_volume],
self.driver.create_cloned_volume, VOLUME, SRC_VOL)
self.assertFalse(self.array.extend_volume.called)
# Branch where extend needed
SRC_VOL["size"] = 1 # resize so smaller than VOLUME
self.driver.create_cloned_volume(VOLUME, SRC_VOL)
expected = [mock.call.copy_volume(src_name, vol_name),
mock.call.extend_volume(vol_name, 2 * units.Gi)]
self.array.assert_has_calls(expected)
self.assert_error_propagates(
[self.array.copy_volume, self.array.extend_volume],
self.driver.create_cloned_volume, VOLUME, SRC_VOL)
SRC_VOL["size"] = 2 # reset size
# Tests cloning a volume that is part of a consistency group
@mock.patch(BASE_DRIVER_OBJ + "._add_volume_to_consistency_group",
autospec=True)
@mock.patch(BASE_DRIVER_OBJ + "._is_volume_replicated_type", autospec=True)
def test_create_cloned_volume_with_cgroup(self, mock_is_replicated_type,
mock_add_to_cgroup):
vol_name = VOLUME_WITH_CGROUP["name"] + "-cinder"
mock_is_replicated_type.return_value = False
self.driver.create_cloned_volume(VOLUME_WITH_CGROUP, SRC_VOL)
mock_add_to_cgroup\
.assert_called_with(self.driver,
VOLUME_WITH_CGROUP['consistencygroup_id'],
vol_name)
def test_delete_volume_already_deleted(self):
self.array.list_volume_private_connections.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Volume does not exist"
)
self.driver.delete_volume(VOLUME)
self.assertFalse(self.array.destroy_volume.called)
self.assertFalse(self.array.eradicate_volume.called)
# Testing case where array.destroy_volume returns an exception
# because volume has already been deleted
self.array.list_volume_private_connections.side_effect = None
self.array.list_volume_private_connections.return_value = {}
self.array.destroy_volume.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Volume does not exist"
)
self.driver.delete_volume(VOLUME)
self.assertTrue(self.array.destroy_volume.called)
self.assertFalse(self.array.eradicate_volume.called)
def test_delete_volume(self):
vol_name = VOLUME["name"] + "-cinder"
self.array.list_volume_private_connections.return_value = {}
self.driver.delete_volume(VOLUME)
expected = [mock.call.destroy_volume(vol_name)]
self.array.assert_has_calls(expected)
self.assertFalse(self.array.eradicate_volume.called)
self.array.destroy_volume.side_effect = (
self.purestorage_module.PureHTTPError(code=400, text="does not "
"exist"))
self.driver.delete_volume(VOLUME)
self.array.destroy_volume.side_effect = None
self.assert_error_propagates([self.array.destroy_volume],
self.driver.delete_volume, VOLUME)
def test_delete_volume_eradicate_now(self):
vol_name = VOLUME["name"] + "-cinder"
self.array.list_volume_private_connections.return_value = {}
self.mock_config.pure_eradicate_on_delete = True
self.driver.delete_volume(VOLUME)
expected = [mock.call.destroy_volume(vol_name),
mock.call.eradicate_volume(vol_name)]
self.array.assert_has_calls(expected)
def test_delete_connected_volume(self):
vol_name = VOLUME["name"] + "-cinder"
host_name_a = "ha"
host_name_b = "hb"
self.array.list_volume_private_connections.return_value = [{
"host": host_name_a,
"lun": 7,
"name": vol_name,
"size": 3221225472,
}, {
"host": host_name_b,
"lun": 2,
"name": vol_name,
"size": 3221225472,
}]
self.driver.delete_volume(VOLUME)
expected = [mock.call.list_volume_private_connections(vol_name),
mock.call.disconnect_host(host_name_a, vol_name),
mock.call.disconnect_host(host_name_b, vol_name),
mock.call.destroy_volume(vol_name)]
self.array.assert_has_calls(expected)
def test_create_snapshot(self):
vol_name = SRC_VOL["name"] + "-cinder"
self.driver.create_snapshot(SNAPSHOT)
self.array.create_snapshot.assert_called_with(
vol_name,
suffix=SNAPSHOT["name"]
)
self.assert_error_propagates([self.array.create_snapshot],
self.driver.create_snapshot, SNAPSHOT)
def test_delete_snapshot(self):
snap_name = SNAPSHOT["volume_name"] + "-cinder." + SNAPSHOT["name"]
self.driver.delete_snapshot(SNAPSHOT)
expected = [mock.call.destroy_volume(snap_name)]
self.array.assert_has_calls(expected)
self.assertFalse(self.array.eradicate_volume.called)
self.array.destroy_volume.side_effect = (
self.purestorage_module.PureHTTPError(code=400, text="does not "
"exist"))
self.driver.delete_snapshot(SNAPSHOT)
self.array.destroy_volume.side_effect = None
self.assert_error_propagates([self.array.destroy_volume],
self.driver.delete_snapshot, SNAPSHOT)
def test_delete_snapshot_eradicate_now(self):
snap_name = SNAPSHOT["volume_name"] + "-cinder." + SNAPSHOT["name"]
self.mock_config.pure_eradicate_on_delete = True
self.driver.delete_snapshot(SNAPSHOT)
expected = [mock.call.destroy_volume(snap_name),
mock.call.eradicate_volume(snap_name)]
self.array.assert_has_calls(expected)
@mock.patch(BASE_DRIVER_OBJ + "._get_host", autospec=True)
def test_terminate_connection(self, mock_host):
vol_name = VOLUME["name"] + "-cinder"
mock_host.return_value = {"name": "some-host"}
# Branch with manually created host
self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with("some-host", vol_name)
self.assertFalse(self.array.list_host_connections.called)
self.assertFalse(self.array.delete_host.called)
# Branch with host added to host group
self.array.reset_mock()
self.array.list_host_connections.return_value = []
mock_host.return_value = PURE_HOST.copy()
mock_host.return_value.update(hgroup="some-group")
self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.assertTrue(self.array.list_host_connections.called)
self.assertTrue(self.array.delete_host.called)
# Branch with host still having connected volumes
self.array.reset_mock()
self.array.list_host_connections.return_value = [
{"lun": 2, "name": PURE_HOST_NAME, "vol": "some-vol"}]
mock_host.return_value = PURE_HOST
self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.array.list_host_connections.assert_called_with(PURE_HOST_NAME,
private=True)
self.assertFalse(self.array.delete_host.called)
# Branch where host gets deleted
self.array.reset_mock()
self.array.list_host_connections.return_value = []
self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.array.list_host_connections.assert_called_with(PURE_HOST_NAME,
private=True)
self.array.delete_host.assert_called_with(PURE_HOST_NAME)
# Branch where connection is missing and the host is still deleted
self.array.reset_mock()
self.array.disconnect_host.side_effect = \
self.purestorage_module.PureHTTPError(code=400,
text="is not connected")
self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.array.list_host_connections.assert_called_with(PURE_HOST_NAME,
private=True)
self.array.delete_host.assert_called_with(PURE_HOST_NAME)
# Branch where an unexpected exception occurs
self.array.reset_mock()
self.array.disconnect_host.side_effect = \
self.purestorage_module.PureHTTPError(
code=500,
text="Some other error"
)
self.assertRaises(self.purestorage_module.PureHTTPError,
self.driver.terminate_connection,
VOLUME,
ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.assertFalse(self.array.list_host_connections.called)
self.assertFalse(self.array.delete_host.called)
@mock.patch(BASE_DRIVER_OBJ + "._get_host", autospec=True)
def test_terminate_connection_host_deleted(self, mock_host):
vol_name = VOLUME["name"] + "-cinder"
mock_host.return_value = PURE_HOST.copy()
self.array.reset_mock()
self.array.list_host_connections.return_value = []
self.array.delete_host.side_effect = \
self.purestorage_module.PureHTTPError(code=400,
text='Host does not exist.')
self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.array.list_host_connections.assert_called_with(PURE_HOST_NAME,
private=True)
self.array.delete_host.assert_called_once_with(PURE_HOST_NAME)
def test_extend_volume(self):
vol_name = VOLUME["name"] + "-cinder"
self.driver.extend_volume(VOLUME, 3)
self.array.extend_volume.assert_called_with(vol_name, 3 * units.Gi)
self.assert_error_propagates([self.array.extend_volume],
self.driver.extend_volume, VOLUME, 3)
def test_get_pgroup_name_from_id(self):
id = "4a2f7e3a-312a-40c5-96a8-536b8a0fe074"
expected_name = "consisgroup-%s-cinder" % id
actual_name = self.driver._get_pgroup_name_from_id(id)
self.assertEqual(expected_name, actual_name)
def test_get_pgroup_snap_suffix(self):
cgsnap = mock.Mock()
cgsnap.id = "4a2f7e3a-312a-40c5-96a8-536b8a0fe074"
expected_suffix = "cgsnapshot-%s-cinder" % cgsnap.id
actual_suffix = self.driver._get_pgroup_snap_suffix(cgsnap)
self.assertEqual(expected_suffix, actual_suffix)
def test_get_pgroup_snap_name(self):
cg_id = "4a2f7e3a-312a-40c5-96a8-536b8a0fe074"
cgsnap_id = "4a2f7e3a-312a-40c5-96a8-536b8a0fe075"
mock_cgsnap = mock.Mock()
mock_cgsnap.consistencygroup_id = cg_id
mock_cgsnap.id = cgsnap_id
expected_name = "consisgroup-%(cg)s-cinder.cgsnapshot-%(snap)s-cinder"\
% {"cg": cg_id, "snap": cgsnap_id}
actual_name = self.driver._get_pgroup_snap_name(mock_cgsnap)
self.assertEqual(expected_name, actual_name)
def test_get_pgroup_snap_name_from_snapshot(self):
cgsnapshot_id = 'b919b266-23b4-4b83-9a92-e66031b9a921'
volume_name = 'volume-a3b8b294-8494-4a72-bec7-9aadec561332'
cg_id = '0cfc0e4e-5029-4839-af20-184fbc42a9ed'
pgsnap_name_base = (
'consisgroup-%s-cinder.cgsnapshot-%s-cinder.%s-cinder')
pgsnap_name = pgsnap_name_base % (cg_id, cgsnapshot_id, volume_name)
self.driver.db = mock.MagicMock()
mock_cgsnap = mock.MagicMock()
mock_cgsnap.id = cgsnapshot_id
mock_cgsnap.consistencygroup_id = cg_id
self.driver.db.cgsnapshot_get.return_value = mock_cgsnap
mock_snap = mock.Mock()
mock_snap.cgsnapshot_id = cgsnapshot_id
mock_snap.volume_name = volume_name
actual_name = self.driver._get_pgroup_snap_name_from_snapshot(
mock_snap
)
self.assertEqual(pgsnap_name, actual_name)
def test_create_consistencygroup(self):
mock_cgroup = mock.Mock()
mock_cgroup.id = "4a2f7e3a-312a-40c5-96a8-536b8a0fe074"
model_update = self.driver.create_consistencygroup(None, mock_cgroup)
expected_name = self.driver._get_pgroup_name_from_id(mock_cgroup.id)
self.array.create_pgroup.assert_called_with(expected_name)
self.assertEqual({'status': 'available'}, model_update)
self.assert_error_propagates(
[self.array.create_pgroup],
self.driver.create_consistencygroup, None, mock_cgroup)
@mock.patch(BASE_DRIVER_OBJ + ".create_volume_from_snapshot")
@mock.patch(BASE_DRIVER_OBJ + ".create_consistencygroup")
def test_create_consistencygroup_from_cgsnapshot(self, mock_create_cg,
mock_create_vol):
mock_context = mock.Mock()
mock_group = mock.Mock()
mock_cgsnapshot = mock.Mock()
mock_snapshots = [mock.Mock() for i in range(5)]
mock_volumes = [mock.Mock() for i in range(5)]
self.driver.create_consistencygroup_from_src(
mock_context,
mock_group,
mock_volumes,
cgsnapshot=mock_cgsnapshot,
snapshots=mock_snapshots,
source_cg=None,
source_vols=None
)
mock_create_cg.assert_called_with(mock_context, mock_group)
expected_calls = [mock.call(vol, snap)
for vol, snap in zip(mock_volumes, mock_snapshots)]
mock_create_vol.assert_has_calls(expected_calls,
any_order=True)
self.assert_error_propagates(
[mock_create_vol, mock_create_cg],
self.driver.create_consistencygroup_from_src,
mock_context,
mock_group,
mock_volumes,
cgsnapshot=mock_cgsnapshot,
snapshots=mock_snapshots,
source_cg=None,
source_vols=None
)
@mock.patch(BASE_DRIVER_OBJ + ".create_consistencygroup")
def test_create_consistencygroup_from_cg(self, mock_create_cg):
num_volumes = 5
mock_context = mock.MagicMock()
mock_group = mock.MagicMock()
mock_source_cg = mock.MagicMock()
mock_volumes = [mock.MagicMock() for i in range(num_volumes)]
mock_source_vols = [mock.MagicMock() for i in range(num_volumes)]
self.driver.create_consistencygroup_from_src(
mock_context,
mock_group,
mock_volumes,
source_cg=mock_source_cg,
source_vols=mock_source_vols
)
mock_create_cg.assert_called_with(mock_context, mock_group)
self.assertTrue(self.array.create_pgroup_snapshot.called)
self.assertEqual(num_volumes, self.array.copy_volume.call_count)
self.assertEqual(num_volumes, self.array.set_pgroup.call_count)
self.assertTrue(self.array.destroy_pgroup.called)
@mock.patch(BASE_DRIVER_OBJ + ".create_consistencygroup")
def test_create_consistencygroup_from_cg_with_error(self, mock_create_cg):
num_volumes = 5
mock_context = mock.MagicMock()
mock_group = mock.MagicMock()
mock_source_cg = mock.MagicMock()
mock_volumes = [mock.MagicMock() for i in range(num_volumes)]
mock_source_vols = [mock.MagicMock() for i in range(num_volumes)]
self.array.copy_volume.side_effect = FakePureStorageHTTPError()
self.assertRaises(
FakePureStorageHTTPError,
self.driver.create_consistencygroup_from_src,
mock_context,
mock_group,
mock_volumes,
source_cg=mock_source_cg,
source_vols=mock_source_vols
)
mock_create_cg.assert_called_with(mock_context, mock_group)
self.assertTrue(self.array.create_pgroup_snapshot.called)
# Make sure that the temp snapshot is cleaned up even when copying
# the volume fails!
self.assertTrue(self.array.destroy_pgroup.called)
@mock.patch(BASE_DRIVER_OBJ + ".delete_volume", autospec=True)
def test_delete_consistencygroup(self, mock_delete_volume):
mock_cgroup = mock.MagicMock()
mock_cgroup.id = "4a2f7e3a-312a-40c5-96a8-536b8a0fe074"
mock_cgroup['status'] = "deleted"
mock_context = mock.Mock()
mock_volume = mock.MagicMock()
model_update, volumes = self.driver.delete_consistencygroup(
mock_context, mock_cgroup, [mock_volume])
expected_name = self.driver._get_pgroup_name_from_id(mock_cgroup.id)
self.array.destroy_pgroup.assert_called_with(expected_name)
self.assertFalse(self.array.eradicate_pgroup.called)
expected_volume_updates = [{
'id': mock_volume.id,
'status': 'deleted'
}]
self.assertEqual(expected_volume_updates, volumes)
self.assertEqual(mock_cgroup['status'], model_update['status'])
mock_delete_volume.assert_called_with(self.driver, mock_volume)
self.array.destroy_pgroup.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Protection group has been destroyed."
)
self.driver.delete_consistencygroup(mock_context,
mock_cgroup,
[mock_volume])
self.array.destroy_pgroup.assert_called_with(expected_name)
self.assertFalse(self.array.eradicate_pgroup.called)
mock_delete_volume.assert_called_with(self.driver, mock_volume)
self.array.destroy_pgroup.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Protection group does not exist"
)
self.driver.delete_consistencygroup(mock_context,
mock_cgroup,
[mock_volume])
self.array.destroy_pgroup.assert_called_with(expected_name)
self.assertFalse(self.array.eradicate_pgroup.called)
mock_delete_volume.assert_called_with(self.driver, mock_volume)
self.array.destroy_pgroup.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Some other error"
)
self.assertRaises(self.purestorage_module.PureHTTPError,
self.driver.delete_consistencygroup,
mock_context,
mock_volume,
[mock_volume])
self.array.destroy_pgroup.side_effect = \
self.purestorage_module.PureHTTPError(
code=500,
text="Another different error"
)
self.assertRaises(self.purestorage_module.PureHTTPError,
self.driver.delete_consistencygroup,
mock_context,
mock_volume,
[mock_volume])
self.array.destroy_pgroup.side_effect = None
self.assert_error_propagates(
[self.array.destroy_pgroup],
self.driver.delete_consistencygroup,
mock_context,
mock_cgroup,
[mock_volume]
)
def _create_mock_cg(self):
mock_group = mock.MagicMock()
mock_group.id = "4a2f7e3a-312a-40c5-96a8-536b8a0fe074"
mock_group.status = "Available"
mock_group.cg_name = "consisgroup-" + mock_group.id + "-cinder"
return mock_group
def test_update_consistencygroup(self):
mock_group = self._create_mock_cg()
add_vols = [
{'name': 'vol1'},
{'name': 'vol2'},
{'name': 'vol3'},
]
expected_addvollist = [vol['name'] + '-cinder' for vol in add_vols]
remove_vols = [
{'name': 'vol4'},
{'name': 'vol5'},
]
expected_remvollist = [vol['name'] + '-cinder' for vol in remove_vols]
self.driver.update_consistencygroup(mock.Mock(), mock_group,
add_vols, remove_vols)
self.array.set_pgroup.assert_called_with(
mock_group.cg_name,
addvollist=expected_addvollist,
remvollist=expected_remvollist
)
def test_update_consistencygroup_no_add_vols(self):
mock_group = self._create_mock_cg()
expected_addvollist = []
remove_vols = [
{'name': 'vol4'},
{'name': 'vol5'},
]
expected_remvollist = [vol['name'] + '-cinder' for vol in remove_vols]
self.driver.update_consistencygroup(mock.Mock(), mock_group,
None, remove_vols)
self.array.set_pgroup.assert_called_with(
mock_group.cg_name,
addvollist=expected_addvollist,
remvollist=expected_remvollist
)
def test_update_consistencygroup_no_remove_vols(self):
mock_group = self._create_mock_cg()
add_vols = [
{'name': 'vol1'},
{'name': 'vol2'},
{'name': 'vol3'},
]
expected_addvollist = [vol['name'] + '-cinder' for vol in add_vols]
expected_remvollist = []
self.driver.update_consistencygroup(mock.Mock(), mock_group,
add_vols, None)
self.array.set_pgroup.assert_called_with(
mock_group.cg_name,
addvollist=expected_addvollist,
remvollist=expected_remvollist
)
def test_update_consistencygroup_no_vols(self):
mock_group = self._create_mock_cg()
self.driver.update_consistencygroup(mock.Mock(), mock_group,
None, None)
self.array.set_pgroup.assert_called_with(
mock_group.cg_name,
addvollist=[],
remvollist=[]
)
def test_create_cgsnapshot(self):
mock_cgsnap = mock.Mock()
mock_cgsnap.id = "4a2f7e3a-312a-40c5-96a8-536b8a0fe074"
mock_cgsnap.consistencygroup_id = \
"4a2f7e3a-312a-40c5-96a8-536b8a0fe075"
mock_context = mock.Mock()
mock_snap = mock.MagicMock()
model_update, snapshots = self.driver.create_cgsnapshot(mock_context,
mock_cgsnap,
[mock_snap])
cg_id = mock_cgsnap.consistencygroup_id
expected_pgroup_name = self.driver._get_pgroup_name_from_id(cg_id)
expected_snap_suffix = self.driver._get_pgroup_snap_suffix(mock_cgsnap)
self.array.create_pgroup_snapshot\
.assert_called_with(expected_pgroup_name,
suffix=expected_snap_suffix)
self.assertEqual({'status': 'available'}, model_update)
expected_snapshot_update = [{
'id': mock_snap.id,
'status': 'available',
'provider_location': self.array.array_id
}]
self.assertEqual(expected_snapshot_update, snapshots)
self.assert_error_propagates(
[self.array.create_pgroup_snapshot],
self.driver.create_cgsnapshot, mock_context, mock_cgsnap, [])
@mock.patch(BASE_DRIVER_OBJ + "._get_pgroup_snap_name",
spec=pure.PureBaseVolumeDriver._get_pgroup_snap_name)
def test_delete_cgsnapshot(self, mock_get_snap_name):
snap_name = "consisgroup-4a2f7e3a-312a-40c5-96a8-536b8a0f" \
"e074-cinder.4a2f7e3a-312a-40c5-96a8-536b8a0fe075"
mock_get_snap_name.return_value = snap_name
mock_cgsnap = mock.Mock()
mock_cgsnap.status = 'deleted'
mock_context = mock.Mock()
mock_snap = mock.Mock()
model_update, snapshots = self.driver.delete_cgsnapshot(mock_context,
mock_cgsnap,
[mock_snap])
self.array.destroy_pgroup.assert_called_with(snap_name)
self.assertFalse(self.array.eradicate_pgroup.called)
self.assertEqual({'status': mock_cgsnap.status}, model_update)
expected_snapshot_update = [{
'id': mock_snap.id,
'status': 'deleted'
}]
self.assertEqual(expected_snapshot_update, snapshots)
self.array.destroy_pgroup.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Protection group snapshot has been destroyed."
)
self.driver.delete_cgsnapshot(mock_context, mock_cgsnap, [mock_snap])
self.array.destroy_pgroup.assert_called_with(snap_name)
self.assertFalse(self.array.eradicate_pgroup.called)
self.array.destroy_pgroup.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Protection group snapshot does not exist"
)
self.driver.delete_cgsnapshot(mock_context, mock_cgsnap, [mock_snap])
self.array.destroy_pgroup.assert_called_with(snap_name)
self.assertFalse(self.array.eradicate_pgroup.called)
self.array.destroy_pgroup.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Some other error"
)
self.assertRaises(self.purestorage_module.PureHTTPError,
self.driver.delete_cgsnapshot,
mock_context,
mock_cgsnap,
[mock_snap])
self.array.destroy_pgroup.side_effect = \
self.purestorage_module.PureHTTPError(
code=500,
text="Another different error"
)
self.assertRaises(self.purestorage_module.PureHTTPError,
self.driver.delete_cgsnapshot,
mock_context,
mock_cgsnap,
[mock_snap])
self.array.destroy_pgroup.side_effect = None
self.assert_error_propagates(
[self.array.destroy_pgroup],
self.driver.delete_cgsnapshot,
mock_context,
mock_cgsnap,
[mock_snap]
)
@mock.patch(BASE_DRIVER_OBJ + "._get_pgroup_snap_name",
spec=pure.PureBaseVolumeDriver._get_pgroup_snap_name)
def test_delete_cgsnapshot_eradicate_now(self, mock_get_snap_name):
snap_name = "consisgroup-4a2f7e3a-312a-40c5-96a8-536b8a0f" \
"e074-cinder.4a2f7e3a-312a-40c5-96a8-536b8a0fe075"
mock_get_snap_name.return_value = snap_name
self.mock_config.pure_eradicate_on_delete = True
model_update, snapshots = self.driver.delete_cgsnapshot(mock.Mock(),
mock.Mock(),
[mock.Mock()])
self.array.destroy_pgroup.assert_called_once_with(snap_name)
self.array.eradicate_pgroup.assert_called_once_with(snap_name)
def test_manage_existing(self):
ref_name = 'vol1'
volume_ref = {'name': ref_name}
self.array.list_volume_private_connections.return_value = []
vol_name = VOLUME['name'] + '-cinder'
self.driver.manage_existing(VOLUME, volume_ref)
self.array.list_volume_private_connections.assert_called_with(ref_name)
self.array.rename_volume.assert_called_with(ref_name, vol_name)
def test_manage_existing_error_propagates(self):
self.array.list_volume_private_connections.return_value = []
self.assert_error_propagates(
[self.array.list_volume_private_connections,
self.array.rename_volume],
self.driver.manage_existing,
VOLUME, {'name': 'vol1'}
)
def test_manage_existing_bad_ref(self):
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
VOLUME, {'bad_key': 'bad_value'})
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
VOLUME, {'name': ''})
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
VOLUME, {'name': None})
self.array.get_volume.side_effect = \
self.purestorage_module.PureHTTPError(
text="Volume does not exist.",
code=400
)
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
VOLUME, {'name': 'non-existing-volume'})
def test_manage_existing_with_connected_hosts(self):
ref_name = 'vol1'
self.array.list_volume_private_connections.return_value = \
["host1", "host2"]
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
VOLUME, {'name': ref_name})
self.array.list_volume_private_connections.assert_called_with(ref_name)
self.assertFalse(self.array.rename_volume.called)
def test_manage_existing_get_size(self):
ref_name = 'vol1'
volume_ref = {'name': ref_name}
expected_size = 5
self.array.get_volume.return_value = {"size": 5368709120}
size = self.driver.manage_existing_get_size(VOLUME, volume_ref)
self.assertEqual(expected_size, size)
self.array.get_volume.assert_called_with(ref_name, snap=False)
def test_manage_existing_get_size_error_propagates(self):
self.array.get_volume.return_value = mock.MagicMock()
self.assert_error_propagates([self.array.get_volume],
self.driver.manage_existing_get_size,
VOLUME, {'name': 'vol1'})
def test_manage_existing_get_size_bad_ref(self):
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_get_size,
VOLUME, {'bad_key': 'bad_value'})
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_get_size,
VOLUME, {'name': ''})
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_get_size,
VOLUME, {'name': None})
def test_unmanage(self):
vol_name = VOLUME['name'] + "-cinder"
unmanaged_vol_name = vol_name + "-unmanaged"
self.driver.unmanage(VOLUME)
self.array.rename_volume.assert_called_with(vol_name,
unmanaged_vol_name)
def test_unmanage_error_propagates(self):
self.assert_error_propagates([self.array.rename_volume],
self.driver.unmanage,
VOLUME)
def test_unmanage_with_deleted_volume(self):
vol_name = VOLUME['name'] + "-cinder"
unmanaged_vol_name = vol_name + "-unmanaged"
self.array.rename_volume.side_effect = \
self.purestorage_module.PureHTTPError(
text="Volume does not exist.",
code=400
)
self.driver.unmanage(VOLUME)
self.array.rename_volume.assert_called_with(vol_name,
unmanaged_vol_name)
def test_manage_existing_snapshot(self):
ref_name = PURE_SNAPSHOT['name']
snap_ref = {'name': ref_name}
self.array.get_volume.return_value = [PURE_SNAPSHOT]
self.driver.manage_existing_snapshot(SNAPSHOT, snap_ref)
self.array.rename_volume.assert_called_once_with(ref_name,
SNAPSHOT_PURITY_NAME)
self.array.get_volume.assert_called_with(PURE_SNAPSHOT['source'],
snap=True)
def test_manage_existing_snapshot_multiple_snaps_on_volume(self):
ref_name = PURE_SNAPSHOT['name']
snap_ref = {'name': ref_name}
pure_snaps = [PURE_SNAPSHOT]
for i in range(5):
snap = PURE_SNAPSHOT.copy()
snap['name'] += str(i)
pure_snaps.append(snap)
self.array.get_volume.return_value = pure_snaps
self.driver.manage_existing_snapshot(SNAPSHOT, snap_ref)
self.array.rename_volume.assert_called_once_with(ref_name,
SNAPSHOT_PURITY_NAME)
def test_manage_existing_snapshot_error_propagates(self):
self.array.get_volume.return_value = [PURE_SNAPSHOT]
self.assert_error_propagates(
[self.array.rename_volume],
self.driver.manage_existing_snapshot,
SNAPSHOT, {'name': PURE_SNAPSHOT['name']}
)
def test_manage_existing_snapshot_bad_ref(self):
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_snapshot,
SNAPSHOT, {'bad_key': 'bad_value'})
def test_manage_existing_snapshot_empty_ref(self):
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_snapshot,
SNAPSHOT, {'name': ''})
def test_manage_existing_snapshot_none_ref(self):
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_snapshot,
SNAPSHOT, {'name': None})
def test_manage_existing_snapshot_volume_ref_not_exist(self):
self.array.get_volume.side_effect = \
self.purestorage_module.PureHTTPError(
text="Volume does not exist.",
code=400
)
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_snapshot,
SNAPSHOT, {'name': 'non-existing-volume.snap1'})
def test_manage_existing_snapshot_ref_not_exist(self):
ref_name = PURE_SNAPSHOT['name'] + '-fake'
snap_ref = {'name': ref_name}
self.array.get_volume.return_value = [PURE_SNAPSHOT]
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_snapshot,
SNAPSHOT, snap_ref)
def test_manage_existing_snapshot_bad_api_version(self):
self.array.get_rest_version.return_value = '1.3'
self.assertRaises(exception.PureDriverException,
self.driver.manage_existing_snapshot,
SNAPSHOT, {'name': PURE_SNAPSHOT['name']})
def test_manage_existing_snapshot_get_size(self):
ref_name = PURE_SNAPSHOT['name']
snap_ref = {'name': ref_name}
self.array.get_volume.return_value = [PURE_SNAPSHOT]
size = self.driver.manage_existing_snapshot_get_size(SNAPSHOT,
snap_ref)
expected_size = 3.0
self.assertEqual(expected_size, size)
self.array.get_volume.assert_called_with(PURE_SNAPSHOT['source'],
snap=True)
def test_manage_existing_snapshot_get_size_error_propagates(self):
self.array.get_volume.return_value = [PURE_SNAPSHOT]
self.assert_error_propagates(
[self.array.get_volume],
self.driver.manage_existing_snapshot_get_size,
SNAPSHOT, {'name': PURE_SNAPSHOT['name']}
)
def test_manage_existing_snapshot_get_size_bad_ref(self):
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_snapshot_get_size,
SNAPSHOT, {'bad_key': 'bad_value'})
def test_manage_existing_snapshot_get_size_empty_ref(self):
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_snapshot_get_size,
SNAPSHOT, {'name': ''})
def test_manage_existing_snapshot_get_size_none_ref(self):
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_snapshot_get_size,
SNAPSHOT, {'name': None})
def test_manage_existing_snapshot_get_size_volume_ref_not_exist(self):
self.array.get_volume.side_effect = \
self.purestorage_module.PureHTTPError(
text="Volume does not exist.",
code=400
)
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_snapshot_get_size,
SNAPSHOT, {'name': 'non-existing-volume.snap1'})
def test_manage_existing_snapshot_get_size_bad_api_version(self):
self.array.get_rest_version.return_value = '1.3'
self.assertRaises(exception.PureDriverException,
self.driver.manage_existing_snapshot_get_size,
SNAPSHOT, {'name': PURE_SNAPSHOT['name']})
def test_unmanage_snapshot(self):
unmanaged_snap_name = SNAPSHOT_PURITY_NAME + "-unmanaged"
self.driver.unmanage_snapshot(SNAPSHOT)
self.array.rename_volume.assert_called_with(SNAPSHOT_PURITY_NAME,
unmanaged_snap_name)
def test_unmanage_snapshot_error_propagates(self):
self.assert_error_propagates([self.array.rename_volume],
self.driver.unmanage_snapshot,
SNAPSHOT)
def test_unmanage_snapshot_with_deleted_snapshot(self):
unmanaged_snap_name = SNAPSHOT_PURITY_NAME + "-unmanaged"
self.array.rename_volume.side_effect = \
self.purestorage_module.PureHTTPError(
text="Snapshot does not exist.",
code=400
)
self.driver.unmanage_snapshot(SNAPSHOT)
self.array.rename_volume.assert_called_with(SNAPSHOT_PURITY_NAME,
unmanaged_snap_name)
def test_unmanage_snapshot_bad_api_version(self):
self.array.get_rest_version.return_value = '1.3'
self.assertRaises(exception.PureDriverException,
self.driver.unmanage_snapshot,
SNAPSHOT)
def _test_retype_repl(self, mock_is_repl, is_vol_repl, repl_cabability):
mock_is_repl.return_value = is_vol_repl
context = mock.MagicMock()
volume = fake_volume.fake_volume_obj(context)
new_type = {
'extra_specs': {
pure.EXTRA_SPECS_REPL_ENABLED:
'<is> ' + str(repl_cabability)
}
}
actual = self.driver.retype(context, volume, new_type, None, None)
expected = (True, None)
self.assertEqual(expected, actual)
return context, volume
@mock.patch(BASE_DRIVER_OBJ + '._is_volume_replicated_type', autospec=True)
def test_retype_repl_to_repl(self, mock_is_replicated_type):
self._test_retype_repl(mock_is_replicated_type, True, True)
@mock.patch(BASE_DRIVER_OBJ + '._is_volume_replicated_type', autospec=True)
def test_retype_non_repl_to_non_repl(self, mock_is_replicated_type):
self._test_retype_repl(mock_is_replicated_type, False, False)
@mock.patch(BASE_DRIVER_OBJ + '.replication_enable')
@mock.patch(BASE_DRIVER_OBJ + '.replication_disable')
@mock.patch(BASE_DRIVER_OBJ + '._is_volume_replicated_type', autospec=True)
def test_retype_non_repl_to_repl(self,
mock_is_replicated_type,
mock_replication_disable,
mock_replication_enable):
context, volume = self._test_retype_repl(mock_is_replicated_type,
False,
True)
self.assertFalse(mock_replication_disable.called)
mock_replication_enable.assert_called_with(context, volume)
@mock.patch(BASE_DRIVER_OBJ + '.replication_enable')
@mock.patch(BASE_DRIVER_OBJ + '.replication_disable')
@mock.patch(BASE_DRIVER_OBJ + '._is_volume_replicated_type', autospec=True)
def test_retype_repl_to_non_repl(self,
mock_is_replicated_type,
mock_replication_disable,
mock_replication_enable):
context, volume = self._test_retype_repl(mock_is_replicated_type,
True,
False)
self.assertFalse(mock_replication_enable.called)
mock_replication_disable.assert_called_with(context, volume)
def test_get_arrays_single_array_found(self):
volume = deepcopy(VOLUME)
# Test case where only single array, it's the one we're looking for
volume["provider_location"] = GET_ARRAY_PRIMARY["id"]
rslt_primary, rslt_secondary = self.driver._get_arrays(volume)
self.assertEqual(self.array, rslt_primary)
self.assertEqual(0, len(rslt_secondary))
def test_get_arrays_single_array_not_found(self):
volume = deepcopy(VOLUME)
# Test case where only single array, it's not the one we're looking for
volume["provider_location"] = "won't find me"
rslt_primary, rslt_secondary = self.driver._get_arrays(volume)
self.assertEqual(self.array, rslt_primary)
self.assertEqual(0, len(rslt_secondary))
def test_get_arrays_no_primary_configured(self):
volume = deepcopy(VOLUME)
# Test case where no primary is configured,
del volume["provider_location"]
rslt_primary, rslt_secondary = self.driver._get_arrays(volume)
self.assertEqual(self.array, rslt_primary)
self.assertEqual(0, len(rslt_secondary))
def test_get_arrays_after_failover(self):
volume = deepcopy(VOLUME)
# Test case where 2 arrays, volume is failed over to secondary
volume["provider_location"] = GET_ARRAY_SECONDARY["id"]
self.array.array_name = GET_ARRAY_PRIMARY["array_name"]
self.driver._replication_target_arrays = [self.array2]
rslt_primary, rslt_secondary = self.driver._get_arrays(volume)
self.assertEqual(self.array2, rslt_primary)
self.assertListEqual([self.array], rslt_secondary)
def test_get_arrays_primary_configured(self):
volume = deepcopy(VOLUME)
# Test case where 2 arrays, provider_location is primary
volume["provider_location"] = GET_ARRAY_PRIMARY["id"]
self.driver._replication_target_arrays = [self.array2]
rslt_primary, rslt_secondary = self.driver._get_arrays(volume)
self.assertEqual(self.array, rslt_primary)
self.assertListEqual([self.array2], rslt_secondary)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_is_vol_replicated_no_extra_specs(self, mock_get_vol_type):
mock_get_vol_type.return_value = NON_REPLICATED_VOL_TYPE
volume = fake_volume.fake_volume_obj(mock.MagicMock())
actual = self.driver._is_volume_replicated_type(volume)
self.assertFalse(actual)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_is_vol_replicated_has_repl_extra_specs(self, mock_get_vol_type):
mock_get_vol_type.return_value = REPLICATED_VOL_TYPE
volume = fake_volume.fake_volume_obj(mock.MagicMock())
actual = self.driver._is_volume_replicated_type(volume)
self.assertTrue(actual)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_is_vol_replicated_has_other_extra_specs(self, mock_get_vol_type):
vtype_test = deepcopy(NON_REPLICATED_VOL_TYPE)
vtype_test["extra_specs"] = {"some_key": "some_value"}
mock_get_vol_type.return_value = vtype_test
volume = fake_volume.fake_volume_obj(mock.MagicMock())
actual = self.driver._is_volume_replicated_type(volume)
self.assertFalse(actual)
def test_does_pgroup_exist_not_exists(self):
self.array.get_pgroup.side_effect = (
self.purestorage_module.PureHTTPError(code=400,
text="does not exist"))
exists = self.driver._does_pgroup_exist(self.array, "some_pgroup")
self.assertFalse(exists)
def test_does_pgroup_exist_exists(self):
self.array.get_pgroup.side_effect = None
self.array.get_pgroup.return_value = PGROUP_ON_TARGET_NOT_ALLOWED
exists = self.driver._does_pgroup_exist(self.array, "some_pgroup")
self.assertTrue(exists)
def test_does_pgroup_exist_error_propagates(self):
self.assert_error_propagates([self.array.get_pgroup],
self.driver._does_pgroup_exist,
self.array,
"some_pgroup")
@mock.patch(BASE_DRIVER_OBJ + "._does_pgroup_exist")
def test_wait_until_target_group_setting_propagates_ready(self,
mock_exists):
mock_exists.return_value = True
self.driver._wait_until_target_group_setting_propagates(
self.array,
"some_pgroup"
)
@mock.patch(BASE_DRIVER_OBJ + "._does_pgroup_exist")
def test_wait_until_target_group_setting_propagates_not_ready(self,
mock_exists):
mock_exists.return_value = False
self.assertRaises(
exception.PureDriverException,
self.driver._wait_until_target_group_setting_propagates,
self.array,
"some_pgroup"
)
def test_wait_until_source_array_allowed_ready(self):
self.array.get_pgroup.return_value = PGROUP_ON_TARGET_ALLOWED
self.driver._wait_until_source_array_allowed(
self.array,
"some_pgroup",)
def test_wait_until_source_array_allowed_not_ready(self):
self.array.get_pgroup.return_value = PGROUP_ON_TARGET_NOT_ALLOWED
self.assertRaises(
exception.PureDriverException,
self.driver._wait_until_source_array_allowed,
self.array,
"some_pgroup",
)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_create_volume_replicated(self, mock_get_volume_type):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
self._setup_mocks_for_replication()
self.driver._array = self.array
self.driver._array.array_name = GET_ARRAY_PRIMARY["array_name"]
self.driver._array.array_id = GET_ARRAY_PRIMARY["id"]
self.driver._replication_target_arrays = [mock.Mock()]
self.driver._replication_target_arrays[0].array_name = (
GET_ARRAY_SECONDARY["array_name"])
model_update = self.driver.create_volume(VOLUME)
self.assertEqual(
"enabled", model_update["replication_status"],
"create_volume should return valid replication_status")
self.assertEqual(GET_ARRAY_PRIMARY["id"],
model_update["provider_location"])
self.array.create_volume.assert_called_with(
VOLUME["name"] + "-cinder", 2 * units.Gi)
self.array.set_pgroup.assert_called_with(
REPLICATION_PROTECTION_GROUP,
addvollist=[VOLUME["name"] + "-cinder"])
self.assert_error_propagates([self.array.create_volume],
self.driver.create_volume, VOLUME)
@mock.patch(BASE_DRIVER_OBJ + "._get_flasharray")
def test_get_latest_replicated_vol_snap(self, mock_get_flasharray):
mock_get_flasharray.return_value = self.array
replicated_vol_snaps_return_0 = deepcopy(REPLICATED_VOLUME_SNAPS)
for snap in replicated_vol_snaps_return_0:
snap["source"] = "not_the_volume_we_want"
replicated_vol_snaps_return_1 = deepcopy(REPLICATED_VOLUME_SNAPS)
self.array.get_pgroup.return_value = REPLICATED_PGSNAPS
self.array.get_volume = mock.Mock()
self.array.get_volume.side_effect = [
replicated_vol_snaps_return_0,
replicated_vol_snaps_return_1]
source_array_name = "array1"
replicated_pg_name = "cinder-repl-pg"
replicated_vol_name = "replicated_volume2"
result = self.driver._get_latest_replicated_vol_snap(
self.array,
source_array_name,
replicated_pg_name,
replicated_vol_name)
expected = {
"source": "array1:replicated_volume2",
"serial": "BBA481C01639104E0001D5F8",
"created": "2014-12-04T22:59:38Z",
"name": "array1:cinder-repl-pg.2.replicated_volume2",
"size": 1048576
}
self.assertEqual(expected, result)
# Test when no replicated PG snapshots available
self.array.get_pgroup.return_value = []
result = self.driver._get_latest_replicated_vol_snap(
self.array,
source_array_name,
replicated_pg_name,
replicated_vol_name)
self.assertIsNone(result)
# Test when no replicated PG snapshot contains the volume we want
self.array.get_pgroup.return_value = REPLICATED_PGSNAPS
self.array.get_volume.side_effect = None
self.array.get_volume.return_value = REPLICATED_VOLUME_SNAPS
result = self.driver._get_latest_replicated_vol_snap(
self.array,
source_array_name,
replicated_pg_name,
"missing_volume")
self.assertIsNone(result)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_create_cloned_volume_failed_over(self, mock_get_volume_type):
# Tests cloning a volume that is failed over to secondary
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
vol_name = VOLUME["name"] + "-cinder"
src_name = SRC_VOL["name"] + "-cinder"
volume = deepcopy(VOLUME)
del volume["provider_location"]
src_vol = deepcopy(SRC_VOL)
src_vol["provider_location"] = GET_ARRAY_SECONDARY["id"]
src_vol["replication_status"] = "enabled"
self.driver._array.array_name = GET_ARRAY_PRIMARY["array_name"]
self.driver._array.array_id = GET_ARRAY_PRIMARY["id"]
self.driver._replication_target_arrays = [self.array2]
rslt = self.driver.create_cloned_volume(volume, src_vol)
self.assertFalse(self.array.copy_volume.called)
self.array2.copy_volume.assert_called_with(src_name, vol_name)
self.assertEqual("enabled", rslt["replication_status"])
self.assertEqual(GET_ARRAY_SECONDARY["id"], rslt["provider_location"])
self.assertFalse(self.array.extend_volume.called)
self.assertFalse(self.array2.extend_volume.called)
self.assert_error_propagates(
[self.array2.copy_volume],
self.driver.create_cloned_volume, volume, src_vol)
self.assertTrue(self.array2.set_pgroup.called)
self.assertFalse(self.array.set_pgroup.called)
@mock.patch(BASE_DRIVER_OBJ + "._add_and_replicate_if_needed")
@mock.patch(BASE_DRIVER_OBJ + "._get_arrays")
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_replication_enable(self, mock_get_volume_type,
mock_get_arrays,
mock_add_vol_to_pg):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
mock_get_arrays.return_value = (self.array, self.array2)
volume = deepcopy(VOLUME)
# Test volume is added to replication PG
rslt = self.driver.replication_enable(None, volume)
mock_add_vol_to_pg.assert_called_with(self.array, volume)
# Verify model_update shows replication is re-enabled
self.assertEqual("enabled", rslt["replication_status"])
@mock.patch('cinder.volume.volume_types.get_volume_type')
@mock.patch(BASE_DRIVER_OBJ + "._get_latest_replicated_vol_snap",
autospec=True)
def test_failover_src_volume_has_no_repl_snapshot(
self,
mock_get_latest_replicated_vol_snap,
mock_get_volume_type):
# Test case when target volume doesn't have a replicated snapshot. this
# can happen if replication is still propagating the first snapshot
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
src_vol = deepcopy(VOLUME)
target_array = "dummy_target_device_id"
src_vol["replication_status"] = "enabled"
src_vol["volume_type_id"] = REPLICATED_VOL_TYPE["id"]
self.driver._replication_target_array = mock.Mock()
mock_get_latest_replicated_vol_snap.return_value = None
context = mock.MagicMock()
self.assertRaises(exception.PureDriverException,
self.driver.replication_failover,
context,
src_vol,
target_array)
@mock.patch(BASE_DRIVER_OBJ + "._get_latest_replicated_vol_snap",
autospec=True)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_failover(self, mock_get_volume_type,
mock_get_latest_replicated_vol_snap):
# Test case when replication is set up correctly, expect call
# to copy_volume
src_vol = deepcopy(VOLUME)
src_vol["replication_status"] = "enabled"
src_vol["volume_type_id"] = REPLICATED_VOL_TYPE["id"]
self._setup_mocks_for_replication()
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
self.array2._target_device_id = self.array2.array_id
self.driver._replication_target_arrays = [self.array2]
# Assume array_name is also the san_ip
self.array2._target = GET_ARRAY_SECONDARY["array_name"]
self.driver._array._target = GET_ARRAY_PRIMARY["array_name"]
mock_get_latest_replicated_vol_snap.return_value =\
REPLICATED_VOLUME_SNAPS[0]
target_array = self.driver._replication_target_arrays[0]
target_array.copy_volume = mock.Mock()
context = mock.MagicMock()
self.driver.replication_failover(context, src_vol,
self.array2._target_device_id)
target_array.copy_volume.assert_called_with(
REPLICATED_VOLUME_SNAPS[0]["name"],
src_vol["name"] + "-cinder",
overwrite=True
)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_failover_no_replicated_array_configured(self,
mock_get_volume_type):
# verify exception if no replication target is configured
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
self.driver._replication_target_arrays = None
context = mock.Mock()
src_vol = VOLUME
target_array = "dummy_target_device_id"
self.assertRaises(exception.PureDriverException,
self.driver.replication_failover,
context,
src_vol,
target_array)
@mock.patch(BASE_DRIVER_OBJ + "._get_latest_replicated_vol_snap",
autospec=True)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_failover_invalid_replicated_array_specified(
self,
mock_get_volume_type,
mock_get_latest_replicated_vol_snap):
src_vol = deepcopy(VOLUME)
src_vol["replication_status"] = "enabled"
src_vol["volume_type_id"] = REPLICATED_VOL_TYPE["id"]
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
self.array2._target_device_id = self.array2.array_id
self.driver._replication_target_arrays = [self.array2]
# Assume array_name is also the san_ip
self.array2._target = GET_ARRAY_SECONDARY["array_name"]
self.driver._array._target = GET_ARRAY_PRIMARY["array_name"]
# we should not attempt the operation to find snapshot on secondary
assert not mock_get_latest_replicated_vol_snap.called
@mock.patch('cinder.volume.volume_types.get_volume_type')
@mock.patch(BASE_DRIVER_OBJ + '._get_current_array')
def test_replication_disable(self, mock_get_array, mock_get_volume_type):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
mock_get_array.return_value = self.array
context = mock.MagicMock()
volume = VOLUME
model_update = self.driver.replication_disable(context, volume)
self.assertDictMatch({'replication_status': 'disabled'}, model_update)
self.array.set_pgroup.assert_called_with(
self.driver._replication_pg_name,
remvollist=['volume-' + volume['id'] + '-cinder']
)
@mock.patch('cinder.volume.volume_types.get_volume_type')
@mock.patch(BASE_DRIVER_OBJ + '._get_current_array')
def test_replication_disable_error_propagates(self,
mock_get_array,
mock_get_volume_type):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
mock_get_array.return_value = self.array
context = mock.MagicMock()
volume = VOLUME
self.assert_error_propagates(
[mock_get_array, self.array.set_pgroup],
self.driver.replication_disable,
context, volume
)
@mock.patch('cinder.volume.volume_types.get_volume_type')
@mock.patch(BASE_DRIVER_OBJ + '._get_current_array')
def test_replication_disable_already_disabled(self,
mock_get_array,
mock_get_volume_type):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
mock_get_array.return_value = self.array
context = mock.MagicMock()
volume = VOLUME
self.array.set_pgroup.side_effect = FakePureStorageHTTPError(
code=400, text='could not be found')
model_update = self.driver.replication_disable(context, volume)
self.assertDictMatch({'replication_status': 'disabled'}, model_update)
self.array.set_pgroup.assert_called_with(
self.driver._replication_pg_name,
remvollist=['volume-' + volume["id"] + '-cinder']
)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_list_replication_targets(self, mock_get_volume_type):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
mock_backend_1 = mock.MagicMock()
mock_backend_1.array_name = "cinder-backend-1"
mock_backend_1._target_device_id = "cinder-backend-1-id"
mock_backend_2 = mock.MagicMock()
mock_backend_2.array_name = "cinder-backend-2"
mock_backend_2._target_device_id = "cinder-backend-2-id"
self.driver._replication_target_arrays = [
mock_backend_1,
mock_backend_2
]
context = mock.MagicMock()
vref = VOLUME
self.array.get_pgroup.return_value = {
"hgroups": None,
"hosts": None,
"name": "pg2",
"source": "pure01",
"targets": [
{
"name": mock_backend_1.array_name,
"allowed": True
},
{
"name": mock_backend_2.array_name,
"allowed": False
},
],
"time_remaining": 86395,
"volumes": [
"volume-" + vref['id'] + "-cinder",
"volume-123456-cinder"
]
}
data = self.driver.list_replication_targets(context, vref)
expected_data = {
'volume_id': vref['id'],
'targets': [
{
'target_device_id': mock_backend_1._target_device_id
},
{
'target_device_id': mock_backend_2._target_device_id
}
]
}
self.assertDictMatch(expected_data, data)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_list_replication_targets_error_propagates(self,
mock_get_volume_type):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
self.array.get_pgroup = mock.MagicMock()
self.assert_error_propagates([self.array.get_pgroup],
self.driver.list_replication_targets,
mock.Mock(),
VOLUME)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_list_replication_targets_no_targets(self, mock_get_volume_type):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
mock_backend_1 = mock.MagicMock()
mock_backend_1.array_name = "cinder-backend-1"
mock_backend_1._target_device_id = "cinder-backend-1-id"
mock_backend_2 = mock.MagicMock()
mock_backend_2.array_name = "cinder-backend-2"
mock_backend_2._target_device_id = "cinder-backend-2-id"
self.driver._replication_target_arrays = [
mock_backend_1,
mock_backend_2
]
context = mock.MagicMock()
vref = VOLUME
self.array.get_pgroup.return_value = {
"hgroups": None,
"hosts": None,
"name": "pg2",
"source": "pure01",
"targets": None,
"time_remaining": 86395,
"volumes": [
"volume-" + vref['id'] + "-cinder",
"volume-123456-cinder"
]
}
data = self.driver.list_replication_targets(context, vref)
expected_data = {
'volume_id': vref['id'],
'targets': []
}
self.assertDictMatch(expected_data, data)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_list_replication_targets_no_volumes(self, mock_get_volume_type):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
mock_backend_1 = mock.MagicMock()
mock_backend_1.array_name = "cinder-backend-1"
mock_backend_1._target_device_id = "cinder-backend-1-id"
mock_backend_2 = mock.MagicMock()
mock_backend_2.array_name = "cinder-backend-2"
mock_backend_2._target_device_id = "cinder-backend-2-id"
self.driver._replication_target_arrays = [
mock_backend_1,
mock_backend_2
]
context = mock.MagicMock()
vref = VOLUME
self.array.get_pgroup.return_value = {
"hgroups": None,
"hosts": None,
"name": "pg2",
"source": "pure01",
"targets": None,
"time_remaining": 86395,
"volumes": None
}
data = self.driver.list_replication_targets(context, vref)
expected_data = {
'volume_id': vref['id'],
'targets': []
}
self.assertDictMatch(expected_data, data)
@mock.patch('cinder.volume.volume_types.get_volume_type')
def test_list_replication_targets_no_secondary_configured(
self, mock_get_volume_type):
mock_get_volume_type.return_value = REPLICATED_VOL_TYPE
self.driver._replication_target_arrays = []
context = mock.MagicMock()
vref = VOLUME
self.array.get_pgroup.return_value = {
"hgroups": None,
"hosts": None,
"name": "pg2",
"source": "pure01",
"targets": [
{
"name": "dummy1",
"allowed": True
},
{
"name": "dummy2",
"allowed": False
},
],
"time_remaining": 86395,
"volumes": None
}
data = self.driver.list_replication_targets(context, vref)
expected_data = {
'volume_id': vref['id'],
'targets': []
}
self.assertDictMatch(expected_data, data)
class PureISCSIDriverTestCase(PureDriverTestCase):
def setUp(self):
super(PureISCSIDriverTestCase, self).setUp()
self.mock_config.use_chap_auth = False
self.driver = pure.PureISCSIDriver(configuration=self.mock_config)
self.driver._array = self.array
def test_get_host(self):
good_host = PURE_HOST.copy()
good_host.update(iqn=["another-wrong-iqn", INITIATOR_IQN])
bad_host = {"name": "bad-host", "iqn": ["wrong-iqn"]}
self.array.list_hosts.return_value = [bad_host]
real_result = self.driver._get_host(self.array, ISCSI_CONNECTOR)
self.assertIs(None, real_result)
self.array.list_hosts.return_value.append(good_host)
real_result = self.driver._get_host(self.array, ISCSI_CONNECTOR)
self.assertEqual(good_host, real_result)
self.assert_error_propagates([self.array.list_hosts],
self.driver._get_host,
self.array,
ISCSI_CONNECTOR)
@mock.patch(ISCSI_DRIVER_OBJ + "._connect")
@mock.patch(ISCSI_DRIVER_OBJ + "._get_target_iscsi_ports")
def test_initialize_connection(self, mock_get_iscsi_ports,
mock_connection):
mock_get_iscsi_ports.return_value = ISCSI_PORTS
lun = 1
connection = {
"vol": VOLUME["name"] + "-cinder",
"lun": lun,
}
mock_connection.return_value = connection
result = deepcopy(ISCSI_CONNECTION_INFO)
real_result = self.driver.initialize_connection(VOLUME,
ISCSI_CONNECTOR)
self.assertDictMatch(result, real_result)
mock_get_iscsi_ports.assert_called_with()
mock_connection.assert_called_with(VOLUME, ISCSI_CONNECTOR, None)
self.assert_error_propagates([mock_get_iscsi_ports, mock_connection],
self.driver.initialize_connection,
VOLUME, ISCSI_CONNECTOR)
@mock.patch(ISCSI_DRIVER_OBJ + "._connect")
@mock.patch(ISCSI_DRIVER_OBJ + "._get_target_iscsi_ports")
def test_initialize_connection_with_auth(self, mock_get_iscsi_ports,
mock_connection):
auth_type = "CHAP"
chap_username = ISCSI_CONNECTOR["host"]
chap_password = "password"
mock_get_iscsi_ports.return_value = ISCSI_PORTS
initiator_update = [{"key": pure.CHAP_SECRET_KEY,
"value": chap_password}]
mock_connection.return_value = {
"vol": VOLUME["name"] + "-cinder",
"lun": 1,
"auth_username": chap_username,
"auth_password": chap_password,
}
result = deepcopy(ISCSI_CONNECTION_INFO)
result["data"]["auth_method"] = auth_type
result["data"]["auth_username"] = chap_username
result["data"]["auth_password"] = chap_password
self.mock_config.use_chap_auth = True
# Branch where no credentials were generated
real_result = self.driver.initialize_connection(VOLUME,
ISCSI_CONNECTOR)
mock_connection.assert_called_with(VOLUME, ISCSI_CONNECTOR, None)
self.assertDictMatch(result, real_result)
# Branch where new credentials were generated
mock_connection.return_value["initiator_update"] = initiator_update
result["initiator_update"] = initiator_update
real_result = self.driver.initialize_connection(VOLUME,
ISCSI_CONNECTOR)
mock_connection.assert_called_with(VOLUME, ISCSI_CONNECTOR, None)
self.assertDictMatch(result, real_result)
self.assert_error_propagates([mock_get_iscsi_ports, mock_connection],
self.driver.initialize_connection,
VOLUME, ISCSI_CONNECTOR)
@mock.patch(ISCSI_DRIVER_OBJ + "._connect")
@mock.patch(ISCSI_DRIVER_OBJ + "._get_target_iscsi_ports")
def test_initialize_connection_multipath(self,
mock_get_iscsi_ports,
mock_connection):
mock_get_iscsi_ports.return_value = ISCSI_PORTS
lun = 1
connection = {
"vol": VOLUME["name"] + "-cinder",
"lun": lun,
}
mock_connection.return_value = connection
multipath_connector = deepcopy(ISCSI_CONNECTOR)
multipath_connector["multipath"] = True
result = deepcopy(ISCSI_CONNECTION_INFO)
real_result = self.driver.initialize_connection(VOLUME,
multipath_connector)
self.assertDictMatch(result, real_result)
mock_get_iscsi_ports.assert_called_with()
mock_connection.assert_called_with(VOLUME, multipath_connector, None)
multipath_connector["multipath"] = False
self.driver.initialize_connection(VOLUME, multipath_connector)
def test_get_target_iscsi_ports(self):
self.array.list_ports.return_value = ISCSI_PORTS
ret = self.driver._get_target_iscsi_ports()
self.assertEqual(ISCSI_PORTS, ret)
def test_get_target_iscsi_ports_with_iscsi_and_fc(self):
self.array.list_ports.return_value = PORTS_WITH
ret = self.driver._get_target_iscsi_ports()
self.assertEqual(ISCSI_PORTS, ret)
def test_get_target_iscsi_ports_with_no_ports(self):
# Should raise an exception if there are no ports
self.array.list_ports.return_value = []
self.assertRaises(exception.PureDriverException,
self.driver._get_target_iscsi_ports)
def test_get_target_iscsi_ports_with_only_fc_ports(self):
# Should raise an exception of there are no iscsi ports
self.array.list_ports.return_value = PORTS_WITHOUT
self.assertRaises(exception.PureDriverException,
self.driver._get_target_iscsi_ports)
@mock.patch("cinder.volume.utils.generate_password", autospec=True)
@mock.patch(ISCSI_DRIVER_OBJ + "._get_host", autospec=True)
@mock.patch(ISCSI_DRIVER_OBJ + "._generate_purity_host_name", spec=True)
def test_connect(self, mock_generate, mock_host, mock_gen_secret):
vol_name = VOLUME["name"] + "-cinder"
result = {"vol": vol_name, "lun": 1}
# Branch where host already exists
mock_host.return_value = PURE_HOST
self.array.connect_host.return_value = {"vol": vol_name, "lun": 1}
real_result = self.driver._connect(VOLUME, ISCSI_CONNECTOR, None)
self.assertEqual(result, real_result)
mock_host.assert_called_with(self.driver, self.array, ISCSI_CONNECTOR)
self.assertFalse(mock_generate.called)
self.assertFalse(self.array.create_host.called)
self.array.connect_host.assert_called_with(PURE_HOST_NAME, vol_name)
# Branch where new host is created
mock_host.return_value = None
mock_generate.return_value = PURE_HOST_NAME
real_result = self.driver._connect(VOLUME, ISCSI_CONNECTOR, None)
mock_host.assert_called_with(self.driver, self.array, ISCSI_CONNECTOR)
mock_generate.assert_called_with(HOSTNAME)
self.array.create_host.assert_called_with(PURE_HOST_NAME,
iqnlist=[INITIATOR_IQN])
self.assertEqual(result, real_result)
mock_generate.reset_mock()
self.array.reset_mock()
self.assert_error_propagates(
[mock_host, mock_generate, self.array.connect_host,
self.array.create_host], self.driver._connect, VOLUME,
ISCSI_CONNECTOR, None)
self.mock_config.use_chap_auth = True
chap_user = ISCSI_CONNECTOR["host"]
chap_password = "sOmEseCr3t"
# Branch where chap is used and credentials already exist
initiator_data = [{"key": pure.CHAP_SECRET_KEY,
"value": chap_password}]
self.driver._connect(VOLUME, ISCSI_CONNECTOR, initiator_data)
result["auth_username"] = chap_user
result["auth_password"] = chap_password
self.assertDictMatch(result, real_result)
self.array.set_host.assert_called_with(PURE_HOST_NAME,
host_user=chap_user,
host_password=chap_password)
# Branch where chap is used and credentials are generated
mock_gen_secret.return_value = chap_password
self.driver._connect(VOLUME, ISCSI_CONNECTOR, None)
result["auth_username"] = chap_user
result["auth_password"] = chap_password
result["initiator_update"] = {
"set_values": {
pure.CHAP_SECRET_KEY: chap_password
},
}
self.assertDictMatch(result, real_result)
self.array.set_host.assert_called_with(PURE_HOST_NAME,
host_user=chap_user,
host_password=chap_password)
@mock.patch(ISCSI_DRIVER_OBJ + "._get_host", autospec=True)
def test_connect_already_connected(self, mock_host):
mock_host.return_value = PURE_HOST
expected = {"host": PURE_HOST_NAME, "lun": 1}
self.array.list_volume_private_connections.return_value = \
[expected, {"host": "extra", "lun": 2}]
self.array.connect_host.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Connection already exists"
)
actual = self.driver._connect(VOLUME, ISCSI_CONNECTOR, None)
self.assertEqual(expected, actual)
self.assertTrue(self.array.connect_host.called)
self.assertTrue(self.array.list_volume_private_connections)
@mock.patch(ISCSI_DRIVER_OBJ + "._get_host", autospec=True)
def test_connect_already_connected_list_hosts_empty(self, mock_host):
mock_host.return_value = PURE_HOST
self.array.list_volume_private_connections.return_value = {}
self.array.connect_host.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Connection already exists"
)
self.assertRaises(exception.PureDriverException, self.driver._connect,
VOLUME, ISCSI_CONNECTOR, None)
self.assertTrue(self.array.connect_host.called)
self.assertTrue(self.array.list_volume_private_connections)
@mock.patch(ISCSI_DRIVER_OBJ + "._get_host", autospec=True)
def test_connect_already_connected_list_hosts_exception(self, mock_host):
mock_host.return_value = PURE_HOST
self.array.list_volume_private_connections.side_effect = \
self.purestorage_module.PureHTTPError(code=400, text="")
self.array.connect_host.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Connection already exists"
)
self.assertRaises(self.purestorage_module.PureHTTPError,
self.driver._connect, VOLUME,
ISCSI_CONNECTOR, None)
self.assertTrue(self.array.connect_host.called)
self.assertTrue(self.array.list_volume_private_connections)
class PureFCDriverTestCase(PureDriverTestCase):
def setUp(self):
super(PureFCDriverTestCase, self).setUp()
self.driver = pure.PureFCDriver(configuration=self.mock_config)
self.driver._array = self.array
self.driver._lookup_service = mock.Mock()
def test_get_host(self):
good_host = PURE_HOST.copy()
good_host.update(wwn=["another-wrong-wwn", INITIATOR_WWN])
bad_host = {"name": "bad-host", "wwn": ["wrong-wwn"]}
self.array.list_hosts.return_value = [bad_host]
actual_result = self.driver._get_host(self.array, FC_CONNECTOR)
self.assertIs(None, actual_result)
self.array.list_hosts.return_value.append(good_host)
actual_result = self.driver._get_host(self.array, FC_CONNECTOR)
self.assertEqual(good_host, actual_result)
self.assert_error_propagates([self.array.list_hosts],
self.driver._get_host,
self.array,
FC_CONNECTOR)
@mock.patch(FC_DRIVER_OBJ + "._connect")
def test_initialize_connection(self, mock_connection):
lookup_service = self.driver._lookup_service
(lookup_service.get_device_mapping_from_network.
return_value) = DEVICE_MAPPING
mock_connection.return_value = {"vol": VOLUME["name"] + "-cinder",
"lun": 1,
}
self.array.list_ports.return_value = FC_PORTS
actual_result = self.driver.initialize_connection(VOLUME, FC_CONNECTOR)
self.assertDictMatch(FC_CONNECTION_INFO, actual_result)
@mock.patch(FC_DRIVER_OBJ + "._get_host", autospec=True)
@mock.patch(FC_DRIVER_OBJ + "._generate_purity_host_name", spec=True)
def test_connect(self, mock_generate, mock_host):
vol_name = VOLUME["name"] + "-cinder"
result = {"vol": vol_name, "lun": 1}
# Branch where host already exists
mock_host.return_value = PURE_HOST
self.array.connect_host.return_value = {"vol": vol_name, "lun": 1}
real_result = self.driver._connect(VOLUME, FC_CONNECTOR)
self.assertEqual(result, real_result)
mock_host.assert_called_with(self.driver, self.array, FC_CONNECTOR)
self.assertFalse(mock_generate.called)
self.assertFalse(self.array.create_host.called)
self.array.connect_host.assert_called_with(PURE_HOST_NAME, vol_name)
# Branch where new host is created
mock_host.return_value = None
mock_generate.return_value = PURE_HOST_NAME
real_result = self.driver._connect(VOLUME, FC_CONNECTOR)
mock_host.assert_called_with(self.driver, self.array, FC_CONNECTOR)
mock_generate.assert_called_with(HOSTNAME)
self.array.create_host.assert_called_with(PURE_HOST_NAME,
wwnlist={INITIATOR_WWN})
self.assertEqual(result, real_result)
mock_generate.reset_mock()
self.array.reset_mock()
self.assert_error_propagates(
[mock_host, mock_generate, self.array.connect_host,
self.array.create_host],
self.driver._connect, VOLUME, FC_CONNECTOR)
@mock.patch(FC_DRIVER_OBJ + "._get_host", autospec=True)
def test_connect_already_connected(self, mock_host):
mock_host.return_value = PURE_HOST
expected = {"host": PURE_HOST_NAME, "lun": 1}
self.array.list_volume_private_connections.return_value = \
[expected, {"host": "extra", "lun": 2}]
self.array.connect_host.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Connection already exists"
)
actual = self.driver._connect(VOLUME, FC_CONNECTOR)
self.assertEqual(expected, actual)
self.assertTrue(self.array.connect_host.called)
self.assertTrue(self.array.list_volume_private_connections)
@mock.patch(FC_DRIVER_OBJ + "._get_host", autospec=True)
def test_connect_already_connected_list_hosts_empty(self, mock_host):
mock_host.return_value = PURE_HOST
self.array.list_volume_private_connections.return_value = {}
self.array.connect_host.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Connection already exists"
)
self.assertRaises(exception.PureDriverException, self.driver._connect,
VOLUME, FC_CONNECTOR)
self.assertTrue(self.array.connect_host.called)
self.assertTrue(self.array.list_volume_private_connections)
@mock.patch(FC_DRIVER_OBJ + "._get_host", autospec=True)
def test_connect_already_connected_list_hosts_exception(self, mock_host):
mock_host.return_value = PURE_HOST
self.array.list_volume_private_connections.side_effect = \
self.purestorage_module.PureHTTPError(code=400, text="")
self.array.connect_host.side_effect = \
self.purestorage_module.PureHTTPError(
code=400,
text="Connection already exists"
)
self.assertRaises(self.purestorage_module.PureHTTPError,
self.driver._connect, VOLUME, FC_CONNECTOR)
self.assertTrue(self.array.connect_host.called)
self.assertTrue(self.array.list_volume_private_connections)
@ddt.ddt
class PureVolumeUpdateStatsTestCase(PureBaseSharedDriverTestCase):
def setUp(self):
super(PureVolumeUpdateStatsTestCase, self).setUp()
self.array.get.side_effect = self.fake_get_array
@ddt.data(dict(used=10,
provisioned=100,
config_ratio=5,
expected_ratio=5,
auto=False),
dict(used=10,
provisioned=100,
config_ratio=5,
expected_ratio=10,
auto=True),
dict(used=0,
provisioned=100,
config_ratio=5,
expected_ratio=5,
auto=True),
dict(used=10,
provisioned=0,
config_ratio=5,
expected_ratio=5,
auto=True))
@ddt.unpack
def test_get_thin_provisioning(self,
used,
provisioned,
config_ratio,
expected_ratio,
auto):
self.mock_config.pure_automatic_max_oversubscription_ratio = auto
self.mock_config.max_over_subscription_ratio = config_ratio
actual_ratio = self.driver._get_thin_provisioning(provisioned, used)
self.assertEqual(expected_ratio, actual_ratio)
@mock.patch(BASE_DRIVER_OBJ + '.get_goodness_function')
@mock.patch(BASE_DRIVER_OBJ + '.get_filter_function')
@mock.patch(BASE_DRIVER_OBJ + '._get_provisioned_space')
@mock.patch(BASE_DRIVER_OBJ + '._get_thin_provisioning')
def test_get_volume_stats(self, mock_get_thin_provisioning, mock_get_space,
mock_get_filter, mock_get_goodness):
filter_function = 'capabilities.total_volumes < 10'
goodness_function = '90'
num_hosts = 20
num_snaps = 175
num_pgroups = 15
reserved_percentage = 12
self.array.list_hosts.return_value = [PURE_HOST] * num_hosts
self.array.list_volumes.return_value = [PURE_SNAPSHOT] * num_snaps
self.array.list_pgroups.return_value = [PURE_PGROUP] * num_pgroups
self.mock_config.reserved_percentage = reserved_percentage
mock_get_space.return_value = (PROVISIONED_CAPACITY * units.Gi, 100)
mock_get_filter.return_value = filter_function
mock_get_goodness.return_value = goodness_function
mock_get_thin_provisioning.return_value = (PROVISIONED_CAPACITY /
USED_SPACE)
expected_result = {
'volume_backend_name': VOLUME_BACKEND_NAME,
'vendor_name': 'Pure Storage',
'driver_version': self.driver.VERSION,
'storage_protocol': None,
'consistencygroup_support': True,
'thin_provisioning_support': True,
'multiattach': True,
'total_capacity_gb': TOTAL_CAPACITY,
'free_capacity_gb': TOTAL_CAPACITY - USED_SPACE,
'reserved_percentage': reserved_percentage,
'provisioned_capacity': PROVISIONED_CAPACITY,
'max_over_subscription_ratio': (PROVISIONED_CAPACITY /
USED_SPACE),
'filter_function': filter_function,
'goodness_function': goodness_function,
'total_volumes': 100,
'total_snapshots': num_snaps,
'total_hosts': num_hosts,
'total_pgroups': num_pgroups,
'writes_per_sec': PERF_INFO['writes_per_sec'],
'reads_per_sec': PERF_INFO['reads_per_sec'],
'input_per_sec': PERF_INFO['input_per_sec'],
'output_per_sec': PERF_INFO['output_per_sec'],
'usec_per_read_op': PERF_INFO['usec_per_read_op'],
'usec_per_write_op': PERF_INFO['usec_per_write_op'],
'queue_depth': PERF_INFO['queue_depth'],
'replication_enabled': False,
'replication_type': ['async'],
'replication_count': 0
}
real_result = self.driver.get_volume_stats(refresh=True)
self.assertDictMatch(expected_result, real_result)
# Make sure when refresh=False we are using cached values and not
# sending additional requests to the array.
self.array.reset_mock()
real_result = self.driver.get_volume_stats(refresh=False)
self.assertDictMatch(expected_result, real_result)
self.assertFalse(self.array.get.called)
self.assertFalse(self.array.list_volumes.called)
self.assertFalse(self.array.list_hosts.called)
self.assertFalse(self.array.list_pgroups.called)