# 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
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(
# 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
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()
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)
# 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
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)
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'}
# 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
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"
# 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
iSCSI driver for NetApp E-series storage systems.
"""
+import copy
import socket
import time
import uuid
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
'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)
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."""
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 =\
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':
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):
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