diff --git a/cinder/tests/test_netapp_eseries_iscsi.py b/cinder/tests/test_netapp_eseries_iscsi.py index 5d2b983494b..e9b6a2ced3a 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)