Support Manila pools in NetApp Clustered Data ONTAP driver
The Cinder pools feature is being ported to Manila, and drivers must be updated to report pools in order to use this feature. This patch adds support for pools to the NetApp Clustered Data ONTAP driver for Manila. Implements blueprint support-pools-in-netapp-cdot-driver Change-Id: Iba327a745227e6219e6e1fc77af407f097905d8f
This commit is contained in:
parent
d361c666b5
commit
dc2aa75e7c
|
@ -149,6 +149,10 @@ class InvalidContentType(Invalid):
|
|||
message = _("Invalid content type %(content_type)s.")
|
||||
|
||||
|
||||
class InvalidHost(Invalid):
|
||||
message = _("Invalid host: %(reason)s")
|
||||
|
||||
|
||||
# Cannot be templated as the error syntax varies.
|
||||
# msg needs to be constructed when raised.
|
||||
class InvalidParameterValue(Invalid):
|
||||
|
|
|
@ -127,8 +127,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||
|
||||
try:
|
||||
root_volume_name = vserver_info.get_child_by_name(
|
||||
'attributes-list').get_child_by_name('vserver-info')\
|
||||
.get_child_content('root-volume')
|
||||
'attributes-list').get_child_by_name(
|
||||
'vserver-info').get_child_content('root-volume')
|
||||
except AttributeError:
|
||||
msg = _('Could not determine root volume name '
|
||||
'for Vserver %s.') % vserver_name
|
||||
|
@ -264,9 +264,9 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||
}
|
||||
port_info = self.send_request('net-port-get-iter', api_args)
|
||||
try:
|
||||
port = port_info.get_child_by_name('attributes-list')\
|
||||
.get_child_by_name('net-port-info')\
|
||||
.get_child_content('port')
|
||||
port = port_info.get_child_by_name(
|
||||
'attributes-list').get_child_by_name(
|
||||
'net-port-info').get_child_content('port')
|
||||
except AttributeError:
|
||||
msg = _("Data port does not exist for node %s.")
|
||||
raise exception.NetAppException(msg % node)
|
||||
|
@ -414,35 +414,48 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||
self.send_request('net-interface-delete', api_args)
|
||||
|
||||
@na_utils.trace
|
||||
def calculate_aggregate_capacity(self, aggregate_names):
|
||||
"""Calculates capacity of one or more aggregates
|
||||
def get_cluster_aggregate_capacities(self, aggregate_names):
|
||||
"""Calculates capacity of one or more aggregates.
|
||||
|
||||
Returns tuple (total, free) in bytes.
|
||||
Returns dictionary of aggregate capacity metrics.
|
||||
'size-used' is the actual space consumed on the aggregate.
|
||||
'size-available' is the actual space remaining.
|
||||
'size-total' is the defined total aggregate size, such that
|
||||
used + available = total.
|
||||
"""
|
||||
desired_attributes = {
|
||||
'aggr-attributes': {
|
||||
'aggregate-name': None,
|
||||
'aggr-space-attributes': {
|
||||
'size-total': None,
|
||||
'size-available': None,
|
||||
'size-total': None,
|
||||
'size-used': None,
|
||||
},
|
||||
},
|
||||
}
|
||||
aggrs = self._get_aggregates(aggregate_names=aggregate_names,
|
||||
desired_attributes=desired_attributes)
|
||||
aggr_space_attrs = [aggr.get_child_by_name('aggr-space-attributes')
|
||||
for aggr in aggrs]
|
||||
total = sum([int(aggr.get_child_content('size-total'))
|
||||
for aggr in aggr_space_attrs]) if aggr_space_attrs else 0
|
||||
free = max([int(aggr.get_child_content('size-available'))
|
||||
for aggr in aggr_space_attrs]) if aggr_space_attrs else 0
|
||||
return total, free
|
||||
aggr_space_dict = dict()
|
||||
for aggr in aggrs:
|
||||
aggr_name = aggr.get_child_content('aggregate-name')
|
||||
aggr_space_attrs = aggr.get_child_by_name('aggr-space-attributes')
|
||||
|
||||
aggr_space_dict[aggr_name] = {
|
||||
'available':
|
||||
int(aggr_space_attrs.get_child_content('size-available')),
|
||||
'total':
|
||||
int(aggr_space_attrs.get_child_content('size-total')),
|
||||
'used':
|
||||
int(aggr_space_attrs.get_child_content('size-used')),
|
||||
}
|
||||
return aggr_space_dict
|
||||
|
||||
@na_utils.trace
|
||||
def get_aggregates_for_vserver(self, vserver_name):
|
||||
"""Returns aggregate list and size info for a Vserver.
|
||||
def get_vserver_aggregate_capacities(self, vserver_name):
|
||||
"""Calculates capacity of one or more aggregates for a vserver.
|
||||
|
||||
Must be called against a Vserver API client.
|
||||
Returns dictionary of aggregate capacity metrics. This must
|
||||
be called against a Vserver LIF.
|
||||
"""
|
||||
LOG.debug('Finding available aggregates for Vserver %s', vserver_name)
|
||||
|
||||
|
@ -466,19 +479,19 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||
vserver_aggr_info_list = vserver_aggr_info_element.get_children()
|
||||
|
||||
if not vserver_aggr_info_list:
|
||||
msg = _("No aggregates assigned to Vserver %s")
|
||||
raise exception.NetAppException(msg % vserver_name)
|
||||
LOG.warning(_LW('No aggregates assigned to Vserver %s.'),
|
||||
vserver_name)
|
||||
|
||||
# Return dict of key-value pair of aggr_name:aggr_size_available.
|
||||
aggr_dict = {}
|
||||
aggr_space_dict = {}
|
||||
|
||||
for aggr_info in vserver_aggr_info_list:
|
||||
aggr_name = aggr_info.get_child_content('aggr-name')
|
||||
aggr_size = int(aggr_info.get_child_content('aggr-availsize'))
|
||||
aggr_dict[aggr_name] = aggr_size
|
||||
aggr_space_dict[aggr_name] = {'available': aggr_size}
|
||||
|
||||
LOG.debug('Found available aggregates: %s', aggr_dict)
|
||||
return aggr_dict
|
||||
LOG.debug('Found available Vserver aggregates: %s', aggr_space_dict)
|
||||
return aggr_space_dict
|
||||
|
||||
def _get_aggregates(self, aggregate_names=None, desired_attributes=None):
|
||||
|
||||
|
@ -701,6 +714,45 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||
result = self.send_request('volume-get-iter', api_args)
|
||||
return self._has_records(result)
|
||||
|
||||
@na_utils.trace
|
||||
def get_aggregate_for_volume(self, volume_name):
|
||||
"""Get the name of the aggregate containing a volume."""
|
||||
|
||||
api_args = {
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'name': volume_name,
|
||||
},
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'containing-aggregate-name': None,
|
||||
'name': None,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
result = self.send_request('volume-get-iter', api_args)
|
||||
|
||||
attributes_list = result.get_child_by_name(
|
||||
'attributes-list') or netapp_api.NaElement('none')
|
||||
volume_attributes = attributes_list.get_child_by_name(
|
||||
'volume-attributes') or netapp_api.NaElement('none')
|
||||
volume_id_attributes = volume_attributes.get_child_by_name(
|
||||
'volume-id-attributes') or netapp_api.NaElement('none')
|
||||
|
||||
aggregate = volume_id_attributes.get_child_content(
|
||||
'containing-aggregate-name')
|
||||
|
||||
if not aggregate:
|
||||
msg = _('Could not find aggregate for volume %s.')
|
||||
raise exception.NetAppException(msg % volume_name)
|
||||
|
||||
return aggregate
|
||||
|
||||
@na_utils.trace
|
||||
def create_volume_clone(self, volume_name, parent_volume_name,
|
||||
parent_snapshot_name=None):
|
||||
|
|
|
@ -40,6 +40,9 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
|
|||
def check_for_setup_error(self):
|
||||
self.library.check_for_setup_error()
|
||||
|
||||
def get_pool(self, share):
|
||||
return self.library.get_pool(share)
|
||||
|
||||
def create_share(self, context, share, **kwargs):
|
||||
return self.library.create_share(context, share, **kwargs)
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ from manila.share.drivers.netapp.dataontap.protocols import cifs_cmode
|
|||
from manila.share.drivers.netapp.dataontap.protocols import nfs_cmode
|
||||
from manila.share.drivers.netapp import options as na_opts
|
||||
from manila.share.drivers.netapp import utils as na_utils
|
||||
from manila.share import utils as share_utils
|
||||
from manila import utils
|
||||
|
||||
|
||||
|
@ -73,7 +74,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||
AUTOSUPPORT_INTERVAL_SECONDS = 3600 # hourly
|
||||
|
||||
def __init__(self, db, driver_name, **kwargs):
|
||||
na_utils.validate_instantiation(**kwargs)
|
||||
na_utils.validate_driver_instantiation(**kwargs)
|
||||
|
||||
self.db = db
|
||||
self.driver_name = driver_name
|
||||
|
@ -157,17 +158,39 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||
@na_utils.trace
|
||||
def get_share_stats(self):
|
||||
"""Retrieve stats info from Cluster Mode backend."""
|
||||
total, free = self._client.calculate_aggregate_capacity(
|
||||
aggr_space = self._client.get_cluster_aggregate_capacities(
|
||||
self._find_matching_aggregates())
|
||||
|
||||
data = dict(
|
||||
share_backend_name=self.backend_name,
|
||||
driver_name=self.driver_name,
|
||||
vendor_name='NetApp',
|
||||
driver_version='1.0',
|
||||
storage_protocol='NFS_CIFS',
|
||||
total_capacity_gb=(total / units.Gi),
|
||||
free_capacity_gb=(free / units.Gi))
|
||||
data = {
|
||||
'share_backend_name': self.backend_name,
|
||||
'driver_name': self.driver_name,
|
||||
'vendor_name': 'NetApp',
|
||||
'driver_version': '1.0',
|
||||
'storage_protocol': 'NFS_CIFS',
|
||||
'total_capacity_gb': 0.0,
|
||||
'free_capacity_gb': 0.0,
|
||||
}
|
||||
|
||||
pools = []
|
||||
for aggr_name in sorted(aggr_space.keys()):
|
||||
|
||||
total_capacity_gb = na_utils.round_down(
|
||||
float(aggr_space[aggr_name]['total']) / units.Gi, '0.01')
|
||||
free_capacity_gb = na_utils.round_down(
|
||||
float(aggr_space[aggr_name]['available']) / units.Gi, '0.01')
|
||||
allocated_capacity_gb = na_utils.round_down(
|
||||
float(aggr_space[aggr_name]['used']) / units.Gi, '0.01')
|
||||
|
||||
pools.append({
|
||||
'pool_name': aggr_name,
|
||||
'total_capacity_gb': total_capacity_gb,
|
||||
'free_capacity_gb': free_capacity_gb,
|
||||
'allocated_capacity_gb': allocated_capacity_gb,
|
||||
'QoS_support': 'False',
|
||||
'reserved_percentage': 0,
|
||||
})
|
||||
|
||||
data['pools'] = pools
|
||||
|
||||
self._handle_ems_logging()
|
||||
|
||||
|
@ -317,6 +340,15 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||
ip, netmask, vlan, node, port, vserver_name, allocation_id,
|
||||
self.configuration.netapp_lif_name_template)
|
||||
|
||||
@na_utils.trace
|
||||
def get_pool(self, share):
|
||||
pool = share_utils.extract_host(share['host'], level='pool')
|
||||
if pool:
|
||||
return pool
|
||||
|
||||
volume_name = self._get_valid_share_name(share['id'])
|
||||
return self._client.get_aggregate_for_volume(volume_name)
|
||||
|
||||
@ensure_vserver
|
||||
@na_utils.trace
|
||||
def create_share(self, context, share, share_server):
|
||||
|
@ -340,12 +372,17 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||
def _allocate_container(self, share, vserver, vserver_client):
|
||||
"""Create new share on aggregate."""
|
||||
share_name = self._get_valid_share_name(share['id'])
|
||||
aggregates = vserver_client.get_aggregates_for_vserver(vserver)
|
||||
aggregate = max(aggregates, key=lambda m: aggregates[m])
|
||||
|
||||
# Get Data ONTAP aggregate name as pool name.
|
||||
aggregate_name = share_utils.extract_host(share['host'], level='pool')
|
||||
|
||||
if aggregate_name is None:
|
||||
msg = _("Pool is not available in the share host field.")
|
||||
raise exception.InvalidHost(reason=msg)
|
||||
|
||||
LOG.debug('Creating volume %(share_name)s on aggregate %(aggregate)s',
|
||||
{'share_name': share_name, 'aggregate': aggregate})
|
||||
vserver_client.create_volume(aggregate, share_name, share['size'])
|
||||
{'share_name': share_name, 'aggregate': aggregate_name})
|
||||
vserver_client.create_volume(aggregate_name, share_name, share['size'])
|
||||
|
||||
@na_utils.trace
|
||||
def _allocate_container_from_snapshot(self, share, snapshot,
|
||||
|
|
|
@ -15,10 +15,12 @@
|
|||
# under the License.
|
||||
"""Utilities for NetApp drivers."""
|
||||
|
||||
import decimal
|
||||
import platform
|
||||
|
||||
from oslo_concurrency import processutils as putils
|
||||
from oslo_log import log
|
||||
import six
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _, _LI, _LW
|
||||
|
@ -32,6 +34,36 @@ TRACE_METHOD = False
|
|||
TRACE_API = False
|
||||
|
||||
|
||||
def check_flags(required_flags, configuration):
|
||||
"""Ensure that the flags we care about are set."""
|
||||
for flag in required_flags:
|
||||
if not getattr(configuration, flag, None):
|
||||
msg = _('Configuration value %s is not set.') % flag
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
|
||||
def validate_driver_instantiation(**kwargs):
|
||||
"""Checks if a driver is instantiated other than by the unified driver.
|
||||
|
||||
Helps check direct instantiation of netapp drivers.
|
||||
Call this function in every netapp block driver constructor.
|
||||
"""
|
||||
if kwargs and kwargs.get('netapp_mode') == 'proxy':
|
||||
return
|
||||
LOG.warning(_LW('Please use NetAppDriver in the configuration file '
|
||||
'to load the driver instead of directly specifying '
|
||||
'the driver module name.'))
|
||||
|
||||
|
||||
def round_down(value, precision):
|
||||
"""Round a number downward using a specified level of precision.
|
||||
|
||||
Example: round_down(float(total_space_in_bytes) / units.Gi, '0.01')
|
||||
"""
|
||||
return float(decimal.Decimal(six.text_type(value)).quantize(
|
||||
decimal.Decimal(precision), rounding=decimal.ROUND_DOWN))
|
||||
|
||||
|
||||
def setup_tracing(trace_flags_string):
|
||||
global TRACE_METHOD
|
||||
global TRACE_API
|
||||
|
@ -57,27 +89,6 @@ def trace(f):
|
|||
return trace_wrapper
|
||||
|
||||
|
||||
def check_flags(required_flags, configuration):
|
||||
"""Ensure that the flags we care about are set."""
|
||||
for flag in required_flags:
|
||||
if not getattr(configuration, flag, None):
|
||||
msg = _('Configuration value %s is not set.') % flag
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
|
||||
def validate_instantiation(**kwargs):
|
||||
"""Checks if a driver is instantiated other than by the unified driver.
|
||||
|
||||
Helps check direct instantiation of netapp drivers.
|
||||
Call this function in every netapp block driver constructor.
|
||||
"""
|
||||
if kwargs and kwargs.get('netapp_mode') == 'proxy':
|
||||
return
|
||||
LOG.warning(_LW('Please use NetAppDriver in the configuration file '
|
||||
'to load the driver instead of directly specifying '
|
||||
'the driver module name.'))
|
||||
|
||||
|
||||
class OpenStackInfo(object):
|
||||
"""OS/distribution, release, and version.
|
||||
|
||||
|
|
|
@ -123,7 +123,14 @@ VSERVER_DATA_LIST_RESPONSE = etree.XML("""
|
|||
</results>
|
||||
""" % {'vserver': VSERVER_NAME})
|
||||
|
||||
VSERVER_AGGREGATES = {'aggr0': 45678592, 'manila': 6448431104}
|
||||
VSERVER_AGGREGATES = {
|
||||
'aggr0': {
|
||||
'available': 45678592,
|
||||
},
|
||||
'manila': {
|
||||
'available': 6448431104,
|
||||
},
|
||||
}
|
||||
|
||||
VSERVER_GET_RESPONSE_NO_AGGREGATES = etree.XML("""
|
||||
<results status="passed">
|
||||
|
@ -577,8 +584,9 @@ AGGR_GET_SPACE_RESPONSE = etree.XML("""
|
|||
</plexes>
|
||||
</aggr-raid-attributes>
|
||||
<aggr-space-attributes>
|
||||
<size-available>45678592</size-available>
|
||||
<size-available>45670400</size-available>
|
||||
<size-total>943718400</size-total>
|
||||
<size-used>898048000</size-used>
|
||||
</aggr-space-attributes>
|
||||
<aggregate-name>aggr0</aggregate-name>
|
||||
</aggr-attributes>
|
||||
|
@ -599,8 +607,9 @@ AGGR_GET_SPACE_RESPONSE = etree.XML("""
|
|||
</plexes>
|
||||
</aggr-raid-attributes>
|
||||
<aggr-space-attributes>
|
||||
<size-available>6448435200</size-available>
|
||||
<size-available>4267659264</size-available>
|
||||
<size-total>7549747200</size-total>
|
||||
<size-used>3282087936</size-used>
|
||||
</aggr-space-attributes>
|
||||
<aggregate-name>manila</aggregate-name>
|
||||
</aggr-attributes>
|
||||
|
@ -965,3 +974,21 @@ NFS_EXPORTFS_LIST_RULES_2_RESPONSE = etree.XML("""
|
|||
'host1': NFS_EXPORT_RULES[0],
|
||||
'host2': NFS_EXPORT_RULES[1],
|
||||
})
|
||||
|
||||
GET_AGGREGATE_FOR_VOLUME_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
<volume-attributes>
|
||||
<volume-id-attributes>
|
||||
<containing-aggregate-name>%(aggr)s</containing-aggregate-name>
|
||||
<name>%(share)s</name>
|
||||
<owning-vserver-name>os_aa666789-5576-4835-87b7-868069856459</owning-vserver-name>
|
||||
</volume-id-attributes>
|
||||
</volume-attributes>
|
||||
</attributes-list>
|
||||
<num-records>1</num-records>
|
||||
</results>
|
||||
""" % {
|
||||
'aggr': SHARE_AGGREGATE_NAME,
|
||||
'share': SHARE_NAME
|
||||
})
|
||||
|
|
|
@ -81,11 +81,11 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
'desired-attributes': {'vserver-info': {'vserver-name': None}}
|
||||
}
|
||||
|
||||
response = self.client.vserver_exists(fake.VSERVER_NAME)
|
||||
result = self.client.vserver_exists(fake.VSERVER_NAME)
|
||||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('vserver-get-iter', vserver_get_args)])
|
||||
self.assertTrue(response)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_vserver_exists_not_found(self):
|
||||
|
||||
|
@ -137,11 +137,11 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
'desired-attributes': {'vserver-info': {'root-volume': None}}
|
||||
}
|
||||
|
||||
response = self.client.get_vserver_root_volume_name(fake.VSERVER_NAME)
|
||||
result = self.client.get_vserver_root_volume_name(fake.VSERVER_NAME)
|
||||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('vserver-get-iter', vserver_get_args)])
|
||||
self.assertEqual(fake.ROOT_VOLUME_NAME, response)
|
||||
self.assertEqual(fake.ROOT_VOLUME_NAME, result)
|
||||
|
||||
def test_get_vserver_root_volume_name_not_found(self):
|
||||
|
||||
|
@ -587,13 +587,13 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
}
|
||||
}
|
||||
}
|
||||
response = self.client.network_interface_exists(
|
||||
result = self.client.network_interface_exists(
|
||||
fake.VSERVER_NAME, fake.NODE_NAME, fake.PORT, fake.IP_ADDRESS,
|
||||
fake.NETMASK, fake.VLAN)
|
||||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('net-interface-get-iter', net_interface_get_args)])
|
||||
self.assertTrue(response)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_network_interface_exists_not_found(self):
|
||||
|
||||
|
@ -602,11 +602,11 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
response = self.client.network_interface_exists(
|
||||
result = self.client.network_interface_exists(
|
||||
fake.VSERVER_NAME, fake.NODE_NAME, fake.PORT, fake.IP_ADDRESS,
|
||||
fake.NETMASK, fake.VLAN)
|
||||
|
||||
self.assertFalse(response)
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_list_network_interfaces(self):
|
||||
|
||||
|
@ -669,7 +669,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
self.client.send_request.assert_has_calls([
|
||||
mock.call('net-interface-delete', net_interface_get_args)])
|
||||
|
||||
def test_calculate_aggregate_capacity(self):
|
||||
def test_get_cluster_aggregate_capacities(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
fake.AGGR_GET_SPACE_RESPONSE).get_child_by_name(
|
||||
|
@ -678,14 +678,15 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
'_get_aggregates',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
result = self.client.calculate_aggregate_capacity(fake.AGGR_NAMES)
|
||||
result = self.client.get_cluster_aggregate_capacities(fake.AGGR_NAMES)
|
||||
|
||||
desired_attributes = {
|
||||
'aggr-attributes': {
|
||||
'aggregate-name': None,
|
||||
'aggr-space-attributes': {
|
||||
'size-total': None,
|
||||
'size-available': None,
|
||||
'size-total': None,
|
||||
'size-used': None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -694,27 +695,40 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
mock.call(
|
||||
aggregate_names=fake.AGGR_NAMES,
|
||||
desired_attributes=desired_attributes)])
|
||||
self.assertEqual((8493465600, 6448435200), result)
|
||||
|
||||
def test_calculate_aggregate_capacity_not_found(self):
|
||||
expected = {
|
||||
'manila': {
|
||||
'available': 4267659264,
|
||||
'total': 7549747200,
|
||||
'used': 3282087936,
|
||||
},
|
||||
'aggr0': {
|
||||
'available': 45670400,
|
||||
'total': 943718400,
|
||||
'used': 898048000,
|
||||
}
|
||||
}
|
||||
self.assertDictEqual(expected, result)
|
||||
|
||||
def test_get_cluster_aggregate_capacities_not_found(self):
|
||||
|
||||
api_response = netapp_api.NaElement('none').get_children()
|
||||
self.mock_object(self.client,
|
||||
'_get_aggregates',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
result = self.client.calculate_aggregate_capacity(fake.AGGR_NAMES)
|
||||
result = self.client.get_cluster_aggregate_capacities(fake.AGGR_NAMES)
|
||||
|
||||
self.assertEqual((0, 0), result)
|
||||
self.assertEqual({}, result)
|
||||
|
||||
def test_get_aggregates_for_vserver(self):
|
||||
def test_get_vserver_aggregate_capacities(self):
|
||||
|
||||
api_response = netapp_api.NaElement(fake.VSERVER_GET_RESPONSE)
|
||||
self.mock_object(self.vserver_client,
|
||||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
result = self.vserver_client.get_aggregates_for_vserver(
|
||||
result = self.vserver_client.get_vserver_aggregate_capacities(
|
||||
fake.VSERVER_NAME)
|
||||
|
||||
vserver_args = {
|
||||
|
@ -734,7 +748,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
mock.call('vserver-get', vserver_args)])
|
||||
self.assertDictEqual(fake.VSERVER_AGGREGATES, result)
|
||||
|
||||
def test_get_aggregates_for_vserver_not_found(self):
|
||||
def test_get_vserver_aggregate_capacities_not_found(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
fake.VSERVER_GET_RESPONSE_NO_AGGREGATES)
|
||||
|
@ -742,9 +756,11 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.vserver_client.get_aggregates_for_vserver,
|
||||
fake.VSERVER_NAME)
|
||||
result = self.vserver_client.get_vserver_aggregate_capacities(
|
||||
fake.VSERVER_NAME)
|
||||
|
||||
self.assertDictEqual({}, result)
|
||||
self.assertEqual(1, client_cmode.LOG.warning.call_count)
|
||||
|
||||
def test_get_aggregates(self):
|
||||
|
||||
|
@ -1181,7 +1197,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
response = self.client.volume_exists(fake.SHARE_NAME)
|
||||
result = self.client.volume_exists(fake.SHARE_NAME)
|
||||
|
||||
volume_get_iter_args = {
|
||||
'query': {
|
||||
|
@ -1202,7 +1218,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-get-iter', volume_get_iter_args)])
|
||||
self.assertTrue(response)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_volume_exists_not_found(self):
|
||||
|
||||
|
@ -1213,6 +1229,49 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
|
||||
self.assertFalse(self.client.volume_exists(fake.SHARE_NAME))
|
||||
|
||||
def test_get_aggregate_for_volume(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
fake.GET_AGGREGATE_FOR_VOLUME_RESPONSE)
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
result = self.client.get_aggregate_for_volume(fake.SHARE_NAME)
|
||||
|
||||
volume_get_iter_args = {
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'name': fake.SHARE_NAME
|
||||
}
|
||||
}
|
||||
},
|
||||
'desired-attributes': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'containing-aggregate-name': None,
|
||||
'name': None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-get-iter', volume_get_iter_args)])
|
||||
self.assertEqual(fake.SHARE_AGGREGATE_NAME, result)
|
||||
|
||||
def test_get_aggregate_for_volume_not_found(self):
|
||||
|
||||
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.client.get_aggregate_for_volume,
|
||||
fake.SHARE_NAME)
|
||||
|
||||
def test_create_volume_clone(self):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
|
|
|
@ -21,7 +21,6 @@ import socket
|
|||
|
||||
import mock
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import units
|
||||
|
||||
from manila import context
|
||||
from manila import exception
|
||||
|
@ -99,7 +98,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||
def setUp(self):
|
||||
super(NetAppFileStorageLibraryTestCase, self).setUp()
|
||||
|
||||
self.mock_object(na_utils, 'validate_instantiation')
|
||||
self.mock_object(na_utils, 'validate_driver_instantiation')
|
||||
self.mock_object(na_utils, 'setup_tracing')
|
||||
self.mock_object(lib_base, 'LOG')
|
||||
|
||||
|
@ -126,8 +125,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||
config.netapp_server_port = fake.CLIENT_KWARGS['port']
|
||||
config.netapp_vserver = fake.VSERVER1
|
||||
config.netapp_volume_name_template = fake.VOLUME_NAME_TEMPLATE
|
||||
config.netapp_aggregate_name_search_pattern = \
|
||||
fake.AGGREGATE_NAME_SEARCH_PATTERN
|
||||
config.netapp_aggregate_name_search_pattern = (
|
||||
fake.AGGREGATE_NAME_SEARCH_PATTERN)
|
||||
config.netapp_vserver_name_template = fake.VSERVER_NAME_TEMPLATE
|
||||
config.netapp_root_volume_aggregate = fake.ROOT_VOLUME_AGGREGATE
|
||||
config.netapp_root_volume = fake.ROOT_VOLUME
|
||||
|
@ -137,7 +136,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||
def test_init(self):
|
||||
self.assertEqual(fake.DRIVER_NAME, self.library.driver_name)
|
||||
self.assertEqual(self.mock_db, self.library.db)
|
||||
self.assertEqual(1, na_utils.validate_instantiation.call_count)
|
||||
self.assertEqual(1, na_utils.validate_driver_instantiation.call_count)
|
||||
self.assertEqual(1, na_utils.setup_tracing.call_count)
|
||||
self.assertIsNone(self.library._helpers)
|
||||
self.assertListEqual([], self.library._licenses)
|
||||
|
@ -263,9 +262,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||
|
||||
self.mock_object(self.library, '_find_matching_aggregates')
|
||||
self.mock_object(self.client,
|
||||
'calculate_aggregate_capacity',
|
||||
mock.Mock(return_value=(fake.TOTAL_CAPACITY,
|
||||
fake.FREE_CAPACITY)))
|
||||
'get_cluster_aggregate_capacities',
|
||||
mock.Mock(return_value=fake.AGGREGATE_CAPACITIES))
|
||||
mock_handle_ems_logging = self.mock_object(self.library,
|
||||
'_handle_ems_logging')
|
||||
|
||||
|
@ -277,10 +275,27 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||
'vendor_name': 'NetApp',
|
||||
'driver_version': '1.0',
|
||||
'storage_protocol': 'NFS_CIFS',
|
||||
'total_capacity_gb': fake.TOTAL_CAPACITY / units.Gi,
|
||||
'free_capacity_gb': fake.FREE_CAPACITY / units.Gi
|
||||
'total_capacity_gb': 0.0,
|
||||
'free_capacity_gb': 0.0,
|
||||
'pools': [
|
||||
{'pool_name': 'manila1',
|
||||
'total_capacity_gb': 3.3,
|
||||
'free_capacity_gb': 1.1,
|
||||
'allocated_capacity_gb': 2.2,
|
||||
'QoS_support': 'False',
|
||||
'reserved_percentage': 0,
|
||||
},
|
||||
{'pool_name': 'manila2',
|
||||
'total_capacity_gb': 6.0,
|
||||
'free_capacity_gb': 2.0,
|
||||
'allocated_capacity_gb': 4.0,
|
||||
'QoS_support': 'False',
|
||||
'reserved_percentage': 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
self.maxDiff = None
|
||||
self.assertDictEqual(expected, result)
|
||||
self.assertTrue(mock_handle_ems_logging.called)
|
||||
|
||||
|
@ -339,13 +354,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||
'list_aggregates',
|
||||
mock.Mock(return_value=fake.AGGREGATES))
|
||||
|
||||
self.library.configuration.netapp_aggregate_name_search_pattern =\
|
||||
fake.AGGREGATE_NAME_SEARCH_PATTERN
|
||||
self.library.configuration.netapp_aggregate_name_search_pattern = (
|
||||
fake.AGGREGATE_NAME_SEARCH_PATTERN)
|
||||
result = self.library._find_matching_aggregates()
|
||||
self.assertListEqual(result, fake.AGGREGATES)
|
||||
|
||||
self.library.configuration.netapp_aggregate_name_search_pattern =\
|
||||
'aggr.*'
|
||||
self.library.configuration.netapp_aggregate_name_search_pattern = (
|
||||
'aggr.*')
|
||||
result = self.library._find_matching_aggregates()
|
||||
self.assertListEqual(result, ['aggr0'])
|
||||
|
||||
|
@ -618,6 +633,35 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||
|
||||
self.assertFalse(self.library._client.create_network_interface.called)
|
||||
|
||||
def test_get_pool_has_pool(self):
|
||||
result = self.library.get_pool(fake.SHARE)
|
||||
self.assertEqual(fake.POOL_NAME, result)
|
||||
self.assertFalse(self.client.get_aggregate_for_volume.called)
|
||||
|
||||
def test_get_pool_no_pool(self):
|
||||
|
||||
fake_share = copy.deepcopy(fake.SHARE)
|
||||
fake_share['host'] = '%(host)s@%(backend)s' % {
|
||||
'host': fake.HOST_NAME, 'backend': fake.BACKEND_NAME}
|
||||
self.client.get_aggregate_for_volume.return_value = fake.POOL_NAME
|
||||
|
||||
result = self.library.get_pool(fake_share)
|
||||
|
||||
self.assertEqual(fake.POOL_NAME, result)
|
||||
self.assertTrue(self.client.get_aggregate_for_volume.called)
|
||||
|
||||
def test_get_pool_raises(self):
|
||||
|
||||
fake_share = copy.deepcopy(fake.SHARE)
|
||||
fake_share['host'] = '%(host)s@%(backend)s' % {
|
||||
'host': fake.HOST_NAME, 'backend': fake.BACKEND_NAME}
|
||||
self.client.get_aggregate_for_volume.side_effect = (
|
||||
exception.NetAppException)
|
||||
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.library.get_pool,
|
||||
fake_share)
|
||||
|
||||
def test_create_share(self):
|
||||
|
||||
vserver_client = mock.Mock()
|
||||
|
@ -674,16 +718,14 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||
|
||||
def test_allocate_container(self):
|
||||
|
||||
aggregates = {'aggr0': 10000000000, 'aggr1': 20000000000}
|
||||
vserver_client = mock.Mock()
|
||||
vserver_client.get_aggregates_for_vserver.return_value = aggregates
|
||||
|
||||
self.library._allocate_container(fake.SHARE,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
|
||||
share_name = self.library._get_valid_share_name(fake.SHARE['id'])
|
||||
vserver_client.create_volume.assert_called_with('aggr1',
|
||||
vserver_client.create_volume.assert_called_with(fake.POOL_NAME,
|
||||
share_name,
|
||||
fake.SHARE['size'])
|
||||
|
||||
|
@ -938,8 +980,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||
|
||||
def test_get_network_allocations_number(self):
|
||||
|
||||
self.library._client.list_cluster_nodes.return_value = \
|
||||
fake.CLUSTER_NODES
|
||||
self.library._client.list_cluster_nodes.return_value = (
|
||||
fake.CLUSTER_NODES)
|
||||
|
||||
result = self.library.get_network_allocations_number()
|
||||
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
BACKEND_NAME = 'fake_backend_name'
|
||||
DRIVER_NAME = 'fake_driver_name'
|
||||
APP_VERSION = 'fake_app_vsersion'
|
||||
HOST_NAME = 'fake_host'
|
||||
POOL_NAME = 'fake_pool'
|
||||
VSERVER1 = 'fake_vserver_1'
|
||||
VSERVER2 = 'fake_vserver_2'
|
||||
LICENSES = ['base', 'cifs', 'fcp', 'flexclone', 'iscsi', 'nfs', 'snapmirror',
|
||||
|
@ -51,6 +51,8 @@ CLIENT_KWARGS = {
|
|||
|
||||
SHARE = {
|
||||
'id': SHARE_ID,
|
||||
'host': '%(host)s@%(backend)s#%(pool)s' % {
|
||||
'host': HOST_NAME, 'backend': BACKEND_NAME, 'pool': POOL_NAME},
|
||||
'project_id': TENANT_ID,
|
||||
'name': SHARE_NAME,
|
||||
'size': SHARE_SIZE,
|
||||
|
@ -124,3 +126,16 @@ EMS_MESSAGE = {
|
|||
'log-level': '6',
|
||||
'auto-support': 'false'
|
||||
}
|
||||
|
||||
AGGREGATE_CAPACITIES = {
|
||||
'manila1': {
|
||||
'available': 1181116007, # 1.1 GB
|
||||
'total': 3543348020, # 3.3 GB
|
||||
'used': 2362232013, # 2.2 GB
|
||||
},
|
||||
'manila2': {
|
||||
'available': 2147483648, # 2.0 GB
|
||||
'total': 6442450944, # 6.0 GB
|
||||
'used': 4294967296, # 4.0 GB
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,8 +65,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
|||
|
||||
def test_allow_access_preexisting(self):
|
||||
|
||||
self.mock_client.add_cifs_share_access.side_effect = \
|
||||
netapp_api.NaApiError(code=netapp_api.EDUPLICATEENTRY)
|
||||
self.mock_client.add_cifs_share_access.side_effect = (
|
||||
netapp_api.NaApiError(code=netapp_api.EDUPLICATEENTRY))
|
||||
|
||||
self.assertRaises(exception.ShareAccessExists,
|
||||
self.helper.allow_access,
|
||||
|
@ -76,8 +76,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
|||
|
||||
def test_allow_access_api_error(self):
|
||||
|
||||
self.mock_client.add_cifs_share_access.side_effect = \
|
||||
netapp_api.NaApiError()
|
||||
self.mock_client.add_cifs_share_access.side_effect = (
|
||||
netapp_api.NaApiError())
|
||||
|
||||
self.assertRaises(netapp_api.NaApiError,
|
||||
self.helper.allow_access,
|
||||
|
@ -105,8 +105,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
|||
|
||||
def test_deny_access_nonexistent_user(self):
|
||||
|
||||
self.mock_client.remove_cifs_share_access.side_effect = \
|
||||
netapp_api.NaApiError(code=netapp_api.EONTAPI_EINVAL)
|
||||
self.mock_client.remove_cifs_share_access.side_effect = (
|
||||
netapp_api.NaApiError(code=netapp_api.EONTAPI_EINVAL))
|
||||
|
||||
self.helper.deny_access(self.mock_context, fake.CIFS_SHARE,
|
||||
fake.ACCESS)
|
||||
|
@ -117,8 +117,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
|||
|
||||
def test_deny_access_nonexistent_rule(self):
|
||||
|
||||
self.mock_client.remove_cifs_share_access.side_effect = \
|
||||
netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND)
|
||||
self.mock_client.remove_cifs_share_access.side_effect = (
|
||||
netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND))
|
||||
|
||||
self.helper.deny_access(self.mock_context, fake.CIFS_SHARE,
|
||||
fake.ACCESS)
|
||||
|
@ -129,8 +129,8 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
|||
|
||||
def test_deny_access_api_error(self):
|
||||
|
||||
self.mock_client.remove_cifs_share_access.side_effect = \
|
||||
netapp_api.NaApiError()
|
||||
self.mock_client.remove_cifs_share_access.side_effect = (
|
||||
netapp_api.NaApiError())
|
||||
|
||||
self.assertRaises(netapp_api.NaApiError,
|
||||
self.helper.deny_access,
|
||||
|
|
|
@ -40,8 +40,8 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase):
|
|||
|
||||
def test_create_share(self):
|
||||
|
||||
self.mock_client.get_volume_junction_path.return_value = \
|
||||
fake.NFS_SHARE_PATH
|
||||
self.mock_client.get_volume_junction_path.return_value = (
|
||||
fake.NFS_SHARE_PATH)
|
||||
|
||||
result = self.helper.create_share(fake.SHARE_NAME, fake.SHARE_ADDRESS)
|
||||
|
||||
|
@ -145,8 +145,8 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase):
|
|||
|
||||
def test_get_existing_rules(self):
|
||||
|
||||
self.mock_client.get_nfs_export_rules.return_value = \
|
||||
fake.NFS_ACCESS_HOSTS
|
||||
self.mock_client.get_nfs_export_rules.return_value = (
|
||||
fake.NFS_ACCESS_HOSTS)
|
||||
|
||||
result = self.helper._get_existing_rules(fake.NFS_SHARE)
|
||||
|
||||
|
|
|
@ -84,18 +84,18 @@ class NetAppDriverUtilsTestCase(test.TestCase):
|
|||
self.assertEqual('OK', result)
|
||||
self.assertEqual(2, na_utils.LOG.debug.call_count)
|
||||
|
||||
def test_validate_instantiation_proxy(self):
|
||||
def test_validate_driver_instantiation_proxy(self):
|
||||
kwargs = {'netapp_mode': 'proxy'}
|
||||
|
||||
na_utils.validate_instantiation(**kwargs)
|
||||
na_utils.validate_driver_instantiation(**kwargs)
|
||||
|
||||
self.assertEqual(0, na_utils.LOG.warning.call_count)
|
||||
|
||||
def test_validate_instantiation_no_proxy(self):
|
||||
def test_validate_driver_instantiation_no_proxy(self):
|
||||
self.mock_object(na_utils, 'LOG')
|
||||
kwargs = {'netapp_mode': 'asdf'}
|
||||
|
||||
na_utils.validate_instantiation(**kwargs)
|
||||
na_utils.validate_driver_instantiation(**kwargs)
|
||||
|
||||
self.assertEqual(1, na_utils.LOG.warning.call_count)
|
||||
|
||||
|
|
Loading…
Reference in New Issue