Yadro Tatlin Unified FC driver
Added support of FC to Tatlin Unified driver Implements: blueprint yadro-tatlin-unified-fc Co-Authored-By: Sergey Karpenkov <s.karpenkov@yadro.com> Change-Id: Ia8192854a7095354f2616d53d2c5b71580384dcb
This commit is contained in:
parent
25455c476f
commit
3f3112f796
@ -35,39 +35,55 @@ from cinder.volume.drivers.yadro.tatlin_client import TatlinClientV25
|
|||||||
from cinder.volume.drivers.yadro.tatlin_exception import TatlinAPIException
|
from cinder.volume.drivers.yadro.tatlin_exception import TatlinAPIException
|
||||||
|
|
||||||
|
|
||||||
RES_PORTS_RESP = [
|
VOL_ID = 'cinder-volume-id'
|
||||||
{
|
|
||||||
"port": "fc20",
|
LUN_ID = 75
|
||||||
"port_status": "healthy",
|
|
||||||
"port_status_desc": "resource is available",
|
HOST_ID = 'host-id'
|
||||||
"running": [
|
|
||||||
"sp-0",
|
HOST_ID_2 = 'host-id-2'
|
||||||
"sp-1"
|
|
||||||
],
|
HOST_GROUP_ID = 'group-id'
|
||||||
"wwn": [
|
|
||||||
"10:00:14:52:90:00:03:10",
|
HOST_GROUP_NAME = 'cinder-group'
|
||||||
"10:00:14:52:90:00:03:90"
|
|
||||||
],
|
HOST_IQN = 'iqn.1994-05.com.redhat:12345'
|
||||||
"lun": "scsi-lun-fc20-5",
|
|
||||||
"volume": "pty-vol-0d9627cb-c52e-49f1-878c-57c9bc3010c9",
|
POOL_NAME = 'cinder-pool-name'
|
||||||
"lun_index": "5"
|
|
||||||
}
|
POOL_ID = 'cinder-pool-id'
|
||||||
]
|
|
||||||
|
|
||||||
ALL_HOSTS_RESP = [
|
ALL_HOSTS_RESP = [
|
||||||
{
|
{
|
||||||
"version": "d6a2d310d9adb16f0d24d5352b5c4837",
|
"version": "c7216b2e14c8edc718e1664178f75777",
|
||||||
"id": "5e37d335-8fff-4aee-840a-34749301a16a",
|
"id": HOST_ID_2,
|
||||||
"name": "victoria-fc",
|
"name": "cinder-host-2",
|
||||||
"port_type": "fc",
|
"port_type": "fc",
|
||||||
"initiators": [
|
"initiators": ["21:00:34:80:0d:74:17:30", "21:00:34:80:0d:74:17:31"],
|
||||||
"21:00:34:80:0d:6b:aa:e3",
|
},
|
||||||
"21:00:34:80:0d:6b:aa:e2"
|
{
|
||||||
],
|
"version": "216d08e98f8d4a695b6632fc3c79b1cc",
|
||||||
"tags": [],
|
"id": HOST_ID,
|
||||||
"comment": "",
|
"name": "cinder-host-1",
|
||||||
"auth": {}
|
"port_type": "fc",
|
||||||
}
|
"initiators": ['21:00:00:24:ff:7f:35:b7', '21:00:00:24:ff:7f:35:b6'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "301fc82d355a691248b1e1dd8164f5e5",
|
||||||
|
"id": HOST_ID,
|
||||||
|
"name": "cinder-host-1",
|
||||||
|
"port_type": "iscsi",
|
||||||
|
"initiators": [HOST_IQN],
|
||||||
|
"auth": {"auth_type": "none"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "401fc82d355a691248b1e1dd8164f5e5",
|
||||||
|
"id": HOST_ID_2,
|
||||||
|
"name": "cinder-host-2",
|
||||||
|
"port_type": "iscsi",
|
||||||
|
"initiators": ["iqn.1994-05.com.redhat:5daf702e9655"],
|
||||||
|
"auth": {"auth_type": "none"},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
RES_MAPPING_RESP = [
|
RES_MAPPING_RESP = [
|
||||||
@ -75,19 +91,45 @@ RES_MAPPING_RESP = [
|
|||||||
"resource_id": "62bbb941-ba4a-4101-927d-e527ce5ee011",
|
"resource_id": "62bbb941-ba4a-4101-927d-e527ce5ee011",
|
||||||
"host_id": "5e37d335-8fff-4aee-840a-34749301a16a",
|
"host_id": "5e37d335-8fff-4aee-840a-34749301a16a",
|
||||||
"mapped_lun_id": 1
|
"mapped_lun_id": 1
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"resource_id": VOL_ID,
|
||||||
|
"host_id": HOST_ID,
|
||||||
|
"mapped_lun_id": LUN_ID
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource_id": "62bbb941-ba4a-4101-927d-e527ce5ee011",
|
||||||
|
"host_id": "5e37d335-8fff-4aee-840a-34749301a16a",
|
||||||
|
"mapped_lun_id": 1
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
RES_MAPPING_RESP2 = [
|
||||||
|
{
|
||||||
|
"resource_id": "62bbb941-ba4a-4101-927d-e527ce5ee011",
|
||||||
|
"host_id": "5e37d335-8fff-4aee-840a-34749301a16a",
|
||||||
|
"mapped_lun_id": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource_id": "62bbb941-ba4a-4101-927d-e527ce5ee011",
|
||||||
|
"host_id": "5e37d335-8fff-4aee-840a-34749301a16a",
|
||||||
|
"mapped_lun_id": 1
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
POOL_LIST_RESPONCE = [
|
POOL_LIST_RESPONCE = [
|
||||||
{
|
{
|
||||||
"id": "7e259486-deb8-4d11-8cb0-e2c5874aaa5e",
|
"id": POOL_ID,
|
||||||
"name": "cinder-pool",
|
"name": POOL_NAME,
|
||||||
|
"status": "ready"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "123",
|
||||||
|
"name": "some-name",
|
||||||
"status": "ready"
|
"status": "ready"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
VOL_ID = 'cinder-volume-id'
|
|
||||||
|
|
||||||
ERROR_VOLUME = [
|
ERROR_VOLUME = [
|
||||||
{
|
{
|
||||||
"ptyId": "f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
|
"ptyId": "f28ee814-22ed-4bb0-8b6a-f7ce9075034a",
|
||||||
@ -132,7 +174,7 @@ RESOURCE_INFORMATION = {
|
|||||||
"lbaFormat": "4kn",
|
"lbaFormat": "4kn",
|
||||||
"volume_id": "pty-vol-62bbb941-ba4a-4101-927d-e527ce5ee011",
|
"volume_id": "pty-vol-62bbb941-ba4a-4101-927d-e527ce5ee011",
|
||||||
"wwid": "naa.614529011650000c4000800000000004",
|
"wwid": "naa.614529011650000c4000800000000004",
|
||||||
"lun_id": "4",
|
"lun_id": LUN_ID,
|
||||||
"cached": "true",
|
"cached": "true",
|
||||||
"rCacheMode": "enabled",
|
"rCacheMode": "enabled",
|
||||||
"wCacheMode": "enabled",
|
"wCacheMode": "enabled",
|
||||||
@ -152,7 +194,7 @@ RESOURCE_INFORMATION = {
|
|||||||
],
|
],
|
||||||
"lun": "scsi-lun-fc21-4",
|
"lun": "scsi-lun-fc21-4",
|
||||||
"volume": "pty-vol-62bbb941-ba4a-4101-927d-e527ce5ee011",
|
"volume": "pty-vol-62bbb941-ba4a-4101-927d-e527ce5ee011",
|
||||||
"lun_index": "4"
|
"lun_index": LUN_ID
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"port": "fc20",
|
"port": "fc20",
|
||||||
@ -169,7 +211,7 @@ RESOURCE_INFORMATION = {
|
|||||||
],
|
],
|
||||||
"lun": "scsi-lun-fc20-4",
|
"lun": "scsi-lun-fc20-4",
|
||||||
"volume": "pty-vol-62bbb941-ba4a-4101-927d-e527ce5ee011",
|
"volume": "pty-vol-62bbb941-ba4a-4101-927d-e527ce5ee011",
|
||||||
"lun_index": "4"
|
"lun_index": LUN_ID
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"volume_path": "/dev/mapper/dmc-89382c6c-7cf9-4ff8-bdbb-f438d20c960a",
|
"volume_path": "/dev/mapper/dmc-89382c6c-7cf9-4ff8-bdbb-f438d20c960a",
|
||||||
@ -179,13 +221,44 @@ RESOURCE_INFORMATION = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VOL_PORTS_RESP = [
|
||||||
|
{
|
||||||
|
"port": "p01",
|
||||||
|
"port_status": "healthy",
|
||||||
|
"running": ["sp-0", "sp-1"],
|
||||||
|
"wwn": ["iqn.2017-01.com.yadro:tatlin:sn.09082200a51002"],
|
||||||
|
"lun_index": LUN_ID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": "p11",
|
||||||
|
"port_status": "healthy",
|
||||||
|
"running": ["sp-0", "sp-1"],
|
||||||
|
"wwn": ["iqn.2017-01.com.yadro:tatlin:sn.09082200a51002"],
|
||||||
|
"lun_index": LUN_ID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": "p10",
|
||||||
|
"port_status": "healthy",
|
||||||
|
"running": ["sp-0", "sp-1"],
|
||||||
|
"wwn": ["iqn.2017-01.com.yadro:tatlin:sn.09082200a51002"],
|
||||||
|
"lun_index": LUN_ID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": "p00",
|
||||||
|
"port_status": "healthy",
|
||||||
|
"running": ["sp-0", "sp-1"],
|
||||||
|
"wwn": ["iqn.2017-01.com.yadro:tatlin:sn.09082200a51002"],
|
||||||
|
"lun_index": LUN_ID
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
ALL_HOST_GROUP_RESP = [
|
ALL_HOST_GROUP_RESP = [
|
||||||
{
|
{
|
||||||
"version": "20c28d21549fb7ec5777637f72f50043",
|
"version": "20c28d21549fb7ec5777637f72f50043",
|
||||||
"id": "314b5546-45da-4c8f-a24c-b615265fbc32",
|
"id": HOST_GROUP_ID,
|
||||||
"name": "cinder-group",
|
"name": HOST_GROUP_NAME,
|
||||||
"host_ids": [
|
"host_ids": [
|
||||||
"5e37d335-8fff-4aee-840a-34749301a16a"
|
HOST_ID,
|
||||||
],
|
],
|
||||||
"tags": None,
|
"tags": None,
|
||||||
"comment": ""
|
"comment": ""
|
||||||
@ -413,18 +486,34 @@ class TatlinClientTest(TestCase):
|
|||||||
def test_get_host_group_id_success(self, send_request):
|
def test_get_host_group_id_success(self, send_request):
|
||||||
send_request.return_value = MockResponse(
|
send_request.return_value = MockResponse(
|
||||||
ALL_HOST_GROUP_RESP, codes.ok)
|
ALL_HOST_GROUP_RESP, codes.ok)
|
||||||
self.assertEqual(self.client.get_host_group_id('cinder-group'),
|
self.assertEqual(self.client.get_host_group_id(HOST_GROUP_NAME),
|
||||||
'314b5546-45da-4c8f-a24c-b615265fbc32')
|
HOST_GROUP_ID)
|
||||||
|
|
||||||
@mock.patch.object(TatlinClientCommon,
|
|
||||||
'is_volume_exists',
|
|
||||||
return_value=True)
|
|
||||||
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
||||||
def test_get_resource_ports_array(self, send_request, *args):
|
def test_get_volume_ports(self, send_request):
|
||||||
send_request.return_value = MockResponse(RES_PORTS_RESP, codes.ok)
|
send_request.return_value = MockResponse(
|
||||||
|
VOL_PORTS_RESP, requests.codes.ok)
|
||||||
|
self.assertEqual(VOL_PORTS_RESP, self.client.get_volume_ports(VOL_ID))
|
||||||
|
|
||||||
self.assertListEqual(self.client.get_resource_ports_array(VOL_ID),
|
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
||||||
["fc20"])
|
def test_get_volume_ports_negative(self, send_request):
|
||||||
|
send_request.return_value = MockResponse(
|
||||||
|
{}, requests.codes.internal_server_error)
|
||||||
|
self.assertRaises(VolumeBackendAPIException,
|
||||||
|
self.client.get_volume_ports,
|
||||||
|
VOL_ID)
|
||||||
|
|
||||||
|
@mock.patch.object(TatlinClientCommon, 'get_volume_ports')
|
||||||
|
def test_get_resource_ports_array_empty(self, vol_ports):
|
||||||
|
vol_ports.return_value = []
|
||||||
|
self.assertListEqual([], self.client.get_resource_ports_array(VOL_ID))
|
||||||
|
|
||||||
|
@mock.patch.object(TatlinClientCommon, 'get_volume_ports')
|
||||||
|
def test_get_resource_ports_array(self, vol_ports):
|
||||||
|
vol_ports.return_value = VOL_PORTS_RESP
|
||||||
|
self.assertListEqual(
|
||||||
|
['p00', 'p01', 'p10', 'p11'],
|
||||||
|
sorted(self.client.get_resource_ports_array(VOL_ID)))
|
||||||
|
|
||||||
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
||||||
def test_get_resource_mapping_negative(self, send_request):
|
def test_get_resource_mapping_negative(self, send_request):
|
||||||
@ -436,8 +525,8 @@ class TatlinClientTest(TestCase):
|
|||||||
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
||||||
def test_get_pool_id_by_name(self, send_request, *args):
|
def test_get_pool_id_by_name(self, send_request, *args):
|
||||||
send_request.return_value = MockResponse(POOL_LIST_RESPONCE, codes.ok)
|
send_request.return_value = MockResponse(POOL_LIST_RESPONCE, codes.ok)
|
||||||
self.assertEqual(self.client.get_pool_id_by_name('cinder-pool'),
|
self.assertEqual(self.client.get_pool_id_by_name(POOL_NAME),
|
||||||
'7e259486-deb8-4d11-8cb0-e2c5874aaa5e')
|
POOL_ID)
|
||||||
|
|
||||||
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
||||||
def test_get_all_hosts(self, send_request):
|
def test_get_all_hosts(self, send_request):
|
||||||
|
@ -391,7 +391,7 @@ class TatlinCommonVolumeDriverTest(TestCase):
|
|||||||
@mock.patch.object(TatlinClientCommon,
|
@mock.patch.object(TatlinClientCommon,
|
||||||
'is_volume_ready',
|
'is_volume_ready',
|
||||||
return_value=True)
|
return_value=True)
|
||||||
def test_wait_volume_reay_success(self, is_ready):
|
def test_wait_volume_ready_success(self, is_ready):
|
||||||
self.driver.wait_volume_ready(DummyVolume('cinder_volume'))
|
self.driver.wait_volume_ready(DummyVolume('cinder_volume'))
|
||||||
|
|
||||||
@mock.patch.object(TatlinCommonVolumeDriver, '_update_qos')
|
@mock.patch.object(TatlinCommonVolumeDriver, '_update_qos')
|
||||||
@ -505,7 +505,6 @@ class TatlinCommonVolumeDriverTest(TestCase):
|
|||||||
'62bbb941-ba4a-4101-927d-e527ce5ee011', '')
|
'62bbb941-ba4a-4101-927d-e527ce5ee011', '')
|
||||||
|
|
||||||
@mock.patch.object(TatlinCommonVolumeDriver, '_update_qos')
|
@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, 'add_vol_to_host')
|
||||||
@mock.patch.object(TatlinClientCommon,
|
@mock.patch.object(TatlinClientCommon,
|
||||||
'is_volume_exists',
|
'is_volume_exists',
|
||||||
|
366
cinder/tests/unit/volume/drivers/yadro/test_tatlin_fc.py
Normal file
366
cinder/tests/unit/volume/drivers/yadro/test_tatlin_fc.py
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from cinder import exception
|
||||||
|
from cinder.tests.unit.volume.drivers.yadro import test_tatlin_client as tc
|
||||||
|
from cinder.volume import configuration
|
||||||
|
from cinder.volume.drivers.yadro import tatlin_client
|
||||||
|
from cinder.volume.drivers.yadro import tatlin_common
|
||||||
|
from cinder.volume.drivers.yadro import tatlin_fc
|
||||||
|
from cinder.volume.drivers.yadro import tatlin_utils
|
||||||
|
|
||||||
|
|
||||||
|
FC_PORTS_RESP = [
|
||||||
|
{
|
||||||
|
"id": "fc-sp-0-1000145290000320",
|
||||||
|
"meta": {"tatlin-node": "sp-0", "type": "fc", "port-type": "active"},
|
||||||
|
"params": {"ifname": "fc40", "wwpn": "10:00:14:52:90:00:03:20"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fc-sp-0-1000145290000321",
|
||||||
|
"meta": {"tatlin-node": "sp-0", "type": "fc", "port-type": "active"},
|
||||||
|
"params": {"ifname": "fc41", "wwpn": "10:00:14:52:90:00:03:21"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fc-sp-0-1000145290000310",
|
||||||
|
"meta": {"tatlin-node": "sp-0", "type": "fc", "port-type": "active"},
|
||||||
|
"params": {"ifname": "fc20", "wwpn": "10:00:14:52:90:00:03:10"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fc-sp-0-1000145290000311",
|
||||||
|
"meta": {"tatlin-node": "sp-0", "type": "fc", "port-type": "active"},
|
||||||
|
"params": {"ifname": "fc21", "wwpn": "10:00:14:52:90:00:03:11"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fc-sp-1-1000145290000390",
|
||||||
|
"meta": {"tatlin-node": "sp-1", "type": "fc", "port-type": "active"},
|
||||||
|
"params": {"ifname": "fc20", "wwpn": "10:00:14:52:90:00:03:90"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fc-sp-1-1000145290000391",
|
||||||
|
"meta": {"tatlin-node": "sp-1", "type": "fc", "port-type": "active"},
|
||||||
|
"params": {"ifname": "fc21", "wwpn": "10:00:14:52:90:00:03:91"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fc-sp-1-10001452900003a0",
|
||||||
|
"meta": {"tatlin-node": "sp-1", "type": "fc", "port-type": "active"},
|
||||||
|
"params": {"ifname": "fc40", "wwpn": "10:00:14:52:90:00:03:a0"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fc-sp-1-10001452900003a1",
|
||||||
|
"meta": {"tatlin-node": "sp-1", "type": "fc", "port-type": "active"},
|
||||||
|
"params": {"ifname": "fc41", "wwpn": "10:00:14:52:90:00:03:a1"}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
FC_PORTS_PORTALS = {
|
||||||
|
'fc21': ['10:00:14:52:90:00:03:11', '10:00:14:52:90:00:03:91'],
|
||||||
|
'fc20': ['10:00:14:52:90:00:03:10', '10:00:14:52:90:00:03:90'],
|
||||||
|
}
|
||||||
|
|
||||||
|
FC_TARGET_WWNS = [
|
||||||
|
'1000145290000390',
|
||||||
|
'1000145290000311',
|
||||||
|
'1000145290000310',
|
||||||
|
'1000145290000391',
|
||||||
|
]
|
||||||
|
|
||||||
|
FC_VOL_PORTS_RESP = [
|
||||||
|
{
|
||||||
|
"port": "fc21",
|
||||||
|
"port_status": "healthy",
|
||||||
|
"running": ["sp-0", "sp-1"],
|
||||||
|
"wwn": ["10:00:14:52:90:00:03:11", "10:00:14:52:90:00:03:91"],
|
||||||
|
"lun_index": tc.LUN_ID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": "fc20",
|
||||||
|
"port_status": "healthy",
|
||||||
|
"running": ["sp-0", "sp-1"],
|
||||||
|
"wwn": ["10:00:14:52:90:00:03:10", "10:00:14:52:90:00:03:90"],
|
||||||
|
"lun_index": tc.LUN_ID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": "fc40",
|
||||||
|
"port_status": "healthy",
|
||||||
|
"running": ["sp-0", "sp-1"],
|
||||||
|
"wwn": ["10:00:14:52:90:00:03:09", "10:00:14:52:90:00:03:89"],
|
||||||
|
"lun_index": tc.LUN_ID,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
HOST_WWNS = [
|
||||||
|
'21000024ff7f35b7',
|
||||||
|
'21000024ff7f35b6',
|
||||||
|
]
|
||||||
|
|
||||||
|
INITIATOR_TARGET_MAP = {
|
||||||
|
'21000024ff7f35b7': FC_TARGET_WWNS,
|
||||||
|
'21000024ff7f35b6': FC_TARGET_WWNS,
|
||||||
|
}
|
||||||
|
|
||||||
|
FC_CONNECTOR = {'wwpns': HOST_WWNS, 'host': 'myhost'}
|
||||||
|
|
||||||
|
FC_CONNECTOR_2 = {'wwpns': ['123', '456'], 'host': 'myhost'}
|
||||||
|
|
||||||
|
VOLUME_DATA = {
|
||||||
|
'discard': False,
|
||||||
|
'target_discovered': True,
|
||||||
|
'target_lun': tc.LUN_ID,
|
||||||
|
'target_wwn': [
|
||||||
|
'10:00:14:52:90:00:03:11',
|
||||||
|
'10:00:14:52:90:00:03:91',
|
||||||
|
'10:00:14:52:90:00:03:10',
|
||||||
|
'10:00:14:52:90:00:03:90',
|
||||||
|
],
|
||||||
|
'initiator_target_map': INITIATOR_TARGET_MAP,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_fake_tatlin_config():
|
||||||
|
config = configuration.Configuration(
|
||||||
|
tatlin_common.tatlin_opts,
|
||||||
|
configuration.SHARED_CONF_GROUP)
|
||||||
|
config.san_ip = '127.0.0.1'
|
||||||
|
config.san_password = 'pwd'
|
||||||
|
config.san_login = 'admin'
|
||||||
|
config.pool_name = tc.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'
|
||||||
|
config.export_ports = 'fc20,fc21'
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
class TatlinFCVolumeDriverTest(unittest.TestCase):
|
||||||
|
@mock.patch.object(tatlin_utils.TatlinVolumeConnections,
|
||||||
|
'create_store')
|
||||||
|
@mock.patch.object(tatlin_client.TatlinAccessAPI,
|
||||||
|
'_authenticate_access')
|
||||||
|
def setUp(self, auth_access, create_store):
|
||||||
|
access_api = tatlin_client.TatlinAccessAPI(
|
||||||
|
'127.0.0.1', '443', 'user', 'passwd', False)
|
||||||
|
access_api._authenticate_access = mock.MagicMock()
|
||||||
|
self.client = tatlin_client.TatlinClientCommon(
|
||||||
|
access_api, api_retry_count=1, wait_interval=1, wait_retry_count=1)
|
||||||
|
mock.patch.object(tatlin_client.TatlinAccessAPI,
|
||||||
|
'_authenticate_access')
|
||||||
|
self.driver = tatlin_fc.TatlinFCVolumeDriver(
|
||||||
|
configuration=get_fake_tatlin_config())
|
||||||
|
self.driver._get_tatlin_client = mock.MagicMock()
|
||||||
|
self.driver._get_tatlin_client.return_value = self.client
|
||||||
|
self.driver.do_setup(None)
|
||||||
|
|
||||||
|
@mock.patch.object(tatlin_fc.fczm_utils, 'add_fc_zone')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'_is_cinder_host_connection')
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'_create_volume_data')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'_find_mapped_lun')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'add_volume_to_host')
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'find_current_host')
|
||||||
|
def test_initialize_connection(self,
|
||||||
|
find_current_host,
|
||||||
|
add_volume_to_host,
|
||||||
|
find_mapped_lun,
|
||||||
|
create_volume_data,
|
||||||
|
is_cinder_connection,
|
||||||
|
add_fc_zone):
|
||||||
|
find_current_host.return_value = tc.HOST_ID
|
||||||
|
find_mapped_lun.return_value = tc.LUN_ID
|
||||||
|
is_cinder_connection.return_value = False
|
||||||
|
create_volume_data.return_value = VOLUME_DATA
|
||||||
|
volume = tc.DummyVolume(tc.VOL_ID)
|
||||||
|
connector = FC_CONNECTOR
|
||||||
|
data = self.driver.initialize_connection(volume, FC_CONNECTOR)
|
||||||
|
self.assertDictEqual(
|
||||||
|
data,
|
||||||
|
{'driver_volume_type': 'fibre_channel', 'data': VOLUME_DATA}
|
||||||
|
)
|
||||||
|
find_current_host.assert_called_once()
|
||||||
|
add_volume_to_host.assert_called_once_with(volume, tc.HOST_ID)
|
||||||
|
is_cinder_connection.assert_called_once_with(connector)
|
||||||
|
create_volume_data.assert_called_once_with(volume, connector)
|
||||||
|
add_fc_zone.assert_called_once_with(data)
|
||||||
|
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'_create_volume_data')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'add_volume_to_host')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'_find_mapped_lun')
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'find_current_host')
|
||||||
|
@mock.patch.object(tatlin_utils.TatlinVolumeConnections,
|
||||||
|
'increment')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'_is_cinder_host_connection')
|
||||||
|
def test_initialize_connection_cinder_attachement(self,
|
||||||
|
is_cinder_connection,
|
||||||
|
increment, *args):
|
||||||
|
is_cinder_connection.return_value = True
|
||||||
|
volume = tc.DummyVolume(tc.VOL_ID)
|
||||||
|
self.driver.initialize_connection(volume, FC_CONNECTOR)
|
||||||
|
is_cinder_connection.assert_called_once_with(FC_CONNECTOR)
|
||||||
|
increment.assert_called_once_with(tc.VOL_ID)
|
||||||
|
|
||||||
|
@mock.patch.object(tatlin_client.TatlinClientCommon,
|
||||||
|
'get_port_portal')
|
||||||
|
def test_get_ports_portals(self, get_port_portal):
|
||||||
|
get_port_portal.return_value = FC_PORTS_RESP
|
||||||
|
pp = self.driver._get_ports_portals()
|
||||||
|
self.assertDictEqual(pp, FC_PORTS_PORTALS)
|
||||||
|
|
||||||
|
@mock.patch.object(tatlin_client.TatlinClientCommon,
|
||||||
|
'get_all_hosts')
|
||||||
|
def test_find_current_host(self, get_all_hosts):
|
||||||
|
get_all_hosts.return_value = tc.ALL_HOSTS_RESP
|
||||||
|
host_id = self.driver.find_current_host(FC_CONNECTOR)
|
||||||
|
self.assertEqual(host_id, tc.HOST_ID)
|
||||||
|
|
||||||
|
@mock.patch.object(tatlin_client.TatlinClientCommon,
|
||||||
|
'get_all_hosts')
|
||||||
|
def test_find_current_host_not_found(self,
|
||||||
|
get_all_hosts):
|
||||||
|
get_all_hosts.return_value = tc.ALL_HOSTS_RESP
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.driver.find_current_host, FC_CONNECTOR_2)
|
||||||
|
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'_build_initiator_target_map')
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'_get_ports_portals')
|
||||||
|
@mock.patch.object(tatlin_client.TatlinClientCommon,
|
||||||
|
'get_volume_ports')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'_find_mapped_lun')
|
||||||
|
def test_create_volume_data(self,
|
||||||
|
find_lun,
|
||||||
|
volume_ports,
|
||||||
|
ports_portals,
|
||||||
|
build_map):
|
||||||
|
find_lun.return_value = tc.LUN_ID
|
||||||
|
volume_ports.return_value = FC_VOL_PORTS_RESP
|
||||||
|
ports_portals.return_value = FC_PORTS_PORTALS
|
||||||
|
build_map.return_value = INITIATOR_TARGET_MAP
|
||||||
|
volume = tc.DummyVolume(tc.VOL_ID)
|
||||||
|
connector = FC_CONNECTOR
|
||||||
|
data = self.driver._create_volume_data(volume, connector)
|
||||||
|
self.assertEqual(data['target_lun'], tc.LUN_ID)
|
||||||
|
self.assertEqual(sorted(data['target_wwn']), sorted(FC_TARGET_WWNS))
|
||||||
|
self.assertDictEqual(data['initiator_target_map'],
|
||||||
|
INITIATOR_TARGET_MAP)
|
||||||
|
|
||||||
|
@mock.patch.object(tatlin_fc.fczm_utils, 'remove_fc_zone')
|
||||||
|
@mock.patch.object(tatlin_client.TatlinClientCommon,
|
||||||
|
'get_resource_mapping')
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'_create_volume_data')
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'find_current_host')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'_is_cinder_host_connection')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'remove_volume_from_host')
|
||||||
|
def test_terminate_connection(self,
|
||||||
|
remove_host,
|
||||||
|
is_cinder,
|
||||||
|
find_host,
|
||||||
|
create_data,
|
||||||
|
resource_mapping,
|
||||||
|
remove_fc_zone):
|
||||||
|
is_cinder.return_value = True
|
||||||
|
find_host.return_value = tc.HOST_ID
|
||||||
|
resource_mapping.return_value = tc.RES_MAPPING_RESP
|
||||||
|
volume = tc.DummyVolume(tc.VOL_ID)
|
||||||
|
connector = FC_CONNECTOR
|
||||||
|
self.driver.terminate_connection(volume, connector)
|
||||||
|
remove_host.assert_called_once_with(volume, tc.HOST_ID)
|
||||||
|
remove_fc_zone.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch.object(tatlin_fc.fczm_utils, 'remove_fc_zone')
|
||||||
|
@mock.patch.object(tatlin_client.TatlinClientCommon,
|
||||||
|
'get_resource_mapping')
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'_create_volume_data')
|
||||||
|
@mock.patch.object(tatlin_fc.TatlinFCVolumeDriver,
|
||||||
|
'find_current_host')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'_is_cinder_host_connection')
|
||||||
|
@mock.patch.object(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
'remove_volume_from_host')
|
||||||
|
def test_terminate_connection_with_zone_removal(self,
|
||||||
|
remove_host,
|
||||||
|
is_cinder,
|
||||||
|
find_host,
|
||||||
|
create_data,
|
||||||
|
resource_mapping,
|
||||||
|
remove_fc_zone):
|
||||||
|
is_cinder.return_value = True
|
||||||
|
find_host.return_value = tc.HOST_ID_2
|
||||||
|
resource_mapping.side_effect = [
|
||||||
|
tc.RES_MAPPING_RESP,
|
||||||
|
tc.RES_MAPPING_RESP2,
|
||||||
|
]
|
||||||
|
create_data.return_value = VOLUME_DATA
|
||||||
|
volume = tc.DummyVolume(tc.VOL_ID)
|
||||||
|
connector = FC_CONNECTOR
|
||||||
|
self.driver.terminate_connection(volume, connector)
|
||||||
|
remove_host.assert_called_once_with(volume, tc.HOST_ID_2)
|
||||||
|
remove_fc_zone.assert_called_once_with({
|
||||||
|
'driver_volume_type': 'fibre_channel',
|
||||||
|
'data': VOLUME_DATA,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_build_initiator_target_map(self):
|
||||||
|
self.driver._lookup_service = None
|
||||||
|
connector = FC_CONNECTOR
|
||||||
|
targets = FC_TARGET_WWNS
|
||||||
|
itmap = self.driver._build_initiator_target_map(targets, connector)
|
||||||
|
self.assertListEqual(sorted(itmap.keys()),
|
||||||
|
sorted(INITIATOR_TARGET_MAP.keys()))
|
||||||
|
for initiator in itmap:
|
||||||
|
self.assertListEqual(sorted(itmap[initiator]),
|
||||||
|
sorted(INITIATOR_TARGET_MAP[initiator]))
|
||||||
|
|
||||||
|
def test_build_initiator_target_map_with_lookup(self):
|
||||||
|
lookup_service = mock.MagicMock()
|
||||||
|
lookup_service.get_device_mapping_from_network.return_value = {
|
||||||
|
'san-1': {
|
||||||
|
'initiator_port_wwn_list': HOST_WWNS,
|
||||||
|
'target_port_wwn_list': FC_TARGET_WWNS,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.driver._lookup_service = lookup_service
|
||||||
|
connector = FC_CONNECTOR
|
||||||
|
targets = FC_TARGET_WWNS
|
||||||
|
itmap = self.driver._build_initiator_target_map(targets, connector)
|
||||||
|
self.assertListEqual(sorted(itmap.keys()),
|
||||||
|
sorted(INITIATOR_TARGET_MAP.keys()))
|
||||||
|
for initiator in itmap:
|
||||||
|
self.assertListEqual(sorted(itmap[initiator]),
|
||||||
|
sorted(INITIATOR_TARGET_MAP[initiator]))
|
||||||
|
lookup_service.get_device_mapping_from_network.assert_called_once_with(
|
||||||
|
connector['wwpns'], targets)
|
@ -312,7 +312,7 @@ class TatlinISCSIVolumeDriverTest(TestCase):
|
|||||||
(MockResponse(ISCSI_HOST_INFO, 200)),
|
(MockResponse(ISCSI_HOST_INFO, 200)),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.driver.find_current_host(
|
self.assertEqual(self.driver.find_current_host(
|
||||||
'iqn.1994-05.com.redhat:4e5d7ab85a4c'),
|
{'initiator': 'iqn.1994-05.com.redhat:4e5d7ab85a4c'}),
|
||||||
'5e37d335-8fff-4aee-840a-34749301a16a')
|
'5e37d335-8fff-4aee-840a-34749301a16a')
|
||||||
|
|
||||||
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
@mock.patch.object(TatlinAccessAPI, 'send_request')
|
||||||
|
@ -35,6 +35,7 @@ from cinder.volume import qos_specs
|
|||||||
from cinder.volume import volume_types
|
from cinder.volume import volume_types
|
||||||
from cinder.volume import volume_utils
|
from cinder.volume import volume_utils
|
||||||
from cinder.volume.volume_utils import brick_get_connector_properties
|
from cinder.volume.volume_utils import brick_get_connector_properties
|
||||||
|
from cinder.zonemanager import utils as fczm_utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -372,32 +373,50 @@ class TatlinCommonVolumeDriver(driver.VolumeDriver, object):
|
|||||||
ignore_errors=ignore_errors)
|
ignore_errors=ignore_errors)
|
||||||
_do_detach_volume()
|
_do_detach_volume()
|
||||||
|
|
||||||
|
@volume_utils.trace
|
||||||
|
def initialize_connection(self, volume, connector):
|
||||||
|
@utils.synchronized("tatlin-volume-connections-%s" % volume.name_id)
|
||||||
|
def _initialize_connection():
|
||||||
|
LOG.debug('Init %s with connector %s', volume.name_id, connector)
|
||||||
|
current_host = self.find_current_host(connector)
|
||||||
|
self.add_volume_to_host(volume, current_host)
|
||||||
|
if self._is_cinder_host_connection(connector):
|
||||||
|
self._connections.increment(volume.name_id)
|
||||||
|
connection_info = self._create_connection_info(volume, connector)
|
||||||
|
fczm_utils.add_fc_zone(connection_info)
|
||||||
|
return connection_info
|
||||||
|
return _initialize_connection()
|
||||||
|
|
||||||
@volume_utils.trace
|
@volume_utils.trace
|
||||||
def terminate_connection(self, volume, connector, **kwargs):
|
def terminate_connection(self, volume, connector, **kwargs):
|
||||||
@utils.synchronized("tatlin-volume-connections-%s" % volume.name_id)
|
@utils.synchronized("tatlin-volume-connections-%s" % volume.name_id)
|
||||||
def _terminate_connection():
|
def _terminate_connection():
|
||||||
LOG.debug('Terminate connection for %s with connector %s',
|
LOG.debug('Terminate connection for %s with connector %s',
|
||||||
volume.name_id, connector)
|
volume.name_id, connector)
|
||||||
|
connection_info = self._create_connection_info(volume, connector)
|
||||||
if not connector:
|
if not connector:
|
||||||
return
|
self.remove_volume_from_all_hosts(volume)
|
||||||
|
return connection_info
|
||||||
if self._is_cinder_host_connection(connector):
|
if self._is_cinder_host_connection(connector):
|
||||||
connections = self._connections.decrement(volume.name_id)
|
connections = self._connections.decrement(volume.name_id)
|
||||||
if connections > 0:
|
if connections > 0:
|
||||||
LOG.debug('Not terminating connection: '
|
LOG.debug('Not terminating connection: '
|
||||||
'volume %s, existing connections: %s',
|
'volume %s, existing connections: %s',
|
||||||
volume.name_id, connections)
|
volume.name_id, connections)
|
||||||
return
|
return connection_info
|
||||||
hostname = connector['host']
|
hostname = connector['host']
|
||||||
if self._is_nova_multiattached(volume, hostname):
|
if self._is_nova_multiattached(volume, hostname):
|
||||||
LOG.debug('Volume %s is attached on host %s to multiple VMs.'
|
LOG.debug('Volume %s is attached on host %s to multiple VMs.'
|
||||||
' Not terminating connection', volume.name_id,
|
' Not terminating connection', volume.name_id,
|
||||||
hostname)
|
hostname)
|
||||||
return
|
return connection_info
|
||||||
|
host_id = self.find_current_host(connector)
|
||||||
host = self.find_current_host(connector['initiator'])
|
self.remove_volume_from_host(volume, host_id)
|
||||||
LOG.debug('Terminate connection volume %s removing from host %s',
|
resources = [r for r in self.tatlin_api.get_resource_mapping()
|
||||||
volume.name_id, host)
|
if r.get('host_id', '') == host_id]
|
||||||
self.remove_volume_from_host(volume, host)
|
if not resources:
|
||||||
|
fczm_utils.remove_fc_zone(connection_info)
|
||||||
|
return connection_info
|
||||||
_terminate_connection()
|
_terminate_connection()
|
||||||
|
|
||||||
def _is_cinder_host_connection(self, connector):
|
def _is_cinder_host_connection(self, connector):
|
||||||
@ -673,6 +692,13 @@ class TatlinCommonVolumeDriver(driver.VolumeDriver, object):
|
|||||||
def remove_volume_from_host(self, volume, host_id):
|
def remove_volume_from_host(self, volume, host_id):
|
||||||
self.tatlin_api.remove_vol_from_host(volume.name_id, host_id)
|
self.tatlin_api.remove_vol_from_host(volume.name_id, host_id)
|
||||||
|
|
||||||
|
def remove_volume_from_all_hosts(self, volume):
|
||||||
|
mappings = self.tatlin_api.get_resource_mapping()
|
||||||
|
hosts = [m['host_id'] for m in mappings
|
||||||
|
if 'resource_id' in m and m['resource_id'] == volume.name_id]
|
||||||
|
for host_id in hosts:
|
||||||
|
self.tatlin_api.remove_vol_from_host(volume.name_id, host_id)
|
||||||
|
|
||||||
def _is_port_assigned(self, volume_id, port):
|
def _is_port_assigned(self, volume_id, port):
|
||||||
LOG.debug('VOLUME %s: Checking port %s ', volume_id, port)
|
LOG.debug('VOLUME %s: Checking port %s ', volume_id, port)
|
||||||
cur_ports = self.tatlin_api.get_resource_ports_array(volume_id)
|
cur_ports = self.tatlin_api.get_resource_ports_array(volume_id)
|
||||||
@ -684,20 +710,19 @@ class TatlinCommonVolumeDriver(driver.VolumeDriver, object):
|
|||||||
def _get_ports_portals(self):
|
def _get_ports_portals(self):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _find_mapped_lun(self, volume_id, iqn):
|
def _create_connection_info(self, volume, connector):
|
||||||
host_id = self.find_current_host(iqn)
|
return {}
|
||||||
|
|
||||||
|
def _find_mapped_lun(self, volume_id, connector):
|
||||||
|
host_id = self.find_current_host(connector)
|
||||||
result = self.tatlin_api.get_resource_mapping()
|
result = self.tatlin_api.get_resource_mapping()
|
||||||
for r in result:
|
for r in result:
|
||||||
if 'host_id' in r:
|
if 'host_id' in r:
|
||||||
if r['resource_id'] == volume_id and r['host_id'] == host_id:
|
if r['resource_id'] == volume_id and r['host_id'] == host_id:
|
||||||
LOG.debug('Current mapped lun record %s volume_id: %s '
|
|
||||||
'host_id: is %s', r, volume_id, host_id)
|
|
||||||
return r['mapped_lun_id']
|
return r['mapped_lun_id']
|
||||||
|
|
||||||
mess = (_('Unable to get mapped lun for volume %s on host %s') %
|
mess = (_('Unable to get mapped lun for volume %s on host %s') %
|
||||||
(volume_id, host_id))
|
(volume_id, host_id))
|
||||||
LOG.error(mess)
|
LOG.error(mess)
|
||||||
|
|
||||||
raise exception.VolumeBackendAPIException(message=mess)
|
raise exception.VolumeBackendAPIException(message=mess)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -727,7 +752,7 @@ class TatlinCommonVolumeDriver(driver.VolumeDriver, object):
|
|||||||
wait_interval=self._wait_interval,
|
wait_interval=self._wait_interval,
|
||||||
wait_retry_count=self._wait_retry_count)
|
wait_retry_count=self._wait_retry_count)
|
||||||
|
|
||||||
def find_current_host(self, wwn):
|
def find_current_host(self, connector):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
124
cinder/volume/drivers/yadro/tatlin_fc.py
Normal file
124
cinder/volume/drivers/yadro/tatlin_fc.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
# Copyright, 2023, KNS Group LLC (YADRO)
|
||||||
|
#
|
||||||
|
# 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 oslo_log import log as logging
|
||||||
|
|
||||||
|
from cinder.common import constants
|
||||||
|
from cinder import exception
|
||||||
|
from cinder.i18n import _
|
||||||
|
from cinder import interface
|
||||||
|
from cinder.volume import driver
|
||||||
|
from cinder.volume.drivers.yadro import tatlin_common
|
||||||
|
from cinder.zonemanager import utils as fczm_utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@interface.volumedriver
|
||||||
|
class TatlinFCVolumeDriver(tatlin_common.TatlinCommonVolumeDriver,
|
||||||
|
driver.FibreChannelDriver):
|
||||||
|
"""ACCESS Tatlin FC Driver.
|
||||||
|
|
||||||
|
Executes commands relating to FC.
|
||||||
|
Supports creation of volumes.
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
API version history:
|
||||||
|
|
||||||
|
1.0 - Initial version.
|
||||||
|
"""
|
||||||
|
|
||||||
|
VERSION = '1.0'
|
||||||
|
|
||||||
|
SUPPORTS_ACTIVE_ACTIVE = True
|
||||||
|
|
||||||
|
# ThirdPartySystems wiki
|
||||||
|
CI_WIKI_NAME = "Yadro_Tatlin_Unified_CI"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(TatlinFCVolumeDriver, self).__init__(*args, **kwargs)
|
||||||
|
self.backend_name = self.configuration.safe_get(
|
||||||
|
'volume_backend_name') or 'TatlinFC'
|
||||||
|
self.DRIVER_VOLUME_TYPE = constants.FC
|
||||||
|
self._lookup_service = fczm_utils.create_lookup_service()
|
||||||
|
|
||||||
|
def _create_connection_info(self, volume, connector):
|
||||||
|
info = {
|
||||||
|
'driver_volume_type': constants.FC_VARIANT_1,
|
||||||
|
'data': self._create_volume_data(volume, connector)
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
|
||||||
|
def _get_ports_portals(self):
|
||||||
|
result = self.tatlin_api.get_port_portal("fc")
|
||||||
|
ports = {}
|
||||||
|
for p in result:
|
||||||
|
iface = p['params']['ifname']
|
||||||
|
if self._export_ports and iface not in self._export_ports:
|
||||||
|
continue
|
||||||
|
ports.setdefault(iface, []).append(p['params']['wwpn'])
|
||||||
|
return ports
|
||||||
|
|
||||||
|
def _create_volume_data(self, volume, connector):
|
||||||
|
if connector is None:
|
||||||
|
return {}
|
||||||
|
lun_id = self._find_mapped_lun(volume.name_id, connector)
|
||||||
|
volume_ports = self.tatlin_api.get_volume_ports(volume.name_id)
|
||||||
|
ports_portals = self._get_ports_portals()
|
||||||
|
data = {
|
||||||
|
'target_discovered': True,
|
||||||
|
'target_lun': lun_id,
|
||||||
|
'discard': False,
|
||||||
|
}
|
||||||
|
target_wwns = []
|
||||||
|
for port in volume_ports:
|
||||||
|
wwpns = ports_portals.get(port['port'])
|
||||||
|
if not wwpns:
|
||||||
|
continue
|
||||||
|
target_wwns += [w.replace(':', '') for w in wwpns]
|
||||||
|
|
||||||
|
data['target_wwn'] = target_wwns
|
||||||
|
data['initiator_target_map'] = self._build_initiator_target_map(
|
||||||
|
target_wwns, connector)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def find_current_host(self, connector):
|
||||||
|
wwns = connector['wwpns']
|
||||||
|
LOG.debug('Try to find host id for %s', wwns)
|
||||||
|
result = self.tatlin_api.get_all_hosts()
|
||||||
|
for h in result:
|
||||||
|
for wwn in h['initiators']:
|
||||||
|
if wwn.replace(':', '') in wwns:
|
||||||
|
LOG.debug('Current host is %s', h['id'])
|
||||||
|
return h['id']
|
||||||
|
message = _('Unable to get host information for wwns: %s') % str(wwns)
|
||||||
|
LOG.error(message)
|
||||||
|
raise exception.VolumeBackendAPIException(message=message)
|
||||||
|
|
||||||
|
def _build_initiator_target_map(self, target_wwns, connector):
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
if self._lookup_service:
|
||||||
|
mapping = self._lookup_service.get_device_mapping_from_network(
|
||||||
|
connector['wwpns'], target_wwns)
|
||||||
|
for fabric in mapping.values():
|
||||||
|
for initiator in fabric['initiator_port_wwn_list']:
|
||||||
|
result.setdefault(initiator, []).extend(
|
||||||
|
fabric['target_port_wwn_list'])
|
||||||
|
result = {i: list(set(t)) for i, t in result.items()}
|
||||||
|
else:
|
||||||
|
result = dict.fromkeys(connector['wwpns'], target_wwns)
|
||||||
|
|
||||||
|
return result
|
@ -18,11 +18,9 @@ from oslo_log import log as logging
|
|||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import interface
|
from cinder import interface
|
||||||
from cinder import utils
|
|
||||||
from cinder.volume import driver
|
from cinder.volume import driver
|
||||||
from cinder.volume.drivers.yadro.tatlin_common import TatlinCommonVolumeDriver
|
from cinder.volume.drivers.yadro.tatlin_common import TatlinCommonVolumeDriver
|
||||||
from cinder.volume.drivers.yadro.tatlin_exception import TatlinAPIException
|
from cinder.volume.drivers.yadro.tatlin_exception import TatlinAPIException
|
||||||
from cinder.volume import volume_utils
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -39,9 +37,10 @@ class TatlinISCSIVolumeDriver(TatlinCommonVolumeDriver, driver.ISCSIDriver):
|
|||||||
API version history:
|
API version history:
|
||||||
|
|
||||||
1.0 - Initial version.
|
1.0 - Initial version.
|
||||||
|
1.1 - Common code sharing with FC driver
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = "1.0"
|
VERSION = "1.1"
|
||||||
|
|
||||||
SUPPORTS_ACTIVE_ACTIVE = True
|
SUPPORTS_ACTIVE_ACTIVE = True
|
||||||
|
|
||||||
@ -56,31 +55,12 @@ class TatlinISCSIVolumeDriver(TatlinCommonVolumeDriver, driver.ISCSIDriver):
|
|||||||
'volume_backend_name') or 'TatlinISCSI')
|
'volume_backend_name') or 'TatlinISCSI')
|
||||||
self.DRIVER_VOLUME_TYPE = 'iSCSI'
|
self.DRIVER_VOLUME_TYPE = 'iSCSI'
|
||||||
|
|
||||||
@volume_utils.trace
|
def _create_connection_info(self, volume, connector):
|
||||||
def initialize_connection(self, volume, connector):
|
info = {
|
||||||
@utils.synchronized("tatlin-volume-connections-%s" % volume.name_id)
|
'driver_volume_type': 'iscsi',
|
||||||
def _initialize_connection():
|
'data': self._create_volume_data(volume, connector)
|
||||||
LOG.debug('Init %s with connector %s', volume.name_id, connector)
|
}
|
||||||
eth_ports = self._get_ports_portals()
|
return info
|
||||||
current_host = self.find_current_host(connector['initiator'])
|
|
||||||
self.add_volume_to_host(volume, current_host)
|
|
||||||
mapped_lun = self._find_mapped_lun(
|
|
||||||
volume.name_id, connector['initiator'])
|
|
||||||
port_result = self.tatlin_api.get_volume_ports(volume.name_id)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'driver_volume_type': 'iscsi',
|
|
||||||
'data': self._create_volume_data(port_result, eth_ports,
|
|
||||||
mapped_lun)
|
|
||||||
}
|
|
||||||
|
|
||||||
if self._is_cinder_host_connection(connector):
|
|
||||||
self._connections.increment(volume.name_id)
|
|
||||||
|
|
||||||
LOG.debug('Current connection info %s', result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
return _initialize_connection()
|
|
||||||
|
|
||||||
def _get_ports_portals(self):
|
def _get_ports_portals(self):
|
||||||
try:
|
try:
|
||||||
@ -105,70 +85,56 @@ class TatlinISCSIVolumeDriver(TatlinCommonVolumeDriver, driver.ISCSIDriver):
|
|||||||
|
|
||||||
return ports
|
return ports
|
||||||
|
|
||||||
def _create_volume_data(self, port_inf, eth_ports, lun_id):
|
def _create_volume_data(self, volume, connector):
|
||||||
|
if connector is None:
|
||||||
|
return {}
|
||||||
|
eth_ports = self._get_ports_portals()
|
||||||
|
lun_id = self._find_mapped_lun(volume.name_id, connector)
|
||||||
|
vol_ports = self.tatlin_api.get_volume_ports(volume.name_id)
|
||||||
res = {'target_discovered': True, 'target_lun': lun_id}
|
res = {'target_discovered': True, 'target_lun': lun_id}
|
||||||
|
if self._auth_method == 'CHAP':
|
||||||
tatlin_version = self.tatlin_api.get_tatlin_version()
|
|
||||||
|
|
||||||
if tatlin_version > (2, 3):
|
|
||||||
if self._auth_method == 'CHAP':
|
|
||||||
res['auth_method'] = 'CHAP'
|
|
||||||
res['auth_username'] = self._chap_username
|
|
||||||
res['auth_password'] = self._chap_password
|
|
||||||
else:
|
|
||||||
cred = self.tatlin_api.get_iscsi_cred()
|
|
||||||
res['auth_method'] = 'CHAP'
|
res['auth_method'] = 'CHAP'
|
||||||
res['auth_username'] = cred['userid']
|
res['auth_username'] = self._chap_username
|
||||||
res['auth_password'] = cred['password']
|
res['auth_password'] = self._chap_password
|
||||||
|
|
||||||
target_luns = []
|
target_luns = []
|
||||||
target_iqns = []
|
target_iqns = []
|
||||||
target_portals = []
|
target_portals = []
|
||||||
LOG.debug('Port data: %s', port_inf)
|
for port in vol_ports:
|
||||||
for port in port_inf:
|
|
||||||
if port['port'] not in eth_ports.keys():
|
if port['port'] not in eth_ports.keys():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ips = eth_ports[port['port']]
|
ips = eth_ports[port['port']]
|
||||||
target_portals += ips
|
target_portals += ips
|
||||||
|
|
||||||
luns = [lun_id for _ in ips]
|
luns = [lun_id for _ in ips]
|
||||||
target_luns += luns
|
target_luns += luns
|
||||||
|
|
||||||
if 'running' in port:
|
if 'running' in port:
|
||||||
target_iqns += port['wwn'] * len(port['running'])
|
target_iqns += port['wwn'] * len(port['running'])
|
||||||
else:
|
else:
|
||||||
target_iqns += port['wwn']
|
target_iqns += port['wwn']
|
||||||
|
|
||||||
if not target_portals or not target_iqns or not target_luns:
|
if not target_portals or not target_iqns or not target_luns:
|
||||||
message = (_('Not enough connection data, '
|
message = (_('Not enough connection data, '
|
||||||
'luns: %s, portals: %s, iqns: %s') %
|
'luns: %s, portals: %s, iqns: %s') %
|
||||||
target_luns, target_portals, target_iqns)
|
target_luns, target_portals, target_iqns)
|
||||||
LOG.error(message)
|
LOG.error(message)
|
||||||
raise exception.VolumeBackendAPIException(message=message)
|
raise exception.VolumeBackendAPIException(message=message)
|
||||||
|
|
||||||
res['target_lun'] = target_luns[0]
|
res['target_lun'] = target_luns[0]
|
||||||
res['target_luns'] = target_luns
|
res['target_luns'] = target_luns
|
||||||
res['target_iqn'] = target_iqns[0]
|
res['target_iqn'] = target_iqns[0]
|
||||||
res['target_iqns'] = target_iqns
|
res['target_iqns'] = target_iqns
|
||||||
res['target_portal'] = target_portals[0]
|
res['target_portal'] = target_portals[0]
|
||||||
res['target_portals'] = target_portals
|
res['target_portals'] = target_portals
|
||||||
|
|
||||||
LOG.debug("Volume data = %s", res)
|
LOG.debug("Volume data = %s", res)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def find_current_host(self, wwn):
|
def find_current_host(self, connector):
|
||||||
LOG.debug('Try to find host id for %s', wwn)
|
iqn = connector['initiator']
|
||||||
|
LOG.debug('Try to find host id for %s', iqn)
|
||||||
gr_id = self.tatlin_api.get_host_group_id(self._host_group)
|
gr_id = self.tatlin_api.get_host_group_id(self._host_group)
|
||||||
|
|
||||||
group_info = self.tatlin_api.get_host_group_info(gr_id)
|
group_info = self.tatlin_api.get_host_group_info(gr_id)
|
||||||
LOG.debug('Group info for %s is %s', self._host_group, group_info)
|
LOG.debug('Group info for %s is %s', self._host_group, group_info)
|
||||||
for host_id in group_info['host_ids']:
|
for host_id in group_info['host_ids']:
|
||||||
if wwn in self.tatlin_api.get_host_info(host_id)['initiators']:
|
if iqn in self.tatlin_api.get_host_info(host_id)['initiators']:
|
||||||
LOG.debug('Found host %s for initiator %s', host_id, wwn)
|
LOG.debug('Found host %s for initiator %s', host_id, iqn)
|
||||||
return host_id
|
return host_id
|
||||||
|
message = _('Unable to find host for initiator %s' % iqn)
|
||||||
mess = _('Unable to find host for initiator %s' % wwn)
|
LOG.error(message)
|
||||||
LOG.error(mess)
|
raise exception.VolumeBackendAPIException(message=message)
|
||||||
raise exception.VolumeBackendAPIException(message=mess)
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
YADRO Cinder Driver
|
YADRO Cinder Driver
|
||||||
============================
|
============================
|
||||||
|
|
||||||
YADRO Cinder driver provides iSCSI support for
|
YADRO Cinder driver provides iSCSI and FC support for
|
||||||
TATLIN.UNIFIED storages.
|
TATLIN.UNIFIED storages.
|
||||||
|
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ details about each setting, see the user's guide of the storage system.
|
|||||||
|
|
||||||
#. Ports
|
#. Ports
|
||||||
|
|
||||||
Setup data ETH ports you want to export volumes to.
|
Setup Ethernet or FC ports you want to export volumes to.
|
||||||
|
|
||||||
#. Hosts
|
#. Hosts
|
||||||
|
|
||||||
@ -87,6 +87,22 @@ Add the following configuration to ``/etc/cinder/cinder.conf``:
|
|||||||
chap_username=<chap_username>
|
chap_username=<chap_username>
|
||||||
chap_password=<chap_password>
|
chap_password=<chap_password>
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[fc-1]
|
||||||
|
volume_driver=cinder.volume.drivers.yadro.tatlin_fc.TatlinFCVolumeDriver
|
||||||
|
san_ip=<management_ip>
|
||||||
|
san_login=<login>
|
||||||
|
san_password=<password>
|
||||||
|
tat_api_retry_count=<count>
|
||||||
|
api_port=<management_port>
|
||||||
|
pool_name=<cinder_volumes_pool>
|
||||||
|
export_ports=<port1>,<port2>
|
||||||
|
host_group=<name>
|
||||||
|
max_resource_count=<count>
|
||||||
|
|
||||||
``volume_driver``
|
``volume_driver``
|
||||||
Volume driver name.
|
Volume driver name.
|
||||||
|
|
||||||
|
@ -223,7 +223,7 @@ title=Windows iSCSI Driver
|
|||||||
title=Windows SMB Driver
|
title=Windows SMB Driver
|
||||||
|
|
||||||
[driver.yadro]
|
[driver.yadro]
|
||||||
title=Yadro Tatlin Unified Driver (iSCSI)
|
title=Yadro Tatlin Unified Driver (iSCSI, FC)
|
||||||
|
|
||||||
[driver.zadara]
|
[driver.zadara]
|
||||||
title=Zadara Storage Driver (iSCSI, NFS)
|
title=Zadara Storage Driver (iSCSI, NFS)
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Yadro Tatlin Unified: Added initial version of the FC driver.
|
Loading…
Reference in New Issue
Block a user