manila/manila/tests/share/drivers/nexenta/ns4/test_nexenta_nas.py

614 lines
22 KiB
Python

# Copyright 2016 Nexenta Systems, Inc.
# 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.
import base64
import json
from unittest import mock
from oslo_serialization import jsonutils
from oslo_utils import units
from manila import context
from manila import exception
from manila.share import configuration as conf
from manila.share.drivers.nexenta.ns4 import nexenta_nas
from manila import test
PATH_TO_RPC = 'requests.post'
CODE = mock.PropertyMock(return_value=200)
class FakeResponse(object):
def __init__(self, response={}):
self.content = json.dumps(response)
super(FakeResponse, self).__init__()
def close(self):
pass
class RequestParams(object):
def __init__(self, scheme, host, port, path, user, password):
self.scheme = scheme.lower()
self.host = host
self.port = port
self.path = path
self.user = user
self.password = password
@property
def url(self):
return '%s://%s:%s%s' % (self.scheme, self.host, self.port, self.path)
@property
def headers(self):
auth = base64.b64encode(
('%s:%s' % (self.user, self.password)).encode('utf-8'))
headers = {
'Content-Type': 'application/json',
'Authorization': 'Basic %s' % auth,
}
return headers
def build_post_args(self, obj, method, *args):
data = jsonutils.dumps({
'object': obj,
'method': method,
'params': args,
})
return data
class TestNexentaNasDriver(test.TestCase):
def _get_share_path(self, share_name):
return '%s/%s/%s' % (self.volume, self.share, share_name)
def setUp(self):
def _safe_get(opt):
return getattr(self.cfg, opt)
self.cfg = mock.Mock(spec=conf.Configuration)
self.cfg.nexenta_nas_host = '1.1.1.1'
super(TestNexentaNasDriver, self).setUp()
self.ctx = context.get_admin_context()
self.cfg.safe_get = mock.Mock(side_effect=_safe_get)
self.cfg.nexenta_rest_port = 1000
self.cfg.reserved_share_percentage = 0
self.cfg.reserved_share_from_snapshot_percentage = 0
self.cfg.reserved_share_extend_percentage = 0
self.cfg.max_over_subscription_ratio = 0
self.cfg.nexenta_rest_protocol = 'auto'
self.cfg.nexenta_volume = 'volume'
self.cfg.nexenta_nfs_share = 'nfs_share'
self.cfg.nexenta_user = 'user'
self.cfg.nexenta_password = 'password'
self.cfg.nexenta_thin_provisioning = False
self.cfg.enabled_share_protocols = 'NFS'
self.cfg.nexenta_mount_point_base = '$state_path/mnt'
self.cfg.share_backend_name = 'NexentaStor'
self.cfg.nexenta_dataset_compression = 'on'
self.cfg.nexenta_smb = 'on'
self.cfg.nexenta_nfs = 'on'
self.cfg.nexenta_dataset_dedupe = 'on'
self.cfg.network_config_group = 'DEFAULT'
self.cfg.admin_network_config_group = (
'fake_admin_network_config_group')
self.cfg.driver_handles_share_servers = False
self.request_params = RequestParams(
'http', self.cfg.nexenta_nas_host, self.cfg.nexenta_rest_port,
'/rest/nms/', self.cfg.nexenta_user, self.cfg.nexenta_password)
self.drv = nexenta_nas.NexentaNasDriver(configuration=self.cfg)
self.drv.do_setup(self.ctx)
self.volume = self.cfg.nexenta_volume
self.share = self.cfg.nexenta_nfs_share
@mock.patch(PATH_TO_RPC)
def test_check_for_setup_error__volume_doesnt_exist(self, post):
post.return_value = FakeResponse()
self.assertRaises(
exception.NexentaException, self.drv.check_for_setup_error)
@mock.patch(PATH_TO_RPC)
def test_check_for_setup_error__folder_doesnt_exist(self, post):
folder = '%s/%s' % (self.volume, self.share)
create_folder_props = {
'recordsize': '4K',
'quota': '1G',
'compression': self.cfg.nexenta_dataset_compression,
'sharesmb': self.cfg.nexenta_smb,
'sharenfs': self.cfg.nexenta_nfs,
}
share_opts = {
'read_write': '*',
'read_only': '',
'root': 'nobody',
'extra_options': 'anon=0',
'recursive': 'true',
'anonymous_rw': 'true',
}
def my_side_effect(*args, **kwargs):
if kwargs['data'] == self.request_params.build_post_args(
'volume', 'object_exists', self.volume):
return FakeResponse({'result': 'OK'})
elif kwargs['data'] == self.request_params.build_post_args(
'folder', 'object_exists', folder):
return FakeResponse()
elif kwargs['data'] == self.request_params.build_post_args(
'folder', 'create_with_props', self.volume, self.share,
create_folder_props):
return FakeResponse()
elif kwargs['data'] == self.request_params.build_post_args(
'netstorsvc', 'share_folder',
'svc:/network/nfs/server:default', folder, share_opts):
return FakeResponse()
else:
raise exception.ManilaException('Unexpected request')
post.side_effect = my_side_effect
self.assertRaises(
exception.ManilaException, self.drv.check_for_setup_error)
post.assert_any_call(
self.request_params.url, data=self.request_params.build_post_args(
'volume', 'object_exists', self.volume),
headers=self.request_params.headers)
post.assert_any_call(
self.request_params.url, data=self.request_params.build_post_args(
'folder', 'object_exists', folder),
headers=self.request_params.headers)
@mock.patch(PATH_TO_RPC)
def test_create_share(self, post):
share = {
'name': 'share',
'size': 1,
'share_proto': self.cfg.enabled_share_protocols
}
self.cfg.nexenta_thin_provisioning = False
path = '%s/%s/%s' % (self.volume, self.share, share['name'])
location = {'path': '%s:/volumes/%s' % (
self.cfg.nexenta_nas_host, path)}
post.return_value = FakeResponse()
self.assertEqual([location],
self.drv.create_share(self.ctx, share))
@mock.patch(PATH_TO_RPC)
def test_create_share__wrong_proto(self, post):
share = {
'name': 'share',
'size': 1,
'share_proto': 'A_VERY_WRONG_PROTO'
}
post.return_value = FakeResponse()
self.assertRaises(exception.InvalidShare, self.drv.create_share,
self.ctx, share)
@mock.patch(PATH_TO_RPC)
def test_create_share__thin_provisioning(self, post):
share = {'name': 'share', 'size': 1,
'share_proto': self.cfg.enabled_share_protocols}
create_folder_props = {
'recordsize': '4K',
'quota': '1G',
'compression': self.cfg.nexenta_dataset_compression,
}
parent_path = '%s/%s' % (self.volume, self.share)
post.return_value = FakeResponse()
self.cfg.nexenta_thin_provisioning = True
self.drv.create_share(self.ctx, share)
post.assert_called_with(
self.request_params.url,
data=self.request_params.build_post_args(
'folder',
'create_with_props',
parent_path,
share['name'],
create_folder_props),
headers=self.request_params.headers)
@mock.patch(PATH_TO_RPC)
def test_create_share__thick_provisioning(self, post):
share = {
'name': 'share',
'size': 1,
'share_proto': self.cfg.enabled_share_protocols
}
quota = '%sG' % share['size']
create_folder_props = {
'recordsize': '4K',
'quota': quota,
'compression': self.cfg.nexenta_dataset_compression,
'reservation': quota,
}
parent_path = '%s/%s' % (self.volume, self.share)
post.return_value = FakeResponse()
self.cfg.nexenta_thin_provisioning = False
self.drv.create_share(self.ctx, share)
post.assert_called_with(
self.request_params.url,
data=self.request_params.build_post_args(
'folder',
'create_with_props',
parent_path,
share['name'],
create_folder_props),
headers=self.request_params.headers)
@mock.patch(PATH_TO_RPC)
def test_create_share_from_snapshot(self, post):
share = {
'name': 'share',
'size': 1,
'share_proto': self.cfg.enabled_share_protocols
}
snapshot = {'name': 'sn1', 'share_name': share['name']}
post.return_value = FakeResponse()
path = '%s/%s/%s' % (self.volume, self.share, share['name'])
location = {'path': '%s:/volumes/%s' % (
self.cfg.nexenta_nas_host, path)}
snapshot_name = '%s/%s/%s@%s' % (
self.volume, self.share, snapshot['share_name'], snapshot['name'])
self.assertEqual([location], self.drv.create_share_from_snapshot(
self.ctx, share, snapshot))
post.assert_any_call(
self.request_params.url,
data=self.request_params.build_post_args(
'folder',
'clone',
snapshot_name,
'%s/%s/%s' % (self.volume, self.share, share['name'])),
headers=self.request_params.headers)
@mock.patch(PATH_TO_RPC)
def test_delete_share(self, post):
share = {
'name': 'share',
'size': 1,
'share_proto': self.cfg.enabled_share_protocols
}
post.return_value = FakeResponse()
folder = '%s/%s/%s' % (self.volume, self.share, share['name'])
self.drv.delete_share(self.ctx, share)
post.assert_any_call(
self.request_params.url,
data=self.request_params.build_post_args(
'folder',
'destroy',
folder.strip(),
'-r'),
headers=self.request_params.headers)
@mock.patch(PATH_TO_RPC)
def test_delete_share__exists_error(self, post):
share = {
'name': 'share',
'size': 1,
'share_proto': self.cfg.enabled_share_protocols
}
post.return_value = FakeResponse()
post.side_effect = exception.NexentaException('does not exist')
self.drv.delete_share(self.ctx, share)
@mock.patch(PATH_TO_RPC)
def test_delete_share__some_error(self, post):
share = {
'name': 'share',
'size': 1,
'share_proto': self.cfg.enabled_share_protocols
}
post.return_value = FakeResponse()
post.side_effect = exception.ManilaException('Some error')
self.assertRaises(
exception.ManilaException, self.drv.delete_share, self.ctx, share)
@mock.patch(PATH_TO_RPC)
def test_extend_share__thin_provisoning(self, post):
share = {
'name': 'share',
'size': 1,
'share_proto': self.cfg.enabled_share_protocols
}
new_size = 5
quota = '%sG' % new_size
post.return_value = FakeResponse()
self.cfg.nexenta_thin_provisioning = True
self.drv.extend_share(share, new_size)
post.assert_called_with(
self.request_params.url,
data=self.request_params.build_post_args(
'folder',
'set_child_prop',
'%s/%s/%s' % (self.volume, self.share, share['name']),
'quota', quota),
headers=self.request_params.headers)
@mock.patch(PATH_TO_RPC)
def test_extend_share__thick_provisoning(self, post):
share = {
'name': 'share',
'size': 1,
'share_proto': self.cfg.enabled_share_protocols
}
new_size = 5
post.return_value = FakeResponse()
self.cfg.nexenta_thin_provisioning = False
self.drv.extend_share(share, new_size)
post.assert_not_called()
@mock.patch(PATH_TO_RPC)
def test_create_snapshot(self, post):
snapshot = {'share_name': 'share', 'name': 'share@first'}
post.return_value = FakeResponse()
folder = '%s/%s/%s' % (self.volume, self.share, snapshot['share_name'])
self.drv.create_snapshot(self.ctx, snapshot)
post.assert_called_with(
self.request_params.url, data=self.request_params.build_post_args(
'folder', 'create_snapshot', folder, snapshot['name'], '-r'),
headers=self.request_params.headers)
@mock.patch(PATH_TO_RPC)
def test_delete_snapshot(self, post):
snapshot = {'share_name': 'share', 'name': 'share@first'}
post.return_value = FakeResponse()
self.drv.delete_snapshot(self.ctx, snapshot)
post.assert_called_with(
self.request_params.url, data=self.request_params.build_post_args(
'snapshot', 'destroy', '%s@%s' % (
self._get_share_path(snapshot['share_name']),
snapshot['name']),
''),
headers=self.request_params.headers)
@mock.patch(PATH_TO_RPC)
def test_delete_snapshot__nexenta_error_1(self, post):
snapshot = {'share_name': 'share', 'name': 'share@first'}
post.return_value = FakeResponse()
post.side_effect = exception.NexentaException('does not exist')
self.drv.delete_snapshot(self.ctx, snapshot)
@mock.patch(PATH_TO_RPC)
def test_delete_snapshot__nexenta_error_2(self, post):
snapshot = {'share_name': 'share', 'name': 'share@first'}
post.return_value = FakeResponse()
post.side_effect = exception.NexentaException('has dependent clones')
self.drv.delete_snapshot(self.ctx, snapshot)
@mock.patch(PATH_TO_RPC)
def test_delete_snapshot__some_error(self, post):
snapshot = {'share_name': 'share', 'name': 'share@first'}
post.return_value = FakeResponse()
post.side_effect = exception.ManilaException('Some error')
self.assertRaises(exception.ManilaException, self.drv.delete_snapshot,
self.ctx, snapshot)
@mock.patch(PATH_TO_RPC)
def test_update_access__unsupported_access_type(self, post):
share = {
'name': 'share',
'share_proto': self.cfg.enabled_share_protocols
}
access = {
'access_type': 'group',
'access_to': 'ordinary_users',
'access_level': 'rw'
}
self.assertRaises(exception.InvalidShareAccess,
self.drv.update_access,
self.ctx,
share,
[access],
None,
None)
@mock.patch(PATH_TO_RPC)
def test_update_access__cidr(self, post):
share = {
'name': 'share',
'share_proto': self.cfg.enabled_share_protocols
}
access1 = {
'access_type': 'ip',
'access_to': '1.1.1.1/24',
'access_level': 'rw'
}
access2 = {
'access_type': 'ip',
'access_to': '1.2.3.4',
'access_level': 'rw'
}
access_rules = [access1, access2]
share_opts = {
'auth_type': 'none',
'read_write': '%s:%s' % (
access1['access_to'], access2['access_to']),
'read_only': '',
'recursive': 'true',
'anonymous_rw': 'true',
'anonymous': 'true',
'extra_options': 'anon=0',
}
def my_side_effect(*args, **kwargs):
if kwargs['data'] == self.request_params.build_post_args(
'netstorsvc', 'share_folder',
'svc:/network/nfs/server:default',
self._get_share_path(share['name']), share_opts):
return FakeResponse()
else:
raise exception.ManilaException('Unexpected request')
post.return_value = FakeResponse()
post.side_effect = my_side_effect
self.drv.update_access(self.ctx, share, access_rules, None, None)
post.assert_called_with(
self.request_params.url, data=self.request_params.build_post_args(
'netstorsvc', 'share_folder',
'svc:/network/nfs/server:default',
self._get_share_path(share['name']), share_opts),
headers=self.request_params.headers)
self.assertRaises(exception.ManilaException, self.drv.update_access,
self.ctx, share,
[access1, {'access_type': 'ip',
'access_to': '2.2.2.2',
'access_level': 'rw'}],
None, None)
@mock.patch(PATH_TO_RPC)
def test_update_access__add_one_ip_to_empty_access_list(self, post):
share = {'name': 'share',
'share_proto': self.cfg.enabled_share_protocols}
access = {
'access_type': 'ip',
'access_to': '1.1.1.1',
'access_level': 'rw'
}
rw_list = None
share_opts = {
'auth_type': 'none',
'read_write': access['access_to'],
'read_only': '',
'recursive': 'true',
'anonymous_rw': 'true',
'anonymous': 'true',
'extra_options': 'anon=0',
}
def my_side_effect(*args, **kwargs):
if kwargs['data'] == self.request_params.build_post_args(
'netstorsvc', 'get_shareopts',
'svc:/network/nfs/server:default',
self._get_share_path(share['name'])):
return FakeResponse({'result': {'read_write': rw_list}})
elif kwargs['data'] == self.request_params.build_post_args(
'netstorsvc', 'share_folder',
'svc:/network/nfs/server:default',
self._get_share_path(share['name']), share_opts):
return FakeResponse()
else:
raise exception.ManilaException('Unexpected request')
post.return_value = FakeResponse()
self.drv.update_access(self.ctx, share, [access], None, None)
post.assert_called_with(
self.request_params.url, data=self.request_params.build_post_args(
'netstorsvc', 'share_folder',
'svc:/network/nfs/server:default',
self._get_share_path(share['name']), share_opts),
headers=self.request_params.headers)
post.side_effect = my_side_effect
self.assertRaises(exception.ManilaException, self.drv.update_access,
self.ctx, share,
[{'access_type': 'ip',
'access_to': '1111',
'access_level': 'rw'}],
None, None)
@mock.patch(PATH_TO_RPC)
def test_deny_access__unsupported_access_type(self, post):
share = {'name': 'share',
'share_proto': self.cfg.enabled_share_protocols}
access = {
'access_type': 'group',
'access_to': 'ordinary_users',
'access_level': 'rw'
}
self.assertRaises(exception.InvalidShareAccess, self.drv.update_access,
self.ctx, share, [access], None, None)
def test_share_backend_name(self):
self.assertEqual('NexentaStor', self.drv.share_backend_name)
@mock.patch(PATH_TO_RPC)
def test_get_capacity_info(self, post):
post.return_value = FakeResponse({'result': {
'available': 9 * units.Gi, 'used': 1 * units.Gi}})
self.assertEqual(
(10, 9, 1), self.drv.helper._get_capacity_info())
@mock.patch('manila.share.drivers.nexenta.ns4.nexenta_nfs_helper.'
'NFSHelper._get_capacity_info')
@mock.patch('manila.share.driver.ShareDriver._update_share_stats')
def test_update_share_stats(self, super_stats, info):
info.return_value = (100, 90, 10)
stats = {
'vendor_name': 'Nexenta',
'storage_protocol': 'NFS',
'nfs_mount_point_base': self.cfg.nexenta_mount_point_base,
'driver_version': '1.0',
'share_backend_name': self.cfg.share_backend_name,
'pools': [{
'total_capacity_gb': 100,
'free_capacity_gb': 90,
'pool_name': 'volume',
'reserved_percentage': (
self.cfg.reserved_share_percentage),
'reserved_snapshot_percentage': (
self.cfg.reserved_share_from_snapshot_percentage),
'reserved_share_extend_percentage': (
self.cfg.reserved_share_extend_percentage),
'compression': True,
'dedupe': True,
'thin_provisioning': self.cfg.nexenta_thin_provisioning,
'max_over_subscription_ratio': (
self.cfg.safe_get(
'max_over_subscription_ratio')),
}],
}
self.drv._update_share_stats()
self.assertEqual(stats, self.drv._stats)