cinder/cinder/tests/unit/volume/drivers/test_reduxio.py

650 lines
20 KiB
Python

# Copyright (c) 2016 Reduxio Systems
# 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.
import copy
import random
import string
import mock
from oslo_utils import units
from cinder import exception
from cinder import test
from cinder.volume.drivers.reduxio import rdx_cli_api
from cinder.volume.drivers.reduxio import rdx_iscsi_driver
DRIVER_PATH = ("cinder.volume.drivers."
"reduxio.rdx_iscsi_driver.ReduxioISCSIDriver")
API_PATH = "cinder.volume.drivers.reduxio.rdx_cli_api.ReduxioAPI"
TARGET = "mock_target"
TARGET_USER = "rdxadmin"
TARGET_PASSWORD = "mock_password"
VOLUME_BACKEND_NAME = "REDUXIO_VOLUME_TYPE"
CINDER_ID_LENGTH = 36
VOLUME_ID = "abcdabcd-1234-abcd-1234-abcdeffedcba"
VOLUME = {
"name": "volume-" + VOLUME_ID,
"id": VOLUME_ID,
"display_name": "fake_volume",
"size": 2,
"host": "irrelevant",
"volume_type": None,
"volume_type_id": None,
"consistencygroup_id": None,
'metadata': {}
}
VOLUME_RDX_NAME = "abcdabcd1234abcd1234abcdeffedcb"
VOLUME_RDX_DESC = "openstack_" + VOLUME["name"]
SRC_VOL_ID = "4c7a294d-5964-4379-a15f-ce5554734efc"
SRC_VOL_RDX_NAME = "ac7a294d59644379a15fce5554734ef"
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_RDX_NAME = "a4fe2f9ad0c44564a30d693cc3657b4"
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,
"metadata": {}
}
CONNECTOR = {
"initiator": "iqn.2013-12.com.stub:af4032f00014000e",
}
LS_SETTINGS = {
"system_configuration": [
{
"name": "host_name",
"value": "reduxio"
},
{
"name": "serial_number",
"value": "af4032f00014000e"
},
{
"name": "primary_ntp",
"value": "mickey.il.reduxio"
},
{
"name": "secondary_ntp",
"value": "minnie.il.reduxio"
},
{
"name": "timezone",
"value": "Asia/Jerusalem"
},
{
"name": "capacity_threshold",
"value": "93%"
},
{
"name": "storsense_enabled",
"value": True
}
],
"network_configuration": [
{
"name": "iscsi_target_iqn",
"value": "iqn.2013-12.com.reduxio:af4032f00014000e"
},
{
"name": "iscsi_target_tcp_port",
"value": "3260"
},
{
"name": "mtu",
"value": "9000"
}
],
"iscsi_network1": [
{
"name": "controller_1_port_1",
"value": "10.46.93.11"
},
{
"name": "controller_2_port_1",
"value": "10.46.93.22"
},
{
"name": "subnet_mask",
"value": "255.0.0.0"
},
{
"name": "default_gateway",
"value": None
},
{
"name": "vlan_tag",
"value": None
}
],
"iscsi_network2": [
{
"name": "controller_1_port_2",
"value": "10.64.93.11"
},
{
"name": "controller_2_port_2",
"value": "10.64.93.22"
},
{
"name": "subnet_mask",
"value": "255.0.0.0"
},
{
"name": "default_gateway",
"value": None
},
{
"name": "vlan_tag",
"value": None
}
],
"management_settings": [
{
"name": "floating_ip",
"value": "172.17.46.93"
},
{
"name": "management_ip1",
"value": "172.17.46.91"
},
{
"name": "management_ip2",
"value": "172.17.46.92"
},
{
"name": "subnet_mask",
"value": "255.255.254.0"
},
{
"name": "default_gateway",
"value": "172.17.47.254"
},
{
"name": "primary_dns",
"value": "172.17.32.11"
},
{
"name": "secondary_dns",
"value": "8.8.8.8"
},
{
"name": "domain_name",
"value": "il.reduxio"
}
],
"snmp": [
{
"Name": "trap_destination",
"value": None
},
{
"Name": "udp_port",
"value": "162"
},
{
"Name": "community",
"value": "public"
}
],
"email_notification": [
{
"Name": "smtp_server",
"value": None
},
{
"Name": "tcp_port",
"value": None
},
{
"Name": "smtp_authentication",
"value": "None"
},
{
"Name": "user_name",
"value": None
},
{
"Name": "sender_address",
"value": None
}
],
"email_recipient_list": [
{
"email": None
}
],
"directories": [
{
"name": "history_policies/"
}
]
}
TEST_ASSIGN_LUN_NUM = 7
ISCSI_CONNECTION_INFO_NO_MULTIPATH = {
"driver_volume_type": "iscsi",
"data": {
"target_discovered": False,
"discard": False,
"volume_id": VOLUME["id"],
"target_lun": TEST_ASSIGN_LUN_NUM,
"target_iqn": "iqn.2013-12.com.reduxio:af4032f00014000e",
"target_portal": "10.46.93.11:3260",
}
}
connection_copied = copy.deepcopy(
ISCSI_CONNECTION_INFO_NO_MULTIPATH["data"]
)
connection_copied.update({
"target_luns": [TEST_ASSIGN_LUN_NUM] * 4,
"target_iqns": ["iqn.2013-12.com.reduxio:af4032f00014000e",
"iqn.2013-12.com.reduxio:af4032f00014000e",
"iqn.2013-12.com.reduxio:af4032f00014000e",
"iqn.2013-12.com.reduxio:af4032f00014000e"],
"target_portals": ["10.46.93.11:3260", "10.46.93.22:3260",
"10.64.93.11:3260", "10.64.93.22:3260"]
})
ISCSI_CONNECTION_INFO = {
"driver_volume_type": "iscsi",
"data": connection_copied
}
def mock_api(to_mock=False):
def client_mock_wrapper(func):
def inner_client_mock(self, *args, **kwargs):
rdx_cli_api.ReduxioAPI._connect = mock.Mock()
if to_mock:
self.driver = rdx_iscsi_driver.ReduxioISCSIDriver(
configuration=self.mock_config)
self.mock_api = mock.Mock(spec=rdx_cli_api.ReduxioAPI)
self.driver.rdxApi = self.mock_api
else:
self.driver = rdx_iscsi_driver.ReduxioISCSIDriver(
configuration=self.mock_config)
self.driver.do_setup(None)
func(self, *args)
return inner_client_mock
return client_mock_wrapper
class ReduxioISCSIDriverTestCase(test.TestCase):
def setUp(self):
super(ReduxioISCSIDriverTestCase, self).setUp()
self.mock_config = mock.Mock()
self.mock_config.san_ip = TARGET
self.mock_config.san_login = TARGET_USER
self.mock_config.san_password = TARGET_PASSWORD
self.mock_config.volume_backend_name = VOLUME_BACKEND_NAME
self.driver = None # type: ReduxioISCSIDriver
@staticmethod
def generate_random_uuid():
return ''.join(
random.choice(string.ascii_uppercase + string.digits) for _ in
range(rdx_iscsi_driver.RDX_CLI_MAX_VOL_LENGTH))
@mock_api(False)
def test_cinder_id_to_rdx(self):
random_uuid1 = self.generate_random_uuid()
random_uuid2 = self.generate_random_uuid()
result1 = self.driver._cinder_id_to_rdx(random_uuid1)
result2 = self.driver._cinder_id_to_rdx(random_uuid2)
self.assertEqual(rdx_iscsi_driver.RDX_CLI_MAX_VOL_LENGTH, len(result1))
self.assertEqual(rdx_iscsi_driver.RDX_CLI_MAX_VOL_LENGTH, len(result2))
self.assertNotEqual(result1, result2)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_create_volume(self, mock_run_cmd):
self.driver.create_volume(VOLUME)
expected_cmd = rdx_cli_api.RdxApiCmd("volumes new",
argument=VOLUME_RDX_NAME,
flags=[
["size", VOLUME["size"]],
["description",
VOLUME_RDX_DESC]
])
mock_run_cmd.assert_called_with(expected_cmd)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_manage_existing(self, mock_run_cmd):
source_name = 'test-source'
self.driver.rdxApi.find_volume_by_name = mock.Mock()
self.driver.rdxApi.find_volume_by_name.return_value = {
'name': source_name,
'description': None
}
self.driver.manage_existing(VOLUME, {'source-name': source_name})
expected_cmd = rdx_cli_api.RdxApiCmd("volumes update",
argument=source_name,
flags=[
["new-name", VOLUME_RDX_NAME],
["description",
VOLUME_RDX_DESC]
])
mock_run_cmd.assert_called_with(expected_cmd)
self.driver.rdxApi.find_volume_by_name.return_value = {
'name': source_name,
'description': "openstack_1234"
}
self.assertRaises(
exception.ManageExistingAlreadyManaged,
self.driver.manage_existing,
VOLUME, {'source-name': source_name}
)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_manage_existing_get_size(self, mock_run_cmd):
source_name = 'test-source'
self.driver.rdxApi.find_volume_by_name = mock.Mock()
vol_cli_ret = {
'name': source_name,
'description': None,
"size": units.Gi * 10
}
source_vol = {'source-name': source_name}
self.driver.rdxApi.find_volume_by_name.return_value = vol_cli_ret
ret = self.driver.manage_existing_get_size(VOLUME, source_vol)
self.assertEqual(10, ret)
vol_cli_ret["size"] = units.Gi * 9
self.driver.rdxApi.find_volume_by_name.return_value = vol_cli_ret
ret = self.driver.manage_existing_get_size(VOLUME, source_vol)
self.assertNotEqual(10, ret)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_unmanage(self, mock_run_cmd):
source_name = 'test-source'
self.driver.rdxApi.find_volume_by_name = mock.Mock()
self.driver.rdxApi.find_volume_by_name.return_value = {
'name': source_name,
'description': "openstack_1234"
}
self.driver.unmanage(VOLUME)
expected_cmd = rdx_cli_api.RdxApiCmd(
"volumes update",
argument=VOLUME_RDX_NAME,
flags=[["description", ""]])
mock_run_cmd.assert_called_with(expected_cmd)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_delete_volume(self, mock_run_cmd):
self.driver.delete_volume(VOLUME)
expected_cmd = rdx_cli_api.RdxApiCmd(
"volumes delete {} -force".format(VOLUME_RDX_NAME))
mock_run_cmd.assert_called_with(expected_cmd)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_create_volume_from_snapshot(self, mock_run_cmd):
self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT)
expected_cmd = rdx_cli_api.RdxApiCmd(
"volumes clone",
argument=SRC_VOL_RDX_NAME,
flags={
"name": VOLUME_RDX_NAME,
"bookmark": SNAPSHOT_RDX_NAME,
"description": VOLUME_RDX_DESC}
)
mock_run_cmd.assert_called_with(expected_cmd)
# Test resize
bigger_vol = copy.deepcopy(VOLUME)
bigger_size = SNAPSHOT['volume_size'] + 10
bigger_vol['size'] = bigger_size
self.driver.create_volume_from_snapshot(bigger_vol, SNAPSHOT)
expected_cmd = rdx_cli_api.RdxApiCmd("volumes update",
argument=VOLUME_RDX_NAME,
flags={"size": bigger_size})
mock_run_cmd.assert_called_with(expected_cmd)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_create_cloned_volume(self, mock_run_cmd):
self.driver.create_cloned_volume(VOLUME, SRC_VOL)
expected_cmd = rdx_cli_api.RdxApiCmd(
"volumes clone",
argument=SRC_VOL_RDX_NAME,
flags={"name": VOLUME_RDX_NAME, "description": VOLUME_RDX_DESC})
mock_run_cmd.assert_called_with(expected_cmd)
# Test clone from date
backdated_clone = copy.deepcopy(VOLUME)
clone_date = "02/17/2015-11:39:00"
backdated_clone["metadata"]["backdate"] = clone_date
self.driver.create_cloned_volume(backdated_clone, SRC_VOL)
expected_cmd.add_flag("timestamp", clone_date)
mock_run_cmd.assert_called_with(expected_cmd)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_create_snapshot(self, mock_run_cmd):
self.driver.create_snapshot(SNAPSHOT)
expected_cmd = rdx_cli_api.RdxApiCmd(
"volumes bookmark",
argument=SRC_VOL_RDX_NAME,
flags={"name": SNAPSHOT_RDX_NAME, "type": "manual"})
mock_run_cmd.assert_called_with(expected_cmd)
backdated_snap = copy.deepcopy(SNAPSHOT)
clone_date = "02/17/2015-11:39:00"
backdated_snap["metadata"]["backdate"] = clone_date
self.driver.create_snapshot(backdated_snap)
expected_cmd = rdx_cli_api.RdxApiCmd(
"volumes bookmark",
argument=SRC_VOL_RDX_NAME,
flags={
"name": SNAPSHOT_RDX_NAME,
"type": "manual",
"timestamp": clone_date}
)
mock_run_cmd.assert_called_with(expected_cmd)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_delete_snapshot(self, mock_run_cmd):
self.driver.delete_snapshot(SNAPSHOT)
expected_cmd = rdx_cli_api.RdxApiCmd("volumes delete-bookmark",
argument=SRC_VOL_RDX_NAME,
flags={"name": SNAPSHOT_RDX_NAME})
mock_run_cmd.assert_called_with(expected_cmd)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_get_volume_stats(self, mock_run_cmd):
pass
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_extend_volume(self, mock_run_cmd):
new_size = VOLUME["size"] + 1
self.driver.extend_volume(VOLUME, new_size)
expected_cmd = rdx_cli_api.RdxApiCmd("volumes update",
argument=VOLUME_RDX_NAME,
flags={"size": new_size})
mock_run_cmd.assert_called_with(expected_cmd)
def settings_side_effect(*args):
if args[0].cmd == "settings ls":
return LS_SETTINGS
else:
return mock.Mock()
def get_single_assignment_side_effect(*args, **kwargs):
if "raise_on_non_exists" in kwargs:
raise_given = kwargs["raise_on_non_exists"]
else:
raise_given = True
if (raise_given is True) or (raise_given is None):
return {
"host": kwargs["host"],
"vol": kwargs["vol"],
"lun": TEST_ASSIGN_LUN_NUM
}
else:
return None
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd",
side_effect=settings_side_effect)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "get_single_assignment",
side_effect=get_single_assignment_side_effect)
@mock_api(False)
def test_initialize_connection(self, mock_list_assignmnet, mock_run_cmd):
generated_host_name = "openstack-123456789012"
self.driver.rdxApi.list_hosts = mock.Mock()
self.driver.rdxApi.list_hosts.return_value = []
self.driver._generate_initiator_name = mock.Mock()
self.driver._generate_initiator_name.return_value = generated_host_name
ret_connection_info = self.driver.initialize_connection(VOLUME,
CONNECTOR)
create_host_cmd = rdx_cli_api.RdxApiCmd(
"hosts new",
argument=generated_host_name,
flags={"iscsi-name": CONNECTOR["initiator"]}
)
assign_cmd = rdx_cli_api.RdxApiCmd(
"volumes assign",
argument=VOLUME_RDX_NAME,
flags={"host": generated_host_name}
)
calls = [
mock.call.driver._run_cmd(create_host_cmd),
mock.call.driver._run_cmd(assign_cmd)
]
mock_run_cmd.assert_has_calls(calls)
self.assertDictEqual(
ret_connection_info,
ISCSI_CONNECTION_INFO_NO_MULTIPATH
)
connector = copy.deepcopy(CONNECTOR)
connector["multipath"] = True
ret_connection_info = self.driver.initialize_connection(VOLUME,
connector)
create_host_cmd = rdx_cli_api.RdxApiCmd(
"hosts new",
argument=generated_host_name,
flags={"iscsi-name": CONNECTOR["initiator"]})
assign_cmd = rdx_cli_api.RdxApiCmd(
"volumes assign",
argument=VOLUME_RDX_NAME,
flags={"host": generated_host_name}
)
calls = [
mock.call.driver._run_cmd(create_host_cmd),
mock.call.driver._run_cmd(assign_cmd)
]
mock_run_cmd.assert_has_calls(calls)
self.assertDictEqual(ret_connection_info, ISCSI_CONNECTION_INFO)
self.driver.rdxApi.list_hosts.return_value = [{
"iscsi_name": CONNECTOR["initiator"],
"name": generated_host_name
}]
ret_connection_info = self.driver.initialize_connection(VOLUME,
connector)
mock_run_cmd.assert_has_calls([mock.call.driver._run_cmd(assign_cmd)])
self.assertDictEqual(ISCSI_CONNECTION_INFO, ret_connection_info)
@mock.patch.object(rdx_cli_api.ReduxioAPI, "_run_cmd")
@mock_api(False)
def test_terminate_connection(self, mock_run_cmd):
generated_host_name = "openstack-123456789012"
self.driver.rdxApi.list_hosts = mock.Mock()
self.driver.rdxApi.list_hosts.return_value = [{
"iscsi_name": CONNECTOR["initiator"],
"name": generated_host_name
}]
self.driver.terminate_connection(VOLUME, CONNECTOR)
unassign_cmd = rdx_cli_api.RdxApiCmd(
"volumes unassign",
argument=VOLUME_RDX_NAME,
flags={"host": generated_host_name}
)
mock_run_cmd.assert_has_calls(
[mock.call.driver._run_cmd(unassign_cmd)])