Merge "Add multiattach in Nimble driver"
This commit is contained in:
commit
0ca496fc48
@ -17,12 +17,14 @@
|
|||||||
import sys
|
import sys
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from oslo_utils import uuidutils
|
||||||
from six.moves import http_client
|
from six.moves import http_client
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.objects import volume as obj_volume
|
from cinder.objects import volume as obj_volume
|
||||||
from cinder.tests.unit import fake_constants as fake
|
from cinder.tests.unit import fake_constants as fake
|
||||||
|
from cinder.tests.unit import fake_volume
|
||||||
from cinder.tests.unit import test
|
from cinder.tests.unit import test
|
||||||
from cinder.volume.drivers import nimble
|
from cinder.volume.drivers import nimble
|
||||||
from cinder.volume import volume_types
|
from cinder.volume import volume_types
|
||||||
@ -83,6 +85,9 @@ FAKE_CREATE_VOLUME_POSITIVE_RESPONSE_QOS = {
|
|||||||
'clone': False,
|
'clone': False,
|
||||||
'name': "testvolume-qos"}
|
'name': "testvolume-qos"}
|
||||||
|
|
||||||
|
FAKE_EXTRA_SPECS = {'multiattach': '<is> True',
|
||||||
|
'nimble:iops-limit': '1024'}
|
||||||
|
|
||||||
FAKE_GET_VOL_INFO_RESPONSE = {'name': 'testvolume',
|
FAKE_GET_VOL_INFO_RESPONSE = {'name': 'testvolume',
|
||||||
'clone': False,
|
'clone': False,
|
||||||
'target_name': 'iqn.test',
|
'target_name': 'iqn.test',
|
||||||
@ -99,6 +104,13 @@ FAKE_GET_VOL_INFO_ONLINE = {'name': 'testvolume',
|
|||||||
'online': True,
|
'online': True,
|
||||||
'agent_type': 'none'}
|
'agent_type': 'none'}
|
||||||
|
|
||||||
|
FAKE_GET_VOL_INFO_RETYPE = {'name': 'testvolume',
|
||||||
|
'size': 2048,
|
||||||
|
'online': True,
|
||||||
|
'agent_type': 'none',
|
||||||
|
'pool_id': 'none',
|
||||||
|
'pool_name': 'none'}
|
||||||
|
|
||||||
FAKE_GET_VOL_INFO_BACKUP_RESPONSE = {'name': 'testvolume',
|
FAKE_GET_VOL_INFO_BACKUP_RESPONSE = {'name': 'testvolume',
|
||||||
'clone': True,
|
'clone': True,
|
||||||
'target_name': 'iqn.test',
|
'target_name': 'iqn.test',
|
||||||
@ -250,6 +262,26 @@ class NimbleDriverBaseTestCase(test.TestCase):
|
|||||||
return inner_client_mock
|
return inner_client_mock
|
||||||
return client_mock_wrapper
|
return client_mock_wrapper
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def client_mock_decorator_nimble_api(username, password, ip, verify):
|
||||||
|
def client_mock_wrapper(func):
|
||||||
|
def inner_client_mock(
|
||||||
|
self, mock_client_class, mock_urllib2, *args, **kwargs):
|
||||||
|
self.mock_client_class = mock_client_class
|
||||||
|
self.mock_client_service = mock.MagicMock(name='Client')
|
||||||
|
self.mock_client_class.return_value = (
|
||||||
|
self.mock_client_service)
|
||||||
|
self.driver = nimble.NimbleRestAPIExecutor(
|
||||||
|
username=username, password=password, ip=ip, verify=verify)
|
||||||
|
mock_login_response = mock_urllib2.post.return_value
|
||||||
|
mock_login_response = mock.MagicMock()
|
||||||
|
mock_login_response.status_code.return_value = http_client.OK
|
||||||
|
mock_login_response.json.return_value = (
|
||||||
|
FAKE_LOGIN_POST_RESPONSE)
|
||||||
|
func(self, *args, **kwargs)
|
||||||
|
return inner_client_mock
|
||||||
|
return client_mock_wrapper
|
||||||
|
|
||||||
|
|
||||||
class NimbleDriverLoginTestCase(NimbleDriverBaseTestCase):
|
class NimbleDriverLoginTestCase(NimbleDriverBaseTestCase):
|
||||||
|
|
||||||
@ -371,7 +403,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
|||||||
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
||||||
'nimble:perfpol-name': 'default',
|
'nimble:perfpol-name': 'default',
|
||||||
'nimble:encryption': 'yes',
|
'nimble:encryption': 'yes',
|
||||||
'nimble:multi-initiator': 'false'}))
|
'multiattach': 'false'}))
|
||||||
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
def test_create_volume_encryption_positive(self):
|
def test_create_volume_encryption_positive(self):
|
||||||
@ -412,7 +444,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
|||||||
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
||||||
'nimble:perfpol-name': 'VMware ESX',
|
'nimble:perfpol-name': 'VMware ESX',
|
||||||
'nimble:encryption': 'no',
|
'nimble:encryption': 'no',
|
||||||
'nimble:multi-initiator': 'false'}))
|
'multiattach': 'false'}))
|
||||||
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
def test_create_volume_perfpolicy_positive(self):
|
def test_create_volume_perfpolicy_positive(self):
|
||||||
@ -452,7 +484,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
|||||||
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
||||||
'nimble:perfpol-name': 'default',
|
'nimble:perfpol-name': 'default',
|
||||||
'nimble:encryption': 'no',
|
'nimble:encryption': 'no',
|
||||||
'nimble:multi-initiator': 'true'}))
|
'multiattach': 'true'}))
|
||||||
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
def test_create_volume_multi_initiator_positive(self):
|
def test_create_volume_multi_initiator_positive(self):
|
||||||
@ -573,7 +605,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
|||||||
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
||||||
'nimble:perfpol-name': 'default',
|
'nimble:perfpol-name': 'default',
|
||||||
'nimble:encryption': 'no',
|
'nimble:encryption': 'no',
|
||||||
'nimble:multi-initiator': 'true'}))
|
'multiattach': 'false'}))
|
||||||
def test_create_volume_negative(self):
|
def test_create_volume_negative(self):
|
||||||
self.mock_client_service.get_vol_info.side_effect = (
|
self.mock_client_service.get_vol_info.side_effect = (
|
||||||
FAKE_CREATE_VOLUME_NEGATIVE_RESPONSE)
|
FAKE_CREATE_VOLUME_NEGATIVE_RESPONSE)
|
||||||
@ -761,7 +793,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
|||||||
return_value={
|
return_value={
|
||||||
'nimble:perfpol-name': 'default',
|
'nimble:perfpol-name': 'default',
|
||||||
'nimble:encryption': 'yes',
|
'nimble:encryption': 'yes',
|
||||||
'nimble:multi-initiator': 'false',
|
'multiattach': False,
|
||||||
'nimble:iops-limit': '1024'}))
|
'nimble:iops-limit': '1024'}))
|
||||||
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*', False))
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*', False))
|
||||||
@ -979,7 +1011,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
|||||||
'extra_specs':
|
'extra_specs':
|
||||||
{'nimble:perfpol-name': 'default',
|
{'nimble:perfpol-name': 'default',
|
||||||
'nimble:encryption': 'yes',
|
'nimble:encryption': 'yes',
|
||||||
'nimble:multi-initiator': 'false',
|
'multiattach': False,
|
||||||
'nimble:iops-limit': '1024'}}))
|
'nimble:iops-limit': '1024'}}))
|
||||||
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
@ -994,6 +1026,27 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
|||||||
self.assertTrue(retype)
|
self.assertTrue(retype)
|
||||||
self.assertIsNone(update)
|
self.assertIsNone(update)
|
||||||
|
|
||||||
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
|
@mock.patch(NIMBLE_ISCSI_DRIVER)
|
||||||
|
@mock.patch.object(nimble.NimbleRestAPIExecutor, 'login')
|
||||||
|
@mock.patch.object(nimble.NimbleRestAPIExecutor,
|
||||||
|
'get_performance_policy_id')
|
||||||
|
@mock.patch.object(nimble.NimbleRestAPIExecutor, 'get_pool_info')
|
||||||
|
@mock.patch.object(nimble.NimbleRestAPIExecutor, 'get_folder_id')
|
||||||
|
@NimbleDriverBaseTestCase.client_mock_decorator_nimble_api(
|
||||||
|
'nimble', 'nimble_pass', '10.18.108.55', 'False')
|
||||||
|
def test_nimble_extraspecs_retype(self, mock_folder,
|
||||||
|
mock_pool, mock_perf_id,
|
||||||
|
mock_login):
|
||||||
|
mock_folder.return_value = None
|
||||||
|
mock_pool.return_value = None
|
||||||
|
mock_perf_id.return_value = None
|
||||||
|
mock_login.return_value = None
|
||||||
|
data = self.driver.get_valid_nimble_extraspecs(
|
||||||
|
FAKE_EXTRA_SPECS,
|
||||||
|
FAKE_GET_VOL_INFO_RETYPE)
|
||||||
|
self.assertTrue(data['multi_initiator'])
|
||||||
|
|
||||||
@mock.patch(NIMBLE_URLLIB2)
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
@mock.patch(NIMBLE_CLIENT)
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
|
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
|
||||||
@ -1011,7 +1064,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
|||||||
'total_capacity_gb': 7466.30419921875,
|
'total_capacity_gb': 7466.30419921875,
|
||||||
'free_capacity_gb': 7463.567649364471,
|
'free_capacity_gb': 7463.567649364471,
|
||||||
'reserved_percentage': 0,
|
'reserved_percentage': 0,
|
||||||
'QoS_support': False}]}
|
'QoS_support': False,
|
||||||
|
'multiattach': True}]}
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
expected_res,
|
expected_res,
|
||||||
self.driver.get_volume_stats(refresh=True))
|
self.driver.get_volume_stats(refresh=True))
|
||||||
@ -1095,7 +1149,7 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase):
|
|||||||
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
||||||
'nimble:perfpol-name': 'default',
|
'nimble:perfpol-name': 'default',
|
||||||
'nimble:encryption': 'yes',
|
'nimble:encryption': 'yes',
|
||||||
'nimble:multi-initiator': 'false'}))
|
'multiattach': False}))
|
||||||
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
def test_create_volume_from_snapshot(self):
|
def test_create_volume_from_snapshot(self):
|
||||||
@ -1361,10 +1415,15 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
|
|||||||
def test_terminate_connection_positive(self):
|
def test_terminate_connection_positive(self):
|
||||||
self.mock_client_service.get_initiator_grp_list.return_value = (
|
self.mock_client_service.get_initiator_grp_list.return_value = (
|
||||||
FAKE_IGROUP_LIST_RESPONSE)
|
FAKE_IGROUP_LIST_RESPONSE)
|
||||||
|
ctx = context.get_admin_context()
|
||||||
|
volume = fake_volume.fake_volume_obj(
|
||||||
|
ctx, name='test-volume',
|
||||||
|
host='fakehost@nimble#Openstack',
|
||||||
|
provider_location='12 13',
|
||||||
|
id=12, multiattach=False)
|
||||||
|
|
||||||
self.driver.terminate_connection(
|
self.driver.terminate_connection(
|
||||||
{'name': 'test-volume',
|
volume,
|
||||||
'provider_location': '12 13',
|
|
||||||
'id': 12},
|
|
||||||
{'initiator': 'test-initiator1'})
|
{'initiator': 'test-initiator1'})
|
||||||
expected_calls = [mock.call._get_igroupname_for_initiator(
|
expected_calls = [mock.call._get_igroupname_for_initiator(
|
||||||
'test-initiator1'),
|
'test-initiator1'),
|
||||||
@ -1406,10 +1465,15 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
|
|||||||
mock_wwpns.return_value = ["1111111111111101"]
|
mock_wwpns.return_value = ["1111111111111101"]
|
||||||
self.mock_client_service.get_initiator_grp_list.return_value = (
|
self.mock_client_service.get_initiator_grp_list.return_value = (
|
||||||
FAKE_IGROUP_LIST_RESPONSE_FC)
|
FAKE_IGROUP_LIST_RESPONSE_FC)
|
||||||
|
ctx = context.get_admin_context()
|
||||||
|
volume = fake_volume.fake_volume_obj(
|
||||||
|
ctx, name='test-volume',
|
||||||
|
host='fakehost@nimble#Openstack',
|
||||||
|
provider_location='12 13',
|
||||||
|
id=14, multiattach=False)
|
||||||
|
|
||||||
self.driver.terminate_connection(
|
self.driver.terminate_connection(
|
||||||
{'name': 'test-volume',
|
volume,
|
||||||
'provider_location': 'array1',
|
|
||||||
'id': 12},
|
|
||||||
{'initiator': 'test-initiator1',
|
{'initiator': 'test-initiator1',
|
||||||
'wwpns': ['1000000000000000']})
|
'wwpns': ['1000000000000000']})
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
@ -1430,9 +1494,159 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
|
|||||||
def test_terminate_connection_negative(self):
|
def test_terminate_connection_negative(self):
|
||||||
self.mock_client_service.get_initiator_grp_list.return_value = (
|
self.mock_client_service.get_initiator_grp_list.return_value = (
|
||||||
FAKE_IGROUP_LIST_RESPONSE)
|
FAKE_IGROUP_LIST_RESPONSE)
|
||||||
|
ctx = context.get_admin_context()
|
||||||
|
|
||||||
|
volume = fake_volume.fake_volume_obj(
|
||||||
|
ctx, name='test-volume',
|
||||||
|
host='fakehost@nimble#Openstack',
|
||||||
|
provider_location='12 13',
|
||||||
|
id=12, multiattach=False)
|
||||||
|
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.VolumeDriverException,
|
exception.VolumeDriverException,
|
||||||
self.driver.terminate_connection,
|
self.driver.terminate_connection,
|
||||||
{'name': 'test-volume',
|
volume,
|
||||||
'provider_location': '12 13', 'id': 12},
|
|
||||||
{'initiator': 'test-initiator3'})
|
{'initiator': 'test-initiator3'})
|
||||||
|
|
||||||
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
|
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
@NimbleDriverBaseTestCase.client_mock_decorator_fc(create_configuration(
|
||||||
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
|
@mock.patch(NIMBLE_FC_DRIVER + ".get_wwpns_from_array")
|
||||||
|
def test_terminate_connection_negative_fc(self, mock_wwpns):
|
||||||
|
mock_wwpns.return_value = ["1111111111111101"]
|
||||||
|
self.mock_client_service.get_initiator_grp_list.return_value = (
|
||||||
|
FAKE_IGROUP_LIST_RESPONSE_FC)
|
||||||
|
ctx = context.get_admin_context()
|
||||||
|
volume = fake_volume.fake_volume_obj(
|
||||||
|
ctx, name='test-volume',
|
||||||
|
host='fakehost@nimble#Openstack',
|
||||||
|
provider_location='12 13',
|
||||||
|
id=12, multiattach=False)
|
||||||
|
self.assertRaises(
|
||||||
|
exception.VolumeDriverException,
|
||||||
|
self.driver.terminate_connection,
|
||||||
|
volume,
|
||||||
|
{'initiator': 'test-initiator3',
|
||||||
|
'wwpns': ['1000000000000010']})
|
||||||
|
|
||||||
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
|
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
|
def test_terminate_connection_multiattach(self):
|
||||||
|
self.mock_client_service.get_initiator_grp_list.return_value = (
|
||||||
|
FAKE_IGROUP_LIST_RESPONSE)
|
||||||
|
ctx = context.get_admin_context()
|
||||||
|
|
||||||
|
att_1 = fake_volume.volume_attachment_ovo(
|
||||||
|
ctx, id=uuidutils.generate_uuid())
|
||||||
|
att_2 = fake_volume.volume_attachment_ovo(
|
||||||
|
ctx, id=uuidutils.generate_uuid())
|
||||||
|
volume = fake_volume.fake_volume_obj(
|
||||||
|
ctx, name='test-volume',
|
||||||
|
host='fakehost@nimble#Openstack',
|
||||||
|
provider_location='12 13',
|
||||||
|
id=12, multiattach=True)
|
||||||
|
volume.volume_attachment.objects = [att_1, att_2]
|
||||||
|
self.driver.terminate_connection(
|
||||||
|
volume,
|
||||||
|
{'initiator': 'test-initiator1'})
|
||||||
|
self.mock_client_service.remove_acl.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
|
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
|
def test_terminate_connection_multiattach_complete(self):
|
||||||
|
self.mock_client_service.get_initiator_grp_list.return_value = (
|
||||||
|
FAKE_IGROUP_LIST_RESPONSE)
|
||||||
|
ctx = context.get_admin_context()
|
||||||
|
|
||||||
|
att_1 = fake_volume.volume_attachment_ovo(
|
||||||
|
ctx, id=uuidutils.generate_uuid())
|
||||||
|
volume = fake_volume.fake_volume_obj(
|
||||||
|
ctx, name='test-volume',
|
||||||
|
host='fakehost@nimble#Openstack',
|
||||||
|
provider_location='12 13',
|
||||||
|
id=12, multiattach=True)
|
||||||
|
volume.volume_attachment.objects = [att_1]
|
||||||
|
self.driver.terminate_connection(
|
||||||
|
volume,
|
||||||
|
{'initiator': 'test-initiator1'})
|
||||||
|
expected_calls = [mock.call._get_igroupname_for_initiator(
|
||||||
|
'test-initiator1'),
|
||||||
|
mock.call.remove_acl({'name': 'test-volume'},
|
||||||
|
'test-igrp1')]
|
||||||
|
self.mock_client_service.assert_has_calls(
|
||||||
|
self.mock_client_service.method_calls,
|
||||||
|
expected_calls)
|
||||||
|
|
||||||
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
|
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
@NimbleDriverBaseTestCase.client_mock_decorator_fc(create_configuration(
|
||||||
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
|
@mock.patch(NIMBLE_FC_DRIVER + ".get_wwpns_from_array")
|
||||||
|
def test_terminate_connection_multiattach_fc(self, mock_wwpns):
|
||||||
|
mock_wwpns.return_value = ["1111111111111101"]
|
||||||
|
self.mock_client_service.get_initiator_grp_list.return_value = (
|
||||||
|
FAKE_IGROUP_LIST_RESPONSE_FC)
|
||||||
|
ctx = context.get_admin_context()
|
||||||
|
|
||||||
|
att_1 = fake_volume.volume_attachment_ovo(
|
||||||
|
ctx, id=uuidutils.generate_uuid())
|
||||||
|
att_2 = fake_volume.volume_attachment_ovo(
|
||||||
|
ctx, id=uuidutils.generate_uuid())
|
||||||
|
volume = fake_volume.fake_volume_obj(
|
||||||
|
ctx, name='test-volume',
|
||||||
|
host='fakehost@nimble#Openstack',
|
||||||
|
provider_location='12 13',
|
||||||
|
id=12, multiattach=True)
|
||||||
|
volume.volume_attachment.objects = [att_1, att_2]
|
||||||
|
self.driver.terminate_connection(
|
||||||
|
volume,
|
||||||
|
{'initiator': 'test-initiator1',
|
||||||
|
'wwpns': ['1000000000000000']})
|
||||||
|
self.mock_client_service.remove_acl.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
|
@mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
@NimbleDriverBaseTestCase.client_mock_decorator_fc(create_configuration(
|
||||||
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
|
@mock.patch(NIMBLE_FC_DRIVER + ".get_wwpns_from_array")
|
||||||
|
def test_terminate_connection_multiattach_complete_fc(self, mock_wwpns):
|
||||||
|
mock_wwpns.return_value = ["1111111111111101"]
|
||||||
|
self.mock_client_service.get_initiator_grp_list.return_value = (
|
||||||
|
FAKE_IGROUP_LIST_RESPONSE_FC)
|
||||||
|
ctx = context.get_admin_context()
|
||||||
|
|
||||||
|
att_1 = fake_volume.volume_attachment_ovo(
|
||||||
|
ctx, id=uuidutils.generate_uuid())
|
||||||
|
volume = fake_volume.fake_volume_obj(
|
||||||
|
ctx, name='test-volume',
|
||||||
|
host='fakehost@nimble#Openstack',
|
||||||
|
provider_location='12 13',
|
||||||
|
id=12, multiattach=True)
|
||||||
|
volume.volume_attachment.objects = [att_1]
|
||||||
|
self.driver.terminate_connection(
|
||||||
|
volume,
|
||||||
|
{'initiator': 'test-initiator1',
|
||||||
|
'wwpns': ['1000000000000000']})
|
||||||
|
expected_calls = [
|
||||||
|
mock.call.get_igroupname_for_initiator_fc(
|
||||||
|
"10:00:00:00:00:00:00:00"),
|
||||||
|
mock.call.remove_acl({'name': 'test-volume'},
|
||||||
|
'test-igrp1')]
|
||||||
|
self.mock_client_service.assert_has_calls(
|
||||||
|
self.mock_client_service.method_calls,
|
||||||
|
expected_calls)
|
||||||
|
@ -50,7 +50,6 @@ AES_256_XTS_CIPHER = 'aes_256_xts'
|
|||||||
DEFAULT_CIPHER = 'none'
|
DEFAULT_CIPHER = 'none'
|
||||||
EXTRA_SPEC_ENCRYPTION = 'nimble:encryption'
|
EXTRA_SPEC_ENCRYPTION = 'nimble:encryption'
|
||||||
EXTRA_SPEC_PERF_POLICY = 'nimble:perfpol-name'
|
EXTRA_SPEC_PERF_POLICY = 'nimble:perfpol-name'
|
||||||
EXTRA_SPEC_MULTI_INITIATOR = 'nimble:multi-initiator'
|
|
||||||
EXTRA_SPEC_DEDUPE = 'nimble:dedupe'
|
EXTRA_SPEC_DEDUPE = 'nimble:dedupe'
|
||||||
EXTRA_SPEC_IOPS_LIMIT = 'nimble:iops-limit'
|
EXTRA_SPEC_IOPS_LIMIT = 'nimble:iops-limit'
|
||||||
EXTRA_SPEC_FOLDER = 'nimble:folder'
|
EXTRA_SPEC_FOLDER = 'nimble:folder'
|
||||||
@ -58,7 +57,6 @@ DEFAULT_PERF_POLICY_SETTING = 'default'
|
|||||||
DEFAULT_ENCRYPTION_SETTING = 'no'
|
DEFAULT_ENCRYPTION_SETTING = 'no'
|
||||||
DEFAULT_DEDUPE_SETTING = 'false'
|
DEFAULT_DEDUPE_SETTING = 'false'
|
||||||
DEFAULT_IOPS_LIMIT_SETTING = None
|
DEFAULT_IOPS_LIMIT_SETTING = None
|
||||||
DEFAULT_MULTI_INITIATOR_SETTING = 'false'
|
|
||||||
DEFAULT_FOLDER_SETTING = None
|
DEFAULT_FOLDER_SETTING = None
|
||||||
DEFAULT_SNAP_QUOTA = sys.maxsize
|
DEFAULT_SNAP_QUOTA = sys.maxsize
|
||||||
BACKUP_VOL_PREFIX = 'backup-vol-'
|
BACKUP_VOL_PREFIX = 'backup-vol-'
|
||||||
@ -393,7 +391,8 @@ class NimbleBaseVolumeDriver(san.SanDriver):
|
|||||||
total_capacity_gb=total_capacity,
|
total_capacity_gb=total_capacity,
|
||||||
free_capacity_gb=free_space,
|
free_capacity_gb=free_space,
|
||||||
reserved_percentage=0,
|
reserved_percentage=0,
|
||||||
QoS_support=False)
|
QoS_support=False,
|
||||||
|
multiattach=True)
|
||||||
self.group_stats['pools'] = [single_pool]
|
self.group_stats['pools'] = [single_pool]
|
||||||
return self.group_stats
|
return self.group_stats
|
||||||
|
|
||||||
@ -721,6 +720,24 @@ class NimbleBaseVolumeDriver(san.SanDriver):
|
|||||||
{'vol': volume['name'],
|
{'vol': volume['name'],
|
||||||
'igroup': initiator_group_name})
|
'igroup': initiator_group_name})
|
||||||
|
|
||||||
|
def _is_multiattach(self, volume):
|
||||||
|
if volume.multiattach:
|
||||||
|
attachment_list = volume.volume_attachment
|
||||||
|
try:
|
||||||
|
attachment_list = attachment_list.objects
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if attachment_list is not None and len(attachment_list) > 1:
|
||||||
|
LOG.info("Volume %(volume)s is attached to multiple "
|
||||||
|
"instances on host %(host_name)s, "
|
||||||
|
"skip terminate volume connection",
|
||||||
|
{'volume': volume.name,
|
||||||
|
'host_name': volume.host.split('@')[0]})
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@interface.volumedriver
|
@interface.volumedriver
|
||||||
class NimbleISCSIDriver(NimbleBaseVolumeDriver, san.SanISCSIDriver):
|
class NimbleISCSIDriver(NimbleBaseVolumeDriver, san.SanISCSIDriver):
|
||||||
@ -799,6 +816,8 @@ class NimbleISCSIDriver(NimbleBaseVolumeDriver, san.SanISCSIDriver):
|
|||||||
volume)
|
volume)
|
||||||
self.APIExecutor.remove_all_acls(volume)
|
self.APIExecutor.remove_all_acls(volume)
|
||||||
return
|
return
|
||||||
|
if self._is_multiattach(volume):
|
||||||
|
return
|
||||||
|
|
||||||
initiator_name = connector['initiator']
|
initiator_name = connector['initiator']
|
||||||
initiator_group_name = self._get_igroupname_for_initiator(
|
initiator_group_name = self._get_igroupname_for_initiator(
|
||||||
@ -996,6 +1015,8 @@ class NimbleFCDriver(NimbleBaseVolumeDriver, driver.FibreChannelDriver):
|
|||||||
volume)
|
volume)
|
||||||
self.APIExecutor.remove_all_acls(volume)
|
self.APIExecutor.remove_all_acls(volume)
|
||||||
return
|
return
|
||||||
|
if self._is_multiattach(volume):
|
||||||
|
return
|
||||||
|
|
||||||
initiator_name = connector['initiator']
|
initiator_name = connector['initiator']
|
||||||
for wwpn in connector['wwpns']:
|
for wwpn in connector['wwpns']:
|
||||||
@ -1161,8 +1182,6 @@ class NimbleRestAPIExecutor(object):
|
|||||||
DEFAULT_PERF_POLICY_SETTING)
|
DEFAULT_PERF_POLICY_SETTING)
|
||||||
encryption = extra_specs.get(EXTRA_SPEC_ENCRYPTION,
|
encryption = extra_specs.get(EXTRA_SPEC_ENCRYPTION,
|
||||||
DEFAULT_ENCRYPTION_SETTING)
|
DEFAULT_ENCRYPTION_SETTING)
|
||||||
multi_initiator = extra_specs.get(EXTRA_SPEC_MULTI_INITIATOR,
|
|
||||||
DEFAULT_MULTI_INITIATOR_SETTING)
|
|
||||||
iops_limit = extra_specs.get(EXTRA_SPEC_IOPS_LIMIT,
|
iops_limit = extra_specs.get(EXTRA_SPEC_IOPS_LIMIT,
|
||||||
DEFAULT_IOPS_LIMIT_SETTING)
|
DEFAULT_IOPS_LIMIT_SETTING)
|
||||||
folder_name = extra_specs.get(EXTRA_SPEC_FOLDER,
|
folder_name = extra_specs.get(EXTRA_SPEC_FOLDER,
|
||||||
@ -1172,11 +1191,9 @@ class NimbleRestAPIExecutor(object):
|
|||||||
extra_specs_map = {}
|
extra_specs_map = {}
|
||||||
extra_specs_map[EXTRA_SPEC_PERF_POLICY] = perf_policy_name
|
extra_specs_map[EXTRA_SPEC_PERF_POLICY] = perf_policy_name
|
||||||
extra_specs_map[EXTRA_SPEC_ENCRYPTION] = encryption
|
extra_specs_map[EXTRA_SPEC_ENCRYPTION] = encryption
|
||||||
extra_specs_map[EXTRA_SPEC_MULTI_INITIATOR] = multi_initiator
|
|
||||||
extra_specs_map[EXTRA_SPEC_IOPS_LIMIT] = iops_limit
|
extra_specs_map[EXTRA_SPEC_IOPS_LIMIT] = iops_limit
|
||||||
extra_specs_map[EXTRA_SPEC_DEDUPE] = dedupe
|
extra_specs_map[EXTRA_SPEC_DEDUPE] = dedupe
|
||||||
extra_specs_map[EXTRA_SPEC_FOLDER] = folder_name
|
extra_specs_map[EXTRA_SPEC_FOLDER] = folder_name
|
||||||
|
|
||||||
return extra_specs_map
|
return extra_specs_map
|
||||||
|
|
||||||
def get_valid_nimble_extraspecs(self, extra_specs_map, vol_info):
|
def get_valid_nimble_extraspecs(self, extra_specs_map, vol_info):
|
||||||
@ -1192,10 +1209,10 @@ class NimbleRestAPIExecutor(object):
|
|||||||
if encrypt.lower() == 'yes':
|
if encrypt.lower() == 'yes':
|
||||||
cipher = AES_256_XTS_CIPHER
|
cipher = AES_256_XTS_CIPHER
|
||||||
data['cipher'] = cipher
|
data['cipher'] = cipher
|
||||||
|
if extra_specs_map.get('multiattach') == "<is> True":
|
||||||
multi_initiator = extra_specs_map_updated[EXTRA_SPEC_MULTI_INITIATOR]
|
data['multi_initiator'] = True
|
||||||
data['multi_initiator'] = multi_initiator
|
else:
|
||||||
|
data['multi_initiator'] = False
|
||||||
folder_name = extra_specs_map_updated[EXTRA_SPEC_FOLDER]
|
folder_name = extra_specs_map_updated[EXTRA_SPEC_FOLDER]
|
||||||
folder_id = None
|
folder_id = None
|
||||||
pool_id = vol_info['pool_id']
|
pool_id = vol_info['pool_id']
|
||||||
@ -1290,7 +1307,7 @@ class NimbleRestAPIExecutor(object):
|
|||||||
perf_policy_name = extra_specs_map[EXTRA_SPEC_PERF_POLICY]
|
perf_policy_name = extra_specs_map[EXTRA_SPEC_PERF_POLICY]
|
||||||
perf_policy_id = self.get_performance_policy_id(perf_policy_name)
|
perf_policy_id = self.get_performance_policy_id(perf_policy_name)
|
||||||
encrypt = extra_specs_map[EXTRA_SPEC_ENCRYPTION]
|
encrypt = extra_specs_map[EXTRA_SPEC_ENCRYPTION]
|
||||||
multi_initiator = extra_specs_map[EXTRA_SPEC_MULTI_INITIATOR]
|
multi_initiator = volume.get('multiattach', False)
|
||||||
folder_name = extra_specs_map[EXTRA_SPEC_FOLDER]
|
folder_name = extra_specs_map[EXTRA_SPEC_FOLDER]
|
||||||
iops_limit = extra_specs_map[EXTRA_SPEC_IOPS_LIMIT]
|
iops_limit = extra_specs_map[EXTRA_SPEC_IOPS_LIMIT]
|
||||||
dedupe = extra_specs_map[EXTRA_SPEC_DEDUPE]
|
dedupe = extra_specs_map[EXTRA_SPEC_DEDUPE]
|
||||||
@ -1521,11 +1538,11 @@ class NimbleRestAPIExecutor(object):
|
|||||||
"initiator_group_id": initiator_group_id}
|
"initiator_group_id": initiator_group_id}
|
||||||
api = "access_control_records"
|
api = "access_control_records"
|
||||||
r = self.get_query(api, filter)
|
r = self.get_query(api, filter)
|
||||||
|
LOG.info("ACL record is %result", {'result': r.json()})
|
||||||
if not r.json()['data']:
|
if not r.json()['data']:
|
||||||
raise NimbleAPIException(_("Unable to retrieve ACL for volume: "
|
LOG.warning('ACL is not available for this volume %vol_id', {
|
||||||
"%(vol)s %(igroup)s ") %
|
'vol_id': volume_id})
|
||||||
{'vol': volume_id,
|
return
|
||||||
'igroup': initiator_group_id})
|
|
||||||
return r.json()['data'][0]
|
return r.json()['data'][0]
|
||||||
|
|
||||||
def get_volume_acl_records(self, volume_id):
|
def get_volume_acl_records(self, volume_id):
|
||||||
@ -1560,6 +1577,7 @@ class NimbleRestAPIExecutor(object):
|
|||||||
try:
|
try:
|
||||||
acl_record = self.get_acl_record(volume_id, initiator_group_id)
|
acl_record = self.get_acl_record(volume_id, initiator_group_id)
|
||||||
LOG.debug("ACL Record %(acl)s", {"acl": acl_record})
|
LOG.debug("ACL Record %(acl)s", {"acl": acl_record})
|
||||||
|
if acl_record is not None:
|
||||||
acl_id = acl_record['id']
|
acl_id = acl_record['id']
|
||||||
api = 'access_control_records/' + six.text_type(acl_id)
|
api = 'access_control_records/' + six.text_type(acl_id)
|
||||||
self.delete(api)
|
self.delete(api)
|
||||||
@ -1700,7 +1718,7 @@ class NimbleRestAPIExecutor(object):
|
|||||||
perf_policy_name = extra_specs_map.get(EXTRA_SPEC_PERF_POLICY)
|
perf_policy_name = extra_specs_map.get(EXTRA_SPEC_PERF_POLICY)
|
||||||
perf_policy_id = self.get_performance_policy_id(perf_policy_name)
|
perf_policy_id = self.get_performance_policy_id(perf_policy_name)
|
||||||
encrypt = extra_specs_map.get(EXTRA_SPEC_ENCRYPTION)
|
encrypt = extra_specs_map.get(EXTRA_SPEC_ENCRYPTION)
|
||||||
multi_initiator = extra_specs_map.get(EXTRA_SPEC_MULTI_INITIATOR)
|
multi_initiator = volume.get('multiattach', False)
|
||||||
iops_limit = extra_specs_map[EXTRA_SPEC_IOPS_LIMIT]
|
iops_limit = extra_specs_map[EXTRA_SPEC_IOPS_LIMIT]
|
||||||
folder_name = extra_specs_map[EXTRA_SPEC_FOLDER]
|
folder_name = extra_specs_map[EXTRA_SPEC_FOLDER]
|
||||||
pool_id = self.get_pool_id(pool_name)
|
pool_id = self.get_pool_id(pool_name)
|
||||||
|
@ -37,6 +37,8 @@ Supported operations
|
|||||||
* Force backup of an in-use volume
|
* Force backup of an in-use volume
|
||||||
* Retype a volume
|
* Retype a volume
|
||||||
* Create a Thinly Provisioned Volume
|
* Create a Thinly Provisioned Volume
|
||||||
|
* Attach a volume to multiple servers simultaneously (multiattach)
|
||||||
|
|
||||||
|
|
||||||
Nimble Storage driver configuration
|
Nimble Storage driver configuration
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -126,9 +128,6 @@ The Nimble volume driver also supports the following extra spec options:
|
|||||||
PERF_POL_NAME is the name of a performance policy which exists on the
|
PERF_POL_NAME is the name of a performance policy which exists on the
|
||||||
Nimble array and should be enabled for every volume in a volume type.
|
Nimble array and should be enabled for every volume in a volume type.
|
||||||
|
|
||||||
'nimble:multi-initiator'='true'
|
|
||||||
Used to enable multi-initiator access for a volume-type.
|
|
||||||
|
|
||||||
nimble:dedupe'='true'
|
nimble:dedupe'='true'
|
||||||
Used to enable dedupe support for a volume-type.
|
Used to enable dedupe support for a volume-type.
|
||||||
|
|
||||||
|
@ -768,7 +768,7 @@ driver.netapp_ontap=complete
|
|||||||
driver.netapp_solidfire=complete
|
driver.netapp_solidfire=complete
|
||||||
driver.nexenta=missing
|
driver.nexenta=missing
|
||||||
driver.nfs=missing
|
driver.nfs=missing
|
||||||
driver.nimble=missing
|
driver.nimble=complete
|
||||||
driver.prophetstor=missing
|
driver.prophetstor=missing
|
||||||
driver.pure=complete
|
driver.pure=complete
|
||||||
driver.qnap=missing
|
driver.qnap=missing
|
||||||
|
8
releasenotes/notes/1885946-17bc5c3dc0535044.yaml
Normal file
8
releasenotes/notes/1885946-17bc5c3dc0535044.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add Multi-attach feature in Nimble driver.
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
Nimble specific extra-spec nimble:multi-initiator is removed.
|
||||||
|
Common extra-spec multiattach is added.
|
Loading…
x
Reference in New Issue
Block a user