Support extend_share in NetApp cDOT drivers

Now that extend_share is in Manila core, we can support the new API in
the NetApp cDOT drivers.  As this patch adds methods to the driver shim
layer, also add a unit test to ensure the shims remain in sync for each
cDOT driver.

Implements: blueprint extend-share-in-netapp-cdot-drivers
Change-Id: I407a12a0cd44f37a62bd500251049c50a097f2c4
This commit is contained in:
Clinton Knight 2015-05-15 09:16:07 -04:00
parent 687153838b
commit 385c062c90
8 changed files with 206 additions and 0 deletions

View File

@ -21,6 +21,7 @@ import time
from oslo_log import log
from oslo_utils import strutils
from oslo_utils import units
import six
from manila import exception
@ -940,6 +941,36 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
}
self.send_request('volume-modify-iter', api_args)
@na_utils.trace
def set_volume_size(self, volume_name, size_gb):
"""Set volume size."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'attributes': {
'volume-attributes': {
'volume-space-attributes': {
'size': int(size_gb) * units.Gi,
},
},
},
}
result = self.send_request('volume-modify-iter', api_args)
failures = result.get_child_content('num-failed')
if failures and int(failures) > 0:
failure_list = result.get_child_by_name(
'failure-list') or netapp_api.NaElement('none')
errors = failure_list.get_children()
if errors:
raise netapp_api.NaApiError(
errors[0].get_child_content('error-code'),
errors[0].get_child_content('error-message'))
@na_utils.trace
def volume_exists(self, volume_name):
"""Checks if volume exists."""

View File

@ -61,6 +61,9 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
def delete_snapshot(self, context, snapshot, **kwargs):
self.library.delete_snapshot(context, snapshot, **kwargs)
def extend_share(self, share, new_size, **kwargs):
self.library.extend_share(share, new_size, **kwargs)
def ensure_share(self, context, share, **kwargs):
pass

View File

@ -61,6 +61,9 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
def delete_snapshot(self, context, snapshot, **kwargs):
self.library.delete_snapshot(context, snapshot, **kwargs)
def extend_share(self, share, new_size, **kwargs):
self.library.extend_share(share, new_size, **kwargs)
def ensure_share(self, context, share, **kwargs):
pass

View File

@ -631,6 +631,15 @@ class NetAppCmodeFileStorageLibrary(object):
raise exception.ShareSnapshotIsBusy(snapshot_name=snapshot_name)
@na_utils.trace
def extend_share(self, share, new_size, share_server=None):
"""Extends size of existing share."""
vserver, vserver_client = self._get_vserver(share_server=share_server)
share_name = self._get_valid_share_name(share['id'])
LOG.debug('Extending share %(name)s to %(size)s GB.',
{'name': share_name, 'size': new_size})
vserver_client.set_volume_size(share_name, new_size)
@na_utils.trace
def allow_access(self, context, share, access, share_server=None):
"""Allows access to a given NAS storage."""

View File

@ -1037,6 +1037,47 @@ VOLUME_GET_VOLUME_PATH_CIFS_RESPONSE = etree.XML("""
VOLUME_JUNCTION_PATH = '/' + SHARE_NAME
VOLUME_JUNCTION_PATH_CIFS = '\\' + SHARE_NAME
VOLUME_MODIFY_ITER_RESPONSE = etree.XML("""
<results status="passed">
<failure-list />
<num-failed>0</num-failed>
<num-succeeded>1</num-succeeded>
<success-list>
<volume-modify-iter-info>
<volume-key>
<volume-attributes>
<volume-id-attributes>
<name>%(volume)s</name>
<owning-vserver-name>%(vserver)s</owning-vserver-name>
</volume-id-attributes>
</volume-attributes>
</volume-key>
</volume-modify-iter-info>
</success-list>
</results>
""" % {'volume': SHARE_NAME, 'vserver': VSERVER_NAME})
VOLUME_MODIFY_ITER_ERROR_RESPONSE = etree.XML("""
<results status="passed">
<failure-list>
<volume-modify-iter-info>
<error-code>160</error-code>
<error-message>Unable to set volume attribute "size"</error-message>
<volume-key>
<volume-attributes>
<volume-id-attributes>
<name>%(volume)s</name>
<owning-vserver-name>%(vserver)s</owning-vserver-name>
</volume-id-attributes>
</volume-attributes>
</volume-key>
</volume-modify-iter-info>
</failure-list>
<num-failed>1</num-failed>
<num-succeeded>0</num-succeeded>
</results>
""" % {'volume': SHARE_NAME, 'vserver': VSERVER_NAME})
SNAPSHOT_GET_ITER_NOT_BUSY_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>

View File

@ -1718,6 +1718,47 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_request.assert_called_once_with('sis-set-config',
sis_set_config_args)
def test_set_volume_size(self):
api_response = netapp_api.NaElement(fake.VOLUME_MODIFY_ITER_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
self.client.set_volume_size(fake.SHARE_NAME, 10)
volume_modify_iter_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': fake.SHARE_NAME
}
}
},
'attributes': {
'volume-attributes': {
'volume-space-attributes': {
'size': 10737418240,
},
},
},
}
self.client.send_request.assert_has_calls([
mock.call('volume-modify-iter', volume_modify_iter_args)])
def test_set_volume_size_api_error(self):
api_response = netapp_api.NaElement(
fake.VOLUME_MODIFY_ITER_ERROR_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
self.assertRaises(netapp_api.NaApiError,
self.client.set_volume_size,
fake.SHARE_NAME,
10)
def test_volume_exists(self):
api_response = netapp_api.NaElement(fake.VOLUME_GET_NAME_RESPONSE)

View File

@ -0,0 +1,63 @@
# Copyright (c) 2015 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 file share driver interfaces
"""
import mock
import six
from manila.share.drivers.netapp.dataontap.cluster_mode import drv_multi_svm
from manila.share.drivers.netapp.dataontap.cluster_mode import drv_single_svm
from manila import test
class NetAppFileStorageDriverInterfaceTestCase(test.TestCase):
def setUp(self):
super(NetAppFileStorageDriverInterfaceTestCase, self).setUp()
self.mock_object(drv_multi_svm.NetAppCmodeMultiSvmShareDriver,
'__init__',
mock.Mock(return_value=None))
self.mock_object(drv_single_svm.NetAppCmodeSingleSvmShareDriver,
'__init__',
mock.Mock(return_value=None))
self.drv_multi_svm = drv_multi_svm.NetAppCmodeMultiSvmShareDriver()
self.drv_single_svm = drv_single_svm.NetAppCmodeSingleSvmShareDriver()
def test_driver_interfaces_match(self):
"""Ensure the NetApp file storage driver interfaces match.
The two file share Manila drivers from NetApp (cDOT multi-SVM,
cDOT single-SVM) are merely passthrough shim layers atop a common
file storage library. Bugs are easily introduced when a Manila
method is exposed via a subset of those driver shims. This test
ensures they remain in sync and the library features are uniformly
available in the drivers.
"""
# Get local functions of each driver interface
multi_svm_methods = self._get_local_functions(self.drv_multi_svm)
single_svm_methods = self._get_local_functions(self.drv_single_svm)
# Ensure NetApp file share driver shims are identical
self.assertSetEqual(multi_svm_methods, single_svm_methods)
def _get_local_functions(self, obj):
"""Get function names of an object without superclass functions."""
return set([key for key, value in six.iteritems(type(obj).__dict__)
if callable(value)])

View File

@ -1086,6 +1086,21 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_sleep.assert_has_calls([mock.call(3)] * 20)
self.assertEqual(20, lib_base.LOG.debug.call_count)
def test_extend_share(self):
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
mock_set_volume_size = self.mock_object(vserver_client,
'set_volume_size')
new_size = fake.SHARE['size'] * 2
self.library.extend_share(fake.SHARE, new_size)
mock_set_volume_size.assert_called_once_with(fake.SHARE_NAME, new_size)
def test_allow_access(self):
protocol_helper = mock.Mock()