From a120ede9ae3f18756db07d1d6696b9ac773b84bf Mon Sep 17 00:00:00 2001 From: Navneet Singh Date: Fri, 23 May 2014 10:27:33 +0530 Subject: [PATCH] NetApp fix for default host type in eseries This fixes the issue where the default host type provided in mapping should be high performing LnxALUA type for eseries. It also makes it configurable in case users want to configure a different host type. Closes-Bug: #1365884 Change-Id: I30992ca69c25c3c02334470aae90c32731a5f3f4 --- cinder/tests/test_netapp_eseries_iscsi.py | 60 ++++++++++++++++++- .../volume/drivers/netapp/eseries/client.py | 6 ++ cinder/volume/drivers/netapp/eseries/iscsi.py | 58 ++++++++++++++---- cinder/volume/drivers/netapp/options.py | 8 ++- etc/cinder/cinder.conf.sample | 6 ++ 5 files changed, 124 insertions(+), 14 deletions(-) diff --git a/cinder/tests/test_netapp_eseries_iscsi.py b/cinder/tests/test_netapp_eseries_iscsi.py index 7b1784514fe..132f51ee8c2 100644 --- a/cinder/tests/test_netapp_eseries_iscsi.py +++ b/cinder/tests/test_netapp_eseries_iscsi.py @@ -216,8 +216,8 @@ class FakeEseriesServerHandler(object): "index" : 5 }, { "id" : "6", - "code" : "LNX", - "name" : "Linux", + "code" : "LnxALUA", + "name" : "LnxALUA", "index" : 6 }]""" elif re.match("^/storage-systems/[0-9a-zA-Z]+/snapshot-groups$", path): @@ -878,3 +878,59 @@ class NetAppEseriesIscsiDriverTestCase(test.TestCase): self.assertRaises(exception.NetAppDriverException, self.driver._get_iscsi_portal_for_vol, vol_nomatch, portals, False) + + def test_get_host_right_type(self): + self.driver._get_host_with_port = mock.Mock( + return_value={'hostTypeIndex': 2, 'name': 'test'}) + self.driver._get_host_type_definition = mock.Mock( + return_value={'index': 2, 'name': 'LnxALUA'}) + host = self.driver._get_or_create_host('port', 'LinuxALUA') + self.assertEqual(host, {'hostTypeIndex': 2, 'name': 'test'}) + self.driver._get_host_with_port.assert_called_once_with('port') + self.driver._get_host_type_definition.assert_called_once_with( + 'LinuxALUA') + + def test_get_host_update_type(self): + self.driver._get_host_with_port = mock.Mock( + return_value={'hostTypeIndex': 2, 'hostRef': 'test'}) + self.driver._get_host_type_definition = mock.Mock( + return_value={'index': 3, 'name': 'LnxALUA'}) + self.driver._client.update_host_type = mock.Mock( + return_value={'hostTypeIndex': 3, 'hostRef': 'test'}) + host = self.driver._get_or_create_host('port', 'LinuxALUA') + self.assertEqual(host, {'hostTypeIndex': 3, 'hostRef': 'test'}) + self.driver._get_host_with_port.assert_called_once_with('port') + self.driver._get_host_type_definition.assert_called_once_with( + 'LinuxALUA') + self.assertEqual(self.driver._client.update_host_type.call_count, 1) + + def test_get_host_update_type_failed(self): + self.driver._get_host_with_port = mock.Mock( + return_value={'hostTypeIndex': 2, 'hostRef': 'test', + 'label': 'test'}) + self.driver._get_host_type_definition = mock.Mock( + return_value={'index': 3, 'name': 'LnxALUA'}) + self.driver._client.update_host_type = mock.Mock( + side_effect=exception.NetAppDriverException) + host = self.driver._get_or_create_host('port', 'LinuxALUA') + self.assertEqual(host, {'hostTypeIndex': 2, 'hostRef': 'test', + 'label': 'test'}) + self.driver._get_host_with_port.assert_called_once_with('port') + self.driver._get_host_type_definition.assert_called_once_with( + 'LinuxALUA') + self.assertEqual(self.driver._client.update_host_type.call_count, 1) + + def test_get_host_not_found(self): + self.driver._get_host_with_port = mock.Mock( + side_effect=exception.NotFound) + self.driver._create_host = mock.Mock() + self.driver._get_or_create_host('port', 'LnxALUA') + self.driver._get_host_with_port.assert_called_once_with('port') + self.driver._create_host.assert_called_once_with('port', 'LnxALUA') + + def test_setup_error_unsupported_host_type(self): + configuration = self._set_config(create_configuration()) + configuration.netapp_eseries_host_type = 'garbage' + driver = common.NetAppDriver(configuration=configuration) + self.assertRaises(exception.NetAppDriverException, + driver.check_for_setup_error) diff --git a/cinder/volume/drivers/netapp/eseries/client.py b/cinder/volume/drivers/netapp/eseries/client.py index 52b376a2f5a..3b144f91f1c 100644 --- a/cinder/volume/drivers/netapp/eseries/client.py +++ b/cinder/volume/drivers/netapp/eseries/client.py @@ -217,6 +217,12 @@ class RestClient(WebserviceClient): port = {'type': port_type, 'port': port_id, 'label': port_label} return self.create_host(label, host_type, [port], group_id) + def update_host_type(self, host_ref, host_type): + """Updates host type for a given host.""" + path = "/storage-systems/{system-id}/hosts/{object-id}" + data = {'hostType': host_type} + return self._invoke('POST', path, data, **{'object-id': host_ref}) + def list_host_types(self): """Lists host types in storage system.""" path = "/storage-systems/{system-id}/host-types" diff --git a/cinder/volume/drivers/netapp/eseries/iscsi.py b/cinder/volume/drivers/netapp/eseries/iscsi.py index bfb08d79ff3..4f76baface3 100644 --- a/cinder/volume/drivers/netapp/eseries/iscsi.py +++ b/cinder/volume/drivers/netapp/eseries/iscsi.py @@ -58,6 +58,26 @@ class Driver(driver.ISCSIDriver): 'netapp_storage_pools'] SLEEP_SECS = 5 MAX_LUNS_PER_HOST = 255 + HOST_TYPES = {'aix': 'AIX MPIO', + 'avt': 'AVT_4M', + 'factoryDefault': 'FactoryDefault', + 'hpux': 'HP-UX TPGS', + 'linux_atto': 'LnxTPGSALUA', + 'linux_dm_mp': 'LnxALUA', + 'linux_mpp_rdac': 'Linux', + 'linux_pathmanager': 'LnxTPGSALUA_PM', + 'macos': 'MacTPGSALUA', + 'ontap': 'ONTAP', + 'svc': 'SVC', + 'solaris_v11': 'SolTPGSALUA', + 'solaris_v10': 'Solaris', + 'vmware': 'VmwTPGSALUA', + 'windows': + 'Windows 2000/Server 2003/Server 2008 Non-Clustered', + 'windows_atto': 'WinTPGSALUA', + 'windows_clustered': + 'Windows 2000/Server 2003/Server 2008 Clustered' + } def __init__(self, *args, **kwargs): super(Driver, self).__init__(*args, **kwargs) @@ -91,6 +111,12 @@ class Driver(driver.ISCSIDriver): raise exception.InvalidInput(reason=msg) def check_for_setup_error(self): + self.host_type =\ + self.HOST_TYPES.get(self.configuration.netapp_eseries_host_type, + None) + if not self.host_type: + raise exception.NetAppDriverException( + _('Configured host type is not supported.')) self._check_storage_system() self._populate_system_objects() @@ -579,7 +605,7 @@ class Driver(driver.ISCSIDriver): @cinder_utils.synchronized('map_es_volume') def _map_volume_to_host(self, vol, initiator): """Maps the e-series volume to host with initiator.""" - host = self._get_or_create_host(initiator) + host = self._get_or_create_host(initiator, self.host_type) vol_maps = self._get_host_mapping_for_vol_frm_array(vol) for vol_map in vol_maps: if vol_map.get('mapRef') == host['hostRef']: @@ -592,30 +618,40 @@ class Driver(driver.ISCSIDriver): return self._client.create_volume_mapping(vol['volumeRef'], host['hostRef'], lun) - def _get_or_create_host(self, port_id, host_type='linux'): + def _get_or_create_host(self, port_id, host_type): """Fetch or create a host by given port.""" try: - return self._get_host_with_port(port_id, host_type) + host = self._get_host_with_port(port_id) + ht_def = self._get_host_type_definition(host_type) + if host.get('hostTypeIndex') == ht_def.get('index'): + return host + else: + try: + return self._client.update_host_type( + host['hostRef'], ht_def) + except exception.NetAppDriverException as e: + msg = _("Unable to update host type for host with" + " label %(l)s. %(e)s") + LOG.warn(msg % {'l': host['label'], 'e': e.msg}) + return host except exception.NotFound as e: LOG.warn(_("Message - %s."), e.msg) return self._create_host(port_id, host_type) - def _get_host_with_port(self, port_id, host_type='linux'): + def _get_host_with_port(self, port_id): """Gets or creates a host with given port id.""" hosts = self._client.list_hosts() - ht_def = self._get_host_type_definition(host_type) for host in hosts: - if (host.get('hostTypeIndex') == ht_def.get('index') - and host.get('hostSidePorts')): + if host.get('hostSidePorts'): ports = host.get('hostSidePorts') for port in ports: if (port.get('type') == 'iscsi' and port.get('address') == port_id): return host - msg = _("Host with port %(port)s and type %(type)s not found.") - raise exception.NotFound(msg % {'port': port_id, 'type': host_type}) + msg = _("Host with port %(port)s not found.") + raise exception.NotFound(msg % {'port': port_id}) - def _create_host(self, port_id, host_type='linux'): + def _create_host(self, port_id, host_type): """Creates host on system with given initiator as port_id.""" LOG.info(_("Creating host with port %s."), port_id) label = utils.convert_uuid_to_es_fmt(uuid.uuid4()) @@ -624,7 +660,7 @@ class Driver(driver.ISCSIDriver): return self._client.create_host_with_port(label, host_type, port_id, port_label) - def _get_host_type_definition(self, host_type='linux'): + def _get_host_type_definition(self, host_type): """Gets supported host type if available on storage system.""" host_types = self._client.list_host_types() for ht in host_types: diff --git a/cinder/volume/drivers/netapp/options.py b/cinder/volume/drivers/netapp/options.py index 8995d438b53..5b01d1f5864 100644 --- a/cinder/volume/drivers/netapp/options.py +++ b/cinder/volume/drivers/netapp/options.py @@ -161,7 +161,13 @@ netapp_eseries_opts = [ 'specified storage pools. Only dynamic disk pools are ' 'currently supported. Specify the value of this option to' ' be a comma separated list of disk pool names to be used' - ' for provisioning.')), ] + ' for provisioning.')), + cfg.StrOpt('netapp_eseries_host_type', + default='linux_dm_mp', + help=('This option is used to define how the controllers in ' + 'the E-Series storage array will work with the ' + 'particular operating system on the hosts that are ' + 'connected to it.')), ] netapp_nfs_extra_opts = [ cfg.StrOpt('netapp_copyoffload_tool_path', default=None, diff --git a/etc/cinder/cinder.conf.sample b/etc/cinder/cinder.conf.sample index de342bd3712..b66275be0bc 100644 --- a/etc/cinder/cinder.conf.sample +++ b/etc/cinder/cinder.conf.sample @@ -1641,6 +1641,12 @@ # provisioning. (string value) #netapp_storage_pools= +# This option is used to define how the controllers in the +# E-Series storage array will work with the particular +# operating system on the hosts that are connected to it. +# (string value) +#netapp_eseries_host_type=linux_dm_mp + # If the percentage of available space for an NFS share has # dropped below the value specified by this option, the NFS # image cache will be cleaned. (integer value)