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',
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>
</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",
</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)
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>
</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
+
+ 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_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
- 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_clone_lun(self):
fake_clone_start = netapp_api.NaElement(
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]))
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
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)
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)
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):
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))
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)
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))
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',
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",
</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)
</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])
+
+ # 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.assertEqual([expected_igroup, expected_igroup], igroup)
+ self.assertSetEqual(igroups, expected)
def test_clone_lun(self):
self.client.clone_lun('volume', 'fakeLUN', 'newFakeLUN')
--- /dev/null
+# 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}
# 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
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):
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."""
'/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)
# 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
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
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,
'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'}))
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)
-
- @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())
+ na_utils.LOG.warning.assert_called_once_with(warn_msg)
+
+ @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':
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)
# 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
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):
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."""
'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)
--- /dev/null
+# 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
{'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':
{
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 {}
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
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()
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):
'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."""
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__)
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']
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)
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']
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:
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):
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']
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}
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
"""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:
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:
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 = []
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')
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,
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):
"""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()
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 = []
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')
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')
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',
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()
--- /dev/null
+# 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)
--- /dev/null
+# 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)
\ No newline at end of file
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',
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.')), ]
'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',
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.