Merge "FibreChannel drivers for NetApp Data ONTAP storage controllers"
This commit is contained in:
commit
894ba3fbdd
@ -20,9 +20,10 @@ import mock
|
||||
import six
|
||||
|
||||
from cinder import test
|
||||
from cinder.tests.volume.drivers.netapp.dataontap import fakes as fake
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_7mode
|
||||
|
||||
from cinder.volume.drivers.netapp.utils import hashabledict
|
||||
|
||||
CONNECTION_INFO = {'hostname': 'hostname',
|
||||
'transport_type': 'https',
|
||||
@ -51,7 +52,7 @@ class NetApp7modeClientTestCase(test.TestCase):
|
||||
def tearDown(self):
|
||||
super(NetApp7modeClientTestCase, self).tearDown()
|
||||
|
||||
def test_get_target_details_no_targets(self):
|
||||
def test_get_iscsi_target_details_no_targets(self):
|
||||
response = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
<iscsi-portal-list-entries>
|
||||
@ -59,11 +60,11 @@ class NetApp7modeClientTestCase(test.TestCase):
|
||||
</results>"""))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
target_list = self.client.get_target_details()
|
||||
target_list = self.client.get_iscsi_target_details()
|
||||
|
||||
self.assertEqual([], target_list)
|
||||
|
||||
def test_get_target_details(self):
|
||||
def test_get_iscsi_target_details(self):
|
||||
expected_target = {
|
||||
"address": "127.0.0.1",
|
||||
"port": "1337",
|
||||
@ -81,7 +82,7 @@ class NetApp7modeClientTestCase(test.TestCase):
|
||||
</results>""" % expected_target))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
target_list = self.client.get_target_details()
|
||||
target_list = self.client.get_iscsi_target_details()
|
||||
|
||||
self.assertEqual([expected_target], target_list)
|
||||
|
||||
@ -121,8 +122,9 @@ class NetApp7modeClientTestCase(test.TestCase):
|
||||
|
||||
self.assertEqual(2, len(luns))
|
||||
|
||||
def test_get_igroup_by_initiator_none_found(self):
|
||||
initiator = 'initiator'
|
||||
def test_get_igroup_by_initiators_none_found(self):
|
||||
initiators = fake.FC_FORMATTED_INITIATORS[0]
|
||||
|
||||
response = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
<initiator-groups>
|
||||
@ -130,36 +132,89 @@ class NetApp7modeClientTestCase(test.TestCase):
|
||||
</results>"""))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
igroup = self.client.get_igroup_by_initiator(initiator)
|
||||
igroup = self.client.get_igroup_by_initiators(initiators)
|
||||
|
||||
self.assertEqual([], igroup)
|
||||
|
||||
def test_get_igroup_by_initiator(self):
|
||||
initiator = 'initiator'
|
||||
expected_igroup = {
|
||||
"initiator-group-os-type": None,
|
||||
"initiator-group-type": "1337",
|
||||
"initiator-group-name": "vserver",
|
||||
}
|
||||
def test_get_igroup_by_initiators(self):
|
||||
initiators = [fake.FC_FORMATTED_INITIATORS[0]]
|
||||
response = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
<initiator-groups>
|
||||
<initiator-group-info>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>initiator</initiator-name>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
</initiator-group-info>
|
||||
</initiator-groups>
|
||||
</results>""" % expected_igroup))
|
||||
<initiator-groups>
|
||||
<initiator-group-info>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-uuid>1477ee47-0e1f-4b35-a82c-dcca0b76fc44
|
||||
</initiator-group-uuid>
|
||||
<initiator-group-os-type>linux</initiator-group-os-type>
|
||||
<initiator-group-throttle-reserve>0</initiator-group-throttle-reserve>
|
||||
<initiator-group-throttle-borrow>false
|
||||
</initiator-group-throttle-borrow>
|
||||
<initiator-group-vsa-enabled>false</initiator-group-vsa-enabled>
|
||||
<initiator-group-alua-enabled>true</initiator-group-alua-enabled>
|
||||
<initiator-group-report-scsi-name-enabled>true
|
||||
</initiator-group-report-scsi-name-enabled>
|
||||
<initiator-group-use-partner>true</initiator-group-use-partner>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>21:00:00:24:ff:40:6c:c3</initiator-name>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
</initiator-group-info>
|
||||
</initiator-groups>
|
||||
</results>""" % fake.IGROUP1))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
igroup = self.client.get_igroup_by_initiator(initiator)
|
||||
igroups = self.client.get_igroup_by_initiators(initiators)
|
||||
|
||||
self.assertEqual([expected_igroup], igroup)
|
||||
# make these lists of dicts comparable using hashable dictionaries
|
||||
igroups = set([hashabledict(igroup) for igroup in igroups])
|
||||
expected = set([hashabledict(fake.IGROUP1)])
|
||||
|
||||
self.assertSetEqual(igroups, expected)
|
||||
|
||||
def test_get_igroup_by_initiators_multiple(self):
|
||||
initiators = fake.FC_FORMATTED_INITIATORS
|
||||
response = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
<initiator-groups>
|
||||
<initiator-group-info>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-uuid>1477ee47-0e1f-4b35-a82c-dcca0b76fc44
|
||||
</initiator-group-uuid>
|
||||
<initiator-group-os-type>linux</initiator-group-os-type>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>21:00:00:24:ff:40:6c:c3</initiator-name>
|
||||
</initiator-info>
|
||||
<initiator-info>
|
||||
<initiator-name>21:00:00:24:ff:40:6c:c2</initiator-name>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
</initiator-group-info>
|
||||
<initiator-group-info>
|
||||
<initiator-group-name>openstack-igroup2</initiator-group-name>
|
||||
<initiator-group-type>fcp</initiator-group-type>
|
||||
<initiator-group-uuid>1477ee47-0e1f-4b35-a82c-dcca0b76fc44
|
||||
</initiator-group-uuid>
|
||||
<initiator-group-os-type>linux</initiator-group-os-type>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>21:00:00:24:ff:40:6c:c2</initiator-name>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
</initiator-group-info> </initiator-groups>
|
||||
</results>""" % fake.IGROUP1))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
igroups = self.client.get_igroup_by_initiators(initiators)
|
||||
|
||||
# make these lists of dicts comparable using hashable dictionaries
|
||||
igroups = set([hashabledict(igroup) for igroup in igroups])
|
||||
expected = set([hashabledict(fake.IGROUP1)])
|
||||
|
||||
self.assertSetEqual(igroups, expected)
|
||||
|
||||
def test_clone_lun(self):
|
||||
fake_clone_start = netapp_api.NaElement(
|
||||
@ -561,3 +616,28 @@ class NetApp7modeClientTestCase(test.TestCase):
|
||||
actual_request = _args[0]
|
||||
self.assertEqual('net-ifconfig-get', actual_request.get_name())
|
||||
self.assertEqual(expected_response, actual_response)
|
||||
|
||||
def test_get_fc_target_wwpns(self):
|
||||
wwpn1 = '50:0a:09:81:90:fe:eb:a5'
|
||||
wwpn2 = '50:0a:09:82:90:fe:eb:a5'
|
||||
response = netapp_api.NaElement(
|
||||
etree.XML("""
|
||||
<results status="passed">
|
||||
<fcp-port-names>
|
||||
<fcp-port-name-info>
|
||||
<port-name>%(wwpn1)s</port-name>
|
||||
<is-used>true</is-used>
|
||||
<fcp-adapter>1a</fcp-adapter>
|
||||
</fcp-port-name-info>
|
||||
<fcp-port-name-info>
|
||||
<port-name>%(wwpn2)s</port-name>
|
||||
<is-used>true</is-used>
|
||||
<fcp-adapter>1b</fcp-adapter>
|
||||
</fcp-port-name-info>
|
||||
</fcp-port-names>
|
||||
</results>""" % {'wwpn1': wwpn1, 'wwpn2': wwpn2}))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
wwpns = self.client.get_fc_target_wwpns()
|
||||
|
||||
self.assertSetEqual(set(wwpns), set([wwpn1, wwpn2]))
|
||||
|
@ -19,6 +19,7 @@ import mock
|
||||
import six
|
||||
|
||||
from cinder import test
|
||||
import cinder.tests.volume.drivers.netapp.dataontap.fakes as fake
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
||||
|
||||
@ -61,7 +62,9 @@ class NetAppBaseClientTestCase(test.TestCase):
|
||||
def test_get_ontapi_version_cached(self):
|
||||
|
||||
self.connection.get_api_version.return_value = (1, 20)
|
||||
|
||||
major, minor = self.client.get_ontapi_version()
|
||||
|
||||
self.assertEqual(1, self.connection.get_api_version.call_count)
|
||||
self.assertEqual(1, major)
|
||||
self.assertEqual(20, minor)
|
||||
@ -69,6 +72,7 @@ class NetAppBaseClientTestCase(test.TestCase):
|
||||
def test_check_is_naelement(self):
|
||||
|
||||
element = netapp_api.NaElement('name')
|
||||
|
||||
self.assertIsNone(self.client.check_is_naelement(element))
|
||||
self.assertRaises(ValueError, self.client.check_is_naelement, None)
|
||||
|
||||
@ -366,6 +370,7 @@ class NetAppBaseClientTestCase(test.TestCase):
|
||||
self.connection.invoke_successfully.return_value = mock_response
|
||||
|
||||
geometry = self.client.get_lun_geometry(path)
|
||||
|
||||
self.assertEqual(expected_keys, set(geometry.keys()))
|
||||
|
||||
def test_get_lun_geometry_with_api_error(self):
|
||||
@ -380,6 +385,7 @@ class NetAppBaseClientTestCase(test.TestCase):
|
||||
fake_response = netapp_api.NaElement('volume')
|
||||
fake_response.add_node_with_children('options', test='blah')
|
||||
self.connection.invoke_successfully.return_value = fake_response
|
||||
|
||||
options = self.client.get_volume_options('volume')
|
||||
|
||||
self.assertEqual(1, len(options))
|
||||
@ -387,6 +393,7 @@ class NetAppBaseClientTestCase(test.TestCase):
|
||||
def test_get_volume_options_with_no_options(self):
|
||||
fake_response = netapp_api.NaElement('options')
|
||||
self.connection.invoke_successfully.return_value = fake_response
|
||||
|
||||
options = self.client.get_volume_options('volume')
|
||||
|
||||
self.assertEqual([], options)
|
||||
@ -396,7 +403,66 @@ class NetAppBaseClientTestCase(test.TestCase):
|
||||
new_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
|
||||
fake_response = netapp_api.NaElement('options')
|
||||
self.connection.invoke_successfully.return_value = fake_response
|
||||
|
||||
self.client.move_lun(path, new_path)
|
||||
|
||||
self.connection.invoke_successfully.assert_called_once_with(
|
||||
mock.ANY, True)
|
||||
|
||||
def test_get_igroup_by_initiators(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.client.get_igroup_by_initiators,
|
||||
fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
def test_get_fc_target_wwpns(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.client.get_fc_target_wwpns)
|
||||
|
||||
def test_has_luns_mapped_to_initiator(self):
|
||||
initiator = fake.FC_FORMATTED_INITIATORS[0]
|
||||
version_response = netapp_api.NaElement(
|
||||
etree.XML("""
|
||||
<results status="passed">
|
||||
<lun-maps>
|
||||
<lun-map-info>
|
||||
<path>/vol/cinder1/volume-9be956b3-9854-4a5c-a7f5-13a16da52c9c</path>
|
||||
<initiator-group>openstack-4b57a80b-ebca-4d27-bd63-48ac5408d08b
|
||||
</initiator-group>
|
||||
<lun-id>0</lun-id>
|
||||
</lun-map-info>
|
||||
<lun-map-info>
|
||||
<path>/vol/cinder1/volume-ac90433c-a560-41b3-9357-7f3f80071eb5</path>
|
||||
<initiator-group>openstack-4b57a80b-ebca-4d27-bd63-48ac5408d08b
|
||||
</initiator-group>
|
||||
<lun-id>1</lun-id>
|
||||
</lun-map-info>
|
||||
</lun-maps>
|
||||
</results>"""))
|
||||
|
||||
self.connection.invoke_successfully.return_value = version_response
|
||||
|
||||
self.assertTrue(self.client._has_luns_mapped_to_initiator(initiator))
|
||||
|
||||
def test_has_luns_mapped_to_initiator_not_mapped(self):
|
||||
initiator = fake.FC_FORMATTED_INITIATORS[0]
|
||||
version_response = netapp_api.NaElement(
|
||||
etree.XML("""
|
||||
<results status="passed">
|
||||
<lun-maps />
|
||||
</results>"""))
|
||||
self.connection.invoke_successfully.return_value = version_response
|
||||
self.assertFalse(self.client._has_luns_mapped_to_initiator(initiator))
|
||||
|
||||
@mock.patch.object(client_base.Client, '_has_luns_mapped_to_initiator')
|
||||
def test_has_luns_mapped_to_initiators(self,
|
||||
mock_has_luns_mapped_to_initiator):
|
||||
initiators = fake.FC_FORMATTED_INITIATORS
|
||||
mock_has_luns_mapped_to_initiator.return_value = True
|
||||
self.assertTrue(self.client.has_luns_mapped_to_initiators(initiators))
|
||||
|
||||
@mock.patch.object(client_base.Client, '_has_luns_mapped_to_initiator')
|
||||
def test_has_luns_mapped_to_initiators_not_mapped(
|
||||
self, mock_has_luns_mapped_to_initiator):
|
||||
initiators = fake.FC_FORMATTED_INITIATORS
|
||||
mock_has_luns_mapped_to_initiator.return_value = False
|
||||
self.assertFalse(self.client.has_luns_mapped_to_initiators(initiators))
|
||||
|
@ -23,6 +23,7 @@ from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
|
||||
from cinder.volume.drivers.netapp.utils import hashabledict
|
||||
|
||||
|
||||
CONNECTION_INFO = {'hostname': 'hostname',
|
||||
@ -52,18 +53,18 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
||||
def tearDown(self):
|
||||
super(NetAppCmodeClientTestCase, self).tearDown()
|
||||
|
||||
def test_get_target_details_no_targets(self):
|
||||
def test_get_iscsi_target_details_no_targets(self):
|
||||
response = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
<num-records>1</num-records>
|
||||
<attributes-list></attributes-list>
|
||||
</results>"""))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
target_list = self.client.get_target_details()
|
||||
target_list = self.client.get_iscsi_target_details()
|
||||
|
||||
self.assertEqual([], target_list)
|
||||
|
||||
def test_get_target_details(self):
|
||||
def test_get_iscsi_target_details(self):
|
||||
expected_target = {
|
||||
"address": "127.0.0.1",
|
||||
"port": "1337",
|
||||
@ -84,7 +85,7 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
||||
</results>""" % expected_target))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
target_list = self.client.get_target_details()
|
||||
target_list = self.client.get_iscsi_target_details()
|
||||
|
||||
self.assertEqual([expected_target], target_list)
|
||||
|
||||
@ -241,67 +242,169 @@ class NetAppCmodeClientTestCase(test.TestCase):
|
||||
</results>"""))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
igroup = self.client.get_igroup_by_initiator(initiator)
|
||||
igroup = self.client.get_igroup_by_initiators([initiator])
|
||||
|
||||
self.assertEqual([], igroup)
|
||||
|
||||
def test_get_igroup_by_initiator(self):
|
||||
initiator = 'initiator'
|
||||
def test_get_igroup_by_initiators(self):
|
||||
initiators = ['11:22:33:44:55:66:77:88']
|
||||
expected_igroup = {
|
||||
"initiator-group-os-type": None,
|
||||
"initiator-group-type": "1337",
|
||||
"initiator-group-name": "vserver",
|
||||
'initiator-group-os-type': 'default',
|
||||
'initiator-group-type': 'fcp',
|
||||
'initiator-group-name': 'openstack-igroup1',
|
||||
}
|
||||
|
||||
response = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
<num-records>1</num-records>
|
||||
<attributes-list>
|
||||
<initiator-group-info>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
</initiator-group-info>
|
||||
</attributes-list>
|
||||
</results>""" % expected_igroup))
|
||||
<attributes-list>
|
||||
<initiator-group-info>
|
||||
<initiator-group-alua-enabled>true</initiator-group-alua-enabled>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
<initiator-group-os-type>default</initiator-group-os-type>
|
||||
<initiator-group-throttle-borrow>false</initiator-group-throttle-borrow>
|
||||
<initiator-group-throttle-reserve>0</initiator-group-throttle-reserve>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-use-partner>true</initiator-group-use-partner>
|
||||
<initiator-group-uuid>f8aa707a-57fa-11e4-ad08-123478563412
|
||||
</initiator-group-uuid>
|
||||
<initiator-group-vsa-enabled>false</initiator-group-vsa-enabled>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>11:22:33:44:55:66:77:88</initiator-name>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
<vserver>cinder-iscsi</vserver>
|
||||
</initiator-group-info>
|
||||
</attributes-list>
|
||||
<num-records>1</num-records>
|
||||
</results>""" % expected_igroup))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
igroup = self.client.get_igroup_by_initiator(initiator)
|
||||
igroups = self.client.get_igroup_by_initiators(initiators)
|
||||
|
||||
self.assertEqual([expected_igroup], igroup)
|
||||
# make these lists of dicts comparable using hashable dictionaries
|
||||
igroups = set([hashabledict(igroup) for igroup in igroups])
|
||||
expected = set([hashabledict(expected_igroup)])
|
||||
|
||||
def test_get_igroup_by_initiator_multiple_pages(self):
|
||||
initiator = 'initiator'
|
||||
self.assertSetEqual(igroups, expected)
|
||||
|
||||
def test_get_igroup_by_initiators_multiple(self):
|
||||
initiators = ['11:22:33:44:55:66:77:88', '88:77:66:55:44:33:22:11']
|
||||
expected_igroup = {
|
||||
"initiator-group-os-type": None,
|
||||
"initiator-group-type": "1337",
|
||||
"initiator-group-name": "vserver",
|
||||
'initiator-group-os-type': 'default',
|
||||
'initiator-group-type': 'fcp',
|
||||
'initiator-group-name': 'openstack-igroup1',
|
||||
}
|
||||
|
||||
response = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
<num-records>1</num-records>
|
||||
<attributes-list>
|
||||
<initiator-group-info>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
</initiator-group-info>
|
||||
</attributes-list>
|
||||
<next-tag>blah</next-tag>
|
||||
</results>""" % expected_igroup))
|
||||
<attributes-list>
|
||||
<initiator-group-info>
|
||||
<initiator-group-alua-enabled>true</initiator-group-alua-enabled>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
<initiator-group-os-type>default</initiator-group-os-type>
|
||||
<initiator-group-throttle-borrow>false</initiator-group-throttle-borrow>
|
||||
<initiator-group-throttle-reserve>0</initiator-group-throttle-reserve>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-use-partner>true</initiator-group-use-partner>
|
||||
<initiator-group-uuid>f8aa707a-57fa-11e4-ad08-123478563412
|
||||
</initiator-group-uuid>
|
||||
<initiator-group-vsa-enabled>false</initiator-group-vsa-enabled>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>11:22:33:44:55:66:77:88</initiator-name>
|
||||
</initiator-info>
|
||||
<initiator-info>
|
||||
<initiator-name>88:77:66:55:44:33:22:11</initiator-name>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
<vserver>cinder-iscsi</vserver>
|
||||
</initiator-group-info>
|
||||
</attributes-list>
|
||||
<num-records>1</num-records>
|
||||
</results>""" % expected_igroup))
|
||||
self.connection.invoke_successfully.return_value = response
|
||||
|
||||
igroups = self.client.get_igroup_by_initiators(initiators)
|
||||
|
||||
# make these lists of dicts comparable using hashable dictionaries
|
||||
igroups = set([hashabledict(igroup) for igroup in igroups])
|
||||
expected = set([hashabledict(expected_igroup)])
|
||||
|
||||
self.assertSetEqual(igroups, expected)
|
||||
|
||||
def test_get_igroup_by_initiators_multiple_pages(self):
|
||||
initiator = '11:22:33:44:55:66:77:88'
|
||||
expected_igroup1 = {
|
||||
'initiator-group-os-type': 'default',
|
||||
'initiator-group-type': 'fcp',
|
||||
'initiator-group-name': 'openstack-igroup1',
|
||||
}
|
||||
expected_igroup2 = {
|
||||
'initiator-group-os-type': 'default',
|
||||
'initiator-group-type': 'fcp',
|
||||
'initiator-group-name': 'openstack-igroup2',
|
||||
}
|
||||
response_1 = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
<attributes-list>
|
||||
<initiator-group-info>
|
||||
<initiator-group-alua-enabled>true</initiator-group-alua-enabled>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
<initiator-group-os-type>default</initiator-group-os-type>
|
||||
<initiator-group-throttle-borrow>false</initiator-group-throttle-borrow>
|
||||
<initiator-group-throttle-reserve>0</initiator-group-throttle-reserve>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-use-partner>true</initiator-group-use-partner>
|
||||
<initiator-group-uuid>f8aa707a-57fa-11e4-ad08-123478563412
|
||||
</initiator-group-uuid>
|
||||
<initiator-group-vsa-enabled>false</initiator-group-vsa-enabled>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>11:22:33:44:55:66:77:88</initiator-name>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
<vserver>cinder-iscsi</vserver>
|
||||
</initiator-group-info>
|
||||
</attributes-list>
|
||||
<next-tag>12345</next-tag>
|
||||
<num-records>1</num-records>
|
||||
</results>""" % expected_igroup1))
|
||||
response_2 = netapp_api.NaElement(
|
||||
etree.XML("""<results status="passed">
|
||||
<num-records>1</num-records>
|
||||
<attributes-list>
|
||||
<initiator-group-info>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
</initiator-group-info>
|
||||
</attributes-list>
|
||||
</results>""" % expected_igroup))
|
||||
self.connection.invoke_successfully.side_effect = [response,
|
||||
<attributes-list>
|
||||
<initiator-group-info>
|
||||
<initiator-group-alua-enabled>true</initiator-group-alua-enabled>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
<initiator-group-os-type>default</initiator-group-os-type>
|
||||
<initiator-group-throttle-borrow>false</initiator-group-throttle-borrow>
|
||||
<initiator-group-throttle-reserve>0</initiator-group-throttle-reserve>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-use-partner>true</initiator-group-use-partner>
|
||||
<initiator-group-uuid>f8aa707a-57fa-11e4-ad08-123478563412
|
||||
</initiator-group-uuid>
|
||||
<initiator-group-vsa-enabled>false</initiator-group-vsa-enabled>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>11:22:33:44:55:66:77:88</initiator-name>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
<vserver>cinder-iscsi</vserver>
|
||||
</initiator-group-info>
|
||||
</attributes-list>
|
||||
<num-records>1</num-records>
|
||||
</results>""" % expected_igroup2))
|
||||
self.connection.invoke_successfully.side_effect = [response_1,
|
||||
response_2]
|
||||
|
||||
igroup = self.client.get_igroup_by_initiator(initiator)
|
||||
igroups = self.client.get_igroup_by_initiators([initiator])
|
||||
|
||||
self.assertEqual([expected_igroup, expected_igroup], igroup)
|
||||
# make these lists of dicts comparable using hashable dictionaries
|
||||
igroups = set([hashabledict(igroup) for igroup in igroups])
|
||||
expected = set([hashabledict(expected_igroup1),
|
||||
hashabledict(expected_igroup2)])
|
||||
|
||||
self.assertSetEqual(igroups, expected)
|
||||
|
||||
def test_clone_lun(self):
|
||||
self.client.clone_lun('volume', 'fakeLUN', 'newFakeLUN')
|
||||
|
75
cinder/tests/volume/drivers/netapp/dataontap/fakes.py
Normal file
75
cinder/tests/volume/drivers/netapp/dataontap/fakes.py
Normal file
@ -0,0 +1,75 @@
|
||||
# Copyright (c) - 2014, Clinton Knight. 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.
|
||||
|
||||
|
||||
VOLUME = 'f10d1a84-9b7b-427e-8fec-63c48b509a56'
|
||||
LUN = 'ee6b4cc7-477b-4016-aa0c-7127b4e3af86'
|
||||
SIZE = '1024'
|
||||
METADATA = {'OsType': 'linux', 'SpaceReserved': 'true'}
|
||||
|
||||
UUID1 = '12345678-1234-5678-1234-567812345678'
|
||||
LUN1 = '/vol/vol0/lun1'
|
||||
IGROUP1_NAME = 'openstack-igroup1'
|
||||
VSERVER1_NAME = 'openstack-vserver'
|
||||
|
||||
FC_VOLUME = {'name': 'fake_volume'}
|
||||
|
||||
FC_INITIATORS = ['21000024ff406cc3', '21000024ff406cc2']
|
||||
FC_FORMATTED_INITIATORS = ['21:00:00:24:ff:40:6c:c3',
|
||||
'21:00:00:24:ff:40:6c:c2']
|
||||
|
||||
FC_TARGET_WWPNS = ['500a098280feeba5', '500a098290feeba5',
|
||||
'500a098190feeba5', '500a098180feeba5']
|
||||
|
||||
FC_FORMATTED_TARGET_WWPNS = ['50:0a:09:82:80:fe:eb:a5',
|
||||
'50:0a:09:82:90:fe:eb:a5',
|
||||
'50:0a:09:81:90:fe:eb:a5',
|
||||
'50:0a:09:81:80:fe:eb:a5']
|
||||
|
||||
FC_CONNECTOR = {'ip': '1.1.1.1',
|
||||
'host': 'fake_host',
|
||||
'wwnns': ['20000024ff406cc3', '20000024ff406cc2'],
|
||||
'wwpns': ['21000024ff406cc3', '21000024ff406cc2']}
|
||||
|
||||
FC_I_T_MAP = {'21000024ff406cc3': ['500a098280feeba5', '500a098290feeba5'],
|
||||
'21000024ff406cc2': ['500a098190feeba5', '500a098180feeba5']}
|
||||
|
||||
FC_I_T_MAP_COMPLETE = {'21000024ff406cc3': FC_TARGET_WWPNS,
|
||||
'21000024ff406cc2': FC_TARGET_WWPNS}
|
||||
|
||||
FC_FABRIC_MAP = {'fabricB':
|
||||
{'target_port_wwn_list':
|
||||
['500a098190feeba5', '500a098180feeba5'],
|
||||
'initiator_port_wwn_list': ['21000024ff406cc2']},
|
||||
'fabricA':
|
||||
{'target_port_wwn_list':
|
||||
['500a098290feeba5', '500a098280feeba5'],
|
||||
'initiator_port_wwn_list': ['21000024ff406cc3']}}
|
||||
|
||||
FC_TARGET_INFO = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {'target_lun': '1',
|
||||
'initiator_target_map': FC_I_T_MAP,
|
||||
'access_mode': 'rw',
|
||||
'target_wwn': FC_TARGET_WWPNS,
|
||||
'target_discovered': True}}
|
||||
|
||||
FC_TARGET_INFO_EMPTY = {'driver_volume_type': 'fibre_channel', 'data': {}}
|
||||
|
||||
FC_TARGET_INFO_UNMAP = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {'target_wwn': FC_TARGET_WWPNS,
|
||||
'initiator_target_map': FC_I_T_MAP}}
|
||||
|
||||
IGROUP1 = {'initiator-group-os-type': 'linux',
|
||||
'initiator-group-type': 'fcp',
|
||||
'initiator-group-name': IGROUP1_NAME}
|
@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# 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
|
||||
@ -17,19 +16,22 @@
|
||||
Mock unit tests for the NetApp block storage 7-mode library
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
import mock
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
import cinder.tests.volume.drivers.netapp.dataontap.fakes as fake
|
||||
import cinder.tests.volume.drivers.netapp.fakes as na_fakes
|
||||
from cinder.volume.drivers.netapp.dataontap import block_7mode
|
||||
from cinder.volume.drivers.netapp.dataontap.block_7mode import \
|
||||
NetAppBlockStorage7modeLibrary as block_lib_7mode
|
||||
from cinder.volume.drivers.netapp.dataontap.block_base import \
|
||||
NetAppBlockStorageLibrary as block_lib
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
|
||||
FAKE_VOLUME = six.text_type(uuid.uuid4())
|
||||
FAKE_LUN = six.text_type(uuid.uuid4())
|
||||
FAKE_SIZE = '1024'
|
||||
FAKE_METADATA = {'OsType': 'linux', 'SpaceReserved': 'true'}
|
||||
from cinder.volume.drivers.netapp.dataontap.client.api import NaApiError
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
||||
|
||||
|
||||
class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
||||
@ -38,17 +40,231 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(NetAppBlockStorage7modeLibraryTestCase, self).setUp()
|
||||
|
||||
kwargs = {'configuration': mock.Mock()}
|
||||
self.library = block_7mode.NetAppBlockStorage7modeLibrary('driver',
|
||||
'protocol',
|
||||
**kwargs)
|
||||
kwargs = {'configuration': self.get_config_7mode()}
|
||||
self.library = block_lib_7mode('driver', 'protocol', **kwargs)
|
||||
|
||||
self.library.zapi_client = mock.Mock()
|
||||
self.zapi_client = self.library.zapi_client
|
||||
self.library.vfiler = mock.Mock()
|
||||
|
||||
def tearDown(self):
|
||||
super(NetAppBlockStorage7modeLibraryTestCase, self).tearDown()
|
||||
|
||||
def get_config_7mode(self):
|
||||
config = na_fakes.create_configuration_7mode()
|
||||
config.netapp_storage_protocol = 'iscsi'
|
||||
config.netapp_login = 'admin'
|
||||
config.netapp_password = 'pass'
|
||||
config.netapp_server_hostname = '127.0.0.1'
|
||||
config.netapp_transport_type = 'http'
|
||||
config.netapp_server_port = '80'
|
||||
return config
|
||||
|
||||
@mock.patch.object(client_base.Client, 'get_ontapi_version',
|
||||
mock.MagicMock(return_value=(1, 20)))
|
||||
@mock.patch.object(block_lib_7mode, '_get_root_volume_name')
|
||||
@mock.patch.object(block_lib_7mode, '_do_partner_setup')
|
||||
@mock.patch.object(block_lib, 'do_setup')
|
||||
def test_do_setup(self, super_do_setup, mock_do_partner_setup,
|
||||
mock_get_root_volume_name):
|
||||
mock_get_root_volume_name.return_value = 'vol0'
|
||||
context = mock.Mock()
|
||||
|
||||
self.library.do_setup(context)
|
||||
|
||||
super_do_setup.assert_called_once_with(context)
|
||||
mock_do_partner_setup.assert_called_once_with()
|
||||
mock_get_root_volume_name.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(client_base.Client, 'get_ontapi_version',
|
||||
mock.MagicMock(return_value=(1, 20)))
|
||||
def test_do_partner_setup(self):
|
||||
self.library.configuration.netapp_partner_backend_name = 'partner'
|
||||
|
||||
self.library._do_partner_setup()
|
||||
|
||||
self.assertIsNotNone(self.library.partner_zapi_client)
|
||||
|
||||
@mock.patch.object(client_base.Client, 'get_ontapi_version',
|
||||
mock.MagicMock(return_value=(1, 20)))
|
||||
def test_do_partner_setup_no_partner(self):
|
||||
|
||||
self.library._do_partner_setup()
|
||||
|
||||
self.assertFalse(hasattr(self.library, 'partner_zapi_client'))
|
||||
|
||||
@mock.patch.object(block_lib, 'check_for_setup_error')
|
||||
def test_check_for_setup_error(self, super_check_for_setup_error):
|
||||
self.zapi_client.get_ontapi_version.return_value = (1, 9)
|
||||
|
||||
self.library.check_for_setup_error()
|
||||
|
||||
super_check_for_setup_error.assert_called_once_with()
|
||||
|
||||
def test_check_for_setup_error_too_old(self):
|
||||
self.zapi_client.get_ontapi_version.return_value = (1, 8)
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.library.check_for_setup_error)
|
||||
|
||||
def test_find_mapped_lun_igroup(self):
|
||||
response = netapp_api.NaElement(etree.XML("""
|
||||
<results status="passed">
|
||||
<initiator-groups>
|
||||
<initiator-group-info>
|
||||
<initiator-group-name>%(initiator-group-name)s</initiator-group-name>
|
||||
<initiator-group-type>%(initiator-group-type)s</initiator-group-type>
|
||||
<initiator-group-uuid>1477ee47-0e1f-4b35-a82c-dcca0b76fc44
|
||||
</initiator-group-uuid>
|
||||
<initiator-group-os-type>linux</initiator-group-os-type>
|
||||
<initiator-group-throttle-reserve>0</initiator-group-throttle-reserve>
|
||||
<initiator-group-throttle-borrow>false
|
||||
</initiator-group-throttle-borrow>
|
||||
<initiator-group-vsa-enabled>false</initiator-group-vsa-enabled>
|
||||
<initiator-group-alua-enabled>true</initiator-group-alua-enabled>
|
||||
<initiator-group-report-scsi-name-enabled>true
|
||||
</initiator-group-report-scsi-name-enabled>
|
||||
<initiator-group-use-partner>true</initiator-group-use-partner>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>21:00:00:24:ff:40:6c:c3</initiator-name>
|
||||
</initiator-info>
|
||||
<initiator-info>
|
||||
<initiator-name>21:00:00:24:ff:40:6c:c2</initiator-name>
|
||||
<initiator-alias-info>
|
||||
<initiator-alias>Centos</initiator-alias>
|
||||
</initiator-alias-info>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
<lun-id>2</lun-id>
|
||||
</initiator-group-info>
|
||||
</initiator-groups>
|
||||
</results>""" % fake.IGROUP1))
|
||||
initiators = fake.FC_FORMATTED_INITIATORS
|
||||
self.zapi_client.get_lun_map.return_value = response
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup('path',
|
||||
initiators)
|
||||
|
||||
self.assertEqual(igroup, fake.IGROUP1_NAME)
|
||||
self.assertEqual(lun_id, '2')
|
||||
|
||||
def test_find_mapped_lun_igroup_initiator_mismatch(self):
|
||||
response = netapp_api.NaElement(etree.XML("""
|
||||
<results status="passed">
|
||||
<initiator-groups>
|
||||
<initiator-group-info>
|
||||
<initiator-group-name>openstack-igroup1</initiator-group-name>
|
||||
<initiator-group-type>fcp</initiator-group-type>
|
||||
<initiator-group-uuid>1477ee47-0e1f-4b35-a82c-dcca0b76fc44
|
||||
</initiator-group-uuid>
|
||||
<initiator-group-os-type>linux</initiator-group-os-type>
|
||||
<initiator-group-throttle-reserve>0</initiator-group-throttle-reserve>
|
||||
<initiator-group-throttle-borrow>false
|
||||
</initiator-group-throttle-borrow>
|
||||
<initiator-group-vsa-enabled>false</initiator-group-vsa-enabled>
|
||||
<initiator-group-alua-enabled>true</initiator-group-alua-enabled>
|
||||
<initiator-group-report-scsi-name-enabled>true
|
||||
</initiator-group-report-scsi-name-enabled>
|
||||
<initiator-group-use-partner>true</initiator-group-use-partner>
|
||||
<initiators>
|
||||
<initiator-info>
|
||||
<initiator-name>21:00:00:24:ff:40:6c:c3</initiator-name>
|
||||
</initiator-info>
|
||||
</initiators>
|
||||
<lun-id>2</lun-id>
|
||||
</initiator-group-info>
|
||||
</initiator-groups>
|
||||
</results>"""))
|
||||
initiators = fake.FC_FORMATTED_INITIATORS
|
||||
self.zapi_client.get_lun_map.return_value = response
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup('path',
|
||||
initiators)
|
||||
|
||||
self.assertIsNone(igroup)
|
||||
self.assertIsNone(lun_id)
|
||||
|
||||
def test_find_mapped_lun_igroup_no_igroups(self):
|
||||
response = netapp_api.NaElement(etree.XML("""
|
||||
<results status="passed">
|
||||
<initiator-groups />
|
||||
</results>"""))
|
||||
initiators = fake.FC_FORMATTED_INITIATORS
|
||||
self.zapi_client.get_lun_map.return_value = response
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup('path',
|
||||
initiators)
|
||||
|
||||
self.assertIsNone(igroup)
|
||||
self.assertIsNone(lun_id)
|
||||
|
||||
def test_find_mapped_lun_igroup_raises(self):
|
||||
self.zapi_client.get_lun_map.side_effect = NaApiError
|
||||
initiators = fake.FC_FORMATTED_INITIATORS
|
||||
self.assertRaises(NaApiError,
|
||||
self.library._find_mapped_lun_igroup,
|
||||
'path',
|
||||
initiators)
|
||||
|
||||
def test_has_luns_mapped_to_initiators_local_map(self):
|
||||
initiator_list = fake.FC_FORMATTED_INITIATORS
|
||||
self.zapi_client.has_luns_mapped_to_initiators.return_value = True
|
||||
self.library.partner_zapi_client = mock.Mock()
|
||||
|
||||
result = self.library._has_luns_mapped_to_initiators(initiator_list)
|
||||
|
||||
self.assertTrue(result)
|
||||
self.zapi_client.has_luns_mapped_to_initiators.assert_called_once_with(
|
||||
initiator_list)
|
||||
self.assertEqual(0, self.library.partner_zapi_client.
|
||||
has_luns_mapped_to_initiators.call_count)
|
||||
|
||||
def test_has_luns_mapped_to_initiators_partner_map(self):
|
||||
initiator_list = fake.FC_FORMATTED_INITIATORS
|
||||
self.zapi_client.has_luns_mapped_to_initiators.return_value = False
|
||||
self.library.partner_zapi_client = mock.Mock()
|
||||
self.library.partner_zapi_client.has_luns_mapped_to_initiators.\
|
||||
return_value = True
|
||||
|
||||
result = self.library._has_luns_mapped_to_initiators(initiator_list)
|
||||
|
||||
self.assertTrue(result)
|
||||
self.zapi_client.has_luns_mapped_to_initiators.assert_called_once_with(
|
||||
initiator_list)
|
||||
self.library.partner_zapi_client.has_luns_mapped_to_initiators.\
|
||||
assert_called_with(initiator_list)
|
||||
|
||||
def test_has_luns_mapped_to_initiators_no_maps(self):
|
||||
initiator_list = fake.FC_FORMATTED_INITIATORS
|
||||
self.zapi_client.has_luns_mapped_to_initiators.return_value = False
|
||||
self.library.partner_zapi_client = mock.Mock()
|
||||
self.library.partner_zapi_client.has_luns_mapped_to_initiators.\
|
||||
return_value = False
|
||||
|
||||
result = self.library._has_luns_mapped_to_initiators(initiator_list)
|
||||
|
||||
self.assertFalse(result)
|
||||
self.zapi_client.has_luns_mapped_to_initiators.assert_called_once_with(
|
||||
initiator_list)
|
||||
self.library.partner_zapi_client.has_luns_mapped_to_initiators.\
|
||||
assert_called_with(initiator_list)
|
||||
|
||||
def test_has_luns_mapped_to_initiators_no_partner(self):
|
||||
initiator_list = fake.FC_FORMATTED_INITIATORS
|
||||
self.zapi_client.has_luns_mapped_to_initiators.return_value = False
|
||||
self.library.partner_zapi_client = mock.Mock()
|
||||
self.library.partner_zapi_client.has_luns_mapped_to_initiators.\
|
||||
return_value = True
|
||||
|
||||
result = self.library._has_luns_mapped_to_initiators(
|
||||
initiator_list, include_partner=False)
|
||||
|
||||
self.assertFalse(result)
|
||||
self.zapi_client.has_luns_mapped_to_initiators.assert_called_once_with(
|
||||
initiator_list)
|
||||
self.assertEqual(0, self.library.partner_zapi_client.
|
||||
has_luns_mapped_to_initiators.call_count)
|
||||
|
||||
def test_clone_lun_zero_block_count(self):
|
||||
"""Test for when clone lun is not passed a block count."""
|
||||
|
||||
@ -88,22 +304,51 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
||||
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
|
||||
'newFakeLUN', 'true', block_count=0, dest_block=0, src_block=0)
|
||||
|
||||
def test_get_fc_target_wwpns(self):
|
||||
ports1 = [fake.FC_FORMATTED_TARGET_WWPNS[0],
|
||||
fake.FC_FORMATTED_TARGET_WWPNS[1]]
|
||||
ports2 = [fake.FC_FORMATTED_TARGET_WWPNS[2],
|
||||
fake.FC_FORMATTED_TARGET_WWPNS[3]]
|
||||
self.zapi_client.get_fc_target_wwpns.return_value = ports1
|
||||
self.library.partner_zapi_client = mock.Mock()
|
||||
self.library.partner_zapi_client.get_fc_target_wwpns.return_value = \
|
||||
ports2
|
||||
|
||||
result = self.library._get_fc_target_wwpns()
|
||||
|
||||
self.assertSetEqual(set(fake.FC_FORMATTED_TARGET_WWPNS), set(result))
|
||||
|
||||
def test_get_fc_target_wwpns_no_partner(self):
|
||||
ports1 = [fake.FC_FORMATTED_TARGET_WWPNS[0],
|
||||
fake.FC_FORMATTED_TARGET_WWPNS[1]]
|
||||
ports2 = [fake.FC_FORMATTED_TARGET_WWPNS[2],
|
||||
fake.FC_FORMATTED_TARGET_WWPNS[3]]
|
||||
self.zapi_client.get_fc_target_wwpns.return_value = ports1
|
||||
self.library.partner_zapi_client = mock.Mock()
|
||||
self.library.partner_zapi_client.get_fc_target_wwpns.return_value = \
|
||||
ports2
|
||||
|
||||
result = self.library._get_fc_target_wwpns(include_partner=False)
|
||||
|
||||
self.assertSetEqual(set(ports1), set(result))
|
||||
|
||||
@mock.patch.object(block_7mode.NetAppBlockStorage7modeLibrary,
|
||||
'_refresh_volume_info', mock.Mock())
|
||||
@mock.patch.object(block_7mode.NetAppBlockStorage7modeLibrary,
|
||||
'_get_pool_stats', mock.Mock())
|
||||
def test_vol_stats_calls_provide_ems(self):
|
||||
self.library.zapi_client.provide_ems = mock.Mock()
|
||||
|
||||
self.library.get_volume_stats(refresh=True)
|
||||
|
||||
self.assertEqual(self.library.zapi_client.provide_ems.call_count, 1)
|
||||
|
||||
def test_create_lun(self):
|
||||
self.library.vol_refresh_voluntary = False
|
||||
|
||||
self.library._create_lun(FAKE_VOLUME, FAKE_LUN, FAKE_SIZE,
|
||||
FAKE_METADATA)
|
||||
self.library._create_lun(fake.VOLUME, fake.LUN,
|
||||
fake.SIZE, fake.METADATA)
|
||||
|
||||
self.library.zapi_client.create_lun.assert_called_once_with(
|
||||
FAKE_VOLUME, FAKE_LUN, FAKE_SIZE, FAKE_METADATA, None)
|
||||
|
||||
fake.VOLUME, fake.LUN, fake.SIZE, fake.METADATA, None)
|
||||
self.assertTrue(self.library.vol_refresh_voluntary)
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# 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
|
||||
@ -17,13 +16,18 @@
|
||||
Mock unit tests for the NetApp block storage library
|
||||
"""
|
||||
|
||||
|
||||
import uuid
|
||||
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.tests.volume.drivers.netapp.dataontap import fakes as fake
|
||||
from cinder.volume.drivers.netapp.dataontap import block_base
|
||||
from cinder.volume.drivers.netapp.dataontap.block_base import \
|
||||
NetAppBlockStorageLibrary as block_lib
|
||||
from cinder.volume.drivers.netapp.dataontap.client.api import NaApiError
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
|
||||
|
||||
@ -33,52 +37,45 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
||||
super(NetAppBlockStorageLibraryTestCase, self).setUp()
|
||||
|
||||
kwargs = {'configuration': mock.Mock()}
|
||||
self.library = block_base.NetAppBlockStorageLibrary('driver',
|
||||
'protocol',
|
||||
**kwargs)
|
||||
self.library = block_lib('driver', 'protocol', **kwargs)
|
||||
self.library.zapi_client = mock.Mock()
|
||||
self.zapi_client = self.library.zapi_client
|
||||
self.mock_request = mock.Mock()
|
||||
|
||||
def tearDown(self):
|
||||
super(NetAppBlockStorageLibraryTestCase, self).tearDown()
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary, '_get_lun_attr',
|
||||
@mock.patch.object(block_lib, '_get_lun_attr',
|
||||
mock.Mock(return_value={'Volume': 'vol1'}))
|
||||
def test_get_pool(self):
|
||||
pool = self.library.get_pool({'name': 'volume-fake-uuid'})
|
||||
self.assertEqual(pool, 'vol1')
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary, '_get_lun_attr',
|
||||
@mock.patch.object(block_lib, '_get_lun_attr',
|
||||
mock.Mock(return_value=None))
|
||||
def test_get_pool_no_metadata(self):
|
||||
pool = self.library.get_pool({'name': 'volume-fake-uuid'})
|
||||
self.assertEqual(pool, None)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary, '_get_lun_attr',
|
||||
@mock.patch.object(block_lib, '_get_lun_attr',
|
||||
mock.Mock(return_value=dict()))
|
||||
def test_get_pool_volume_unknown(self):
|
||||
pool = self.library.get_pool({'name': 'volume-fake-uuid'})
|
||||
self.assertEqual(pool, None)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary, '_create_lun',
|
||||
mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun_handle',
|
||||
mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_add_lun_to_table',
|
||||
mock.Mock())
|
||||
@mock.patch.object(block_lib, '_create_lun', mock.Mock())
|
||||
@mock.patch.object(block_lib, '_create_lun_handle', mock.Mock())
|
||||
@mock.patch.object(block_lib, '_add_lun_to_table', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value=None))
|
||||
@mock.patch.object(block_base, 'LOG',
|
||||
mock.Mock())
|
||||
@mock.patch.object(block_base, 'LOG', mock.Mock())
|
||||
def test_create_volume(self):
|
||||
self.library.create_volume({'name': 'lun1', 'size': 100,
|
||||
'id': uuid.uuid4(),
|
||||
'host': 'hostname@backend#vol1'})
|
||||
self.library._create_lun.assert_called_once_with(
|
||||
'vol1', 'lun1', 107374182400, mock.ANY, None)
|
||||
self.assertEqual(0, block_base.LOG.warn.call_count)
|
||||
self.assertEqual(0, block_base.LOG.warning.call_count)
|
||||
|
||||
def test_create_volume_no_pool_provided_by_scheduler(self):
|
||||
self.assertRaises(exception.InvalidHost, self.library.create_volume,
|
||||
@ -86,12 +83,206 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
||||
'id': uuid.uuid4(),
|
||||
'host': 'hostname@backend'}) # missing pool
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun_handle', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_add_lun_to_table', mock.Mock())
|
||||
@mock.patch.object(block_lib, '_get_lun_attr')
|
||||
@mock.patch.object(block_lib, '_get_or_create_igroup')
|
||||
def test_map_lun(self, mock_get_or_create_igroup, mock_get_lun_attr):
|
||||
os = 'linux'
|
||||
protocol = 'fcp'
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1, 'OsType': os}
|
||||
mock_get_or_create_igroup.return_value = fake.IGROUP1_NAME
|
||||
self.zapi_client.map_lun.return_value = '1'
|
||||
|
||||
lun_id = self.library._map_lun('fake_volume',
|
||||
fake.FC_FORMATTED_INITIATORS,
|
||||
protocol, None)
|
||||
|
||||
self.assertEqual(lun_id, '1')
|
||||
mock_get_or_create_igroup.assert_called_once_with(
|
||||
fake.FC_FORMATTED_INITIATORS, protocol, os)
|
||||
self.zapi_client.map_lun.assert_called_once_with(
|
||||
fake.LUN1, fake.IGROUP1_NAME, lun_id=None)
|
||||
|
||||
@mock.patch.object(block_lib, '_get_lun_attr')
|
||||
@mock.patch.object(block_lib, '_get_or_create_igroup')
|
||||
@mock.patch.object(block_lib, '_find_mapped_lun_igroup')
|
||||
def test_map_lun_preexisting(self, mock_find_mapped_lun_igroup,
|
||||
mock_get_or_create_igroup, mock_get_lun_attr):
|
||||
os = 'linux'
|
||||
protocol = 'fcp'
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1, 'OsType': os}
|
||||
mock_get_or_create_igroup.return_value = fake.IGROUP1_NAME
|
||||
mock_find_mapped_lun_igroup.return_value = (fake.IGROUP1_NAME, '2')
|
||||
self.zapi_client.map_lun.side_effect = NaApiError
|
||||
|
||||
lun_id = self.library._map_lun(
|
||||
'fake_volume', fake.FC_FORMATTED_INITIATORS, protocol, None)
|
||||
|
||||
self.assertEqual(lun_id, '2')
|
||||
mock_find_mapped_lun_igroup.assert_called_once_with(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
@mock.patch.object(block_lib, '_get_lun_attr')
|
||||
@mock.patch.object(block_lib, '_get_or_create_igroup')
|
||||
@mock.patch.object(block_lib, '_find_mapped_lun_igroup')
|
||||
def test_map_lun_api_error(self, mock_find_mapped_lun_igroup,
|
||||
mock_get_or_create_igroup, mock_get_lun_attr):
|
||||
os = 'linux'
|
||||
protocol = 'fcp'
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1, 'OsType': os}
|
||||
mock_get_or_create_igroup.return_value = fake.IGROUP1_NAME
|
||||
mock_find_mapped_lun_igroup.return_value = (None, None)
|
||||
self.zapi_client.map_lun.side_effect = NaApiError
|
||||
|
||||
self.assertRaises(NaApiError, self.library._map_lun, 'fake_volume',
|
||||
fake.FC_FORMATTED_INITIATORS, protocol, None)
|
||||
|
||||
@mock.patch.object(block_lib, '_find_mapped_lun_igroup')
|
||||
def test_unmap_lun(self, mock_find_mapped_lun_igroup):
|
||||
mock_find_mapped_lun_igroup.return_value = (fake.IGROUP1_NAME, 1)
|
||||
|
||||
self.library._unmap_lun(fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.zapi_client.unmap_lun.assert_called_once_with(fake.LUN1,
|
||||
fake.IGROUP1_NAME)
|
||||
|
||||
def test_find_mapped_lun_igroup(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.library._find_mapped_lun_igroup,
|
||||
fake.LUN1,
|
||||
fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
def test_has_luns_mapped_to_initiators(self):
|
||||
self.zapi_client.has_luns_mapped_to_initiators.return_value = True
|
||||
self.assertTrue(self.library._has_luns_mapped_to_initiators(
|
||||
fake.FC_FORMATTED_INITIATORS))
|
||||
self.zapi_client.has_luns_mapped_to_initiators.assert_called_once_with(
|
||||
fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
def test_get_or_create_igroup_preexisting(self):
|
||||
self.zapi_client.get_igroup_by_initiators.return_value = [fake.IGROUP1]
|
||||
|
||||
igroup_name = self.library._get_or_create_igroup(
|
||||
fake.FC_FORMATTED_INITIATORS, 'fcp', 'linux')
|
||||
|
||||
self.assertEqual(igroup_name, fake.IGROUP1_NAME)
|
||||
self.zapi_client.get_igroup_by_initiators.assert_called_once_with(
|
||||
fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
@mock.patch.object(uuid, 'uuid4', mock.Mock(return_value=fake.UUID1))
|
||||
def test_get_or_create_igroup_none_preexisting(self):
|
||||
self.zapi_client.get_igroup_by_initiators.return_value = []
|
||||
|
||||
igroup_name = self.library._get_or_create_igroup(
|
||||
fake.FC_FORMATTED_INITIATORS, 'fcp', 'linux')
|
||||
|
||||
self.assertEqual(igroup_name, 'openstack-' + fake.UUID1)
|
||||
self.zapi_client.create_igroup.assert_called_once_with(
|
||||
igroup_name, 'fcp', 'linux')
|
||||
self.assertEqual(len(fake.FC_FORMATTED_INITIATORS),
|
||||
self.zapi_client.add_igroup_initiator.call_count)
|
||||
|
||||
def test_get_fc_target_wwpns(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self.library._get_fc_target_wwpns)
|
||||
|
||||
@mock.patch.object(block_lib, '_build_initiator_target_map')
|
||||
@mock.patch.object(block_lib, '_map_lun')
|
||||
def test_initialize_connection_fc(self, mock_map_lun,
|
||||
mock_build_initiator_target_map):
|
||||
self.maxDiff = None
|
||||
mock_map_lun.return_value = '1'
|
||||
mock_build_initiator_target_map.return_value = (fake.FC_TARGET_WWPNS,
|
||||
fake.FC_I_T_MAP, 4)
|
||||
|
||||
target_info = self.library.initialize_connection_fc(fake.FC_VOLUME,
|
||||
fake.FC_CONNECTOR)
|
||||
|
||||
self.assertDictEqual(target_info, fake.FC_TARGET_INFO)
|
||||
mock_map_lun.assert_called_once_with(
|
||||
'fake_volume', fake.FC_FORMATTED_INITIATORS, 'fcp', None)
|
||||
|
||||
@mock.patch.object(block_lib, '_build_initiator_target_map')
|
||||
@mock.patch.object(block_lib, '_map_lun')
|
||||
def test_initialize_connection_fc_no_wwpns(
|
||||
self, mock_map_lun, mock_build_initiator_target_map):
|
||||
|
||||
mock_map_lun.return_value = '1'
|
||||
mock_build_initiator_target_map.return_value = (None, None, 0)
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.library.initialize_connection_fc,
|
||||
fake.FC_VOLUME,
|
||||
fake.FC_CONNECTOR)
|
||||
|
||||
@mock.patch.object(block_lib, '_has_luns_mapped_to_initiators')
|
||||
@mock.patch.object(block_lib, '_unmap_lun')
|
||||
@mock.patch.object(block_lib, '_get_lun_attr')
|
||||
def test_terminate_connection_fc(self, mock_get_lun_attr, mock_unmap_lun,
|
||||
mock_has_luns_mapped_to_initiators):
|
||||
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1}
|
||||
mock_unmap_lun.return_value = None
|
||||
mock_has_luns_mapped_to_initiators.return_value = True
|
||||
|
||||
target_info = self.library.terminate_connection_fc(fake.FC_VOLUME,
|
||||
fake.FC_CONNECTOR)
|
||||
|
||||
self.assertDictEqual(target_info, fake.FC_TARGET_INFO_EMPTY)
|
||||
mock_unmap_lun.assert_called_once_with(fake.LUN1,
|
||||
fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
@mock.patch.object(block_lib, '_build_initiator_target_map')
|
||||
@mock.patch.object(block_lib, '_has_luns_mapped_to_initiators')
|
||||
@mock.patch.object(block_lib, '_unmap_lun')
|
||||
@mock.patch.object(block_lib, '_get_lun_attr')
|
||||
def test_terminate_connection_fc_no_more_luns(
|
||||
self, mock_get_lun_attr, mock_unmap_lun,
|
||||
mock_has_luns_mapped_to_initiators,
|
||||
mock_build_initiator_target_map):
|
||||
|
||||
mock_get_lun_attr.return_value = {'Path': fake.LUN1}
|
||||
mock_unmap_lun.return_value = None
|
||||
mock_has_luns_mapped_to_initiators.return_value = False
|
||||
mock_build_initiator_target_map.return_value = (fake.FC_TARGET_WWPNS,
|
||||
fake.FC_I_T_MAP, 4)
|
||||
|
||||
target_info = self.library.terminate_connection_fc(fake.FC_VOLUME,
|
||||
fake.FC_CONNECTOR)
|
||||
|
||||
self.assertDictEqual(target_info, fake.FC_TARGET_INFO_UNMAP)
|
||||
|
||||
@mock.patch.object(block_lib, '_get_fc_target_wwpns')
|
||||
def test_build_initiator_target_map_no_lookup_service(
|
||||
self, mock_get_fc_target_wwpns):
|
||||
|
||||
self.library.lookup_service = None
|
||||
mock_get_fc_target_wwpns.return_value = fake.FC_FORMATTED_TARGET_WWPNS
|
||||
|
||||
(target_wwpns, init_targ_map, num_paths) = \
|
||||
self.library._build_initiator_target_map(fake.FC_CONNECTOR)
|
||||
|
||||
self.assertSetEqual(set(fake.FC_TARGET_WWPNS), set(target_wwpns))
|
||||
self.assertDictEqual(fake.FC_I_T_MAP_COMPLETE, init_targ_map)
|
||||
self.assertEqual(0, num_paths)
|
||||
|
||||
@mock.patch.object(block_lib, '_get_fc_target_wwpns')
|
||||
def test_build_initiator_target_map_with_lookup_service(
|
||||
self, mock_get_fc_target_wwpns):
|
||||
|
||||
self.library.lookup_service = mock.Mock()
|
||||
self.library.lookup_service.get_device_mapping_from_network.\
|
||||
return_value = fake.FC_FABRIC_MAP
|
||||
mock_get_fc_target_wwpns.return_value = fake.FC_FORMATTED_TARGET_WWPNS
|
||||
|
||||
(target_wwpns, init_targ_map, num_paths) = \
|
||||
self.library._build_initiator_target_map(fake.FC_CONNECTOR)
|
||||
|
||||
self.assertSetEqual(set(fake.FC_TARGET_WWPNS), set(target_wwpns))
|
||||
self.assertDictEqual(fake.FC_I_T_MAP, init_targ_map)
|
||||
self.assertEqual(4, num_paths)
|
||||
|
||||
@mock.patch.object(block_lib, '_create_lun', mock.Mock())
|
||||
@mock.patch.object(block_lib, '_create_lun_handle', mock.Mock())
|
||||
@mock.patch.object(block_lib, '_add_lun_to_table', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'LOG', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value={'netapp:raid_type': 'raid4'}))
|
||||
@ -100,16 +291,14 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
||||
self.library.create_volume({'name': 'lun1', 'size': 100,
|
||||
'id': uuid.uuid4(),
|
||||
'host': 'hostname@backend#vol1'})
|
||||
|
||||
warn_msg = 'Extra spec netapp:raid_type is obsolete. ' \
|
||||
'Use netapp_raid_type instead.'
|
||||
na_utils.LOG.warn.assert_called_once_with(warn_msg)
|
||||
na_utils.LOG.warning.assert_called_once_with(warn_msg)
|
||||
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_create_lun_handle', mock.Mock())
|
||||
@mock.patch.object(block_base.NetAppBlockStorageLibrary,
|
||||
'_add_lun_to_table', mock.Mock())
|
||||
@mock.patch.object(block_lib, '_create_lun', mock.Mock())
|
||||
@mock.patch.object(block_lib, '_create_lun_handle', mock.Mock())
|
||||
@mock.patch.object(block_lib, '_add_lun_to_table', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'LOG', mock.Mock())
|
||||
@mock.patch.object(na_utils, 'get_volume_extra_specs',
|
||||
mock.Mock(return_value={'netapp_thick_provisioned':
|
||||
@ -119,6 +308,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
|
||||
self.library.create_volume({'name': 'lun1', 'size': 100,
|
||||
'id': uuid.uuid4(),
|
||||
'host': 'hostname@backend#vol1'})
|
||||
|
||||
warn_msg = 'Extra spec netapp_thick_provisioned is deprecated. ' \
|
||||
'Use netapp_thin_provisioned instead.'
|
||||
na_utils.LOG.warn.assert_called_once_with(warn_msg)
|
||||
na_utils.LOG.warning.assert_called_once_with(warn_msg)
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||
# 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
|
||||
@ -17,20 +16,21 @@
|
||||
Mock unit tests for the NetApp block storage C-mode library
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
from cinder import test
|
||||
import cinder.tests.volume.drivers.netapp.dataontap.fakes as fake
|
||||
import cinder.tests.volume.drivers.netapp.fakes as na_fakes
|
||||
from cinder.volume.drivers.netapp.dataontap.block_base import \
|
||||
NetAppBlockStorageLibrary as block_lib
|
||||
from cinder.volume.drivers.netapp.dataontap import block_cmode
|
||||
from cinder.volume.drivers.netapp.dataontap.block_cmode import \
|
||||
NetAppBlockStorageCmodeLibrary as block_lib_cmode
|
||||
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
||||
from cinder.volume.drivers.netapp.dataontap import ssc_cmode
|
||||
|
||||
FAKE_VOLUME = six.text_type(uuid.uuid4())
|
||||
FAKE_LUN = six.text_type(uuid.uuid4())
|
||||
FAKE_SIZE = '1024'
|
||||
FAKE_METADATA = {'OsType': 'linux', 'SpaceReserved': 'true'}
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
|
||||
|
||||
class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
@ -39,17 +39,114 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(NetAppBlockStorageCmodeLibraryTestCase, self).setUp()
|
||||
|
||||
kwargs = {'configuration': mock.Mock()}
|
||||
self.library = block_cmode.NetAppBlockStorageCmodeLibrary('driver',
|
||||
'protocol',
|
||||
**kwargs)
|
||||
kwargs = {'configuration': self.get_config_cmode()}
|
||||
self.library = block_lib_cmode('driver', 'protocol', **kwargs)
|
||||
|
||||
self.library.zapi_client = mock.Mock()
|
||||
self.zapi_client = self.library.zapi_client
|
||||
self.library.vserver = mock.Mock()
|
||||
self.library.ssc_vols = None
|
||||
|
||||
def tearDown(self):
|
||||
super(NetAppBlockStorageCmodeLibraryTestCase, self).tearDown()
|
||||
|
||||
def get_config_cmode(self):
|
||||
config = na_fakes.create_configuration_cmode()
|
||||
config.netapp_storage_protocol = 'iscsi'
|
||||
config.netapp_login = 'admin'
|
||||
config.netapp_password = 'pass'
|
||||
config.netapp_server_hostname = '127.0.0.1'
|
||||
config.netapp_transport_type = 'https'
|
||||
config.netapp_server_port = '443'
|
||||
config.netapp_vserver = 'openstack'
|
||||
return config
|
||||
|
||||
@mock.patch.object(client_base.Client, 'get_ontapi_version',
|
||||
mock.MagicMock(return_value=(1, 20)))
|
||||
@mock.patch.object(na_utils, 'check_flags')
|
||||
@mock.patch.object(block_lib, 'do_setup')
|
||||
def test_do_setup(self, super_do_setup, mock_check_flags):
|
||||
context = mock.Mock()
|
||||
|
||||
self.library.do_setup(context)
|
||||
|
||||
super_do_setup.assert_called_once_with(context)
|
||||
self.assertEqual(1, mock_check_flags.call_count)
|
||||
|
||||
@mock.patch.object(block_lib, 'check_for_setup_error')
|
||||
@mock.patch.object(ssc_cmode, 'check_ssc_api_permissions')
|
||||
def test_check_for_setup_error(self, mock_check_ssc_api_permissions,
|
||||
super_check_for_setup_error):
|
||||
|
||||
self.library.check_for_setup_error()
|
||||
|
||||
super_check_for_setup_error.assert_called_once_with()
|
||||
mock_check_ssc_api_permissions.assert_called_once_with(
|
||||
self.library.zapi_client)
|
||||
|
||||
def test_find_mapped_lun_igroup(self):
|
||||
igroups = [fake.IGROUP1]
|
||||
self.zapi_client.get_igroup_by_initiators.return_value = igroups
|
||||
|
||||
lun_maps = [{'initiator-group': fake.IGROUP1_NAME,
|
||||
'lun-id': '1',
|
||||
'vserver': fake.VSERVER1_NAME}]
|
||||
self.zapi_client.get_lun_map.return_value = lun_maps
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.assertEqual(fake.IGROUP1_NAME, igroup)
|
||||
self.assertEqual('1', lun_id)
|
||||
|
||||
def test_find_mapped_lun_igroup_initiator_mismatch(self):
|
||||
self.zapi_client.get_igroup_by_initiators.return_value = []
|
||||
|
||||
lun_maps = [{'initiator-group': fake.IGROUP1_NAME,
|
||||
'lun-id': '1',
|
||||
'vserver': fake.VSERVER1_NAME}]
|
||||
self.zapi_client.get_lun_map.return_value = lun_maps
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.assertIsNone(igroup)
|
||||
self.assertIsNone(lun_id)
|
||||
|
||||
def test_find_mapped_lun_igroup_name_mismatch(self):
|
||||
igroups = [{'initiator-group-os-type': 'linux',
|
||||
'initiator-group-type': 'fcp',
|
||||
'initiator-group-name': 'igroup2'}]
|
||||
self.zapi_client.get_igroup_by_initiators.return_value = igroups
|
||||
|
||||
lun_maps = [{'initiator-group': fake.IGROUP1_NAME,
|
||||
'lun-id': '1',
|
||||
'vserver': fake.VSERVER1_NAME}]
|
||||
self.zapi_client.get_lun_map.return_value = lun_maps
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.assertIsNone(igroup)
|
||||
self.assertIsNone(lun_id)
|
||||
|
||||
def test_find_mapped_lun_igroup_no_igroup_prefix(self):
|
||||
igroups = [{'initiator-group-os-type': 'linux',
|
||||
'initiator-group-type': 'fcp',
|
||||
'initiator-group-name': 'igroup2'}]
|
||||
self.zapi_client.get_igroup_by_initiators.return_value = igroups
|
||||
|
||||
lun_maps = [{'initiator-group': 'igroup2',
|
||||
'lun-id': '1',
|
||||
'vserver': fake.VSERVER1_NAME}]
|
||||
self.zapi_client.get_lun_map.return_value = lun_maps
|
||||
|
||||
(igroup, lun_id) = self.library._find_mapped_lun_igroup(
|
||||
fake.LUN1, fake.FC_FORMATTED_INITIATORS)
|
||||
|
||||
self.assertIsNone(igroup)
|
||||
self.assertIsNone(lun_id)
|
||||
|
||||
def test_clone_lun_zero_block_count(self):
|
||||
"""Test for when clone lun is not passed a block count."""
|
||||
|
||||
@ -92,24 +189,31 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'true', block_count=0,
|
||||
dest_block=0, src_block=0)
|
||||
|
||||
def test_get_fc_target_wwpns(self):
|
||||
ports = [fake.FC_FORMATTED_TARGET_WWPNS[0],
|
||||
fake.FC_FORMATTED_TARGET_WWPNS[1]]
|
||||
self.zapi_client.get_fc_target_wwpns.return_value = ports
|
||||
|
||||
result = self.library._get_fc_target_wwpns()
|
||||
|
||||
self.assertSetEqual(set(ports), set(result))
|
||||
|
||||
@mock.patch.object(ssc_cmode, 'refresh_cluster_ssc', mock.Mock())
|
||||
@mock.patch.object(block_cmode.NetAppBlockStorageCmodeLibrary,
|
||||
'_get_pool_stats', mock.Mock())
|
||||
def test_vol_stats_calls_provide_ems(self):
|
||||
self.library.zapi_client.provide_ems = mock.Mock()
|
||||
|
||||
self.library.get_volume_stats(refresh=True)
|
||||
|
||||
self.assertEqual(self.library.zapi_client.provide_ems.call_count, 1)
|
||||
|
||||
def test_create_lun(self):
|
||||
self.library._update_stale_vols = mock.Mock()
|
||||
|
||||
self.library._create_lun(FAKE_VOLUME,
|
||||
FAKE_LUN,
|
||||
FAKE_SIZE,
|
||||
FAKE_METADATA)
|
||||
self.library._create_lun(fake.VOLUME, fake.LUN,
|
||||
fake.SIZE, fake.METADATA)
|
||||
|
||||
self.library.zapi_client.create_lun.assert_called_once_with(
|
||||
FAKE_VOLUME, FAKE_LUN, FAKE_SIZE,
|
||||
FAKE_METADATA, None)
|
||||
|
||||
fake.VOLUME, fake.LUN, fake.SIZE, fake.METADATA, None)
|
||||
self.assertEqual(1, self.library._update_stale_vols.call_count)
|
||||
|
44
cinder/tests/volume/drivers/netapp/fakes.py
Normal file
44
cinder/tests/volume/drivers/netapp/fakes.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Copyright (c) - 2014, Clinton Knight. 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 cinder.volume import configuration as conf
|
||||
import cinder.volume.drivers.netapp.options as na_opts
|
||||
|
||||
|
||||
def create_configuration():
|
||||
config = conf.Configuration(None)
|
||||
config.append_config_values(na_opts.netapp_connection_opts)
|
||||
config.append_config_values(na_opts.netapp_transport_opts)
|
||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||
config.append_config_values(na_opts.netapp_provisioning_opts)
|
||||
return config
|
||||
|
||||
|
||||
def create_configuration_7mode():
|
||||
config = create_configuration()
|
||||
config.append_config_values(na_opts.netapp_7mode_opts)
|
||||
return config
|
||||
|
||||
|
||||
def create_configuration_cmode():
|
||||
config = create_configuration()
|
||||
config.append_config_values(na_opts.netapp_cluster_opts)
|
||||
return config
|
||||
|
||||
|
||||
def create_configuration_eseries():
|
||||
config = create_configuration()
|
||||
config.append_config_values(na_opts.netapp_eseries_opts)
|
||||
return config
|
@ -42,12 +42,14 @@ netapp_unified_plugin_registry =\
|
||||
{'ontap_cluster':
|
||||
{
|
||||
'iscsi': DATAONTAP_PATH + '.iscsi_cmode.NetAppCmodeISCSIDriver',
|
||||
'nfs': DATAONTAP_PATH + '.nfs_cmode.NetAppCmodeNfsDriver'
|
||||
'nfs': DATAONTAP_PATH + '.nfs_cmode.NetAppCmodeNfsDriver',
|
||||
'fc': DATAONTAP_PATH + '.fc_cmode.NetAppCmodeFibreChannelDriver'
|
||||
},
|
||||
'ontap_7mode':
|
||||
{
|
||||
'iscsi': DATAONTAP_PATH + '.iscsi_7mode.NetApp7modeISCSIDriver',
|
||||
'nfs': DATAONTAP_PATH + '.nfs_7mode.NetApp7modeNfsDriver'
|
||||
'nfs': DATAONTAP_PATH + '.nfs_7mode.NetApp7modeNfsDriver',
|
||||
'fc': DATAONTAP_PATH + '.fc_7mode.NetApp7modeFibreChannelDriver'
|
||||
},
|
||||
'eseries':
|
||||
{
|
||||
@ -123,8 +125,7 @@ class NetAppDriverFactory(object):
|
||||
if driver_loc is None:
|
||||
raise exception.InvalidInput(
|
||||
reason=_('Protocol %(storage_protocol)s is not supported'
|
||||
' for storage family %(storage_family)s')
|
||||
% fmt)
|
||||
' for storage family %(storage_family)s') % fmt)
|
||||
|
||||
NetAppDriverFactory.check_netapp_driver(driver_loc)
|
||||
kwargs = kwargs or {}
|
||||
|
@ -28,6 +28,7 @@ import six
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LW
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.volume.configuration import Configuration
|
||||
from cinder.volume.drivers.netapp.dataontap import block_base
|
||||
from cinder.volume.drivers.netapp.dataontap.client import client_7mode
|
||||
from cinder.volume.drivers.netapp import options as na_opts
|
||||
@ -67,12 +68,31 @@ class NetAppBlockStorage7modeLibrary(block_base.
|
||||
port=self.configuration.netapp_server_port,
|
||||
vfiler=self.vfiler)
|
||||
|
||||
self._do_partner_setup()
|
||||
|
||||
self.vol_refresh_time = None
|
||||
self.vol_refresh_interval = 1800
|
||||
self.vol_refresh_running = False
|
||||
self.vol_refresh_voluntary = False
|
||||
self.root_volume_name = self._get_root_volume_name()
|
||||
|
||||
def _do_partner_setup(self):
|
||||
partner_backend = self.configuration.netapp_partner_backend_name
|
||||
if partner_backend:
|
||||
config = Configuration(na_opts.netapp_7mode_opts, partner_backend)
|
||||
config.append_config_values(na_opts.netapp_connection_opts)
|
||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||
config.append_config_values(na_opts.netapp_transport_opts)
|
||||
|
||||
self.partner_zapi_client = client_7mode.Client(
|
||||
None,
|
||||
transport_type=config.netapp_transport_type,
|
||||
username=config.netapp_login,
|
||||
password=config.netapp_password,
|
||||
hostname=config.netapp_server_hostname,
|
||||
port=config.netapp_server_port,
|
||||
vfiler=None)
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Check that the driver is working and can communicate."""
|
||||
api_version = self.zapi_client.get_ontapi_version()
|
||||
@ -120,27 +140,40 @@ class NetAppBlockStorage7modeLibrary(block_base.
|
||||
owner = self._get_owner()
|
||||
return '%s:%s' % (owner, metadata['Path'])
|
||||
|
||||
def _find_mapped_lun_igroup(self, path, initiator, os=None):
|
||||
"""Find the igroup for mapped LUN with initiator."""
|
||||
igroup = None
|
||||
lun_id = None
|
||||
def _find_mapped_lun_igroup(self, path, initiator_list):
|
||||
"""Find an igroup for a LUN mapped to the given initiator(s)."""
|
||||
initiator_set = set(initiator_list)
|
||||
|
||||
result = self.zapi_client.get_lun_map(path)
|
||||
igroups = result.get_child_by_name('initiator-groups')
|
||||
if igroups:
|
||||
found = False
|
||||
igroup_infs = igroups.get_children()
|
||||
for ig in igroup_infs:
|
||||
initiators = ig.get_child_by_name('initiators')
|
||||
init_infs = initiators.get_children()
|
||||
for info in init_infs:
|
||||
if info.get_child_content('initiator-name') == initiator:
|
||||
found = True
|
||||
igroup = ig.get_child_content('initiator-group-name')
|
||||
lun_id = ig.get_child_content('lun-id')
|
||||
break
|
||||
if found:
|
||||
break
|
||||
return igroup, lun_id
|
||||
initiator_groups = result.get_child_by_name('initiator-groups')
|
||||
if initiator_groups:
|
||||
for initiator_group_info in initiator_groups.get_children():
|
||||
|
||||
initiator_set_for_igroup = set()
|
||||
for initiator_info in initiator_group_info.get_child_by_name(
|
||||
'initiators').get_children():
|
||||
initiator_set_for_igroup.add(
|
||||
initiator_info.get_child_content('initiator-name'))
|
||||
|
||||
if initiator_set == initiator_set_for_igroup:
|
||||
igroup = initiator_group_info.get_child_content(
|
||||
'initiator-group-name')
|
||||
lun_id = initiator_group_info.get_child_content(
|
||||
'lun-id')
|
||||
return igroup, lun_id
|
||||
|
||||
return None, None
|
||||
|
||||
def _has_luns_mapped_to_initiators(self, initiator_list,
|
||||
include_partner=True):
|
||||
"""Checks whether any LUNs are mapped to the given initiator(s)."""
|
||||
if self.zapi_client.has_luns_mapped_to_initiators(initiator_list):
|
||||
return True
|
||||
if include_partner and self.partner_zapi_client and \
|
||||
self.partner_zapi_client.has_luns_mapped_to_initiators(
|
||||
initiator_list):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _clone_lun(self, name, new_name, space_reserved='true',
|
||||
src_block=0, dest_block=0, block_count=0):
|
||||
@ -176,6 +209,12 @@ class NetAppBlockStorage7modeLibrary(block_base.
|
||||
'is-space-reservation-enabled')
|
||||
return meta_dict
|
||||
|
||||
def _get_fc_target_wwpns(self, include_partner=True):
|
||||
wwpns = self.zapi_client.get_fc_target_wwpns()
|
||||
if include_partner and self.partner_zapi_client:
|
||||
wwpns.extend(self.partner_zapi_client.get_fc_target_wwpns())
|
||||
return wwpns
|
||||
|
||||
def _update_volume_stats(self):
|
||||
"""Retrieve stats info from filer."""
|
||||
|
||||
|
@ -35,6 +35,7 @@ from cinder.volume.drivers.netapp.dataontap.client.api import NaApiError
|
||||
from cinder.volume.drivers.netapp import options as na_opts
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
from cinder.volume import utils as volume_utils
|
||||
from cinder.zonemanager import utils as fczm_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -81,6 +82,7 @@ class NetAppBlockStorageLibrary(object):
|
||||
self.zapi_client = None
|
||||
self._stats = {}
|
||||
self.lun_table = {}
|
||||
self.lookup_service = fczm_utils.create_lookup_service()
|
||||
self.app_version = kwargs.get("app_version", "unknown")
|
||||
|
||||
self.configuration = kwargs['configuration']
|
||||
@ -232,10 +234,7 @@ class NetAppBlockStorageLibrary(object):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _extract_and_populate_luns(self, api_luns):
|
||||
"""Extracts the LUNs from API.
|
||||
|
||||
Populates in the LUN table.
|
||||
"""
|
||||
"""Extracts the LUNs from API and populates the LUN table."""
|
||||
|
||||
for lun in api_luns:
|
||||
meta_dict = self._create_lun_meta(lun)
|
||||
@ -246,8 +245,8 @@ class NetAppBlockStorageLibrary(object):
|
||||
discovered_lun = NetAppLun(handle, name, size, meta_dict)
|
||||
self._add_lun_to_table(discovered_lun)
|
||||
|
||||
def _map_lun(self, name, initiator, initiator_type='iscsi', lun_id=None):
|
||||
"""Maps LUN to the initiator and returns LUN id assigned."""
|
||||
def _map_lun(self, name, initiator_list, initiator_type, lun_id=None):
|
||||
"""Maps LUN to the initiator(s) and returns LUN ID assigned."""
|
||||
metadata = self._get_lun_attr(name, 'metadata')
|
||||
os = metadata['OsType']
|
||||
path = metadata['Path']
|
||||
@ -255,35 +254,42 @@ class NetAppBlockStorageLibrary(object):
|
||||
os = os
|
||||
else:
|
||||
os = 'default'
|
||||
igroup_name = self._get_or_create_igroup(initiator,
|
||||
igroup_name = self._get_or_create_igroup(initiator_list,
|
||||
initiator_type, os)
|
||||
try:
|
||||
return self.zapi_client.map_lun(path, igroup_name, lun_id=lun_id)
|
||||
except NaApiError:
|
||||
exc_info = sys.exc_info()
|
||||
(_igroup, lun_id) = self._find_mapped_lun_igroup(path, initiator)
|
||||
(_igroup, lun_id) = self._find_mapped_lun_igroup(path,
|
||||
initiator_list)
|
||||
if lun_id is not None:
|
||||
return lun_id
|
||||
else:
|
||||
raise exc_info[0], exc_info[1], exc_info[2]
|
||||
|
||||
def _unmap_lun(self, path, initiator):
|
||||
def _unmap_lun(self, path, initiator_list):
|
||||
"""Unmaps a LUN from given initiator."""
|
||||
(igroup_name, _lun_id) = self._find_mapped_lun_igroup(path, initiator)
|
||||
(igroup_name, _lun_id) = self._find_mapped_lun_igroup(path,
|
||||
initiator_list)
|
||||
self.zapi_client.unmap_lun(path, igroup_name)
|
||||
|
||||
def _find_mapped_lun_igroup(self, path, initiator, os=None):
|
||||
"""Find the igroup for mapped LUN with initiator."""
|
||||
def _find_mapped_lun_igroup(self, path, initiator_list):
|
||||
"""Find an igroup for a LUN mapped to the given initiator(s)."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _get_or_create_igroup(self, initiator, initiator_type='iscsi',
|
||||
def _has_luns_mapped_to_initiators(self, initiator_list):
|
||||
"""Checks whether any LUNs are mapped to the given initiator(s)."""
|
||||
return self.zapi_client.has_luns_mapped_to_initiators(initiator_list)
|
||||
|
||||
def _get_or_create_igroup(self, initiator_list, initiator_type,
|
||||
os='default'):
|
||||
"""Checks for an igroup for an initiator.
|
||||
"""Checks for an igroup for a set of one or more initiators.
|
||||
|
||||
Creates igroup if not found.
|
||||
"""
|
||||
|
||||
igroups = self.zapi_client.get_igroup_by_initiator(initiator=initiator)
|
||||
igroups = self.zapi_client.get_igroup_by_initiators(initiator_list)
|
||||
|
||||
igroup_name = None
|
||||
for igroup in igroups:
|
||||
if igroup['initiator-group-os-type'] == os:
|
||||
@ -296,7 +302,8 @@ class NetAppBlockStorageLibrary(object):
|
||||
if not igroup_name:
|
||||
igroup_name = self.IGROUP_PREFIX + six.text_type(uuid.uuid4())
|
||||
self.zapi_client.create_igroup(igroup_name, initiator_type, os)
|
||||
self.zapi_client.add_igroup_initiator(igroup_name, initiator)
|
||||
for initiator in initiator_list:
|
||||
self.zapi_client.add_igroup_initiator(igroup_name, initiator)
|
||||
return igroup_name
|
||||
|
||||
def _check_allowed_os(self, os):
|
||||
@ -348,6 +355,9 @@ class NetAppBlockStorageLibrary(object):
|
||||
def _create_lun_meta(self, lun):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _get_fc_target_wwpns(self, include_partner=True):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Creates a clone of the specified volume."""
|
||||
vol_size = volume['size']
|
||||
@ -505,12 +515,12 @@ class NetAppBlockStorageLibrary(object):
|
||||
|
||||
initiator_name = connector['initiator']
|
||||
name = volume['name']
|
||||
lun_id = self._map_lun(name, initiator_name, 'iscsi', None)
|
||||
lun_id = self._map_lun(name, [initiator_name], 'iscsi', None)
|
||||
msg = _("Mapped LUN %(name)s to the initiator %(initiator_name)s")
|
||||
msg_fmt = {'name': name, 'initiator_name': initiator_name}
|
||||
LOG.debug(msg % msg_fmt)
|
||||
iqn = self.zapi_client.get_iscsi_service_details()
|
||||
target_details_list = self.zapi_client.get_target_details()
|
||||
target_details_list = self.zapi_client.get_iscsi_target_details()
|
||||
msg = _("Successfully fetched target details for LUN %(name)s and "
|
||||
"initiator %(initiator_name)s")
|
||||
msg_fmt = {'name': name, 'initiator_name': initiator_name}
|
||||
@ -565,7 +575,162 @@ class NetAppBlockStorageLibrary(object):
|
||||
name = volume['name']
|
||||
metadata = self._get_lun_attr(name, 'metadata')
|
||||
path = metadata['Path']
|
||||
self._unmap_lun(path, initiator_name)
|
||||
self._unmap_lun(path, [initiator_name])
|
||||
msg = _("Unmapped LUN %(name)s from the initiator %(initiator_name)s")
|
||||
msg_fmt = {'name': name, 'initiator_name': initiator_name}
|
||||
LOG.debug(msg % msg_fmt)
|
||||
|
||||
def initialize_connection_fc(self, volume, connector):
|
||||
"""Initializes the connection and returns connection info.
|
||||
|
||||
Assign any created volume to a compute node/host so that it can be
|
||||
used from that host.
|
||||
|
||||
The driver returns a driver_volume_type of 'fibre_channel'.
|
||||
The target_wwn can be a single entry or a list of wwns that
|
||||
correspond to the list of remote wwn(s) that will export the volume.
|
||||
Example return values:
|
||||
{
|
||||
'driver_volume_type': 'fibre_channel'
|
||||
'data': {
|
||||
'target_discovered': True,
|
||||
'target_lun': 1,
|
||||
'target_wwn': '500a098280feeba5',
|
||||
'access_mode': 'rw',
|
||||
'initiator_target_map': {
|
||||
'21000024ff406cc3': ['500a098280feeba5'],
|
||||
'21000024ff406cc2': ['500a098280feeba5']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
or
|
||||
|
||||
{
|
||||
'driver_volume_type': 'fibre_channel'
|
||||
'data': {
|
||||
'target_discovered': True,
|
||||
'target_lun': 1,
|
||||
'target_wwn': ['500a098280feeba5', '500a098290feeba5',
|
||||
'500a098190feeba5', '500a098180feeba5'],
|
||||
'access_mode': 'rw',
|
||||
'initiator_target_map': {
|
||||
'21000024ff406cc3': ['500a098280feeba5',
|
||||
'500a098290feeba5'],
|
||||
'21000024ff406cc2': ['500a098190feeba5',
|
||||
'500a098180feeba5']
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
initiators = [fczm_utils.get_formatted_wwn(wwpn)
|
||||
for wwpn in connector['wwpns']]
|
||||
volume_name = volume['name']
|
||||
|
||||
lun_id = self._map_lun(volume_name, initiators, 'fcp', None)
|
||||
|
||||
msg = _("Mapped LUN %(name)s to the initiator(s) %(initiators)s")
|
||||
msg_fmt = {'name': volume_name, 'initiators': initiators}
|
||||
LOG.debug(msg % msg_fmt)
|
||||
|
||||
target_wwpns, initiator_target_map, num_paths = \
|
||||
self._build_initiator_target_map(connector)
|
||||
|
||||
if target_wwpns:
|
||||
msg = _("Successfully fetched target details for LUN %(name)s "
|
||||
"and initiator(s) %(initiators)s")
|
||||
msg_fmt = {'name': volume_name, 'initiators': initiators}
|
||||
LOG.debug(msg % msg_fmt)
|
||||
else:
|
||||
msg = _('Failed to get LUN target details for the LUN %s')
|
||||
raise exception.VolumeBackendAPIException(data=msg % volume_name)
|
||||
|
||||
target_info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {'target_discovered': True,
|
||||
'target_lun': lun_id,
|
||||
'target_wwn': target_wwpns,
|
||||
'access_mode': 'rw',
|
||||
'initiator_target_map': initiator_target_map}}
|
||||
|
||||
return target_info
|
||||
|
||||
def terminate_connection_fc(self, volume, connector, **kwargs):
|
||||
"""Disallow connection from connector.
|
||||
|
||||
Return empty data if other volumes are in the same zone.
|
||||
The FibreChannel ZoneManager doesn't remove zones
|
||||
if there isn't an initiator_target_map in the
|
||||
return of terminate_connection.
|
||||
|
||||
:returns: data - the target_wwns and initiator_target_map if the
|
||||
zone is to be removed, otherwise the same map with
|
||||
an empty dict for the 'data' key
|
||||
"""
|
||||
|
||||
initiators = [fczm_utils.get_formatted_wwn(wwpn)
|
||||
for wwpn in connector['wwpns']]
|
||||
name = volume['name']
|
||||
metadata = self._get_lun_attr(name, 'metadata')
|
||||
path = metadata['Path']
|
||||
|
||||
self._unmap_lun(path, initiators)
|
||||
|
||||
msg = _("Unmapped LUN %(name)s from the initiator %(initiators)s")
|
||||
msg_fmt = {'name': name, 'initiators': initiators}
|
||||
LOG.debug(msg % msg_fmt)
|
||||
|
||||
info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {}}
|
||||
|
||||
if not self._has_luns_mapped_to_initiators(initiators):
|
||||
# No more exports for this host, so tear down zone.
|
||||
LOG.info(_LI("Need to remove FC Zone, building initiator "
|
||||
"target map"))
|
||||
|
||||
target_wwpns, initiator_target_map, num_paths = \
|
||||
self._build_initiator_target_map(connector)
|
||||
|
||||
info['data'] = {'target_wwn': target_wwpns,
|
||||
'initiator_target_map': initiator_target_map}
|
||||
|
||||
return info
|
||||
|
||||
def _build_initiator_target_map(self, connector):
|
||||
"""Build the target_wwns and the initiator target map."""
|
||||
|
||||
# get WWPNs from controller and strip colons
|
||||
all_target_wwpns = self._get_fc_target_wwpns()
|
||||
all_target_wwpns = [six.text_type(wwpn).replace(':', '')
|
||||
for wwpn in all_target_wwpns]
|
||||
|
||||
target_wwpns = []
|
||||
init_targ_map = {}
|
||||
num_paths = 0
|
||||
|
||||
if self.lookup_service is not None:
|
||||
# Use FC SAN lookup to determine which ports are visible.
|
||||
dev_map = self.lookup_service.get_device_mapping_from_network(
|
||||
connector['wwpns'],
|
||||
all_target_wwpns)
|
||||
|
||||
for fabric_name in dev_map:
|
||||
fabric = dev_map[fabric_name]
|
||||
target_wwpns += fabric['target_port_wwn_list']
|
||||
for initiator in fabric['initiator_port_wwn_list']:
|
||||
if initiator not in init_targ_map:
|
||||
init_targ_map[initiator] = []
|
||||
init_targ_map[initiator] += fabric['target_port_wwn_list']
|
||||
init_targ_map[initiator] = list(set(
|
||||
init_targ_map[initiator]))
|
||||
for target in init_targ_map[initiator]:
|
||||
num_paths += 1
|
||||
target_wwpns = list(set(target_wwpns))
|
||||
else:
|
||||
initiator_wwns = connector['wwpns']
|
||||
target_wwpns = all_target_wwpns
|
||||
|
||||
for initiator in initiator_wwns:
|
||||
init_targ_map[initiator] = target_wwpns
|
||||
|
||||
return target_wwpns, init_targ_map, num_paths
|
||||
|
@ -89,10 +89,10 @@ class NetAppBlockStorageCmodeLibrary(block_base.
|
||||
"""Returns LUN handle based on filer type."""
|
||||
return '%s:%s' % (self.vserver, metadata['Path'])
|
||||
|
||||
def _find_mapped_lun_igroup(self, path, initiator, os=None):
|
||||
"""Find the igroup for mapped LUN with initiator."""
|
||||
initiator_igroups = self.zapi_client.get_igroup_by_initiator(
|
||||
initiator=initiator)
|
||||
def _find_mapped_lun_igroup(self, path, initiator_list):
|
||||
"""Find an igroup for a LUN mapped to the given initiator(s)."""
|
||||
initiator_igroups = self.zapi_client.get_igroup_by_initiators(
|
||||
initiator_list)
|
||||
lun_maps = self.zapi_client.get_lun_map(path)
|
||||
if initiator_igroups and lun_maps:
|
||||
for igroup in initiator_igroups:
|
||||
@ -140,6 +140,9 @@ class NetAppBlockStorageCmodeLibrary(block_base.
|
||||
lun.get_child_content('is-space-reservation-enabled')
|
||||
return meta_dict
|
||||
|
||||
def _get_fc_target_wwpns(self, include_partner=True):
|
||||
return self.zapi_client.get_fc_target_wwpns()
|
||||
|
||||
def _configure_tunneling(self, do_tunneling=False):
|
||||
"""Configures tunneling for Data ONTAP cluster."""
|
||||
if do_tunneling:
|
||||
|
@ -48,8 +48,41 @@ class Client(client_base.Client):
|
||||
result = server.invoke_successfully(na_element, True)
|
||||
return result
|
||||
|
||||
def get_target_details(self):
|
||||
"""Gets the target portal details."""
|
||||
def _invoke_7mode_iterator_getter(self, start_api_name, next_api_name,
|
||||
end_api_name, record_container_tag_name,
|
||||
maximum=100):
|
||||
"""Invoke a 7-mode iterator-style getter API."""
|
||||
data = []
|
||||
|
||||
start_api = netapp_api.NaElement(start_api_name)
|
||||
start_result = self.connection.invoke_successfully(start_api)
|
||||
tag = start_result.get_child_content('tag')
|
||||
if not tag:
|
||||
return data
|
||||
|
||||
try:
|
||||
while True:
|
||||
next_api = netapp_api.NaElement(next_api_name)
|
||||
next_api.add_new_child('tag', tag)
|
||||
next_api.add_new_child('maximum', six.text_type(maximum))
|
||||
next_result = self.connection.invoke_successfully(next_api)
|
||||
records = next_result.get_child_content('records') or 0
|
||||
if int(records) == 0:
|
||||
break
|
||||
|
||||
record_container = next_result.get_child_by_name(
|
||||
record_container_tag_name) or netapp_api.NaElement('none')
|
||||
|
||||
data.extend(record_container.get_children())
|
||||
finally:
|
||||
end_api = netapp_api.NaElement(end_api_name)
|
||||
end_api.add_new_child('tag', tag)
|
||||
self.connection.invoke_successfully(end_api)
|
||||
|
||||
return data
|
||||
|
||||
def get_iscsi_target_details(self):
|
||||
"""Gets the iSCSI target portal details."""
|
||||
iscsi_if_iter = netapp_api.NaElement('iscsi-portal-list-info')
|
||||
result = self.connection.invoke_successfully(iscsi_if_iter, True)
|
||||
tgt_list = []
|
||||
@ -65,6 +98,18 @@ class Client(client_base.Client):
|
||||
tgt_list.append(d)
|
||||
return tgt_list
|
||||
|
||||
def get_fc_target_wwpns(self):
|
||||
"""Gets the FC target details."""
|
||||
wwpns = []
|
||||
port_name_list_api = netapp_api.NaElement('fcp-port-name-list-info')
|
||||
result = self.connection.invoke_successfully(port_name_list_api)
|
||||
port_names = result.get_child_by_name('fcp-port-names')
|
||||
if port_names:
|
||||
for port_name_info in port_names.get_children():
|
||||
wwpn = port_name_info.get_child_content('port-name').lower()
|
||||
wwpns.append(wwpn)
|
||||
return wwpns
|
||||
|
||||
def get_iscsi_service_details(self):
|
||||
"""Returns iscsi iqn."""
|
||||
iscsi_service_iter = netapp_api.NaElement('iscsi-node-get-name')
|
||||
@ -97,34 +142,41 @@ class Client(client_base.Client):
|
||||
luns = result.get_child_by_name('luns')
|
||||
return luns.get_children()
|
||||
|
||||
def get_igroup_by_initiator(self, initiator):
|
||||
"""Get igroups by initiator."""
|
||||
igroup_list = netapp_api.NaElement('igroup-list-info')
|
||||
result = self.connection.invoke_successfully(igroup_list, True)
|
||||
igroups = []
|
||||
igs = result.get_child_by_name('initiator-groups')
|
||||
if igs:
|
||||
ig_infos = igs.get_children()
|
||||
if ig_infos:
|
||||
for info in ig_infos:
|
||||
initiators = info.get_child_by_name('initiators')
|
||||
init_infos = initiators.get_children()
|
||||
if init_infos:
|
||||
for init in init_infos:
|
||||
if init.get_child_content('initiator-name')\
|
||||
== initiator:
|
||||
d = dict()
|
||||
d['initiator-group-os-type'] = \
|
||||
info.get_child_content(
|
||||
'initiator-group-os-type')
|
||||
d['initiator-group-type'] = \
|
||||
info.get_child_content(
|
||||
'initiator-group-type')
|
||||
d['initiator-group-name'] = \
|
||||
info.get_child_content(
|
||||
'initiator-group-name')
|
||||
igroups.append(d)
|
||||
return igroups
|
||||
def get_igroup_by_initiators(self, initiator_list):
|
||||
"""Get igroups exactly matching a set of initiators."""
|
||||
igroup_list = []
|
||||
if not initiator_list:
|
||||
return igroup_list
|
||||
|
||||
initiator_set = set(initiator_list)
|
||||
|
||||
igroup_list_info = netapp_api.NaElement('igroup-list-info')
|
||||
result = self.connection.invoke_successfully(igroup_list_info, True)
|
||||
|
||||
initiator_groups = result.get_child_by_name(
|
||||
'initiator-groups') or netapp_api.NaElement('none')
|
||||
for initiator_group_info in initiator_groups.get_children():
|
||||
|
||||
initiator_set_for_igroup = set()
|
||||
initiators = initiator_group_info.get_child_by_name(
|
||||
'initiators') or netapp_api.NaElement('none')
|
||||
for initiator_info in initiators.get_children():
|
||||
initiator_set_for_igroup.add(
|
||||
initiator_info.get_child_content('initiator-name'))
|
||||
|
||||
if initiator_set == initiator_set_for_igroup:
|
||||
igroup = {'initiator-group-os-type':
|
||||
initiator_group_info.get_child_content(
|
||||
'initiator-group-os-type'),
|
||||
'initiator-group-type':
|
||||
initiator_group_info.get_child_content(
|
||||
'initiator-group-type'),
|
||||
'initiator-group-name':
|
||||
initiator_group_info.get_child_content(
|
||||
'initiator-group-name')}
|
||||
igroup_list.append(igroup)
|
||||
|
||||
return igroup_list
|
||||
|
||||
def clone_lun(self, path, clone_path, name, new_name,
|
||||
space_reserved='true', src_block=0,
|
||||
|
@ -207,8 +207,12 @@ class Client(object):
|
||||
lun_move.add_new_child("new-path", new_path)
|
||||
self.connection.invoke_successfully(lun_move, True)
|
||||
|
||||
def get_target_details(self):
|
||||
"""Gets the target portal details."""
|
||||
def get_iscsi_target_details(self):
|
||||
"""Gets the iSCSI target portal details."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_fc_target_wwpns(self):
|
||||
"""Gets the FC target details."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_iscsi_service_details(self):
|
||||
@ -219,10 +223,26 @@ class Client(object):
|
||||
"""Gets the list of LUNs on filer."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_igroup_by_initiator(self, initiator):
|
||||
"""Get igroups by initiator."""
|
||||
def get_igroup_by_initiators(self, initiator_list):
|
||||
"""Get igroups exactly matching a set of initiators."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _has_luns_mapped_to_initiator(self, initiator):
|
||||
"""Checks whether any LUNs are mapped to the given initiator."""
|
||||
lun_list_api = netapp_api.NaElement('lun-initiator-list-map-info')
|
||||
lun_list_api.add_new_child('initiator', initiator)
|
||||
result = self.connection.invoke_successfully(lun_list_api, True)
|
||||
lun_maps_container = result.get_child_by_name(
|
||||
'lun-maps') or netapp_api.NaElement('none')
|
||||
return len(lun_maps_container.get_children()) > 0
|
||||
|
||||
def has_luns_mapped_to_initiators(self, initiator_list):
|
||||
"""Checks whether any LUNs are mapped to the given initiator(s)."""
|
||||
for initiator in initiator_list:
|
||||
if self._has_luns_mapped_to_initiator(initiator):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_lun_by_args(self, **args):
|
||||
"""Retrieves LUNs with specified args."""
|
||||
raise NotImplementedError()
|
||||
|
@ -51,8 +51,8 @@ class Client(client_base.Client):
|
||||
def set_vserver(self, vserver):
|
||||
self.connection.set_vserver(vserver)
|
||||
|
||||
def get_target_details(self):
|
||||
"""Gets the target portal details."""
|
||||
def get_iscsi_target_details(self):
|
||||
"""Gets the iSCSI target portal details."""
|
||||
iscsi_if_iter = netapp_api.NaElement('iscsi-interface-get-iter')
|
||||
result = self.connection.invoke_successfully(iscsi_if_iter, True)
|
||||
tgt_list = []
|
||||
@ -70,6 +70,25 @@ class Client(client_base.Client):
|
||||
tgt_list.append(d)
|
||||
return tgt_list
|
||||
|
||||
def get_fc_target_wwpns(self):
|
||||
"""Gets the FC target details."""
|
||||
wwpns = []
|
||||
port_name_list_api = netapp_api.NaElement('fcp-port-name-get-iter')
|
||||
port_name_list_api.add_new_child('max-records', '100')
|
||||
result = self.connection.invoke_successfully(port_name_list_api, True)
|
||||
num_records = result.get_child_content('num-records')
|
||||
if num_records and int(num_records) >= 1:
|
||||
for port_name_info in result.get_child_by_name(
|
||||
'attributes-list').get_children():
|
||||
|
||||
if port_name_info.get_child_content('is-used') != 'true':
|
||||
continue
|
||||
|
||||
wwpn = port_name_info.get_child_content('port-name').lower()
|
||||
wwpns.append(wwpn)
|
||||
|
||||
return wwpns
|
||||
|
||||
def get_iscsi_service_details(self):
|
||||
"""Returns iscsi iqn."""
|
||||
iscsi_service_iter = netapp_api.NaElement('iscsi-service-get-iter')
|
||||
@ -100,7 +119,7 @@ class Client(client_base.Client):
|
||||
query = netapp_api.NaElement('query')
|
||||
query.add_child_elem(lun_info)
|
||||
api.add_child_elem(query)
|
||||
result = self.connection.invoke_successfully(api)
|
||||
result = self.connection.invoke_successfully(api, True)
|
||||
if result.get_child_by_name('num-records') and\
|
||||
int(result.get_child_content('num-records')) >= 1:
|
||||
attr_list = result.get_child_by_name('attributes-list')
|
||||
@ -139,51 +158,81 @@ class Client(client_base.Client):
|
||||
break
|
||||
return map_list
|
||||
|
||||
def get_igroup_by_initiator(self, initiator):
|
||||
"""Get igroups by initiator."""
|
||||
def _get_igroup_by_initiator_query(self, initiator, tag):
|
||||
igroup_get_iter = netapp_api.NaElement('igroup-get-iter')
|
||||
igroup_get_iter.add_new_child('max-records', '100')
|
||||
if tag:
|
||||
igroup_get_iter.add_new_child('tag', tag, True)
|
||||
|
||||
query = netapp_api.NaElement('query')
|
||||
igroup_info = netapp_api.NaElement('initiator-group-info')
|
||||
query.add_child_elem(igroup_info)
|
||||
igroup_info.add_new_child('vserver', self.vserver)
|
||||
initiators = netapp_api.NaElement('initiators')
|
||||
igroup_info.add_child_elem(initiators)
|
||||
igroup_get_iter.add_child_elem(query)
|
||||
initiators.add_node_with_children(
|
||||
'initiator-info', **{'initiator-name': initiator})
|
||||
|
||||
# limit results to just the attributes of interest
|
||||
desired_attrs = netapp_api.NaElement('desired-attributes')
|
||||
desired_igroup_info = netapp_api.NaElement('initiator-group-info')
|
||||
desired_igroup_info.add_node_with_children(
|
||||
'initiators', **{'initiator-info': None})
|
||||
desired_igroup_info.add_new_child('vserver', None)
|
||||
desired_igroup_info.add_new_child('initiator-group-name', None)
|
||||
desired_igroup_info.add_new_child('initiator-group-type', None)
|
||||
desired_igroup_info.add_new_child('initiator-group-os-type', None)
|
||||
desired_attrs.add_child_elem(desired_igroup_info)
|
||||
igroup_get_iter.add_child_elem(desired_attrs)
|
||||
|
||||
return igroup_get_iter
|
||||
|
||||
def get_igroup_by_initiators(self, initiator_list):
|
||||
"""Get igroups exactly matching a set of initiators."""
|
||||
tag = None
|
||||
igroup_list = []
|
||||
if not initiator_list:
|
||||
return igroup_list
|
||||
|
||||
initiator_set = set(initiator_list)
|
||||
|
||||
while True:
|
||||
igroup_iter = netapp_api.NaElement('igroup-get-iter')
|
||||
igroup_iter.add_new_child('max-records', '100')
|
||||
if tag:
|
||||
igroup_iter.add_new_child('tag', tag, True)
|
||||
query = netapp_api.NaElement('query')
|
||||
igroup_iter.add_child_elem(query)
|
||||
igroup_info = netapp_api.NaElement('initiator-group-info')
|
||||
query.add_child_elem(igroup_info)
|
||||
igroup_info.add_new_child('vserver', self.vserver)
|
||||
initiators = netapp_api.NaElement('initiators')
|
||||
igroup_info.add_child_elem(initiators)
|
||||
initiators.add_node_with_children('initiator-info',
|
||||
**{'initiator-name': initiator})
|
||||
des_attrs = netapp_api.NaElement('desired-attributes')
|
||||
des_ig_info = netapp_api.NaElement('initiator-group-info')
|
||||
des_attrs.add_child_elem(des_ig_info)
|
||||
des_ig_info.add_node_with_children('initiators',
|
||||
**{'initiator-info': None})
|
||||
des_ig_info.add_new_child('vserver', None)
|
||||
des_ig_info.add_new_child('initiator-group-name', None)
|
||||
des_ig_info.add_new_child('initiator-group-type', None)
|
||||
des_ig_info.add_new_child('initiator-group-os-type', None)
|
||||
igroup_iter.add_child_elem(des_attrs)
|
||||
result = self.connection.invoke_successfully(igroup_iter, False)
|
||||
# C-mode getter APIs can't do an 'and' query, so match the first
|
||||
# initiator (which will greatly narrow the search results) and
|
||||
# filter the rest in this method.
|
||||
query = self._get_igroup_by_initiator_query(initiator_list[0], tag)
|
||||
result = self.connection.invoke_successfully(query, True)
|
||||
|
||||
tag = result.get_child_content('next-tag')
|
||||
if result.get_child_content('num-records') and\
|
||||
int(result.get_child_content('num-records')) > 0:
|
||||
attr_list = result.get_child_by_name('attributes-list')
|
||||
igroups = attr_list.get_children()
|
||||
for igroup in igroups:
|
||||
ig = dict()
|
||||
ig['initiator-group-os-type'] = igroup.get_child_content(
|
||||
'initiator-group-os-type')
|
||||
ig['initiator-group-type'] = igroup.get_child_content(
|
||||
'initiator-group-type')
|
||||
ig['initiator-group-name'] = igroup.get_child_content(
|
||||
'initiator-group-name')
|
||||
igroup_list.append(ig)
|
||||
num_records = result.get_child_content('num-records')
|
||||
if num_records and int(num_records) >= 1:
|
||||
|
||||
for igroup_info in result.get_child_by_name(
|
||||
'attributes-list').get_children():
|
||||
|
||||
initiator_set_for_igroup = set()
|
||||
for initiator_info in igroup_info.get_child_by_name(
|
||||
'initiators').get_children():
|
||||
|
||||
initiator_set_for_igroup.add(
|
||||
initiator_info.get_child_content('initiator-name'))
|
||||
|
||||
if initiator_set == initiator_set_for_igroup:
|
||||
igroup = {'initiator-group-os-type':
|
||||
igroup_info.get_child_content(
|
||||
'initiator-group-os-type'),
|
||||
'initiator-group-type':
|
||||
igroup_info.get_child_content(
|
||||
'initiator-group-type'),
|
||||
'initiator-group-name':
|
||||
igroup_info.get_child_content(
|
||||
'initiator-group-name')}
|
||||
igroup_list.append(igroup)
|
||||
|
||||
if tag is None:
|
||||
break
|
||||
|
||||
return igroup_list
|
||||
|
||||
def clone_lun(self, volume, name, new_name, space_reserved='true',
|
||||
@ -240,7 +289,7 @@ class Client(client_base.Client):
|
||||
query = netapp_api.NaElement('query')
|
||||
lun_iter.add_child_elem(query)
|
||||
query.add_node_with_children('lun-info', **args)
|
||||
luns = self.connection.invoke_successfully(lun_iter)
|
||||
luns = self.connection.invoke_successfully(lun_iter, True)
|
||||
attr_list = luns.get_child_by_name('attributes-list')
|
||||
return attr_list.get_children()
|
||||
|
||||
|
85
cinder/volume/drivers/netapp/dataontap/fc_7mode.py
Normal file
85
cinder/volume/drivers/netapp/dataontap/fc_7mode.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright (c) - 2014, Clinton Knight. 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.
|
||||
"""
|
||||
Volume driver for NetApp Data ONTAP (7-mode) FibreChannel storage systems.
|
||||
"""
|
||||
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.netapp.dataontap.block_7mode import \
|
||||
NetAppBlockStorage7modeLibrary as lib_7mode
|
||||
from cinder.zonemanager import utils as fczm_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NetApp7modeFibreChannelDriver(driver.FibreChannelDriver):
|
||||
"""NetApp 7-mode FibreChannel volume driver."""
|
||||
|
||||
DRIVER_NAME = 'NetApp_FibreChannel_7mode_direct'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NetApp7modeFibreChannelDriver, self).__init__(*args, **kwargs)
|
||||
self.library = lib_7mode(self.DRIVER_NAME, 'FC', **kwargs)
|
||||
|
||||
def do_setup(self, context):
|
||||
self.library.do_setup(context)
|
||||
|
||||
def check_for_setup_error(self):
|
||||
self.library.check_for_setup_error()
|
||||
|
||||
def create_volume(self, volume):
|
||||
self.library.create_volume(volume)
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
self.library.create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
self.library.create_cloned_volume(volume, src_vref)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
self.library.delete_volume(volume)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
self.library.create_snapshot(snapshot)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
self.library.delete_snapshot(snapshot)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
return self.library.get_volume_stats(refresh)
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
self.library.extend_volume(volume, new_size)
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
return self.library.ensure_export(context, volume)
|
||||
|
||||
def create_export(self, context, volume):
|
||||
return self.library.create_export(context, volume)
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
self.library.remove_export(context, volume)
|
||||
|
||||
@fczm_utils.AddFCZone
|
||||
def initialize_connection(self, volume, connector):
|
||||
return self.library.initialize_connection_fc(volume, connector)
|
||||
|
||||
@fczm_utils.RemoveFCZone
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
return self.library.terminate_connection_fc(volume, connector,
|
||||
**kwargs)
|
||||
|
||||
def get_pool(self, volume):
|
||||
return self.library.get_pool(volume)
|
85
cinder/volume/drivers/netapp/dataontap/fc_cmode.py
Normal file
85
cinder/volume/drivers/netapp/dataontap/fc_cmode.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright (c) - 2014, Clinton Knight. 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.
|
||||
"""
|
||||
Volume driver for NetApp Data ONTAP (C-mode) FibreChannel storage systems.
|
||||
"""
|
||||
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.netapp.dataontap.block_cmode import \
|
||||
NetAppBlockStorageCmodeLibrary as lib_cmode
|
||||
from cinder.zonemanager import utils as fczm_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NetAppCmodeFibreChannelDriver(driver.FibreChannelDriver):
|
||||
"""NetApp C-mode FibreChannel volume driver."""
|
||||
|
||||
DRIVER_NAME = 'NetApp_FibreChannel_Cluster_direct'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NetAppCmodeFibreChannelDriver, self).__init__(*args, **kwargs)
|
||||
self.library = lib_cmode(self.DRIVER_NAME, 'FC', **kwargs)
|
||||
|
||||
def do_setup(self, context):
|
||||
self.library.do_setup(context)
|
||||
|
||||
def check_for_setup_error(self):
|
||||
self.library.check_for_setup_error()
|
||||
|
||||
def create_volume(self, volume):
|
||||
self.library.create_volume(volume)
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
self.library.create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
self.library.create_cloned_volume(volume, src_vref)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
self.library.delete_volume(volume)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
self.library.create_snapshot(snapshot)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
self.library.delete_snapshot(snapshot)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
return self.library.get_volume_stats(refresh)
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
self.library.extend_volume(volume, new_size)
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
return self.library.ensure_export(context, volume)
|
||||
|
||||
def create_export(self, context, volume):
|
||||
return self.library.create_export(context, volume)
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
self.library.remove_export(context, volume)
|
||||
|
||||
@fczm_utils.AddFCZone
|
||||
def initialize_connection(self, volume, connector):
|
||||
return self.library.initialize_connection_fc(volume, connector)
|
||||
|
||||
@fczm_utils.RemoveFCZone
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
return self.library.terminate_connection_fc(volume, connector,
|
||||
**kwargs)
|
||||
|
||||
def get_pool(self, volume):
|
||||
return self.library.get_pool(volume)
|
@ -36,7 +36,8 @@ netapp_proxy_opts = [
|
||||
cfg.StrOpt('netapp_storage_protocol',
|
||||
default=None,
|
||||
help=('The storage protocol to be used on the data path with '
|
||||
'the storage system; valid values are iscsi or nfs.')), ]
|
||||
'the storage system; valid values are iscsi, fc, or '
|
||||
'nfs.')), ]
|
||||
|
||||
netapp_connection_opts = [
|
||||
cfg.StrOpt('netapp_server_hostname',
|
||||
@ -78,8 +79,8 @@ netapp_provisioning_opts = [
|
||||
cfg.StrOpt('netapp_volume_list',
|
||||
default=None,
|
||||
help=('This option is only utilized when the storage protocol '
|
||||
'is configured to use iSCSI. This option is used to '
|
||||
'restrict provisioning to the specified controller '
|
||||
'is configured to use iSCSI or FC. This option is used '
|
||||
'to restrict provisioning to the specified controller '
|
||||
'volumes. Specify the value of this option to be a '
|
||||
'comma separated list of NetApp controller volume names '
|
||||
'to be used for provisioning.')), ]
|
||||
@ -107,7 +108,14 @@ netapp_7mode_opts = [
|
||||
'driver when connecting to an instance with a storage '
|
||||
'family of Data ONTAP operating in 7-Mode. Only use this '
|
||||
'option when utilizing the MultiStore feature on the '
|
||||
'NetApp storage system.')), ]
|
||||
'NetApp storage system.')),
|
||||
cfg.StrOpt('netapp_partner_backend_name',
|
||||
default=None,
|
||||
help=('The name of the config.conf stanza for a Data ONTAP '
|
||||
'(7-mode) HA partner. This option is only used by the '
|
||||
'driver when connecting to an instance with a storage '
|
||||
'family of Data ONTAP operating in 7-Mode, and it is '
|
||||
'required if the storage protocol selected is FC.')), ]
|
||||
|
||||
netapp_img_cache_opts = [
|
||||
cfg.IntOpt('thres_avl_size_perc_start',
|
||||
|
@ -139,6 +139,12 @@ def log_extra_spec_warnings(extra_specs):
|
||||
LOG.warning(msg % args)
|
||||
|
||||
|
||||
class hashabledict(dict):
|
||||
"""A hashable dictionary that is comparable (i.e. in unit tests, etc.)"""
|
||||
def __hash__(self):
|
||||
return hash(tuple(sorted(self.items())))
|
||||
|
||||
|
||||
class OpenStackInfo(object):
|
||||
"""OS/distribution, release, and version.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user