cinder/cinder/tests/unit/volume/drivers/yadro/test_tatlin_common.py

520 lines
18 KiB
Python

# Copyright (C) 2021-2022 YADRO.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
from unittest.mock import MagicMock
from unittest.mock import Mock
from unittest import TestCase
from cinder.cmd import manage as cinder_manage
from cinder.exception import ManageExistingInvalidReference
from cinder.exception import VolumeBackendAPIException
from cinder.tests.unit.fake_constants import VOLUME_NAME
from cinder.volume import configuration
from cinder.volume.drivers.yadro.tatlin_client import TatlinAccessAPI
from cinder.volume.drivers.yadro.tatlin_client import TatlinClientCommon
from cinder.volume.drivers.yadro.tatlin_common import tatlin_opts
from cinder.volume.drivers.yadro.tatlin_common import TatlinCommonVolumeDriver
from cinder.volume.drivers.yadro.tatlin_exception import TatlinAPIException
from cinder.volume.drivers.yadro.tatlin_utils import TatlinVolumeConnections
OSMGR_ISCSI_PORTS = [
{
"id": "ip-sp-1-98039b04091a",
"meta": {
"tatlin-node": "sp-1",
"type": "ip",
"port-type": "active"
},
"params": {
"dhcp": False,
"ifname": "p30",
"physical-port": "p30",
"ipaddress": "172.20.101.65",
"netmask": "24",
"mtu": "1500",
"gateway": "172.20.101.1",
"roles": "",
"iflabel": "",
"wwpn": ""
}
},
{
"id": "ip-sp-0-b8599f1caf1b",
"meta": {
"tatlin-node": "sp-0",
"type": "ip",
"port-type": "active"
},
"params": {
"dhcp": False,
"ifname": "p31",
"physical-port": "p31",
"ipaddress": "172.20.101.66",
"netmask": "24",
"mtu": "1500",
"gateway": "172.20.101.1",
"roles": "",
"iflabel": "",
"wwpn": ""
}
},
{
"id": "ip-sp-1-98039b04091b",
"meta": {
"tatlin-node": "sp-1",
"type": "ip",
"port-type": "active"
},
"params": {
"dhcp": False,
"ifname": "p31",
"physical-port": "p31",
"ipaddress": "172.20.101.67",
"netmask": "24",
"mtu": "1500",
"gateway": "172.20.101.1",
"roles": "",
"iflabel": "",
"wwpn": ""
}
},
{
"id": "ip-sp-0-b8599f1caf1a",
"meta": {
"tatlin-node": "sp-0",
"type": "ip",
"port-type": "active"
},
"params": {
"dhcp": False,
"ifname": "p30",
"physical-port": "p30",
"ipaddress": "172.20.101.64",
"netmask": "24",
"mtu": "1500",
"gateway": "172.20.101.1",
"roles": "",
"iflabel": "",
"wwpn": ""
}
},
]
ISCSI_PORT_PORTALS = {
'p30': ['172.20.101.65:3260', '172.20.101.64:3260'],
'p31': ['172.20.101.66:3260', '172.20.101.67:3260']
}
RES_MAPPING_RESP = [
{
"resource_id": "62bbb941-ba4a-4101-927d-e527ce5ee011",
"host_id": "5e37d335-8fff-4aee-840a-34749301a16a",
"mapped_lun_id": 1
}
]
POOL_LIST_RESPONCE = [
{
"id": "7e259486-deb8-4d11-8cb0-e2c5874aaa5e",
"name": "cinder-pool",
"status": "ready"
}
]
OK_POOL_ID = '7e259486-deb8-4d11-8cb0-e2c5874aaa5e'
WRONG_POOL_ID = 'wrong-id'
ERROR_VOLUME = [
{
"ptyId": "f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
"id": "f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
"name": "cinder-volume-f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
"type": "block",
"poolId": "92c05782-7529-479f-8db7-b9435e1e9a3d",
"size": 16106127360,
"maxModifySize": 95330557231104,
"status": "error",
}
]
READY_VOLUME = [
{
"ptyId": "f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
"id": "f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
"name": "cinder-volume-f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
"type": "block",
"poolId": "92c05782-7529-479f-8db7-b9435e1e9a3d",
"size": 16106127360,
"maxModifySize": 95330557231104,
"status": "ready",
}
]
ONLINE_VOLUME = [
{
"ptyId": "f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
"id": "f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
"name": "cinder-volume-f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
"type": "block",
"poolId": "92c05782-7529-479f-8db7-b9435e1e9a3d",
"size": 16106127360,
"maxModifySize": 95330557231104,
"status": "online",
}
]
RESOURCE_INFORMATION = {
"ptyId": "62bbb941-ba4a-4101-927d-e527ce5ee011",
"id": "62bbb941-ba4a-4101-927d-e527ce5ee011",
"name": "res1",
"type": "block",
"poolId": "c46584c5-3113-4cc7-8a72-f9262f32c508",
"size": 1073741824,
"maxModifySize": 5761094647808,
"status": "ready",
"stat": {
"used_capacity": 1073741824,
"mapped_blocks": 0,
"dedup_count": 0,
"reduction_ratio": 0
},
"lbaFormat": "4kn",
"volume_id": "pty-vol-62bbb941-ba4a-4101-927d-e527ce5ee011",
"wwid": "naa.614529011650000c4000800000000004",
"lun_id": "4",
"cached": "true",
"rCacheMode": "enabled",
"wCacheMode": "enabled",
"ports": [
{
"port": "fc21",
"port_status": "healthy",
"port_status_desc":
"resource is available on all storage controllers",
"running": [
"sp-1",
"sp-0"
],
"wwn": [
"10:00:14:52:90:00:03:91",
"10:00:14:52:90:00:03:11"
],
"lun": "scsi-lun-fc21-4",
"volume": "pty-vol-62bbb941-ba4a-4101-927d-e527ce5ee011",
"lun_index": "4"
},
{
"port": "fc20",
"port_status": "healthy",
"port_status_desc":
"resource is available on all storage controllers",
"running": [
"sp-1",
"sp-0"
],
"wwn": [
"10:00:14:52:90:00:03:10",
"10:00:14:52:90:00:03:90"
],
"lun": "scsi-lun-fc20-4",
"volume": "pty-vol-62bbb941-ba4a-4101-927d-e527ce5ee011",
"lun_index": "4"
}
],
"volume_path": "/dev/mapper/dmc-89382c6c-7cf9-4ff8-bdbb-f438d20c960a",
"blockSize": "4kn",
"replication": {
"is_enabled": False
}
}
POOL_NAME = 'cinder-pool'
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
class DummyVolume(object):
def __init__(self, volid, volsize=1):
self.id = volid
self._name_id = None
self.size = volsize
self.status = None
self.__volume_type_id = 1
self.attach_status = None
self.volume_attachment = None
self.provider_location = None
self.name = None
self.metadata = {}
@property
def name_id(self):
return self.id if not self._name_id else self._name_id
@property
def name(self):
return self.name_id
@property
def volume_type_id(self):
return self.__volume_type_id
@name_id.setter
def name_id(self, value):
self._name_id = value
@name.setter
def name(self, value):
self._name_id = value
@volume_type_id.setter
def volume_type_id(self, value):
self.__volume_type_id = value
def get_fake_tatlin_config():
config = configuration.Configuration(
tatlin_opts,
configuration.SHARED_CONF_GROUP)
config.san_ip = '127.0.0.1'
config.san_password = 'pwd'
config.san_login = 'admin'
config.pool_name = POOL_NAME
config.host_group = 'cinder-group'
config.tat_api_retry_count = 1
config.wait_interval = 1
config.wait_retry_count = 3
config.chap_username = 'chap_user'
config.chap_password = 'chap_passwd'
config.state_path = '/tmp'
return config
class TatlinCommonVolumeDriverTest(TestCase):
@mock.patch.object(TatlinVolumeConnections, 'create_store')
@mock.patch.object(TatlinAccessAPI, '_authenticate_access')
def setUp(self, auth_access, create_store):
access_api = TatlinAccessAPI('127.0.0.1', '443',
'user', 'passwd', False)
access_api._authenticate_access = MagicMock()
self.client = TatlinClientCommon(access_api,
api_retry_count=1,
wait_interval=1,
wait_retry_count=3)
self.driver = TatlinCommonVolumeDriver(
configuration=get_fake_tatlin_config())
self.driver._get_tatlin_client = MagicMock()
self.driver._get_tatlin_client.return_value = self.client
self.driver.do_setup(None)
@mock.patch.object(TatlinClientCommon, 'delete_volume')
@mock.patch.object(TatlinClientCommon, 'is_volume_exists')
def test_delete_volume_ok(self, is_volume_exist, delete_volume):
cinder_manage.cfg.CONF.set_override('lock_path', '/tmp/locks',
group='oslo_concurrency')
is_volume_exist.side_effect = [True, False, False]
self.driver.delete_volume(DummyVolume(VOLUME_NAME))
@mock.patch.object(TatlinClientCommon, 'delete_volume')
@mock.patch.object(TatlinClientCommon, 'is_volume_exists')
def test_delete_volume_ok_404(self, is_volume_exist, delete_volume):
cinder_manage.cfg.CONF.set_override('lock_path', '/tmp/locks',
group='oslo_concurrency')
is_volume_exist.side_effect = [False]
self.driver.delete_volume(DummyVolume(VOLUME_NAME))
@mock.patch.object(TatlinClientCommon, 'delete_volume')
@mock.patch.object(TatlinClientCommon, 'is_volume_exists')
def test_delete_volume_error_500(self, is_volume_exist, delete_volume):
cinder_manage.cfg.CONF.set_override('lock_path', '/tmp/locks',
group='oslo_concurrency')
is_volume_exist.return_value = True
delete_volume.side_effect = TatlinAPIException(500, 'ERROR')
with self.assertRaises(VolumeBackendAPIException):
self.driver.delete_volume(DummyVolume(VOLUME_NAME))
@mock.patch.object(TatlinCommonVolumeDriver, '_update_qos')
@mock.patch.object(TatlinClientCommon, 'is_volume_ready')
@mock.patch.object(TatlinClientCommon, 'extend_volume')
@mock.patch.object(TatlinClientCommon, 'is_volume_exists')
def test_extend_volume_ok(self,
is_volume_exist,
extend_volume,
is_volume_ready,
update_qos):
cinder_manage.cfg.CONF.set_override('lock_path', '/tmp/locks',
group='oslo_concurrency')
is_volume_ready.return_value = True
is_volume_exist.return_value = True
self.driver.extend_volume(DummyVolume(VOLUME_NAME), 10)
@mock.patch('time.sleep')
@mock.patch.object(TatlinCommonVolumeDriver, '_update_qos')
@mock.patch.object(TatlinClientCommon, 'is_volume_ready')
@mock.patch.object(TatlinClientCommon, 'extend_volume')
@mock.patch.object(TatlinClientCommon, 'is_volume_exists')
def test_extend_volume_error_not_ready(self,
is_volume_exist,
extend_volume,
is_volume_ready,
update_qos,
sleeper):
cinder_manage.cfg.CONF.set_override('lock_path', '/tmp/locks',
group='oslo_concurrency')
is_volume_ready.return_value = False
is_volume_exist.return_value = True
with self.assertRaises(VolumeBackendAPIException):
self.driver.extend_volume(DummyVolume(VOLUME_NAME), 10)
@mock.patch.object(TatlinClientCommon,
'is_volume_ready',
return_value=True)
def test_wait_volume_reay_success(self, is_ready):
self.driver.wait_volume_ready(DummyVolume('cinder_volume'))
@mock.patch.object(TatlinCommonVolumeDriver, '_update_qos')
@mock.patch.object(TatlinAccessAPI, 'send_request')
def test_succeess_manage_existing(self, sendMock, qosMock):
sendMock.side_effect = [
(MockResponse([{'id': '1', 'poolId': OK_POOL_ID}], 200)),
(MockResponse(POOL_LIST_RESPONCE, 200))
]
self.driver.manage_existing(DummyVolume(VOLUME_NAME), {
'source-name': 'existing-resource'
})
@mock.patch.object(TatlinAccessAPI, 'send_request')
def test_fail_manage_existing_volume_not_found(self, sendMock):
self.driver.tatlin_api._send_request = Mock()
sendMock.side_effect = [
(MockResponse([{}], 404)),
]
with self.assertRaises(ManageExistingInvalidReference):
self.driver.manage_existing(DummyVolume('new-vol-id'), {
'source-name': 'existing-resource'
})
self.driver.tatlin_api.get_volume_info.assert_called_once()
self.driver.tatlin_api.get_pool_id_by_name.assert_not_called()
@mock.patch.object(TatlinCommonVolumeDriver, '_update_qos')
@mock.patch.object(TatlinAccessAPI, 'send_request')
def test_fail_manage_existing_wrong_pool(self, sendMock, qosMock):
sendMock.side_effect = [
(MockResponse([{'id': '1', 'poolId': WRONG_POOL_ID}], 200)),
(MockResponse(POOL_LIST_RESPONCE, 200))
]
with self.assertRaises(ManageExistingInvalidReference):
self.driver.manage_existing(DummyVolume('new-vol-id'), {
'source-name': 'existing-resource'
})
self.driver.tatlin_api.get_volume_info.assert_called_once()
self.driver.tatlin_api.get_pool_id_by_name.assert_called_once()
@mock.patch.object(TatlinClientCommon, 'get_resource_count')
@mock.patch.object(TatlinAccessAPI, 'send_request')
def test_success_create_volume(self, send_requst, object_count):
self.driver._stats['overall_resource_count'] = 1
object_count.side_effect = [(1, 1)]
send_requst.side_effect = [
(MockResponse(POOL_LIST_RESPONCE, 200)), # Get pool id
(MockResponse({}, 200)), # Create volume
(MockResponse(READY_VOLUME, 200)), # Is volume ready
(MockResponse(READY_VOLUME, 200)) # Is volume ready
]
self.driver._update_qos = Mock()
self.driver.create_volume(DummyVolume(VOLUME_NAME))
@mock.patch.object(TatlinClientCommon, 'get_resource_count')
@mock.patch.object(TatlinAccessAPI, 'send_request')
def test_fail_create_volume_400(self, send_request, object_count):
self.driver._stats['overall_resource_count'] = 1
object_count.side_effect = [(1, 1)]
send_request.side_effect = [
(MockResponse(POOL_LIST_RESPONCE, 200)),
(MockResponse({}, 500)),
(MockResponse({}, 400))
]
with self.assertRaises(VolumeBackendAPIException):
self.driver.create_volume(DummyVolume(VOLUME_NAME))
self.driver.tatlin_api.create_volume.assert_called_once()
@mock.patch('time.sleep')
@mock.patch.object(TatlinClientCommon, 'get_resource_count')
@mock.patch.object(TatlinAccessAPI, 'send_request')
def test_fail_volume_not_ready_create_volume(self, sendMock,
volume_count, sleeper):
self.driver._stats['overall_resource_count'] = 1
volume_count.side_effect = [(1, 1)]
sendMock.side_effect = [
(MockResponse(POOL_LIST_RESPONCE, 200)),
(MockResponse({}, 200)),
(MockResponse(ERROR_VOLUME, 200)),
(MockResponse(ERROR_VOLUME, 200)),
(MockResponse(ERROR_VOLUME, 200)),
]
with self.assertRaises(VolumeBackendAPIException):
self.driver.create_volume(DummyVolume(VOLUME_NAME))
@mock.patch.object(TatlinCommonVolumeDriver, '_get_ports_portals')
@mock.patch.object(TatlinAccessAPI, 'send_request')
def test_fail_create_export(self, sendMock, portsMock):
sendMock.side_effect = [
(MockResponse(OSMGR_ISCSI_PORTS, 200)),
]
portsMock.side_effect = [
ISCSI_PORT_PORTALS
]
self.driver._is_all_ports_assigned = Mock(return_value=True)
with self.assertRaises(NotImplementedError):
self.driver.create_export(None, DummyVolume(VOLUME_NAME), None)
@mock.patch.object(TatlinAccessAPI, 'send_request')
def test_find_mapped_lun(self, sendMock):
sendMock.side_effect = [
(MockResponse(RES_MAPPING_RESP, 200)),
]
self.driver.find_current_host = Mock(
return_value='5e37d335-8fff-4aee-840a-34749301a16a')
self.driver._find_mapped_lun(
'62bbb941-ba4a-4101-927d-e527ce5ee011', '')
@mock.patch.object(TatlinCommonVolumeDriver, '_update_qos')
@mock.patch.object(TatlinCommonVolumeDriver, 'wait_volume_online')
@mock.patch.object(TatlinClientCommon, 'add_vol_to_host')
@mock.patch.object(TatlinClientCommon,
'is_volume_exists',
return_value=True)
@mock.patch.object(TatlinAccessAPI, 'send_request')
def test_add_volume_to_host(self,
*args):
vol = DummyVolume('62bbb941-ba4a-4101-927d-e527ce5ee011')
self.driver.add_volume_to_host(
vol, '5e37d335-8fff-4aee-840a-34749301a16a'
)