mox.StubOutWithMock(drv, '_delete_files_till_bytes_free')
mox.StubOutWithMock(drv, '_get_capacity_info')
- drv._get_capacity_info('testshare').AndReturn((100, 19, 81))
+ drv._get_capacity_info('testshare').AndReturn((100, 19))
drv._find_old_cache_files('testshare').AndReturn(['f1', 'f2'])
drv._delete_files_till_bytes_free(
['f1', 'f2'], 'testshare', bytes_to_free=31)
# Copyright (c) 2014 Alex Meade. All rights reserved.
+# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
wwpns = self.client.get_fc_target_wwpns()
self.assertSetEqual(set(wwpns), set([wwpn1, wwpn2]))
+
+ def test_get_flexvol_capacity(self):
+ expected_total_bytes = 1000
+ expected_available_bytes = 750
+ fake_flexvol_path = '/fake/vol'
+ response = netapp_api.NaElement(
+ etree.XML("""
+ <results status="passed">
+ <volumes>
+ <volume-info>
+ <size-total>%(total_bytes)s</size-total>
+ <size-available>%(available_bytes)s</size-available>
+ </volume-info>
+ </volumes>
+ </results>""" % {'total_bytes': expected_total_bytes,
+ 'available_bytes': expected_available_bytes}))
+ self.connection.invoke_successfully.return_value = response
+
+ total_bytes, available_bytes = (
+ self.client.get_flexvol_capacity(fake_flexvol_path))
+
+ self.assertEqual(expected_total_bytes, total_bytes)
+ self.assertEqual(expected_available_bytes, available_bytes)
# Copyright (c) 2014 Alex Meade.
+# Copyright (c) 2015 Dustin Schoenbrun.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
self.client.get_operational_network_interface_addresses())
self.assertEqual(expected_result, address_list)
+
+ def test_get_flexvol_capacity(self):
+ expected_total_size = 1000
+ expected_available_size = 750
+ fake_flexvol_path = '/fake/vol'
+ response = netapp_api.NaElement(
+ etree.XML("""
+ <results status="passed">
+ <attributes-list>
+ <volume-attributes>
+ <volume-space-attributes>
+ <size-available>%(available_size)s</size-available>
+ <size-total>%(total_size)s</size-total>
+ </volume-space-attributes>
+ </volume-attributes>
+ </attributes-list>
+ </results>""" % {'available_size': expected_available_size,
+ 'total_size': expected_total_size}))
+ self.connection.invoke_successfully.return_value = response
+
+ total_size, available_size = (
+ self.client.get_flexvol_capacity(fake_flexvol_path))
+
+ self.assertEqual(expected_total_size, total_size)
+ self.assertEqual(expected_available_size, available_size)
{'address': '1.2.3.4', 'port': '3260'},
{'address': '99.98.97.96', 'port': '3260'},
]
+
+HOSTNAME = 'fake.host.com'
+IPV4_ADDRESS = '192.168.14.2'
+IPV6_ADDRESS = 'fe80::6e40:8ff:fe8a:130'
+EXPORT_PATH = '/fake/export/path'
+NFS_SHARE = HOSTNAME + ':' + EXPORT_PATH
+NFS_SHARE_IPV4 = IPV4_ADDRESS + ':' + EXPORT_PATH
+NFS_SHARE_IPV6 = IPV6_ADDRESS + ':' + EXPORT_PATH
+
+RESERVED_PERCENTAGE = 7
+TOTAL_BYTES = 4797892092432
+AVAILABLE_BYTES = 13479932478
+CAPACITY_VALUES = (TOTAL_BYTES, AVAILABLE_BYTES)
--- /dev/null
+# Copyright (c) 2015 Tom Barron. 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 7mode nfs storage driver
+"""
+
+import mock
+from oslo_utils import units
+
+from cinder.brick.remotefs import remotefs as remotefs_brick
+from cinder import test
+from cinder.tests.volume.drivers.netapp.dataontap import fakes as fake
+from cinder.tests.volume.drivers.netapp import fakes as na_fakes
+from cinder import utils
+from cinder.volume.drivers.netapp.dataontap import nfs_7mode
+from cinder.volume.drivers.netapp import utils as na_utils
+
+
+class NetApp7modeNfsDriverTestCase(test.TestCase):
+ def setUp(self):
+ super(NetApp7modeNfsDriverTestCase, self).setUp()
+
+ kwargs = {'configuration': self.get_config_7mode()}
+
+ with mock.patch.object(utils, 'get_root_helper',
+ return_value=mock.Mock()):
+ with mock.patch.object(remotefs_brick, 'RemoteFsClient',
+ return_value=mock.Mock()):
+ self.driver = nfs_7mode.NetApp7modeNfsDriver(**kwargs)
+ self.driver._mounted_shares = [fake.NFS_SHARE]
+ self.driver.ssc_vols = True
+
+ def get_config_7mode(self):
+ config = na_fakes.create_configuration_cmode()
+ config.netapp_storage_protocol = 'nfs'
+ config.netapp_login = 'root'
+ config.netapp_password = 'pass'
+ config.netapp_server_hostname = '127.0.0.1'
+ config.netapp_transport_type = 'http'
+ config.netapp_server_port = '80'
+ return config
+
+ def test_get_pool_stats(self):
+
+ total_capacity_gb = na_utils.round_down(
+ fake.TOTAL_BYTES / units.Gi, '0.01')
+ free_capacity_gb = na_utils.round_down(
+ fake.AVAILABLE_BYTES / units.Gi, '0.01')
+ capacity = dict(
+ reserved_percentage = fake.RESERVED_PERCENTAGE,
+ total_capacity_gb = total_capacity_gb,
+ free_capacity_gb = free_capacity_gb,
+ )
+
+ mock_get_capacity = self.mock_object(
+ self.driver, '_get_share_capacity_info')
+ mock_get_capacity.return_value = capacity
+
+ result = self.driver._get_pool_stats()
+
+ self.assertEqual(fake.RESERVED_PERCENTAGE,
+ result[0]['reserved_percentage'])
+ self.assertEqual(total_capacity_gb, result[0]['total_capacity_gb'])
+ self.assertEqual(free_capacity_gb, result[0]['free_capacity_gb'])
"""
import mock
+from oslo_utils import units
from cinder.brick.remotefs import remotefs as remotefs_brick
from cinder import test
+from cinder.tests.volume.drivers.netapp.dataontap import fakes as fake
from cinder import utils
from cinder.volume.drivers.netapp.dataontap import nfs_base
from cinder.volume.drivers.netapp import utils as na_utils
super(NetAppNfsDriverTestCase, self).setUp()
configuration = mock.Mock()
configuration.nfs_mount_point_base = '/mnt/test'
+ configuration.nfs_used_ratio = 0.89
+ configuration.nfs_oversub_ratio = 3.0
kwargs = {'configuration': configuration}
self.assertTrue(mock_check_flags.called)
self.assertTrue(mock_super_do_setup.called)
+
+ def test_get_share_capacity_info(self):
+ mock_get_capacity = self.mock_object(self.driver, '_get_capacity_info')
+ mock_get_capacity.return_value = fake.CAPACITY_VALUES
+ expected_total_capacity_gb = (na_utils.round_down(
+ (fake.TOTAL_BYTES *
+ self.driver.configuration.nfs_oversub_ratio) /
+ units.Gi, '0.01'))
+ expected_free_capacity_gb = (na_utils.round_down(
+ (fake.AVAILABLE_BYTES *
+ self.driver.configuration.nfs_oversub_ratio) /
+ units.Gi, '0.01'))
+ expected_reserved_percentage = round(
+ 100 * (1 - self.driver.configuration.nfs_used_ratio))
+
+ result = self.driver._get_share_capacity_info(fake.NFS_SHARE)
+
+ self.assertEqual(expected_total_capacity_gb,
+ result['total_capacity_gb'])
+ self.assertEqual(expected_free_capacity_gb,
+ result['free_capacity_gb'])
+ self.assertEqual(expected_reserved_percentage,
+ result['reserved_percentage'])
+
+ def test_get_capacity_info_ipv4_share(self):
+ expected = fake.CAPACITY_VALUES
+ self.driver.zapi_client = mock.Mock()
+ get_capacity = self.driver.zapi_client.get_flexvol_capacity
+ get_capacity.return_value = fake.CAPACITY_VALUES
+
+ result = self.driver._get_capacity_info(fake.NFS_SHARE_IPV4)
+
+ self.assertEqual(expected, result)
+ get_capacity.assert_has_calls([
+ mock.call(fake.EXPORT_PATH)])
+
+ def test_get_capacity_info_ipv6_share(self):
+ expected = fake.CAPACITY_VALUES
+ self.driver.zapi_client = mock.Mock()
+ get_capacity = self.driver.zapi_client.get_flexvol_capacity
+ get_capacity.return_value = fake.CAPACITY_VALUES
+
+ result = self.driver._get_capacity_info(fake.NFS_SHARE_IPV6)
+
+ self.assertEqual(expected, result)
+ get_capacity.assert_has_calls([
+ mock.call(fake.EXPORT_PATH)])
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
-# All Rights Reserved.
+# Copyright (c) 2015 Tom Barron. 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 mock
+from oslo_utils import units
from cinder.brick.remotefs import remotefs as remotefs_brick
from cinder import test
+from cinder.tests.volume.drivers.netapp.dataontap import fakes as fake
from cinder.tests.volume.drivers.netapp import fakes as na_fakes
from cinder import utils
from cinder.volume.drivers.netapp.dataontap.client import client_cmode
with mock.patch.object(remotefs_brick, 'RemoteFsClient',
return_value=mock.Mock()):
self.driver = nfs_cmode.NetAppCmodeNfsDriver(**kwargs)
+ self.driver._mounted_shares = [fake.NFS_SHARE]
+ self.driver.ssc_vols = True
def get_config_cmode(self):
config = na_fakes.create_configuration_cmode()
self.assertTrue(mock_check_flags.called)
self.assertTrue(mock_super_do_setup.called)
+
+ def test_get_pool_stats(self):
+
+ total_capacity_gb = na_utils.round_down(
+ fake.TOTAL_BYTES / units.Gi, '0.01')
+ free_capacity_gb = na_utils.round_down(
+ fake.AVAILABLE_BYTES / units.Gi, '0.01')
+ capacity = dict(
+ reserved_percentage = fake.RESERVED_PERCENTAGE,
+ total_capacity_gb = total_capacity_gb,
+ free_capacity_gb = free_capacity_gb,
+ )
+
+ mock_get_capacity = self.mock_object(
+ self.driver, '_get_share_capacity_info')
+ mock_get_capacity.return_value = capacity
+
+ mock_get_vol_for_share = self.mock_object(
+ self.driver, '_get_vol_for_share')
+ mock_get_vol_for_share.return_value = None
+
+ result = self.driver._get_pool_stats()
+
+ self.assertEqual(fake.RESERVED_PERCENTAGE,
+ result[0]['reserved_percentage'])
+ self.assertEqual(total_capacity_gb, result[0]['total_capacity_gb'])
+ self.assertEqual(free_capacity_gb, result[0]['free_capacity_gb'])
def get_ifconfig(self):
ifconfig = netapp_api.NaElement('net-ifconfig-get')
return self.connection.invoke_successfully(ifconfig)
+
+ def get_flexvol_capacity(self, flexvol_path):
+ """Gets total capacity and free capacity, in bytes, of the flexvol."""
+
+ api_args = {'volume': flexvol_path, 'verbose': 'false'}
+
+ result = self.send_request('volume-list-info', api_args)
+
+ flexvol_info_list = result.get_child_by_name('volumes')
+ flexvol_info = flexvol_info_list.get_children()[0]
+
+ total_bytes = float(
+ flexvol_info.get_child_content('size-total'))
+ available_bytes = float(
+ flexvol_info.get_child_content('size-available'))
+
+ return total_bytes, available_bytes
return [lif_info.get_child_content('address') for lif_info in
lif_info_list.get_children()]
+
+ def get_flexvol_capacity(self, flexvol_path):
+ """Gets total capacity and free capacity, in bytes, of the flexvol."""
+
+ api_args = {
+ 'query': {
+ 'volume-attributes': {
+ 'volume-id-attributes': {
+ 'junction-path': flexvol_path
+ }
+ }
+ },
+ 'desired-attributes': {
+ 'volume-attributes': {
+ 'volume-space-attributes': {
+ 'size-available': None,
+ 'size-total': None,
+ }
+ }
+ },
+ }
+
+ result = self.send_request('volume-get-iter', api_args)
+
+ attributes_list = result.get_child_by_name('attributes-list')
+ volume_attributes = attributes_list.get_child_by_name(
+ 'volume-attributes')
+ volume_space_attributes = volume_attributes.get_child_by_name(
+ 'volume-space-attributes')
+
+ size_available = float(
+ volume_space_attributes.get_child_content('size-available'))
+ size_total = float(
+ volume_space_attributes.get_child_content('size-total'))
+
+ return size_total, size_available
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2014 Bob Callaway. All rights reserved.
+# Copyright (c) 2015 Tom Barron. 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
"""
from oslo_log import log as logging
-from oslo_utils import units
import six
from cinder import exception
for nfs_share in self._mounted_shares:
- capacity = self._get_extended_capacity_info(nfs_share)
+ capacity = self._get_share_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')
+ pool.update(capacity)
pools.append(pool)
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2014 Bob Callaway. All rights reserved.
+# Copyright (c) 2015 Tom Barron. 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
super(NetAppNfsDriver, self).do_setup(context)
self._context = context
na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)
+ self.zapi_client = None
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self.configuration.thres_avl_size_perc_stop
for share in getattr(self, '_mounted_shares', []):
try:
- total_size, total_avl, _total_alc = \
+ total_size, total_avl = \
self._get_capacity_info(share)
avl_percent = int((total_avl / total_size) * 100)
if avl_percent <= thres_size_perc_start:
def _check_share_can_hold_size(self, share, size):
"""Checks if volume can hold image with size."""
- _tot_size, tot_available, _tot_allocated = self._get_capacity_info(
+ _tot_size, tot_available = self._get_capacity_info(
share)
if tot_available < size:
msg = _("Container size smaller than required file size.")
'A volume ID or share was not specified.')
return host_ip, export_path
- def _get_extended_capacity_info(self, nfs_share):
- """Returns an extended set of share capacity metrics."""
+ def _get_share_capacity_info(self, nfs_share):
+ """Returns the share capacity metrics needed by the scheduler."""
- total_size, total_available, total_allocated = \
- self._get_capacity_info(nfs_share)
+ used_ratio = self.configuration.nfs_used_ratio
+ oversub_ratio = self.configuration.nfs_oversub_ratio
- 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)
+ # The scheduler's capacity filter will reduce the amount of
+ # free space that we report to it by the reserved percentage.
+ reserved_ratio = 1 - used_ratio
+ reserved_percentage = round(100 * reserved_ratio)
- 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}
+ total_size, total_available = self._get_capacity_info(nfs_share)
+
+ apparent_size = total_size * oversub_ratio
+ apparent_size_gb = na_utils.round_down(
+ apparent_size / units.Gi, '0.01')
+
+ apparent_free_size = total_available * oversub_ratio
+ apparent_free_gb = na_utils.round_down(
+ float(apparent_free_size) / units.Gi, '0.01')
+
+ capacity = dict()
+ capacity['reserved_percentage'] = reserved_percentage
+ capacity['total_capacity_gb'] = apparent_size_gb
+ capacity['free_capacity_gb'] = apparent_free_gb
+
+ return capacity
+
+ def _get_capacity_info(self, nfs_share):
+ """Get total capacity and free capacity in bytes for an nfs share."""
+ export_path = nfs_share.rsplit(':', 1)[1]
+ return self.zapi_client.get_flexvol_capacity(export_path)
def _check_volume_type(self, volume, share, file_name):
"""Match volume type for share file."""
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2014 Bob Callaway. All rights reserved.
+# Copyright (c) 2015 Tom Barron. 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 uuid
from oslo_log import log as logging
-from oslo_utils import units
import six
from cinder import exception
for nfs_share in self._mounted_shares:
- capacity = self._get_extended_capacity_info(nfs_share)
+ capacity = self._get_share_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')
+ pool.update(capacity)
# add SSC content if available
vol = self._get_vol_for_share(nfs_share)