]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
NetApp E-series: Allow scheduling by disk
authorAlex Meade <mr.alex.meade@gmail.com>
Thu, 16 Oct 2014 20:11:32 +0000 (16:11 -0400)
committerAlex Meade <mr.alex.meade@gmail.com>
Fri, 20 Feb 2015 16:15:16 +0000 (16:15 +0000)
This patch allows volume scheduling by disk type via the
netapp_disk_type volume type extra spec and disk encryption via the
netapp_disk_encryption volume type extra spec

Partially Implements bp: netapp-eseries-extra-specs

Change-Id: I6e11073838c1b63b6a0680d991f8f1289ca63704

cinder/tests/test_netapp_eseries_iscsi.py
cinder/tests/volume/drivers/netapp/eseries/test_iscsi.py
cinder/tests/volume/drivers/netapp/fakes.py
cinder/volume/drivers/netapp/eseries/client.py
cinder/volume/drivers/netapp/eseries/iscsi.py

index 9ba0835000145e55be2ba7947528f1b41dc92e1d..bc94020b36cab0202948f9ce0beb5e4ccfe2d824 100644 (file)
@@ -1,4 +1,6 @@
 # Copyright (c) 2014 NetApp, Inc.
+# Copyright (c) 2015 Alex Meade.  All Rights Reserved.
+# Copyright (c) 2015 Rushil Chugh.  All Rights Reserved.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -771,7 +773,7 @@ class NetAppEseriesISCSIDriverTestCase(test.TestCase):
         self.assertIsNotNone(properties, 'Target portal is none')
 
     def test_vol_stats(self):
-        self.driver.get_volume_stats(refresh=True)
+        self.driver.get_volume_stats(refresh=False)
 
     def test_create_vol_snapshot_diff_size_resize(self):
         self.driver.db = mock.Mock(
index 2b5e2ebf71bf9d583907eb61e1ba28d8d218e5ae..193deade42eb292a4c83e8e0523998ce5ed32aa6 100644 (file)
@@ -1,4 +1,6 @@
 # Copyright (c) 2014 Andrew Kerr.  All rights reserved.
+# Copyright (c) 2015 Alex Meade.  All rights reserved.
+# Copyright (c) 2015 Rushil Chugh.  All rights reserved.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -32,6 +34,7 @@ class NetAppEseriesISCSIDriverTestCase(test.TestCase):
         kwargs = {'configuration': self.get_config_eseries()}
 
         self.driver = es_iscsi.NetAppEseriesISCSIDriver(**kwargs)
+        self.driver._client = mock.Mock()
 
     def get_config_eseries(self):
         config = na_fakes.create_configuration_eseries()
@@ -56,3 +59,143 @@ class NetAppEseriesISCSIDriverTestCase(test.TestCase):
         self.driver.do_setup(mock.Mock())
 
         self.assertTrue(mock_check_flags.called)
+
+    def test_update_ssc_info(self):
+        drives = [{'currentVolumeGroupRef': 'test_vg1',
+                   'driveMediaType': 'ssd'}]
+
+        self.driver._objects["disk_pool_refs"] = ['test_vg1']
+        self.driver._client.list_storage_pools = mock.Mock(return_value=[])
+        self.driver._client.list_drives = mock.Mock(return_value=drives)
+
+        self.driver._update_ssc_info()
+
+        self.assertEqual({'test_vg1': {'netapp_disk_type': 'SSD'}},
+                         self.driver._ssc_stats)
+
+    def test_update_ssc_disk_types_ssd(self):
+        drives = [{'currentVolumeGroupRef': 'test_vg1',
+                   'driveMediaType': 'ssd'}]
+        self.driver._client.list_drives = mock.Mock(return_value=drives)
+
+        ssc_stats = self.driver._update_ssc_disk_types(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_type': 'SSD'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_types_scsi(self):
+        drives = [{'currentVolumeGroupRef': 'test_vg1',
+                   'interfaceType': {'driveType': 'scsi'}}]
+        self.driver._client.list_drives = mock.Mock(return_value=drives)
+
+        ssc_stats = self.driver._update_ssc_disk_types(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_type': 'SCSI'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_types_fcal(self):
+        drives = [{'currentVolumeGroupRef': 'test_vg1',
+                   'interfaceType': {'driveType': 'fibre'}}]
+        self.driver._client.list_drives = mock.Mock(return_value=drives)
+
+        ssc_stats = self.driver._update_ssc_disk_types(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_type': 'FCAL'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_types_sata(self):
+        drives = [{'currentVolumeGroupRef': 'test_vg1',
+                   'interfaceType': {'driveType': 'sata'}}]
+        self.driver._client.list_drives = mock.Mock(return_value=drives)
+
+        ssc_stats = self.driver._update_ssc_disk_types(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_type': 'SATA'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_types_sas(self):
+        drives = [{'currentVolumeGroupRef': 'test_vg1',
+                   'interfaceType': {'driveType': 'sas'}}]
+        self.driver._client.list_drives = mock.Mock(return_value=drives)
+
+        ssc_stats = self.driver._update_ssc_disk_types(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_type': 'SAS'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_types_unknown(self):
+        drives = [{'currentVolumeGroupRef': 'test_vg1',
+                   'interfaceType': {'driveType': 'unknown'}}]
+        self.driver._client.list_drives = mock.Mock(return_value=drives)
+
+        ssc_stats = self.driver._update_ssc_disk_types(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_type': 'unknown'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_types_undefined(self):
+        drives = [{'currentVolumeGroupRef': 'test_vg1',
+                   'interfaceType': {'driveType': '__UNDEFINED'}}]
+        self.driver._client.list_drives = mock.Mock(return_value=drives)
+
+        ssc_stats = self.driver._update_ssc_disk_types(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_type': 'unknown'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_encryption_SecType_enabled(self):
+        pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'enabled'}]
+        self.driver._client.list_storage_pools = mock.Mock(return_value=pools)
+
+        ssc_stats = self.driver._update_ssc_disk_encryption(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'true'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_encryption_SecType_unknown(self):
+        pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'unknown'}]
+        self.driver._client.list_storage_pools = mock.Mock(return_value=pools)
+
+        ssc_stats = self.driver._update_ssc_disk_encryption(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_encryption_SecType_none(self):
+        pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'none'}]
+        self.driver._client.list_storage_pools = mock.Mock(return_value=pools)
+
+        ssc_stats = self.driver._update_ssc_disk_encryption(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_encryption_SecType_capable(self):
+        pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'capable'}]
+        self.driver._client.list_storage_pools = mock.Mock(return_value=pools)
+
+        ssc_stats = self.driver._update_ssc_disk_encryption(['test_vg1'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'}},
+                         ssc_stats)
+
+    def test_update_ssc_disk_encryption_SecType_garbage(self):
+        pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'garbage'}]
+        self.driver._client.list_storage_pools = mock.Mock(return_value=pools)
+
+        ssc_stats = self.driver._update_ssc_disk_encryption(['test_vg1'])
+
+        self.assertRaises(TypeError, 'test_vg1',
+                          {'netapp_disk_encryption': 'false'}, ssc_stats)
+
+    def test_update_ssc_disk_encryption_multiple(self):
+        pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'none'},
+                 {'volumeGroupRef': 'test_vg2', 'securityType': 'enabled'}]
+        self.driver._client.list_storage_pools = mock.Mock(return_value=pools)
+
+        ssc_stats = self.driver._update_ssc_disk_encryption(['test_vg1',
+                                                            'test_vg2'])
+
+        self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'},
+                          'test_vg2': {'netapp_disk_encryption': 'true'}},
+                         ssc_stats)
index b797613c38640fb2e0a4a54ff01d09d572c8bc6a..aef1d4d2d44fec41956fc5c0f2b1f4c47ea2ce82 100644 (file)
@@ -1,5 +1,6 @@
 # Copyright (c) - 2014, Clinton Knight  All rights reserved.
-# Copyright (c) - 2014, Rushil Chugh  All rights reserved.
+# Copyright (c) - 2015, Alex Meade.  All Rights Reserved.
+# Copyright (c) - 2015, Rushil Chugh.  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
@@ -18,6 +19,31 @@ from cinder.volume import configuration as conf
 import cinder.volume.drivers.netapp.options as na_opts
 
 
+ISCSI_FAKE_LUN_ID = 1
+
+ISCSI_FAKE_IQN = 'iqn.1993-08.org.debian:01:10'
+
+ISCSI_FAKE_ADDRESS = '10.63.165.216'
+
+ISCSI_FAKE_PORT = '2232'
+
+ISCSI_FAKE_VOLUME = {'id': 'fake_id'}
+
+ISCSI_FAKE_TARGET = {}
+ISCSI_FAKE_TARGET['address'] = ISCSI_FAKE_ADDRESS
+ISCSI_FAKE_TARGET['port'] = ISCSI_FAKE_PORT
+
+ISCSI_FAKE_VOLUME = {'id': 'fake_id', 'provider_auth': 'None stack password'}
+
+FC_ISCSI_TARGET_INFO_DICT = {'target_discovered': False,
+                             'target_portal': '10.63.165.216:2232',
+                             'target_iqn': ISCSI_FAKE_IQN,
+                             'target_lun': ISCSI_FAKE_LUN_ID,
+                             'volume_id': ISCSI_FAKE_VOLUME['id'],
+                             'auth_method': 'None', 'auth_username': 'stack',
+                             'auth_password': 'password'}
+
+
 def create_configuration():
     config = conf.Configuration(None)
     config.append_config_values(na_opts.netapp_connection_opts)
@@ -43,27 +69,3 @@ def create_configuration_eseries():
     config = create_configuration()
     config.append_config_values(na_opts.netapp_eseries_opts)
     return config
-
-ISCSI_FAKE_LUN_ID = 1
-
-ISCSI_FAKE_IQN = 'iqn.1993-08.org.debian:01:10'
-
-ISCSI_FAKE_ADDRESS = '10.63.165.216'
-
-ISCSI_FAKE_PORT = '2232'
-
-ISCSI_FAKE_VOLUME = {'id': 'fake_id'}
-
-ISCSI_FAKE_TARGET = {}
-ISCSI_FAKE_TARGET['address'] = ISCSI_FAKE_ADDRESS
-ISCSI_FAKE_TARGET['port'] = ISCSI_FAKE_PORT
-
-ISCSI_FAKE_VOLUME = {'id': 'fake_id', 'provider_auth': 'None stack password'}
-
-FC_ISCSI_TARGET_INFO_DICT = {'target_discovered': False,
-                             'target_portal': '10.63.165.216:2232',
-                             'target_iqn': ISCSI_FAKE_IQN,
-                             'target_lun': ISCSI_FAKE_LUN_ID,
-                             'volume_id': ISCSI_FAKE_VOLUME['id'],
-                             'auth_method': 'None', 'auth_username': 'stack',
-                             'auth_password': 'password'}
index af87856e8d9e73294f3a0b086298ee3469341e47..268fb85a42efdd367586df8dad3fdf4ac13f539a 100644 (file)
@@ -1,5 +1,7 @@
 # Copyright (c) 2014 NetApp, Inc.  All rights reserved.
 # Copyright (c) 2014 Navneet Singh.  All rights reserved.
+# Copyright (c) 2015 Alex Meade.  All Rights Reserved.
+# Copyright (c) 2015 Rushil Chugh.  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
@@ -298,6 +300,11 @@ class RestClient(WebserviceClient):
         path = "/storage-systems/{system-id}/storage-pools"
         return self._invoke('GET', path)
 
+    def list_drives(self):
+        """Lists drives in the array."""
+        path = "/storage-systems/{system-id}/drives"
+        return self._invoke('GET', path)
+
     def list_storage_systems(self):
         """Lists managed storage systems registered with web service."""
         path = "/storage-systems"
index 952d2bd36963da0b69db21454907d62e34ec7647..53623f92c9807d8750d9cb012fc838b7e225a71e 100644 (file)
@@ -1,4 +1,6 @@
 # Copyright (c) 2014 NetApp, Inc.  All Rights Reserved.
+# Copyright (c) 2015 Alex Meade.  All Rights Reserved.
+# Copyright (c) 2015 Rushil Chugh.  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
@@ -15,6 +17,7 @@
 iSCSI driver for NetApp E-series storage systems.
 """
 
+import copy
 import socket
 import time
 import uuid
@@ -27,6 +30,7 @@ import six
 from cinder import exception
 from cinder.i18n import _, _LE, _LI, _LW
 from cinder.openstack.common import log as logging
+from cinder.openstack.common import loopingcall
 from cinder import utils as cinder_utils
 from cinder.volume import driver
 from cinder.volume.drivers.netapp.eseries import client
@@ -78,6 +82,16 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
                   'windows_clustered':
                   'Windows 2000/Server 2003/Server 2008 Clustered'
                   }
+    # NOTE(ameade): This maps what is reported by the e-series api to a
+    # consistent set of values that are reported by all NetApp drivers
+    # to the cinder scheduler.
+    SSC_DISK_TYPE_MAPPING = {
+        'scsi': 'SCSI',
+        'fibre': 'FCAL',
+        'sas': 'SAS',
+        'sata': 'SATA',
+    }
+    SSC_UPDATE_INTERVAL = 60  # seconds
 
     def __init__(self, *args, **kwargs):
         super(NetAppEseriesISCSIDriver, self).__init__(*args, **kwargs)
@@ -91,6 +105,7 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
         self._objects = {'disk_pool_refs': [], 'pools': [],
                          'volumes': {'label_ref': {}, 'ref_vol': {}},
                          'snapshots': {'label_ref': {}, 'ref_snap': {}}}
+        self._ssc_stats = {}
 
     def do_setup(self, context):
         """Any initialization the volume driver does while starting."""
@@ -114,11 +129,17 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
             password=self.configuration.netapp_password)
         self._check_mode_get_or_register_storage_system()
 
+    def _start_periodic_tasks(self):
+        ssc_periodic_task = loopingcall.FixedIntervalLoopingCall(
+            self._update_ssc_info)
+        ssc_periodic_task.start(interval=self.SSC_UPDATE_INTERVAL)
+
     def check_for_setup_error(self):
         self._check_host_type()
         self._check_multipath()
         self._check_storage_system()
         self._populate_system_objects()
+        self._start_periodic_tasks()
 
     def _check_host_type(self):
         self.host_type =\
@@ -170,8 +191,8 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
             system = self._client.list_storage_system()
         except exception.NetAppDriverException:
             with excutils.save_and_reraise_exception():
-                msg = _("System with controller addresses [%s] is not"
-                        " registered with web service.")
+                msg = _LI("System with controller addresses [%s] is not"
+                          " registered with web service.")
                 LOG.info(msg % self.configuration.netapp_controller_ips)
         password_not_in_sync = False
         if system.get('status', '').lower() == 'passwordoutofsync':
@@ -723,7 +744,10 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
     def get_volume_stats(self, refresh=False):
         """Return the current state of the volume service."""
         if refresh:
+            if not self._ssc_stats:
+                self._update_ssc_info()
             self._update_volume_stats()
+
         return self._stats
 
     def _update_volume_stats(self):
@@ -748,11 +772,72 @@ class NetAppEseriesISCSIDriver(driver.ISCSIDriver):
                 cinder_pool["free_capacity_gb"] = ((tot_bytes - used_bytes) /
                                                    units.Gi)
                 cinder_pool["total_capacity_gb"] = tot_bytes / units.Gi
+
+                pool_ssc_stats = self._ssc_stats.get(pool["volumeGroupRef"])
+
+                if pool_ssc_stats:
+                    cinder_pool.update(pool_ssc_stats)
                 data["pools"].append(cinder_pool)
 
         self._stats = data
         self._garbage_collect_tmp_vols()
 
+    @cinder_utils.synchronized("netapp_update_ssc_info", external=False)
+    def _update_ssc_info(self):
+        """Periodically runs to update ssc information from the backend.
+
+        The self._ssc_stats attribute is updated with the following format.
+        {<volume_group_ref> : {<ssc_key>: <ssc_value>}}
+        """
+        LOG.info(_LI("Updating storage service catalog information for "
+                     "backend '%s'") % self._backend_name)
+        self._ssc_stats = \
+            self._update_ssc_disk_encryption(self._objects["disk_pool_refs"])
+        self._ssc_stats = \
+            self._update_ssc_disk_types(self._objects["disk_pool_refs"])
+
+    def _update_ssc_disk_types(self, volume_groups):
+        """Updates the given ssc dictionary with new disk type information.
+
+        :param volume_groups: The volume groups this driver cares about
+        """
+        ssc_stats = copy.deepcopy(self._ssc_stats)
+        all_disks = self._client.list_drives()
+        relevant_disks = filter(lambda x: x.get('currentVolumeGroupRef') in
+                                volume_groups, all_disks)
+        for drive in relevant_disks:
+            current_vol_group = drive.get('currentVolumeGroupRef')
+            if current_vol_group not in ssc_stats:
+                ssc_stats[current_vol_group] = {}
+
+            if drive.get("driveMediaType") == 'ssd':
+                ssc_stats[current_vol_group]['netapp_disk_type'] = 'SSD'
+            else:
+                disk_type = drive.get('interfaceType').get('driveType')
+                ssc_stats[current_vol_group]['netapp_disk_type'] = \
+                    self.SSC_DISK_TYPE_MAPPING.get(disk_type, 'unknown')
+
+        return ssc_stats
+
+    def _update_ssc_disk_encryption(self, volume_groups):
+        """Updates the given ssc dictionary with new disk encryption information.
+
+        :param volume_groups: The volume groups this driver cares about
+        """
+        ssc_stats = copy.deepcopy(self._ssc_stats)
+        all_pools = self._client.list_storage_pools()
+        relevant_pools = filter(lambda x: x.get('volumeGroupRef') in
+                                volume_groups, all_pools)
+        for pool in relevant_pools:
+            current_vol_group = pool.get('volumeGroupRef')
+            if current_vol_group not in ssc_stats:
+                ssc_stats[current_vol_group] = {}
+
+            ssc_stats[current_vol_group]['netapp_disk_encryption'] = 'true' \
+                if pool['securityType'] == 'enabled' else 'false'
+
+        return ssc_stats
+
     def _get_sorted_avl_storage_pools(self, size_gb):
         """Returns storage pools sorted on available capacity."""
         size = size_gb * units.Gi