Netapp drivers support for pool-aware scheduling

Adds pools support for all NetApp drivers: eseries, 7mode (iscsi and nfs), and
cmode (iscsi and nfs). With 7mode and cmode drivers, a pool is one-to-one with a
Ontap flexvol. With eseries, a pool is one-to-one with a dynamic disk pool.

DocImpact
Implements: blueprint pool-aware-cinder-scheduler-support-in-netapp-drivers

Change-Id: Ie6f155df7bc1ae2cd5f7fa39f1b1a0ad38075988
This commit is contained in:
Clinton Knight 2014-08-15 11:12:14 -04:00
parent 2664da2467
commit 98aa91b0e2
11 changed files with 852 additions and 394 deletions

View File

@ -54,11 +54,17 @@ class CapacityFilter(filters.BaseHostFilter):
return True
reserved = float(host_state.reserved_percentage) / 100
free = math.floor(free_space * (1 - reserved))
msg_args = {"host": host_state.host,
"requested": volume_size,
"available": free}
if free < volume_size:
LOG.warning(_("Insufficient free space for volume creation "
"(requested / avail): "
"%(requested)s/%(available)s")
% {'requested': volume_size,
'available': free})
"on host %(host)s (requested / avail): "
"%(requested)s/%(available)s") % msg_args)
else:
LOG.debug("Sufficient free space for volume creation "
"on host %(host)s (requested / avail): "
"%(requested)s/%(available)s" % msg_args)
return free >= volume_size

View File

@ -495,7 +495,7 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
'os_type': 'linux', 'provider_location': 'lun1',
'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
'display_name': None, 'display_description': 'lun1',
'volume_type_id': None}
'volume_type_id': None, 'host': 'hostname@backend#vol1'}
snapshot = {'name': 'snapshot1', 'size': 2, 'volume_name': 'lun1',
'volume_size': 2, 'project_id': 'project',
'display_name': None, 'display_description': 'lun1',
@ -524,7 +524,7 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
'os_type': 'linux', 'provider_location': 'lun1',
'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
'display_name': None, 'display_description': 'lun1',
'volume_type_id': None}
'volume_type_id': None, 'host': 'hostname@backend#vol1'}
vol1 = ssc_utils.NetAppVolume('lun1', 'openstack')
vol1.state['vserver_root'] = False
vol1.state['status'] = 'online'
@ -623,12 +623,11 @@ class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
if not properties:
raise AssertionError('Target portal is none')
def test_fail_create_vol(self):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume, self.vol_fail)
def test_vol_stats(self):
self.driver.get_volume_stats(refresh=True)
stats = self.driver._stats
self.assertEqual(stats['vendor_name'], 'NetApp')
self.assertTrue(stats['pools'][0]['pool_name'])
def test_create_vol_snapshot_diff_size_resize(self):
self.driver.create_volume(self.volume)
@ -1133,6 +1132,7 @@ class NetAppDirect7modeISCSIDriverTestCase_NV(
client = driver.client
client.set_api_version(1, 9)
self.driver = driver
self.driver.root_volume_name = 'root'
def _set_config(self, configuration):
configuration.netapp_storage_family = 'ontap_7mode'
@ -1150,19 +1150,6 @@ class NetAppDirect7modeISCSIDriverTestCase_NV(
self.driver.delete_volume(self.volume)
self.driver.volume_list = []
def test_create_fail_on_select_vol(self):
self.driver.volume_list = ['vol2', 'vol3']
success = False
try:
self.driver.create_volume(self.volume)
except exception.VolumeBackendAPIException:
success = True
pass
finally:
self.driver.volume_list = []
if not success:
raise AssertionError('Failed creating on selected volumes')
def test_check_for_setup_error_version(self):
drv = self.driver
delattr(drv.client, '_api_version')
@ -1195,6 +1182,7 @@ class NetAppDirect7modeISCSIDriverTestCase_WV(
client = driver.client
client.set_api_version(1, 9)
self.driver = driver
self.driver.root_volume_name = 'root'
def _set_config(self, configuration):
configuration.netapp_storage_family = 'ontap_7mode'

View File

@ -16,6 +16,7 @@
Tests for NetApp e-series iscsi volume driver.
"""
import copy
import json
import re
@ -27,8 +28,12 @@ from cinder.openstack.common import log as logging
from cinder import test
from cinder.volume import configuration as conf
from cinder.volume.drivers.netapp import common
from cinder.volume.drivers.netapp.eseries import client
from cinder.volume.drivers.netapp.eseries import iscsi
from cinder.volume.drivers.netapp.eseries.iscsi import LOG as driver_log
from cinder.volume.drivers.netapp.options import netapp_basicauth_opts
from cinder.volume.drivers.netapp.options import netapp_eseries_opts
import cinder.volume.drivers.netapp.utils as na_utils
LOG = logging.getLogger(__name__)
@ -562,7 +567,7 @@ class NetAppEseriesIscsiDriverTestCase(test.TestCase):
"""Test case for NetApp e-series iscsi driver."""
volume = {'id': '114774fb-e15a-4fae-8ee2-c9723e3645ef', 'size': 1,
'volume_name': 'lun1',
'volume_name': 'lun1', 'host': 'hostname@backend#DDP',
'os_type': 'linux', 'provider_location': 'lun1',
'id': '114774fb-e15a-4fae-8ee2-c9723e3645ef',
'provider_auth': 'provider a b', 'project_id': 'project',
@ -597,7 +602,10 @@ class NetAppEseriesIscsiDriverTestCase(test.TestCase):
'project_id': 'project', 'display_name': None,
'display_description': 'lun1',
'volume_type_id': None}
fake_eseries_volume_label = na_utils.convert_uuid_to_es_fmt(volume['id'])
connector = {'initiator': 'iqn.1998-01.com.vmware:localhost-28a58148'}
fake_size_gb = volume['size']
fake_eseries_pool_label = 'DDP'
def setUp(self):
super(NetAppEseriesIscsiDriverTestCase, self).setUp()
@ -745,3 +753,84 @@ class NetAppEseriesIscsiDriverTestCase(test.TestCase):
self.volume_clone_large, self.snapshot)
self.driver.delete_snapshot(self.snapshot)
self.driver.delete_volume(self.volume)
@mock.patch.object(iscsi.Driver, '_get_volume',
mock.Mock(return_value={'volumeGroupRef': 'fake_ref'}))
def test_get_pool(self):
self.driver._objects['pools'] = [{'volumeGroupRef': 'fake_ref',
'label': 'ddp1'}]
pool = self.driver.get_pool({'id': 'fake-uuid'})
self.assertEqual(pool, 'ddp1')
@mock.patch.object(iscsi.Driver, '_get_volume',
mock.Mock(return_value={'volumeGroupRef': 'fake_ref'}))
def test_get_pool_no_pools(self):
self.driver._objects['pools'] = []
pool = self.driver.get_pool({'id': 'fake-uuid'})
self.assertEqual(pool, None)
@mock.patch.object(iscsi.Driver, '_get_volume',
mock.Mock(return_value={'volumeGroupRef': 'fake_ref'}))
def test_get_pool_no_match(self):
self.driver._objects['pools'] = [{'volumeGroupRef': 'fake_ref2',
'label': 'ddp2'}]
pool = self.driver.get_pool({'id': 'fake-uuid'})
self.assertEqual(pool, None)
@mock.patch.object(iscsi.Driver, '_create_volume', mock.Mock())
def test_create_volume(self):
self.driver.create_volume(self.volume)
self.driver._create_volume.assert_called_with(
'DDP', self.fake_eseries_volume_label, self.volume['size'])
def test_create_volume_no_pool_provided_by_scheduler(self):
volume = copy.deepcopy(self.volume)
volume['host'] = "host@backend" # missing pool
self.assertRaises(exception.InvalidHost, self.driver.create_volume,
volume)
@mock.patch.object(client.RestClient, 'list_storage_pools')
def test_helper_create_volume_fail(self, fake_list_pools):
fake_pool = {}
fake_pool['label'] = self.fake_eseries_pool_label
fake_pool['volumeGroupRef'] = 'foo'
fake_pools = [fake_pool]
fake_list_pools.return_value = fake_pools
wrong_eseries_pool_label = 'hostname@backend'
self.assertRaises(exception.NetAppDriverException,
self.driver._create_volume, wrong_eseries_pool_label,
self.fake_eseries_volume_label, self.fake_size_gb)
@mock.patch.object(driver_log, 'info')
@mock.patch.object(client.RestClient, 'list_storage_pools')
@mock.patch.object(client.RestClient, 'create_volume',
mock.MagicMock(return_value='CorrectVolume'))
def test_helper_create_volume(self, storage_pools, log_info):
fake_pool = {}
fake_pool['label'] = self.fake_eseries_pool_label
fake_pool['volumeGroupRef'] = 'foo'
fake_pools = [fake_pool]
storage_pools.return_value = fake_pools
drv = self.driver
storage_vol = drv.driver._create_volume(self.fake_eseries_pool_label,
self.fake_eseries_volume_label,
self.fake_size_gb)
log_info.assert_called_once_with("Created volume with label %s.",
self.fake_eseries_volume_label)
self.assertEqual('CorrectVolume', storage_vol)
@mock.patch.object(client.RestClient, 'list_storage_pools')
@mock.patch.object(client.RestClient, 'create_volume',
mock.MagicMock(
side_effect=exception.NetAppDriverException))
@mock.patch.object(driver_log, 'info', mock.Mock())
def test_create_volume_check_exception(self, fake_list_pools):
fake_pool = {}
fake_pool['label'] = self.fake_eseries_pool_label
fake_pool['volumeGroupRef'] = 'foo'
fake_pools = [fake_pool]
fake_list_pools.return_value = fake_pools
self.assertRaises(exception.NetAppDriverException,
self.driver._create_volume,
self.fake_eseries_pool_label,
self.fake_eseries_volume_label, self.fake_size_gb)

View File

@ -50,10 +50,11 @@ def create_configuration():
class FakeVolume(object):
def __init__(self, size=0):
def __init__(self, host='', size=0):
self.size = size
self.id = hash(self)
self.name = None
self.host = host
def __getitem__(self, key):
return self.__dict__[key]
@ -110,10 +111,11 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
"""Tests volume creation from snapshot."""
drv = self._driver
mox = self.mox
volume = FakeVolume(1)
location = '127.0.0.1:/nfs'
host = 'hostname@backend#' + location
volume = FakeVolume(host, 1)
snapshot = FakeSnapshot(1)
location = '127.0.0.1:/nfs'
expected_result = {'provider_location': location}
mox.StubOutWithMock(drv, '_clone_volume')
mox.StubOutWithMock(drv, '_get_volume_location')
@ -797,6 +799,10 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
if location != "nfs://host/path/image-id":
self.fail("Unexpected direct url.")
def test_get_pool(self):
pool = self._driver.get_pool({'provider_location': 'fake-share'})
self.assertEqual(pool, 'fake-share')
class NetappDirectCmodeNfsDriverOnlyTestCase(test.TestCase):
"""Test direct NetApp C Mode driver only and not inherit."""
@ -820,37 +826,43 @@ class NetappDirectCmodeNfsDriverOnlyTestCase(test.TestCase):
extra_specs = {}
mock_volume_extra_specs.return_value = extra_specs
fake_share = 'localhost:myshare'
host = 'hostname@backend#' + fake_share
with mock.patch.object(drv, '_ensure_shares_mounted'):
with mock.patch.object(drv, '_find_shares',
return_value=['localhost:myshare']):
with mock.patch.object(drv, '_do_create_volume'):
volume_info = self._driver.create_volume(FakeVolume(1))
self.assertEqual(volume_info.get('provider_location'),
fake_share)
with mock.patch.object(drv, '_do_create_volume'):
volume_info = self._driver.create_volume(FakeVolume(host, 1))
self.assertEqual(volume_info.get('provider_location'),
fake_share)
def test_create_volume_no_pool_specified(self):
drv = self._driver
drv.ssc_enabled = False
host = 'hostname@backend' # missing pool
with mock.patch.object(drv, '_ensure_shares_mounted'):
self.assertRaises(exception.InvalidHost,
self._driver.create_volume, FakeVolume(host, 1))
@mock.patch.object(netapp_nfs, 'get_volume_extra_specs')
def test_create_volume_with_qos_policy(self, mock_volume_extra_specs):
drv = self._driver
drv.ssc_enabled = False
extra_specs = {'netapp:qos_policy_group': 'qos_policy_1'}
fake_volume = FakeVolume(1)
fake_share = 'localhost:myshare'
host = 'hostname@backend#' + fake_share
fake_volume = FakeVolume(host, 1)
fake_qos_policy = 'qos_policy_1'
mock_volume_extra_specs.return_value = extra_specs
with mock.patch.object(drv, '_ensure_shares_mounted'):
with mock.patch.object(drv, '_find_shares',
return_value=['localhost:myshare']):
with mock.patch.object(drv, '_do_create_volume'):
with mock.patch.object(drv,
'_set_qos_policy_group_on_volume'
) as mock_set_qos:
volume_info = self._driver.create_volume(fake_volume)
self.assertEqual(volume_info.get('provider_location'),
'localhost:myshare')
mock_set_qos.assert_called_once_with(fake_volume,
fake_share,
fake_qos_policy)
with mock.patch.object(drv, '_do_create_volume'):
with mock.patch.object(drv,
'_set_qos_policy_group_on_volume'
) as mock_set_qos:
volume_info = self._driver.create_volume(fake_volume)
self.assertEqual(volume_info.get('provider_location'),
'localhost:myshare')
mock_set_qos.assert_called_once_with(fake_volume,
fake_share,
fake_qos_policy)
def test_copy_img_to_vol_copyoffload_success(self):
drv = self._driver
@ -1089,6 +1101,14 @@ class NetappDirect7modeNfsDriverTestCase(NetappDirectCmodeNfsDriverTestCase):
return mox
def test_create_volume_no_pool_specified(self):
drv = self._driver
drv.ssc_enabled = False
host = 'hostname@backend' # missing pool
with mock.patch.object(drv, '_ensure_shares_mounted'):
self.assertRaises(exception.InvalidHost,
self._driver.create_volume, FakeVolume(host, 1))
def test_check_for_setup_error_version(self):
drv = self._driver
drv._client = api.NaServer("127.0.0.1")
@ -1196,3 +1216,7 @@ class NetappDirect7modeNfsDriverTestCase(NetappDirectCmodeNfsDriverTestCase):
raise
mox.VerifyAll()
def test_get_pool(self):
pool = self._driver.get_pool({'provider_location': 'fake-share'})
self.assertEqual(pool, 'fake-share')

View File

@ -20,30 +20,83 @@ import uuid
import mock
from cinder import exception
from cinder import test
from cinder.tests.test_netapp import create_configuration
import cinder.volume.drivers.netapp.api as ntapi
import cinder.volume.drivers.netapp.iscsi as ntap_iscsi
from cinder.volume.drivers.netapp.iscsi import NetAppDirect7modeISCSIDriver \
as iscsi7modeDriver
from cinder.volume.drivers.netapp.iscsi import NetAppDirectCmodeISCSIDriver \
as iscsiCmodeDriver
from cinder.volume.drivers.netapp.iscsi import NetAppDirectISCSIDriver \
as iscsiDriver
import cinder.volume.drivers.netapp.ssc_utils as ssc_utils
import cinder.volume.drivers.netapp.utils as na_utils
class NetAppDirectISCSIDriverTestCase(test.TestCase):
def setUp(self):
super(NetAppDirectISCSIDriverTestCase, self).setUp()
configuration = self._set_config(create_configuration())
self.driver = ntap_iscsi.NetAppDirectISCSIDriver(
configuration=mock.Mock())
configuration=configuration)
self.driver.client = mock.Mock()
self.fake_volume = str(uuid.uuid4())
self.fake_lun = str(uuid.uuid4())
self.fake_size = '1024'
self.fake_metadata = {
'OsType': 'linux',
'SpaceReserved': 'true',
}
self.fake_metadata = {'OsType': 'linux', 'SpaceReserved': 'true'}
self.mock_request = mock.Mock()
def _set_config(self, configuration):
configuration.netapp_storage_protocol = 'iscsi'
configuration.netapp_login = 'admin'
configuration.netapp_password = 'pass'
configuration.netapp_server_hostname = '127.0.0.1'
configuration.netapp_transport_type = 'http'
configuration.netapp_server_port = '80'
return configuration
def tearDown(self):
super(NetAppDirectISCSIDriverTestCase, self).tearDown()
@mock.patch.object(iscsiDriver, '_get_lun_attr',
mock.Mock(return_value={'Volume': 'vol1'}))
def test_get_pool(self):
pool = self.driver.get_pool({'name': 'volume-fake-uuid'})
self.assertEqual(pool, 'vol1')
@mock.patch.object(iscsiDriver, '_get_lun_attr',
mock.Mock(return_value=None))
def test_get_pool_no_metadata(self):
pool = self.driver.get_pool({'name': 'volume-fake-uuid'})
self.assertEqual(pool, None)
@mock.patch.object(iscsiDriver, '_get_lun_attr',
mock.Mock(return_value=dict()))
def test_get_pool_volume_unknown(self):
pool = self.driver.get_pool({'name': 'volume-fake-uuid'})
self.assertEqual(pool, None)
@mock.patch.object(iscsiDriver, 'create_lun', mock.Mock())
@mock.patch.object(iscsiDriver, '_create_lun_handle', mock.Mock())
@mock.patch.object(iscsiDriver, '_add_lun_to_table', mock.Mock())
@mock.patch.object(na_utils, 'get_volume_extra_specs',
mock.Mock(return_value=None))
def test_create_volume(self):
self.driver.create_volume({'name': 'lun1', 'size': 100,
'id': uuid.uuid4(),
'host': 'hostname@backend#vol1'})
self.driver.create_lun.assert_called_once_with(
'vol1', 'lun1', 107374182400, mock.ANY, None)
def test_create_volume_no_pool_provided_by_scheduler(self):
self.assertRaises(exception.InvalidHost, self.driver.create_volume,
{'name': 'lun1', 'size': 100,
'id': uuid.uuid4(),
'host': 'hostname@backend'}) # missing pool
def test_create_lun(self):
expected_path = '/vol/%s/%s' % (self.fake_volume, self.fake_lun)
@ -89,6 +142,20 @@ class NetAppDirectISCSIDriverTestCase(test.TestCase):
self.driver.client.invoke_successfully.assert_called_once_with(
mock.ANY, True)
def test_create_lun_raises_on_failure(self):
self.driver.client.invoke_successfully = mock.Mock(
side_effect=ntapi.NaApiError)
self.assertRaises(ntapi.NaApiError,
self.driver.create_lun,
self.fake_volume,
self.fake_lun,
self.fake_size,
self.fake_metadata)
def test_update_volume_stats_is_abstract(self):
self.assertRaises(NotImplementedError,
self.driver._update_volume_stats)
class NetAppiSCSICModeTestCase(test.TestCase):
"""Test case for NetApp's C-Mode iSCSI driver."""
@ -99,6 +166,7 @@ class NetAppiSCSICModeTestCase(test.TestCase):
configuration=mock.Mock())
self.driver.client = mock.Mock()
self.driver.vserver = mock.Mock()
self.driver.ssc_vols = None
def tearDown(self):
super(NetAppiSCSICModeTestCase, self).tearDown()
@ -182,6 +250,13 @@ class NetAppiSCSICModeTestCase(test.TestCase):
self.assertEqual(1, self.driver.client.invoke_successfully.call_count)
@mock.patch.object(ssc_utils, 'refresh_cluster_ssc', mock.Mock())
@mock.patch.object(iscsiCmodeDriver, '_get_pool_stats', mock.Mock())
@mock.patch.object(na_utils, 'provide_ems', mock.Mock())
def test_vol_stats_calls_provide_ems(self):
self.driver.get_volume_stats(refresh=True)
self.assertEqual(na_utils.provide_ems.call_count, 1)
class NetAppiSCSI7ModeTestCase(test.TestCase):
"""Test case for NetApp's 7-Mode iSCSI driver."""
@ -284,3 +359,10 @@ class NetAppiSCSI7ModeTestCase(test.TestCase):
self.driver._clone_lun('fakeLUN', 'newFakeLUN')
self.assertEqual(1, self.driver.client.invoke_successfully.call_count)
@mock.patch.object(iscsi7modeDriver, '_refresh_volume_info', mock.Mock())
@mock.patch.object(iscsi7modeDriver, '_get_pool_stats', mock.Mock())
@mock.patch.object(na_utils, 'provide_ems', mock.Mock())
def test_vol_stats_calls_provide_ems(self):
self.driver.get_volume_stats(refresh=True)
self.assertEqual(na_utils.provide_ems.call_count, 1)

View File

@ -0,0 +1,59 @@
# Copyright (c) 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.
"""
Mock unit tests for the NetApp driver utility module
"""
from cinder import test
import cinder.volume.drivers.netapp.utils as na_utils
class NetAppDriverUtilsTestCase(test.TestCase):
def test_to_bool(self):
self.assertTrue(na_utils.to_bool(True))
self.assertTrue(na_utils.to_bool('true'))
self.assertTrue(na_utils.to_bool('yes'))
self.assertTrue(na_utils.to_bool('y'))
self.assertTrue(na_utils.to_bool(1))
self.assertTrue(na_utils.to_bool('1'))
self.assertFalse(na_utils.to_bool(False))
self.assertFalse(na_utils.to_bool('false'))
self.assertFalse(na_utils.to_bool('asdf'))
self.assertFalse(na_utils.to_bool('no'))
self.assertFalse(na_utils.to_bool('n'))
self.assertFalse(na_utils.to_bool(0))
self.assertFalse(na_utils.to_bool('0'))
self.assertFalse(na_utils.to_bool(2))
self.assertFalse(na_utils.to_bool('2'))
def test_convert_uuid_to_es_fmt(self):
value = 'e67e931a-b2ed-4890-938b-3acc6a517fac'
result = na_utils.convert_uuid_to_es_fmt(value)
self.assertEqual(result, '4Z7JGGVS5VEJBE4LHLGGUUL7VQ')
def test_convert_es_fmt_to_uuid(self):
value = '4Z7JGGVS5VEJBE4LHLGGUUL7VQ'
result = str(na_utils.convert_es_fmt_to_uuid(value))
self.assertEqual(result, 'e67e931a-b2ed-4890-938b-3acc6a517fac')
def test_round_down(self):
self.assertAlmostEqual(na_utils.round_down(5.567, '0.00'), 5.56)
self.assertAlmostEqual(na_utils.round_down(5.567, '0.0'), 5.5)
self.assertAlmostEqual(na_utils.round_down(5.567, '0'), 5)
self.assertAlmostEqual(na_utils.round_down(0, '0.00'), 0)
self.assertAlmostEqual(na_utils.round_down(-5.567, '0.00'), -5.56)
self.assertAlmostEqual(na_utils.round_down(-5.567, '0.0'), -5.5)
self.assertAlmostEqual(na_utils.round_down(-5.567, '0'), -5)

View File

@ -21,6 +21,7 @@ import time
import uuid
from oslo.config import cfg
import six
from cinder import exception
from cinder.i18n import _
@ -35,6 +36,7 @@ from cinder.volume.drivers.netapp.options import netapp_connection_opts
from cinder.volume.drivers.netapp.options import netapp_eseries_opts
from cinder.volume.drivers.netapp.options import netapp_transport_opts
from cinder.volume.drivers.netapp import utils
from cinder.volume import utils as volume_utils
LOG = logging.getLogger(__name__)
@ -64,7 +66,7 @@ class Driver(driver.ISCSIDriver):
self.configuration.append_config_values(netapp_connection_opts)
self.configuration.append_config_values(netapp_transport_opts)
self.configuration.append_config_values(netapp_eseries_opts)
self._objects = {'disk_pool_refs': [],
self._objects = {'disk_pool_refs': [], 'pools': [],
'volumes': {'label_ref': {}, 'ref_vol': {}},
'snapshots': {'label_ref': {}, 'ref_snap': {}}}
@ -182,6 +184,7 @@ class Driver(driver.ISCSIDriver):
if (pool.get('raidLevel') == 'raidDiskPool'
and pool['label'].lower() in pools):
self._objects['disk_pool_refs'].append(pool['volumeGroupRef'])
self._objects['pools'].append(pool)
def _cache_volume(self, obj):
"""Caches volumes for further reference."""
@ -284,14 +287,67 @@ class Driver(driver.ISCSIDriver):
return True
return False
def get_pool(self, volume):
"""Return pool name where volume resides.
:param volume: The volume hosted by the driver.
:return: Name of the pool where given volume is hosted.
"""
eseries_volume = self._get_volume(volume['id'])
for pool in self._objects['pools']:
if pool['volumeGroupRef'] == eseries_volume['volumeGroupRef']:
return pool['label']
return None
def create_volume(self, volume):
"""Creates a volume."""
label = utils.convert_uuid_to_es_fmt(volume['id'])
LOG.debug('create_volume on %s' % volume['host'])
# get E-series pool label as pool name
eseries_pool_label = volume_utils.extract_host(volume['host'],
level='pool')
if eseries_pool_label is None:
msg = _("Pool is not available in the volume host field.")
raise exception.InvalidHost(reason=msg)
eseries_volume_label = utils.convert_uuid_to_es_fmt(volume['id'])
# get size of the requested volume creation
size_gb = int(volume['size'])
vol = self._create_volume(label, size_gb)
vol = self._create_volume(eseries_pool_label, eseries_volume_label,
size_gb)
self._cache_volume(vol)
def _create_volume(self, label, size_gb):
def _create_volume(self, eseries_pool_label, eseries_volume_label,
size_gb):
"""Creates volume with given label and size."""
target_pool = None
pools = self._client.list_storage_pools()
for pool in pools:
if pool["label"] == eseries_pool_label:
target_pool = pool
break
if not target_pool:
msg = _("Pools %s does not exist")
raise exception.NetAppDriverException(msg % eseries_pool_label)
try:
vol = self._client.create_volume(target_pool['volumeGroupRef'],
eseries_volume_label, size_gb)
LOG.info(_("Created volume with label %s."), eseries_volume_label)
except exception.NetAppDriverException as e:
with excutils.save_and_reraise_exception():
LOG.error(_("Error creating volume. Msg - %s."),
six.text_type(e))
return vol
def _schedule_and_create_volume(self, label, size_gb):
"""Creates volume with given label and size."""
avl_pools = self._get_sorted_avl_storage_pools(size_gb)
for pool in avl_pools:
@ -305,28 +361,11 @@ class Driver(driver.ISCSIDriver):
msg = _("Failure creating volume %s.")
raise exception.NetAppDriverException(msg % label)
def _get_sorted_avl_storage_pools(self, size_gb):
"""Returns storage pools sorted on available capacity."""
size = size_gb * units.Gi
pools = self._client.list_storage_pools()
sorted_pools = sorted(pools, key=lambda x:
(int(x.get('totalRaidedSpace', 0))
- int(x.get('usedSpace', 0))), reverse=True)
avl_pools = [x for x in sorted_pools
if (x['volumeGroupRef'] in
self._objects['disk_pool_refs']) and
(int(x.get('totalRaidedSpace', 0)) -
int(x.get('usedSpace', 0) >= size))]
if not avl_pools:
msg = _("No storage pool found with available capacity %s.")
exception.NotFound(msg % size_gb)
return avl_pools
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
label = utils.convert_uuid_to_es_fmt(volume['id'])
size = volume['size']
dst_vol = self._create_volume(label, size)
dst_vol = self._schedule_and_create_volume(label, size)
try:
src_vol = None
src_vol = self._create_snapshot_volume(snapshot['id'])
@ -624,31 +663,48 @@ class Driver(driver.ISCSIDriver):
def _update_volume_stats(self):
"""Update volume statistics."""
LOG.debug("Updating volume stats.")
self._stats = self._stats or {}
netapp_backend = 'NetApp_ESeries'
backend_name = self.configuration.safe_get('volume_backend_name')
self._stats["volume_backend_name"] = (
backend_name or netapp_backend)
self._stats["vendor_name"] = 'NetApp'
self._stats["driver_version"] = '1.0'
self._stats["storage_protocol"] = 'iSCSI'
self._stats["total_capacity_gb"] = 0
self._stats["free_capacity_gb"] = 0
self._stats["reserved_percentage"] = 0
self._stats["QoS_support"] = False
self._update_capacity()
self._garbage_collect_tmp_vols()
data = dict()
netapp_backend = "NetApp_ESeries"
backend_name = self.configuration.safe_get("volume_backend_name")
data["volume_backend_name"] = (backend_name or netapp_backend)
data["vendor_name"] = "NetApp"
data["driver_version"] = self.VERSION
data["storage_protocol"] = "iSCSI"
data["pools"] = []
def _update_capacity(self):
"""Get free and total appliance capacity in bytes."""
tot_bytes, used_bytes = 0, 0
pools = self._client.list_storage_pools()
for pool in pools:
if pool['volumeGroupRef'] in self._objects['disk_pool_refs']:
tot_bytes = tot_bytes + int(pool.get('totalRaidedSpace', 0))
used_bytes = used_bytes + int(pool.get('usedSpace', 0))
self._stats['free_capacity_gb'] = (tot_bytes - used_bytes) / units.Gi
self._stats['total_capacity_gb'] = tot_bytes / units.Gi
cinder_pool = {}
cinder_pool["pool_name"] = pool.get("label", 0)
cinder_pool["QoS_support"] = False
cinder_pool["reserved_percentage"] = 0
if pool["volumeGroupRef"] in self._objects["disk_pool_refs"]:
tot_bytes = int(pool.get("totalRaidedSpace", 0))
used_bytes = int(pool.get("usedSpace", 0))
cinder_pool["free_capacity_gb"] = ((tot_bytes - used_bytes) /
units.Gi)
cinder_pool["total_capacity_gb"] = tot_bytes / units.Gi
data["pools"].append(cinder_pool)
self._stats = data
self._garbage_collect_tmp_vols()
def _get_sorted_avl_storage_pools(self, size_gb):
"""Returns storage pools sorted on available capacity."""
size = size_gb * units.Gi
pools = self._client.list_storage_pools()
sorted_pools = sorted(pools, key=lambda x:
(int(x.get('totalRaidedSpace', 0))
- int(x.get('usedSpace', 0))), reverse=True)
avl_pools = [x for x in sorted_pools
if (x['volumeGroupRef'] in
self._objects['disk_pool_refs']) and
(int(x.get('totalRaidedSpace', 0)) -
int(x.get('usedSpace', 0) >= size))]
if not avl_pools:
msg = _("No storage pool found with available capacity %s.")
LOG.warn(msg % size_gb)
return avl_pools
def extend_volume(self, volume, new_size):
"""Extend an existing volume to the new size."""

View File

@ -26,6 +26,8 @@ import sys
import time
import uuid
import six
from cinder import exception
from cinder.i18n import _
from cinder.openstack.common import excutils
@ -44,10 +46,12 @@ from cinder.volume.drivers.netapp.options import netapp_connection_opts
from cinder.volume.drivers.netapp.options import netapp_provisioning_opts
from cinder.volume.drivers.netapp.options import netapp_transport_opts
from cinder.volume.drivers.netapp import ssc_utils
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume.drivers.netapp.utils import get_volume_extra_specs
from cinder.volume.drivers.netapp.utils import provide_ems
from cinder.volume.drivers.netapp.utils import round_down
from cinder.volume.drivers.netapp.utils import set_safe_attr
from cinder.volume.drivers.netapp.utils import validate_instantiation
from cinder.volume import utils as volume_utils
LOG = logging.getLogger(__name__)
@ -150,23 +154,52 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver):
self._get_lun_list()
LOG.debug("Success getting LUN list from server")
def create_volume(self, volume):
"""Driver entry point for creating a new volume."""
default_size = '104857600' # 100 MB
gigabytes = 1073741824L # 2^30
def get_pool(self, volume):
"""Return pool name where volume resides.
:param volume: The volume hosted by the driver.
:return: Name of the pool where given volume is hosted.
"""
name = volume['name']
if int(volume['size']) == 0:
size = default_size
else:
size = str(int(volume['size']) * gigabytes)
metadata = {}
metadata['OsType'] = 'linux'
metadata['SpaceReserved'] = 'true'
metadata = self._get_lun_attr(name, 'metadata') or dict()
return metadata.get('Volume', None)
def create_volume(self, volume):
"""Driver entry point for creating a new volume (aka ONTAP LUN)."""
LOG.debug('create_volume on %s' % volume['host'])
# get ONTAP volume name as pool name
ontap_volume_name = volume_utils.extract_host(volume['host'],
level='pool')
if ontap_volume_name is None:
msg = _("Pool is not available in the volume host field.")
raise exception.InvalidHost(reason=msg)
lun_name = volume['name']
# start with default size, get requested size
default_size = units.Mi * 100 # 100 MB
size = default_size if not int(volume['size'])\
else int(volume['size']) * units.Gi
metadata = {'OsType': 'linux', 'SpaceReserved': 'true'}
extra_specs = get_volume_extra_specs(volume)
self._create_lun_on_eligible_vol(name, size, metadata, extra_specs)
LOG.debug("Created LUN with name %s" % name)
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \
if extra_specs else None
self.create_lun(ontap_volume_name, lun_name, size,
metadata, qos_policy_group)
LOG.debug('Created LUN with name %s' % lun_name)
metadata['Path'] = '/vol/%s/%s' % (ontap_volume_name, lun_name)
metadata['Volume'] = ontap_volume_name
metadata['Qtree'] = None
handle = self._create_lun_handle(metadata)
self._add_lun_to_table(NetAppLun(handle, name, size, metadata))
self._add_lun_to_table(NetAppLun(handle, lun_name, size, metadata))
def delete_volume(self, volume):
"""Driver entry point for destroying existing volumes."""
@ -336,22 +369,29 @@ class NetAppDirectISCSIDriver(driver.ISCSIDriver):
minor = res.get_child_content('minor-version')
return (major, minor)
def _create_lun_on_eligible_vol(self, name, size, metadata,
extra_specs=None):
"""Creates an actual lun on filer."""
raise NotImplementedError()
def create_lun(self, volume_name, lun_name, size,
metadata, qos_policy_group=None):
"""Issues API request for creating LUN on volume."""
def create_lun(self, volume, lun, size, metadata, qos_policy_group=None):
"""Issues api request for creating lun on volume."""
path = '/vol/%s/%s' % (volume, lun)
path = '/vol/%s/%s' % (volume_name, lun_name)
lun_create = NaElement.create_node_with_children(
'lun-create-by-size',
**{'path': path, 'size': size,
**{'path': path, 'size': six.text_type(size),
'ostype': metadata['OsType'],
'space-reservation-enabled': metadata['SpaceReserved']})
if qos_policy_group:
lun_create.add_new_child('qos-policy-group', qos_policy_group)
self.client.invoke_successfully(lun_create, True)
try:
self.client.invoke_successfully(lun_create, True)
except NaApiError as ex:
with excutils.save_and_reraise_exception():
msg = _("Error provisioning volume %(lun_name)s on "
"%(volume_name)s. Details: %(ex)s")
msg_args = {'lun_name': lun_name,
'volume_name': volume_name,
'ex': six.text_type(ex)}
LOG.error(msg % msg_args)
def _get_iscsi_service_details(self):
"""Returns iscsi iqn."""
@ -769,47 +809,15 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver):
ssc_utils.check_ssc_api_permissions(self.client)
super(NetAppDirectCmodeISCSIDriver, self).check_for_setup_error()
def _create_lun_on_eligible_vol(self, name, size, metadata,
extra_specs=None):
"""Creates an actual lun on filer."""
req_size = float(size) *\
float(self.configuration.netapp_size_multiplier)
qos_policy_group = None
if extra_specs:
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None)
volumes = self._get_avl_volumes(req_size, extra_specs)
if not volumes:
msg = _('Failed to get vol with required'
' size and extra specs for volume: %s')
raise exception.VolumeBackendAPIException(data=msg % name)
for volume in volumes:
try:
self.create_lun(volume.id['name'], name, size, metadata,
qos_policy_group=qos_policy_group)
metadata['Path'] = '/vol/%s/%s' % (volume.id['name'], name)
metadata['Volume'] = volume.id['name']
metadata['Qtree'] = None
return
except NaApiError as ex:
msg = _("Error provisioning vol %(name)s on "
"%(volume)s. Details: %(ex)s")
LOG.error(msg % {'name': name,
'volume': volume.id['name'],
'ex': ex})
finally:
self._update_stale_vols(volume=volume)
def create_lun(self, volume_name, lun_name, size,
metadata, qos_policy_group=None):
"""Creates a LUN, handling ONTAP differences as needed."""
def _get_avl_volumes(self, size, extra_specs=None):
"""Get the available volume by size, extra_specs."""
result = []
volumes = ssc_utils.get_volumes_for_specs(
self.ssc_vols, extra_specs)
if volumes:
sorted_vols = sorted(volumes, reverse=True)
for vol in sorted_vols:
if int(vol.space['size_avl_bytes']) >= int(size):
result.append(vol)
return result
super(NetAppDirectCmodeISCSIDriver, self).create_lun(
volume_name, lun_name, size, metadata, qos_policy_group)
self._update_stale_vols(
volume=ssc_utils.NetAppVolume(volume_name, self.vserver))
def _get_target_details(self):
"""Gets the target portal details."""
@ -1036,7 +1044,6 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver):
"""Creates lun metadata dictionary."""
self._is_naelement(lun)
meta_dict = {}
self._is_naelement(lun)
meta_dict['Vserver'] = lun.get_child_content('vserver')
meta_dict['Volume'] = lun.get_child_content('volume')
meta_dict['Qtree'] = lun.get_child_content('qtree')
@ -1054,64 +1061,73 @@ class NetAppDirectCmodeISCSIDriver(NetAppDirectISCSIDriver):
self.client.set_vserver(None)
def _update_volume_stats(self):
"""Retrieve stats info from volume group."""
"""Retrieve stats info from vserver."""
LOG.debug("Updating volume stats")
sync = True if self.ssc_vols is None else False
ssc_utils.refresh_cluster_ssc(self, self.client,
self.vserver, synchronous=sync)
LOG.debug('Updating volume stats')
data = {}
netapp_backend = 'NetApp_iSCSI_Cluster_direct'
backend_name = self.configuration.safe_get('volume_backend_name')
data["volume_backend_name"] = (
backend_name or netapp_backend)
data["vendor_name"] = 'NetApp'
data["driver_version"] = '1.0'
data["storage_protocol"] = 'iSCSI'
data['volume_backend_name'] = backend_name or netapp_backend
data['vendor_name'] = 'NetApp'
data['driver_version'] = self.VERSION
data['storage_protocol'] = 'iSCSI'
data['pools'] = self._get_pool_stats()
data['total_capacity_gb'] = 0
data['free_capacity_gb'] = 0
data['reserved_percentage'] = 0
data['QoS_support'] = False
self._update_cluster_vol_stats(data)
provide_ems(self, self.client, data, netapp_backend)
na_utils.provide_ems(self, self.client, data, netapp_backend)
self._stats = data
def _update_cluster_vol_stats(self, data):
"""Updates vol stats with cluster config."""
sync = True if self.ssc_vols is None else False
ssc_utils.refresh_cluster_ssc(self, self.client, self.vserver,
synchronous=sync)
if self.ssc_vols:
data['netapp_mirrored'] = 'true'\
if self.ssc_vols['mirrored'] else 'false'
data['netapp_unmirrored'] = 'true'\
if len(self.ssc_vols['all']) > len(self.ssc_vols['mirrored'])\
else 'false'
data['netapp_dedup'] = 'true'\
if self.ssc_vols['dedup'] else 'false'
data['netapp_nodedup'] = 'true'\
if len(self.ssc_vols['all']) > len(self.ssc_vols['dedup'])\
else 'false'
data['netapp_compression'] = 'true'\
if self.ssc_vols['compression'] else 'false'
data['netapp_nocompression'] = 'true'\
if len(self.ssc_vols['all']) >\
len(self.ssc_vols['compression'])\
else 'false'
data['netapp_thin_provisioned'] = 'true'\
if self.ssc_vols['thin'] else 'false'
data['netapp_thick_provisioned'] = 'true'\
if len(self.ssc_vols['all']) >\
len(self.ssc_vols['thin']) else 'false'
if self.ssc_vols['all']:
vol_max = max(self.ssc_vols['all'])
data['total_capacity_gb'] =\
int(vol_max.space['size_total_bytes']) / units.Gi
data['free_capacity_gb'] =\
int(vol_max.space['size_avl_bytes']) / units.Gi
else:
data['total_capacity_gb'] = 0
data['free_capacity_gb'] = 0
else:
LOG.warn(_("Cluster ssc is not updated. No volume stats found."))
def _get_pool_stats(self):
"""Retrieve pool (i.e. ONTAP volume) stats info from SSC volumes."""
pools = []
if not self.ssc_vols:
return pools
for vol in self.ssc_vols['all']:
pool = dict()
pool['pool_name'] = vol.id['name']
pool['QoS_support'] = False
pool['reserved_percentage'] = 0
# convert sizes to GB and de-rate by NetApp multiplier
total = float(vol.space['size_total_bytes'])
total /= self.configuration.netapp_size_multiplier
total /= units.Gi
pool['total_capacity_gb'] = round_down(total, '0.01')
free = float(vol.space['size_avl_bytes'])
free /= self.configuration.netapp_size_multiplier
free /= units.Gi
pool['free_capacity_gb'] = round_down(free, '0.01')
pool['netapp:raid_type'] = vol.aggr['raid_type']
pool['netapp:disk_type'] = vol.aggr['disk_type']
pool['netapp:qos_policy_group'] = vol.qos['qos_policy_group']
mirrored = vol in self.ssc_vols['mirrored']
pool['netapp_mirrored'] = six.text_type(mirrored).lower()
pool['netapp_unmirrored'] = six.text_type(not mirrored).lower()
dedup = vol in self.ssc_vols['dedup']
pool['netapp_dedup'] = six.text_type(dedup).lower()
pool['netapp_nodedup'] = six.text_type(not dedup).lower()
compression = vol in self.ssc_vols['compression']
pool['netapp_compression'] = six.text_type(compression).lower()
pool['netapp_nocompression'] = six.text_type(
not compression).lower()
thin = vol in self.ssc_vols['thin']
pool['netapp_thin_provisioned'] = six.text_type(thin).lower()
pool['netapp_thick_provisioned'] = six.text_type(not thin).lower()
pools.append(pool)
return pools
@utils.synchronized('update_stale')
def _update_stale_vols(self, volume=None, reset=False):
@ -1162,10 +1178,7 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
self.vol_refresh_interval = 1800
self.vol_refresh_running = False
self.vol_refresh_voluntary = False
# Setting it infinite at set up
# This will not rule out backend from scheduling
self.total_gb = 'infinite'
self.free_gb = 'infinite'
self.root_volume_name = self._get_root_volume_name()
def check_for_setup_error(self):
"""Check that the driver is working and can communicate."""
@ -1181,19 +1194,13 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
raise exception.VolumeBackendAPIException(data=msg)
super(NetAppDirect7modeISCSIDriver, self).check_for_setup_error()
def _create_lun_on_eligible_vol(self, name, size, metadata,
extra_specs=None):
"""Creates an actual lun on filer."""
req_size = float(size) *\
float(self.configuration.netapp_size_multiplier)
volume = self._get_avl_volume_by_size(req_size)
if not volume:
msg = _('Failed to get vol with required size for volume: %s')
raise exception.VolumeBackendAPIException(data=msg % name)
self.create_lun(volume['name'], name, size, metadata)
metadata['Path'] = '/vol/%s/%s' % (volume['name'], name)
metadata['Volume'] = volume['name']
metadata['Qtree'] = None
def create_lun(self, volume_name, lun_name, size,
metadata, qos_policy_group=None):
"""Creates a LUN, handling ONTAP differences as needed."""
super(NetAppDirect7modeISCSIDriver, self).create_lun(
volume_name, lun_name, size, metadata, qos_policy_group)
self.vol_refresh_voluntary = True
def _get_filer_volumes(self, volume=None):
@ -1207,23 +1214,15 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
return volumes.get_children()
return []
def _get_avl_volume_by_size(self, size):
"""Get the available volume by size."""
def _get_root_volume_name(self):
# switch to volume-get-root-name API when possible
vols = self._get_filer_volumes()
for vol in vols:
avl_size = vol.get_child_content('size-available')
state = vol.get_child_content('state')
if float(avl_size) >= float(size) and state == 'online':
avl_vol = dict()
avl_vol['name'] = vol.get_child_content('name')
avl_vol['block-type'] = vol.get_child_content('block-type')
avl_vol['type'] = vol.get_child_content('type')
avl_vol['size-available'] = avl_size
if self.volume_list:
if avl_vol['name'] in self.volume_list:
return avl_vol
elif self._get_vol_option(avl_vol['name'], 'root') != 'true':
return avl_vol
volume_name = vol.get_child_content('name')
if self._get_vol_option(volume_name, 'root') == 'true':
return volume_name
LOG.warn(_('Could not determine root volume name '
'on %s.') % self._get_owner())
return None
def _get_igroup_by_initiator(self, initiator):
@ -1278,13 +1277,17 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
result = self.client.invoke_successfully(iscsi_service_iter, True)
return result.get_child_content('node-name')
def _create_lun_handle(self, metadata):
"""Returns lun handle based on filer type."""
def _get_owner(self):
if self.vfiler:
owner = '%s:%s' % (self.configuration.netapp_server_hostname,
self.vfiler)
else:
owner = self.configuration.netapp_server_hostname
return owner
def _create_lun_handle(self, metadata):
"""Returns lun handle based on filer type."""
owner = self._get_owner()
return '%s:%s' % (owner, metadata['Path'])
def _get_lun_list(self):
@ -1461,31 +1464,81 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
"""Creates lun metadata dictionary."""
self._is_naelement(lun)
meta_dict = {}
self._is_naelement(lun)
meta_dict['Path'] = lun.get_child_content('path')
meta_dict['Volume'] = lun.get_child_content('path').split('/')[2]
meta_dict['OsType'] = lun.get_child_content('multiprotocol-type')
meta_dict['SpaceReserved'] = lun.get_child_content(
'is-space-reservation-enabled')
return meta_dict
def _update_volume_stats(self):
"""Retrieve status info from volume group."""
LOG.debug("Updating volume stats")
"""Retrieve stats info from filer."""
# ensure we get current data
self.vol_refresh_voluntary = True
self._refresh_volume_info()
LOG.debug('Updating volume stats')
data = {}
netapp_backend = 'NetApp_iSCSI_7mode_direct'
backend_name = self.configuration.safe_get('volume_backend_name')
data["volume_backend_name"] = (
backend_name or 'NetApp_iSCSI_7mode_direct')
data["vendor_name"] = 'NetApp'
data["driver_version"] = self.VERSION
data["storage_protocol"] = 'iSCSI'
data['reserved_percentage'] = 0
data['QoS_support'] = False
self._get_capacity_info(data)
provide_ems(self, self.client, data, netapp_backend,
server_type="7mode")
data['volume_backend_name'] = backend_name or netapp_backend
data['vendor_name'] = 'NetApp'
data['driver_version'] = self.VERSION
data['storage_protocol'] = 'iSCSI'
data['pools'] = self._get_pool_stats()
na_utils.provide_ems(self, self.client, data, netapp_backend,
server_type='7mode')
self._stats = data
def _get_pool_stats(self):
"""Retrieve pool (i.e. ONTAP volume) stats info from volumes."""
pools = []
if not self.vols:
return pools
for vol in self.vols:
# omit volumes not specified in the config
volume_name = vol.get_child_content('name')
if self.volume_list and volume_name not in self.volume_list:
continue
# omit root volume
if volume_name == self.root_volume_name:
continue
# ensure good volume state
state = vol.get_child_content('state')
inconsistent = vol.get_child_content('is-inconsistent')
invalid = vol.get_child_content('is-invalid')
if (state != 'online' or
inconsistent != 'false' or
invalid != 'false'):
continue
pool = dict()
pool['pool_name'] = volume_name
pool['QoS_support'] = False
pool['reserved_percentage'] = 0
# convert sizes to GB and de-rate by NetApp multiplier
total = float(vol.get_child_content('size-total') or 0)
total /= self.configuration.netapp_size_multiplier
total /= units.Gi
pool['total_capacity_gb'] = round_down(total, '0.01')
free = float(vol.get_child_content('size-available') or 0)
free /= self.configuration.netapp_size_multiplier
free /= units.Gi
pool['free_capacity_gb'] = round_down(free, '0.01')
pools.append(pool)
return pools
def _get_lun_block_count(self, path):
"""Gets block counts for the lun."""
bs = super(
@ -1498,8 +1551,9 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
bs = bs - 1
return bs
def _get_capacity_info(self, data):
"""Calculates the capacity information for the filer."""
def _refresh_volume_info(self):
"""Saves the volume information for the filer."""
if (self.vol_refresh_time is None or self.vol_refresh_voluntary or
timeutils.is_newer_than(self.vol_refresh_time,
self.vol_refresh_interval)):
@ -1510,38 +1564,13 @@ class NetAppDirect7modeISCSIDriver(NetAppDirectISCSIDriver):
_("Volume refresh job already running. Returning..."))
return
self.vol_refresh_voluntary = False
self._refresh_capacity_info()
self.vols = self._get_filer_volumes()
self.vol_refresh_time = timeutils.utcnow()
except Exception as e:
LOG.warn(_("Error refreshing vol capacity. Message: %s"), e)
LOG.warn(_("Error refreshing volume info. Message: %s"),
six.text_type(e))
finally:
set_safe_attr(self, 'vol_refresh_running', False)
data['total_capacity_gb'] = self.total_gb
data['free_capacity_gb'] = self.free_gb
def _refresh_capacity_info(self):
"""Gets the latest capacity information."""
LOG.info(_("Refreshing capacity info for %s."), self.client)
total_bytes = 0
free_bytes = 0
vols = self._get_filer_volumes()
for vol in vols:
volume = vol.get_child_content('name')
if self.volume_list and volume not in self.volume_list:
continue
state = vol.get_child_content('state')
inconsistent = vol.get_child_content('is-inconsistent')
invalid = vol.get_child_content('is-invalid')
if (state == 'online' and inconsistent == 'false'
and invalid == 'false'):
total_size = vol.get_child_content('size-total')
if total_size:
total_bytes = total_bytes + int(total_size)
avl_size = vol.get_child_content('size-available')
if avl_size:
free_bytes = free_bytes + int(avl_size)
self.total_gb = total_bytes / units.Gi
self.free_gb = free_bytes / units.Gi
def delete_volume(self, volume):
"""Driver entry point for destroying existing volumes."""

View File

@ -23,6 +23,7 @@ from threading import Timer
import time
import uuid
import six
import six.moves.urllib.parse as urlparse
from cinder import exception
@ -45,9 +46,9 @@ from cinder.volume.drivers.netapp.options import netapp_transport_opts
from cinder.volume.drivers.netapp import ssc_utils
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume.drivers.netapp.utils import get_volume_extra_specs
from cinder.volume.drivers.netapp.utils import provide_ems
from cinder.volume.drivers.netapp.utils import validate_instantiation
from cinder.volume.drivers import nfs
from cinder.volume import utils as volume_utils
LOG = logging.getLogger(__name__)
@ -81,6 +82,14 @@ class NetAppNFSDriver(nfs.NfsDriver):
"""Returns an error if prerequisites aren't met."""
raise NotImplementedError()
def get_pool(self, volume):
"""Return pool name where volume resides.
:param volume: The volume hosted by the driver.
:return: Name of the pool where given volume is hosted.
"""
return volume['provider_location']
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
vol_size = volume.size
@ -217,8 +226,7 @@ class NetAppNFSDriver(nfs.NfsDriver):
def _update_volume_stats(self):
"""Retrieve stats info from volume group."""
super(NetAppNFSDriver, self)._update_volume_stats()
self._spawn_clean_cache_job()
raise NotImplementedError()
def copy_image_to_volume(self, context, volume, image_service, image_id):
"""Fetch the image from image_service and write it to the volume."""
@ -719,6 +727,23 @@ class NetAppDirectNfsDriver (NetAppNFSDriver):
'file-usage-get', **{'path': path})
return file_use
def _get_extended_capacity_info(self, nfs_share):
"""Returns an extended set of share capacity metrics."""
total_size, total_available, total_allocated = \
self._get_capacity_info(nfs_share)
used_ratio = (total_size - total_available) / total_size
subscribed_ratio = total_allocated / total_size
apparent_size = max(0, total_size * self.configuration.nfs_used_ratio)
apparent_available = max(0, apparent_size - total_allocated)
return {'total_size': total_size, 'total_available': total_available,
'total_allocated': total_allocated, 'used_ratio': used_ratio,
'subscribed_ratio': subscribed_ratio,
'apparent_size': apparent_size,
'apparent_available': apparent_available}
class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
"""Executes commands related to volumes on c mode."""
@ -773,34 +798,39 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
:param volume: volume reference
"""
LOG.debug('create_volume on %s' % volume['host'])
self._ensure_shares_mounted()
# get share as pool name
share = volume_utils.extract_host(volume['host'], level='pool')
if share is None:
msg = _("Pool is not available in the volume host field.")
raise exception.InvalidHost(reason=msg)
extra_specs = get_volume_extra_specs(volume)
qos_policy_group = None
if extra_specs:
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None)
eligible = self._find_shares(volume['size'], extra_specs)
if not eligible:
raise exception.NfsNoSuitableShareFound(
volume_size=volume['size'])
for sh in eligible:
try:
volume['provider_location'] = sh
LOG.info(_('casted to %s') % volume['provider_location'])
self._do_create_volume(volume)
if qos_policy_group:
self._set_qos_policy_group_on_volume(volume, sh,
qos_policy_group)
return {'provider_location': volume['provider_location']}
except Exception as ex:
LOG.error(_("Exception creating vol %(name)s on "
"share %(share)s. Details: %(ex)s")
% {'name': volume['name'],
'share': volume['provider_location'],
'ex': ex})
volume['provider_location'] = None
finally:
if self.ssc_enabled:
self._update_stale_vols(self._get_vol_for_share(sh))
qos_policy_group = extra_specs.pop('netapp:qos_policy_group', None) \
if extra_specs else None
try:
volume['provider_location'] = share
LOG.info(_('casted to %s') % volume['provider_location'])
self._do_create_volume(volume)
if qos_policy_group:
self._set_qos_policy_group_on_volume(volume, share,
qos_policy_group)
return {'provider_location': volume['provider_location']}
except Exception as ex:
LOG.error(_("Exception creating vol %(name)s on "
"share %(share)s. Details: %(ex)s")
% {'name': volume['name'],
'share': volume['provider_location'],
'ex': ex})
volume['provider_location'] = None
finally:
if self.ssc_enabled:
self._update_stale_vols(self._get_vol_for_share(share))
msg = _("Volume %s could not be created on shares.")
raise exception.VolumeBackendAPIException(data=msg % (volume['name']))
@ -817,23 +847,6 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
'vserver': self.vserver})
self._invoke_successfully(file_assign_qos)
def _find_shares(self, size, extra_specs):
"""Finds suitable shares for given params."""
shares = []
containers = []
if self.ssc_enabled:
vols = ssc_utils.get_volumes_for_specs(self.ssc_vols, extra_specs)
containers = [x.export['path'] for x in vols]
else:
containers = self._mounted_shares
for sh in containers:
if self._is_share_eligible(sh, size):
size, avl, alloc = self._get_capacity_info(sh)
shares.append((sh, avl))
shares = [a for a, b in sorted(
shares, key=lambda x: x[1], reverse=True)]
return shares
def _clone_volume(self, volume_name, clone_name,
volume_id, share=None):
"""Clones mounted volume on NetApp Cluster."""
@ -927,58 +940,85 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
self._invoke_successfully(clone_create, vserver)
def _update_volume_stats(self):
"""Retrieve stats info from volume group."""
super(NetAppDirectCmodeNfsDriver, self)._update_volume_stats()
netapp_backend = 'NetApp_NFS_cluster_direct'
backend_name = self.configuration.safe_get('volume_backend_name')
self._stats["volume_backend_name"] = (backend_name or
netapp_backend)
self._stats["vendor_name"] = 'NetApp'
self._stats["driver_version"] = '1.0'
self._update_cluster_vol_stats(self._stats)
provide_ems(self, self._client, self._stats, netapp_backend)
"""Retrieve stats info from vserver."""
def _update_cluster_vol_stats(self, data):
"""Updates vol stats with cluster config."""
if self.ssc_enabled:
sync = True if self.ssc_vols is None else False
ssc_utils.refresh_cluster_ssc(self, self._client, self.vserver,
synchronous=sync)
else:
LOG.warn(_("No vserver set in config. SSC will be disabled."))
if self.ssc_vols:
data['netapp_mirrored'] = 'true'\
if self.ssc_vols['mirrored'] else 'false'
data['netapp_unmirrored'] = 'true'\
if len(self.ssc_vols['all']) >\
len(self.ssc_vols['mirrored']) else 'false'
data['netapp_dedup'] = 'true'\
if self.ssc_vols['dedup'] else 'false'
data['netapp_nodedup'] = 'true'\
if len(self.ssc_vols['all']) >\
len(self.ssc_vols['dedup']) else 'false'
data['netapp_compression'] = 'true'\
if self.ssc_vols['compression'] else 'false'
data['netapp_nocompression'] = 'true'\
if len(self.ssc_vols['all']) >\
len(self.ssc_vols['compression']) else 'false'
data['netapp_thin_provisioned'] = 'true'\
if self.ssc_vols['thin'] else 'false'
data['netapp_thick_provisioned'] = 'true'\
if len(self.ssc_vols['all']) >\
len(self.ssc_vols['thin']) else 'false'
if self.ssc_vols['all']:
vol_max = max(self.ssc_vols['all'])
data['total_capacity_gb'] =\
int(vol_max.space['size_total_bytes']) / units.Gi
data['free_capacity_gb'] =\
int(vol_max.space['size_avl_bytes']) / units.Gi
else:
data['total_capacity_gb'] = 0
data['free_capacity_gb'] = 0
elif self.ssc_enabled:
LOG.warn(_("No cluster ssc stats found."
" Wait for next volume stats update."))
self._ensure_shares_mounted()
sync = True if self.ssc_vols is None else False
ssc_utils.refresh_cluster_ssc(self, self._client,
self.vserver, synchronous=sync)
LOG.debug('Updating volume stats')
data = {}
netapp_backend = 'NetApp_NFS_Cluster_direct'
backend_name = self.configuration.safe_get('volume_backend_name')
data['volume_backend_name'] = backend_name or netapp_backend
data['vendor_name'] = 'NetApp'
data['driver_version'] = self.VERSION
data['storage_protocol'] = 'nfs'
data['pools'] = self._get_pool_stats()
self._spawn_clean_cache_job()
na_utils.provide_ems(self, self._client, data, netapp_backend)
self._stats = data
def _get_pool_stats(self):
"""Retrieve pool (i.e. NFS share) stats info from SSC volumes."""
pools = []
for nfs_share in self._mounted_shares:
capacity = self._get_extended_capacity_info(nfs_share)
pool = dict()
pool['pool_name'] = nfs_share
pool['QoS_support'] = False
pool['reserved_percentage'] = 0
# Report pool as reserved when over the configured used_ratio
if capacity['used_ratio'] > self.configuration.nfs_used_ratio:
pool['reserved_percentage'] = 100
# Report pool as reserved when over the subscribed ratio
if capacity['subscribed_ratio'] >=\
self.configuration.nfs_oversub_ratio:
pool['reserved_percentage'] = 100
# convert sizes to GB
total = float(capacity['apparent_size']) / units.Gi
pool['total_capacity_gb'] = na_utils.round_down(total, '0.01')
free = float(capacity['apparent_available']) / units.Gi
pool['free_capacity_gb'] = na_utils.round_down(free, '0.01')
# add SSC content if available
vol = self._get_vol_for_share(nfs_share)
if vol and self.ssc_vols:
pool['netapp:raid_type'] = vol.aggr['raid_type']
pool['netapp:disk_type'] = vol.aggr['disk_type']
pool['netapp:qos_policy_group'] = vol.qos['qos_policy_group']
mirrored = vol in self.ssc_vols['mirrored']
pool['netapp_mirrored'] = six.text_type(mirrored).lower()
pool['netapp_unmirrored'] = six.text_type(not mirrored).lower()
dedup = vol in self.ssc_vols['dedup']
pool['netapp_dedup'] = six.text_type(dedup).lower()
pool['netapp_nodedup'] = six.text_type(not dedup).lower()
compression = vol in self.ssc_vols['compression']
pool['netapp_compression'] = six.text_type(compression).lower()
pool['netapp_nocompression'] = six.text_type(
not compression).lower()
thin = vol in self.ssc_vols['thin']
pool['netapp_thin_provisioned'] = six.text_type(thin).lower()
pool['netapp_thick_provisioned'] = six.text_type(
not thin).lower()
pools.append(pool)
return pools
@utils.synchronized('update_stale')
def _update_stale_vols(self, volume=None, reset=False):
@ -1323,6 +1363,39 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
result = server.invoke_successfully(na_element, True)
return result
def create_volume(self, volume):
"""Creates a volume.
:param volume: volume reference
"""
LOG.debug('create_volume on %s' % volume['host'])
self._ensure_shares_mounted()
# get share as pool name
share = volume_utils.extract_host(volume['host'], level='pool')
if share is None:
msg = _("Pool is not available in the volume host field.")
raise exception.InvalidHost(reason=msg)
volume['provider_location'] = share
LOG.info(_('Creating volume at location %s')
% volume['provider_location'])
try:
self._do_create_volume(volume)
except Exception as ex:
LOG.error(_("Exception creating vol %(name)s on "
"share %(share)s. Details: %(ex)s")
% {'name': volume['name'],
'share': volume['provider_location'],
'ex': six.text_type(ex)})
msg = _("Volume %s could not be created on shares.")
raise exception.VolumeBackendAPIException(
data=msg % (volume['name']))
return {'provider_location': volume['provider_location']}
def _clone_volume(self, volume_name, clone_name,
volume_id, share=None):
"""Clones mounted volume with NetApp filer."""
@ -1420,16 +1493,58 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
retry = retry - 1
def _update_volume_stats(self):
"""Retrieve stats info from volume group."""
super(NetAppDirect7modeNfsDriver, self)._update_volume_stats()
"""Retrieve stats info from vserver."""
self._ensure_shares_mounted()
LOG.debug('Updating volume stats')
data = {}
netapp_backend = 'NetApp_NFS_7mode_direct'
backend_name = self.configuration.safe_get('volume_backend_name')
self._stats["volume_backend_name"] = (backend_name or
'NetApp_NFS_7mode_direct')
self._stats["vendor_name"] = 'NetApp'
self._stats["driver_version"] = self.VERSION
provide_ems(self, self._client, self._stats, netapp_backend,
server_type="7mode")
data['volume_backend_name'] = backend_name or netapp_backend
data['vendor_name'] = 'NetApp'
data['driver_version'] = self.VERSION
data['storage_protocol'] = 'nfs'
data['pools'] = self._get_pool_stats()
self._spawn_clean_cache_job()
na_utils.provide_ems(self, self._client, data, netapp_backend,
server_type="7mode")
self._stats = data
def _get_pool_stats(self):
"""Retrieve pool (i.e. NFS share) stats info from SSC volumes."""
pools = []
for nfs_share in self._mounted_shares:
capacity = self._get_extended_capacity_info(nfs_share)
pool = dict()
pool['pool_name'] = nfs_share
pool['QoS_support'] = False
pool['reserved_percentage'] = 0
# Report pool as reserved when over the configured used_ratio
if capacity['used_ratio'] > self.configuration.nfs_used_ratio:
pool['reserved_percentage'] = 100
# Report pool as reserved when over the subscribed ratio
if capacity['subscribed_ratio'] >=\
self.configuration.nfs_oversub_ratio:
pool['reserved_percentage'] = 100
# convert sizes to GB
total = float(capacity['apparent_size']) / units.Gi
pool['total_capacity_gb'] = na_utils.round_down(total, '0.01')
free = float(capacity['apparent_available']) / units.Gi
pool['free_capacity_gb'] = na_utils.round_down(free, '0.01')
pools.append(pool)
return pools
def _shortlist_del_eligible_files(self, share, old_files):
"""Prepares list of eligible files to be deleted from cache."""

View File

@ -20,6 +20,8 @@ Storage service catalog utility functions and classes for NetApp systems.
import copy
from threading import Timer
import six
from cinder import exception
from cinder.i18n import _
from cinder.openstack.common import log as logging
@ -240,7 +242,7 @@ def create_vol_list(vol_attrs):
vols.add(vol)
except KeyError as e:
LOG.debug('Unexpected error while creating'
' ssc vol list. Message - %s' % (e.message))
' ssc vol list. Message - %s' % six.text_type(e))
continue
return vols

View File

@ -23,9 +23,12 @@ NetApp drivers to achieve the desired functionality.
import base64
import binascii
import copy
import decimal
import socket
import uuid
import six
from cinder import context
from cinder import exception
from cinder.i18n import _
@ -356,3 +359,8 @@ def convert_es_fmt_to_uuid(es_label):
"""Converts e-series name format to uuid."""
es_label_b32 = es_label.ljust(32, '=')
return uuid.UUID(binascii.hexlify(base64.b32decode(es_label_b32)))
def round_down(value, precision):
return float(decimal.Decimal(six.text_type(value)).quantize(
decimal.Decimal(precision), rounding=decimal.ROUND_DOWN))