FibreChannel drivers for NetApp Data ONTAP storage controllers

This patch adds FibreChannel support to NetApp's Cinder drivers
for Data ONTAP (7-mode and Cluster-mode).  The drivers make full
use of Cinder's FibreChannel zone manager.

Implements blueprint add-fibre-channel-support-to-netapp-drivers
Change-Id: Ifbda275e4a60dda144a169ef00a4ea5e548dfa03
This commit is contained in:
Clinton Knight 2014-10-09 10:56:25 -04:00
parent 83bc931790
commit 05eeea8bff
21 changed files with 1689 additions and 269 deletions

View File

@ -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]))

View File

@ -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))

View File

@ -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')

View 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}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View 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

View File

@ -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 {}

View File

@ -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."""

View File

@ -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

View File

@ -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:

View File

@ -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,

View File

@ -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()

View File

@ -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()

View 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)

View 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)

View File

@ -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',

View File

@ -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.