From: Abhiram Moturi Date: Tue, 21 Jul 2015 00:58:23 +0000 (+0000) Subject: Enhance unit tests for zfssa drivers X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=af3a73e720a970ff080cd4f77caebe06216b1f3a;p=openstack-build%2Fcinder-build.git Enhance unit tests for zfssa drivers This fix improves the unit tests for the zfssa drivers and increases the test coverage. Change-Id: I5a143b7ca4bf8e6560c7e389e1fd9b0e23e4ea16 Closes-Bug: #1404339 --- diff --git a/cinder/tests/unit/test_zfssa.py b/cinder/tests/unit/test_zfssa.py index 192d019e7..f519c5ca9 100644 --- a/cinder/tests/unit/test_zfssa.py +++ b/cinder/tests/unit/test_zfssa.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2015, Oracle and/or its affiliates. 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 @@ -17,11 +17,14 @@ import json import mock from oslo_utils import units +import six +from cinder import exception from cinder import test from cinder.tests.unit import fake_utils from cinder.volume import configuration as conf from cinder.volume.drivers.zfssa import restclient as client +from cinder.volume.drivers.zfssa import webdavclient from cinder.volume.drivers.zfssa import zfssaiscsi as iscsi from cinder.volume.drivers.zfssa import zfssanfs from cinder.volume.drivers.zfssa import zfssarest as rest @@ -31,267 +34,15 @@ nfs_logbias = 'latency' nfs_compression = 'off' -class FakeZFSSA(object): - """Fake ZFS SA.""" - def __init__(self): - self.user = None - self.host = None - - def login(self, user): - self.user = user - - def set_host(self, host, timeout=None): - self.host = host - - def create_project(self, pool, project, compression, logbias): - out = {} - if not self.host or not self.user: - return out - - out = {"status": "online", - "name": "pool", - "usage": {"available": 10, - "total": 10, - "dedupratio": 100, - "used": 1}, - "peer": "00000000-0000-0000-0000-000000000000", - "owner": "host", - "asn": "11111111-2222-3333-4444-555555555555"} - return out - - def create_initiator(self, init, initgrp, chapuser, chapsecret): - out = {} - if not self.host or not self.user: - return out - out = {"href": "fake_href", - "alias": "fake_alias", - "initiator": "fake_iqn.1993-08.org.fake:01:000000000000", - "chapuser": "", - "chapsecret": "" - } - - return out - - def add_to_initiatorgroup(self, init, initgrp): - r = rest.ZFSSAApi() - type(r).rclient = mock.PropertyMock(return_value=FakeAddIni2InitGrp()) - r.add_to_initiatorgroup(init, initgrp) - - def create_target(self, tgtalias, inter, tchapuser, tchapsecret): - out = {} - if not self.host or not self.user: - return out - out = {"href": "fake_href", - "alias": "fake_tgtgrp", - "iqn": "iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd", - "auth": "none", - "targetchapuser": "", - "targetchapsecret": "", - "interfaces": ["eth0"] - } - - return out - - def add_to_targetgroup(self, iqn, tgtgrp): - out = {} - if not self.host or not self.user: - return {} - out = {"href": "fake_href", - "name": "fake_tgtgrp", - "targets": ["iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd"] - } - return out - - def get_lun(self, pool, project, lun): - ret = { - 'guid': '600144F0F8FBD5BD000053CE53AB0001', - 'number': 0, - 'initiatorgroup': 'fake_initgrp', - 'size': 1 * units.Gi - } - return ret - - def get_target(self, target): - return 'iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd' - - def create_lun(self, pool, project, lun, volsize, targetgroup, specs): - out = {} - if not self.host and not self.user: - return out - - out = { - "status": "online", - "lunguid": "600144F0F8FBD5BD000053CE53AB0001", - "initiatorgroup": ["fake_initgrp"], - "volsize": volsize, - "pool": pool, - "name": lun, - "project": project, - "targetgroup": targetgroup, - "lun": {"assignednumber": 0}, - } - if specs: - out.update(specs) - - return out - - def delete_lun(self, pool, project, lun): - out = {} - if not self.host and not self.user: - return out - out = {"pool": pool, - "project": project, - "name": lun} - - return out - - def create_snapshot(self, pool, project, vol, snap): - out = {} - if not self.host and not self.user: - return {} - out = {"name": snap, - "numclones": 0, - "share": vol, - "project": project, - "pool": pool} - - return out - - def delete_snapshot(self, pool, project, vol, snap): - out = {} - if not self.host and not self.user: - return {} - out = {"name": snap, - "share": vol, - "project": project, - "pool": pool} - - return out - - def clone_snapshot(self, pool, project, pvol, snap, vol): - out = {} - if not self.host and not self.user: - return out - out = {"origin": {"project": project, - "snapshot": snap, - "share": pvol, - "pool": pool}, - "logbias": "latency", - "assignednumber": 1, - "status": "online", - "lunguid": "600144F0F8FBD5BD000053CE67A50002", - "volsize": 1, - "pool": pool, - "name": vol, - "project": project} - - return out - - def set_lun_initiatorgroup(self, pool, project, vol, initgrp): - out = {} - if not self.host and not self.user: - return out - out = {"lunguid": "600144F0F8FBD5BD000053CE67A50002", - "pool": pool, - "name": vol, - "project": project, - "initiatorgroup": ["fake_initgrp"]} - - return out - - def has_clones(self, pool, project, vol, snapshot): - return False - - def set_lun_props(self, pool, project, vol, **kargs): - out = {} - if not self.host and not self.user: - return out - out = {"pool": pool, - "name": vol, - "project": project, - "volsize": kargs['volsize']} - - return out - - def get_initiator_initiatorgroup(self, initiator): - ret = ['test-init-grp1'] - return ret - - -class FakeNFSZFSSA(FakeZFSSA): - """Fake ZFS SA for the NFS Driver.""" - def set_webdav(self, https_path, auth_str): - self.webdavclient = https_path - - def create_share(self, pool, project, share, args): - out = {} - if not self.host and not self.user: - return out - - out = {"logbias": nfs_logbias, - "compression": nfs_compression, - "status": "online", - "pool": pool, - "name": share, - "project": project, - "mountpoint": '/export/nfs_share'} - - return out - - def get_share(self, pool, project, share): - out = {} - if not self.host and not self.user: - return out - - out = {"logbias": nfs_logbias, - "compression": nfs_compression, - "encryption": "off", - "status": "online", - "pool": pool, - "name": share, - "project": project, - "mountpoint": '/export/nfs_share'} - - return out - - def create_snapshot_of_volume_file(self, src_file="", dst_file=""): - out = {} - if not self.host and not self.user: - return out - out = {"status": 201} - - return out - - def delete_snapshot_of_volume_file(self, src_file=""): - out = {} - if not self.host and not self.user: - return out - out = {"status": 204} - - return out - - def create_volume_from_snapshot_file(self, src_file="", dst_file="", - method='COPY'): - out = {} - if not self.host and not self.user: - return out - out = {"status": 202} - - return out - - def modify_service(self, service, args): - out = {} - if not self.host and not self.user: - return out - out = {"service": {"": "online"}} - return out - - def enable_service(self, service): - out = {} - if not self.host and not self.user: - return out - out = {"service": {"": "online"}} - return out +class FakeResponse(object): + def __init__(self, statuscode, data='data'): + self.status = statuscode + self.data = data + + +class FakeSSL(object): + def _create_unverified_context(self): + return 'fakecontext' class TestZFSSAISCSIDriver(test.TestCase): @@ -321,7 +72,7 @@ class TestZFSSAISCSIDriver(test.TestCase): def setUp(self, _factory_zfssa): super(TestZFSSAISCSIDriver, self).setUp() self._create_fake_config() - _factory_zfssa.return_value = FakeZFSSA() + _factory_zfssa.return_value = mock.MagicMock(spec=rest.ZFSSAApi) iscsi.ZFSSAISCSIDriver._execute = fake_utils.fake_execute self.drv = iscsi.ZFSSAISCSIDriver(configuration=self.configuration) self.drv.do_setup({}) @@ -355,45 +106,117 @@ class TestZFSSAISCSIDriver(test.TestCase): self.configuration.safe_get = self.fake_safe_get def test_create_delete_volume(self): + self.drv.zfssa.get_lun.return_value = {'guid': + '00000000000000000000000000000', + 'number': 0, + 'initiatorgroup': 'default', + 'size': 1, + 'nodestroy': False} + lcfg = self.configuration self.drv.create_volume(self.test_vol) + self.drv.zfssa.create_lun.assert_called_once_with( + lcfg.zfssa_pool, + lcfg.zfssa_project, + self.test_vol['name'], + six.text_type(self.test_vol['size']) + 'g', + lcfg.zfssa_target_group, + mock.ANY) self.drv.delete_volume(self.test_vol) + self.drv.zfssa.get_lun.assert_called_once_with(lcfg.zfssa_pool, + lcfg.zfssa_project, + self.test_vol['name']) + self.drv.zfssa.delete_lun.assert_called_once_with( + pool=lcfg.zfssa_pool, + project=lcfg.zfssa_project, + lun=self.test_vol['name']) def test_create_delete_snapshot(self): - self.drv.create_volume(self.test_vol) + self.drv.zfssa.has_clones.return_value = False + lcfg = self.configuration self.drv.create_snapshot(self.test_snap) + self.drv.zfssa.create_snapshot.assert_called_once_with( + lcfg.zfssa_pool, + lcfg.zfssa_project, + self.test_snap['volume_name'], + self.test_snap['name']) self.drv.delete_snapshot(self.test_snap) - self.drv.delete_volume(self.test_vol) - - def test_create_volume_from_snapshot(self): - self.drv.create_volume(self.test_vol) + self.drv.zfssa.delete_snapshot.assert_called_once_with( + lcfg.zfssa_pool, + lcfg.zfssa_project, + self.test_snap['volume_name'], + self.test_snap['name']) + + @mock.patch.object(iscsi.ZFSSAISCSIDriver, '_verify_clone_size') + def test_create_volume_from_snapshot(self, _verify_clone_size): + self.drv._verify_clone_size.return_value = True + lcfg = self.configuration self.drv.create_snapshot(self.test_snap) + self.drv.zfssa.create_snapshot.assert_called_once_with( + lcfg.zfssa_pool, + lcfg.zfssa_project, + self.test_snap['volume_name'], + self.test_snap['name']) self.drv.create_volume_from_snapshot(self.test_vol_snap, self.test_snap) - self.drv.delete_volume(self.test_vol) - - def test_remove_export(self): - self.drv.create_volume(self.test_vol) - self.drv.terminate_connection(self.test_vol, '') - self.drv.delete_volume(self.test_vol) - - def test_volume_attach_detach(self): - self.drv.create_volume(self.test_vol) + self.drv._verify_clone_size.assert_called_once_with( + self.test_snap, + self.test_vol_snap['size'] * units.Gi) + self.drv.zfssa.clone_snapshot.assert_called_once_with( + lcfg.zfssa_pool, + lcfg.zfssa_project, + self.test_snap['volume_name'], + self.test_snap['name'], + self.test_vol_snap['name']) + + @mock.patch.object(iscsi.ZFSSAISCSIDriver, '_get_provider_info') + def test_volume_attach_detach(self, _get_provider_info): + lcfg = self.configuration + test_target_iqn = 'iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd' + stub_val = {'provider_location': + '%s %s 0' % (lcfg.zfssa_target_portal, test_target_iqn)} + self.drv._get_provider_info.return_value = stub_val connector = dict(initiator='iqn.1-0.org.deb:01:d7') props = self.drv.initialize_connection(self.test_vol, connector) + self.drv._get_provider_info.assert_called_once_with(self.test_vol) self.assertEqual('iscsi', props['driver_volume_type']) self.assertEqual(self.test_vol['id'], props['data']['volume_id']) + self.assertEqual(lcfg.zfssa_target_portal, + props['data']['target_portal']) + self.assertEqual(test_target_iqn, props['data']['target_iqn']) + self.assertEqual('0', props['data']['target_lun']) + self.assertFalse(props['data']['target_discovered']) self.drv.terminate_connection(self.test_vol, '') - self.drv.delete_volume(self.test_vol) + self.drv.zfssa.set_lun_initiatorgroup.assert_called_once_with( + lcfg.zfssa_pool, + lcfg.zfssa_project, + self.test_vol['name'], + '') def test_get_volume_stats(self): - self.drv.get_volume_stats(refresh=False) + self.drv.zfssa.get_pool_stats.return_value = 2 * units.Gi, 3 * units.Gi + lcfg = self.configuration + stats = self.drv.get_volume_stats(refresh=True) + self.drv.zfssa.get_pool_stats.assert_called_once_with(lcfg.zfssa_pool) + self.assertEqual('Oracle', stats['vendor_name']) + self.assertEqual(self.configuration.volume_backend_name, + stats['volume_backend_name']) + self.assertEqual(self.drv.VERSION, stats['driver_version']) + self.assertEqual(self.drv.protocol, stats['storage_protocol']) + self.assertEqual(0, stats['reserved_percentage']) + self.assertFalse(stats['QoS_support']) + self.assertEqual(3, stats['total_capacity_gb']) + self.assertEqual(2, stats['free_capacity_gb']) def test_extend_volume(self): - self.drv.create_volume(self.test_vol) + lcfg = self.configuration self.drv.extend_volume(self.test_vol, 3) - self.drv.delete_volume(self.test_vol) + self.drv.zfssa.set_lun_props.assert_called_once_with( + lcfg.zfssa_pool, + lcfg.zfssa_project, + self.test_vol['name'], + volsize= 3 * units.Gi) @mock.patch('cinder.volume.volume_types.get_volume_type_extra_specs') def test_get_voltype_specs(self, get_volume_type_extra_specs): @@ -422,35 +245,6 @@ class TestZFSSAISCSIDriver(test.TestCase): return val -class FakeAddIni2InitGrp(object): - - def logout(self): - result = client.RestResult() - result.status = client.Status.ACCEPTED - return result - - def get(self, path, **kwargs): - result = client.RestResult() - result.status = client.Status.OK - result.data = json.JSONEncoder().encode({'group': - {'initiators': - ['iqn.1-0.org.deb:01:d7']}}) - return result - - def put(self, path, body="", **kwargs): - result = client.RestResult() - result.status = client.Status.ACCEPTED - return result - - def post(self, path, body="", **kwargs): - result = client.RestResult() - result.status = client.Status.CREATED - return result - - def islogin(self): - return True - - class TestZFSSANFSDriver(test.TestCase): test_vol = { @@ -476,7 +270,7 @@ class TestZFSSANFSDriver(test.TestCase): def setUp(self, _factory_zfssa): super(TestZFSSANFSDriver, self).setUp() self._create_fake_config() - _factory_zfssa.return_value = FakeNFSZFSSA() + _factory_zfssa.return_value = mock.MagicMock(spec=rest.ZFSSANfsApi) self.drv = zfssanfs.ZFSSANFSDriver(configuration=self.configuration) self.drv._execute = fake_utils.fake_execute self.drv.do_setup({}) @@ -499,17 +293,31 @@ class TestZFSSANFSDriver(test.TestCase): self.configuration.nfs_used_ratio = 1 def test_create_delete_snapshot(self): + lcfg = self.configuration self.drv.create_snapshot(self.test_snap) + self.drv.zfssa.create_snapshot.assert_called_once_with( + lcfg.zfssa_nfs_pool, + lcfg.zfssa_nfs_project, + lcfg.zfssa_nfs_share, + mock.ANY) + self.drv.zfssa.create_snapshot_of_volume_file.assert_called_once_with( + src_file=mock.ANY, + dst_file=self.test_snap['name']) self.drv.delete_snapshot(self.test_snap) + self.drv.zfssa.delete_snapshot_of_volume_file.assert_called_with( + src_file=self.test_snap['name']) def test_create_volume_from_snapshot(self): self.drv.create_snapshot(self.test_snap) with mock.patch.object(self.drv, '_ensure_shares_mounted'): - prov_loc = self.drv.create_volume_from_snapshot(self.test_vol_snap, - self.test_snap, - method='COPY') - self.assertEqual('2.2.2.2:/export/nfs_share', - prov_loc['provider_location']) + self.drv.create_volume_from_snapshot(self.test_vol_snap, + self.test_snap, + method='COPY') + + self.drv.zfssa.create_volume_from_snapshot_file.\ + assert_called_once_with(src_file=self.test_snap['name'], + dst_file=self.test_vol_snap['name'], + method='COPY') def test_get_volume_stats(self): self.drv._mounted_shares = ['nfs_share'] @@ -518,7 +326,360 @@ class TestZFSSANFSDriver(test.TestCase): mock_get_share_capacity_info: mock_get_share_capacity_info.return_value = (1073741824, 9663676416) - self.drv.get_volume_stats(refresh=True) + stats = self.drv.get_volume_stats(refresh=True) + self.assertEqual(1, stats['free_capacity_gb']) + self.assertEqual(10, stats['total_capacity_gb']) def tearDown(self): super(TestZFSSANFSDriver, self).tearDown() + + +class TestZFSSAApi(test.TestCase): + + @mock.patch.object(rest, 'factory_restclient') + def setUp(self, _restclient): + super(TestZFSSAApi, self).setUp() + self.host = 'fakehost' + self.user = 'fakeuser' + self.url = None + self.pool = 'fakepool' + self.project = 'fakeproject' + self.vol = 'fakevol' + self.snap = 'fakesnapshot' + self.clone = 'fakeclone' + self.targetalias = 'fakealias' + _restclient.return_value = mock.MagicMock(spec=client.RestClientURL) + self.zfssa = rest.ZFSSAApi() + self.zfssa.set_host('fakehost') + self.pool_url = '/api/storage/v1/pools/' + + def _create_response(self, status, data='data'): + response = FakeResponse(status, data) + return response + + def test_create_project(self): + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.OK) + self.zfssa.create_project(self.pool, self.project) + expected_svc = self.pool_url + self.pool + '/projects/' + self.project + self.zfssa.rclient.get.assert_called_with(expected_svc) + + def test_create_initiator(self): + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.OK) + initiator = 'iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd' + alias = 'init-group' + self.zfssa.create_initiator(initiator, alias) + self.zfssa.rclient.get.assert_called_with( + '/api/san/v1/iscsi/initiators/alias=' + alias) + + def test_create_target(self): + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.NOT_FOUND) + ret_val = json.dumps( + {'target': {'iqn': + 'iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd'}}) + self.zfssa.rclient.post.return_value = self._create_response( + client.Status.CREATED, ret_val) + alias = 'tgt-group' + self.zfssa.create_target(alias) + self.zfssa.rclient.post.assert_called_with('/api/san/v1/iscsi/targets', + {'alias': alias}) + + def test_get_target(self): + ret_val = json.dumps( + {'target': {'href': 'fake_href', + 'alias': 'tgt-group', + 'iqn': + 'iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd', + 'targetchapuser': '', + 'targetchapsecret': '', + 'interfaces': ['nge0']}}) + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.OK, ret_val) + ret = self.zfssa.get_target('tgt-group') + self.zfssa.rclient.get.assert_called_once_with( + '/api/san/v1/iscsi/targets/alias=tgt-group') + self.assertEqual('iqn.1986-03.com.sun:02:00000-aaaa-bbbb-cccc-ddddd', + ret) + + def test_verify_pool(self): + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.OK) + self.zfssa.verify_pool(self.pool) + self.zfssa.rclient.get.assert_called_with(self.pool_url + self.pool) + + def test_verify_project(self): + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.NOT_FOUND) + self.assertRaises(exception.VolumeBackendAPIException, + self.zfssa.verify_project, + self.pool, + self.project) + + def test_verify_initiator(self): + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.OK) + self.zfssa.verify_initiator('iqn.1-0.org.deb:01:d7') + self.zfssa.rclient.get.assert_called_with( + '/api/san/v1/iscsi/initiators/iqn.1-0.org.deb:01:d7') + + def test_verify_target(self): + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.BAD_REQUEST) + self.assertRaises(exception.VolumeBackendAPIException, + self.zfssa.verify_target, + self.targetalias) + + def test_create_delete_lun(self): + arg = json.dumps({'name': self.vol, + 'initiatorgroup': 'com.sun.ms.vss.hg.maskAll'}) + self.zfssa.rclient.post.return_value = self._create_response( + client.Status.CREATED, data=arg) + self.zfssa.create_lun(self.pool, self.project, self.vol, 1, 'tgt-grp', + None) + expected_arg = {'name': self.vol, + 'volsize': 1, + 'targetgroup': 'tgt-grp', + 'initiatorgroup': 'com.sun.ms.vss.hg.maskAll'} + self.zfssa.rclient.post.assert_called_with( + self.pool_url + self.pool + '/projects/' + self.project + '/luns', + expected_arg) + + self.zfssa.rclient.delete.return_value = self._create_response( + client.Status.NO_CONTENT) + self.zfssa.delete_lun(self.pool, self.project, self.vol) + self.zfssa.rclient.delete.assert_called_with( + self.pool_url + self.pool + '/projects/' + self.project + + '/luns/' + self.vol) + + def test_create_delete_snapshot(self): + self.zfssa.rclient.post.return_value = self._create_response( + client.Status.CREATED) + self.zfssa.create_snapshot(self.pool, + self.project, + self.vol, + self.snap) + expected_arg = {'name': self.snap} + self.zfssa.rclient.post.assert_called_with( + self.pool_url + self.pool + '/projects/' + self.project + + '/luns/' + self.vol + '/snapshots', expected_arg) + + self.zfssa.rclient.delete.return_value = self._create_response( + client.Status.NO_CONTENT) + self.zfssa.delete_snapshot(self.pool, + self.project, + self.vol, + self.snap) + self.zfssa.rclient.delete.assert_called_with( + self.pool_url + self.pool + '/projects/' + self.project + + '/luns/' + self.vol + '/snapshots/' + self.snap) + + def test_clone_snapshot(self): + self.zfssa.rclient.put.return_value = self._create_response( + client.Status.CREATED) + self.zfssa.clone_snapshot(self.pool, + self.project, + self.vol, + self.snap, + self.clone) + expected_svc = '/api/storage/v1/pools/' + self.pool + '/projects/' + \ + self.project + '/luns/' + self.vol + '/snapshots/' + self.snap + \ + '/clone' + expected_arg = {'project': self.project, + 'share': self.clone, + 'nodestroy': True} + self.zfssa.rclient.put.assert_called_with(expected_svc, expected_arg) + + +class TestZFSSANfsApi(test.TestCase): + + @mock.patch.object(rest, 'factory_restclient') + def setUp(self, _restclient): + super(TestZFSSANfsApi, self).setUp() + self.host = 'fakehost' + self.user = 'fakeuser' + self.url = None + self.pool = 'fakepool' + self.project = 'fakeproject' + self.share = 'fakeshare' + self.snap = 'fakesnapshot' + self.targetalias = 'fakealias' + _restclient.return_value = mock.MagicMock(spec=client.RestClientURL) + self.webdavclient = mock.MagicMock(spec=webdavclient.ZFSSAWebDAVClient) + self.zfssa = rest.ZFSSANfsApi() + self.zfssa.set_host('fakehost') + self.pool_url = '/api/storage/v1/pools/' + + def _create_response(self, status, data='data'): + response = FakeResponse(status, data) + return response + + def test_verify_share(self): + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.OK) + self.zfssa.verify_share(self.pool, self.project, self.share) + self.zfssa.rclient.get.assert_called_with(self.pool_url + self.pool + + '/projects/' + self.project + + '/filesystems/' + self.share) + + def test_create_delete_snapshot(self): + self.zfssa.rclient.post.return_value = self._create_response( + client.Status.CREATED) + self.zfssa.create_snapshot(self.pool, + self.project, + self.share, + self.snap) + expected_arg = {'name': self.snap} + self.zfssa.rclient.post.assert_called_with( + self.pool_url + self.pool + '/projects/' + self.project + + '/filesystems/' + self.share + '/snapshots', expected_arg) + + self.zfssa.rclient.delete.return_value = self._create_response( + client.Status.NO_CONTENT) + self.zfssa.delete_snapshot(self.pool, + self.project, + self.share, + self.snap) + self.zfssa.rclient.delete.assert_called_with( + self.pool_url + self.pool + '/projects/' + self.project + + '/filesystems/' + self.share + '/snapshots/' + self.snap) + + def create_delete_snapshot_of_volume_file(self): + src_file = "fake_src_file" + dst_file = "fake_dst_file" + self.zfssa.create_snapshot_of_volume_file(src_file=src_file, + dst_file=dst_file) + self.zfssa.webdavclient.request.assert_called_once_with( + src_file=src_file, + dst_file=dst_file, + method='COPY') + self.zfssa.delete_snapshot_of_volume_file(src_file=src_file) + self.zfssa.webdavclient.request.assert_called_once_with( + src_file=src_file, method='DELETE') + + def test_get_share(self): + ret_val = json.dumps({'filesystem': 'test_fs'}) + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.OK, ret_val) + ret = self.zfssa.get_share(self.pool, self.project, self.share) + self.zfssa.rclient.get.assert_called_with(self.pool_url + self.pool + + '/projects/' + self.project + + '/filesystems/' + self.share) + self.assertEqual('test_fs', ret) + + def test_create_share(self): + self.zfssa.rclient.get.return_value = self._create_response( + client.Status.NOT_FOUND) + self.zfssa.rclient.post.return_value = self._create_response( + client.Status.BAD_REQUEST) + self.assertRaises(exception.VolumeBackendAPIException, + self.zfssa.create_share, + self.pool, + self.project, + self.share, + {}) + + @mock.patch.object(rest.ZFSSANfsApi, '_change_service_state') + @mock.patch.object(rest.ZFSSANfsApi, 'verify_service') + def test_enable_disable_modify_service(self, + verify_service, + _change_service_state): + self.zfssa.enable_service('http') + self.zfssa._change_service_state.assert_called_with( + 'http', state='enable') + self.zfssa.verify_service.assert_called_with('http') + + self.zfssa.disable_service('http') + self.zfssa._change_service_state.assert_called_with( + 'http', state='disable') + self.zfssa.verify_service.assert_called_with('http', status='offline') + + ret_val = json.dumps({'service': { + "href": "/api/service/v1/services/http", + "": "online", + "require_login": False, + "protocols": "http/https", + "listen_port": 81, + "https_port": 443}}) + self.zfssa.rclient.put.return_value = self._create_response( + client.Status.ACCEPTED, ret_val) + args = {'listen_port': 81} + self.zfssa.modify_service('http', args) + self.zfssa.rclient.put.called_with('/api/service/v1/services/http', + args) + + +class TestRestClientURL(test.TestCase): + def setUp(self): + super(TestRestClientURL, self).setUp() + self.timeout = 60 + self.url = '1.1.1.1' + self.client = client.RestClientURL(self.url, timeout=self.timeout) + + @mock.patch.object(client.RestClientURL, 'request') + def test_post(self, _request): + path = '/api/storage/v1/pools' + body = {'name': 'fakepool'} + self.client.post(path, body=body) + self.client.request.assert_called_with(path, 'POST', body) + + @mock.patch.object(client.RestClientURL, 'request') + def test_get(self, _request): + path = '/api/storage/v1/pools' + self.client.get(path) + self.client.request.assert_called_with(path, 'GET') + + @mock.patch.object(client.RestClientURL, 'request') + def test_put(self, _request): + path = '/api/storage/v1/pools' + body = {'name': 'fakepool'} + self.client.put(path, body=body) + self.client.request.assert_called_with(path, 'PUT', body) + + @mock.patch.object(client.RestClientURL, 'request') + def test_delete(self, _request): + path = '/api/storage/v1/pools' + self.client.delete(path) + self.client.request.assert_called_with(path, 'DELETE') + + @mock.patch.object(client.RestClientURL, 'request') + def test_head(self, _request): + path = '/api/storage/v1/pools' + self.client.head(path) + self.client.request.assert_called_with(path, 'HEAD') + + @mock.patch.object(client, 'RestResult') + @mock.patch.object(client.urllib.request, 'Request') + @mock.patch.object(client.urllib.request, 'urlopen') + def test_request(self, _urlopen, _Request, _RestResult): + path = '/api/storage/v1/pools' + _urlopen.return_value = mock.Mock() + self.client.request(path, mock.ANY) + _Request.assert_called_with(self.url + path, None, self.client.headers) + _urlopen.assert_called_with(mock.ANY, timeout=self.timeout) + _RestResult.assert_called_with(response=mock.ANY) + + @mock.patch.object(client, 'RestResult') + @mock.patch.object(client.urllib.request, 'Request') + @mock.patch.object(client.urllib.request, 'urlopen') + @mock.patch.object(client, 'ssl', new_callable=FakeSSL) + def test_ssl_with_context(self, _ssl, _urlopen, _Request, _RestResult): + """Test PEP476 certificate opt_out fix. """ + path = '/api/storage/v1/pools' + _urlopen.return_value = mock.Mock() + self.client.request(path, mock.ANY) + _urlopen.assert_called_once_with(mock.ANY, + timeout=self.timeout, + context='fakecontext') + + @mock.patch.object(client, 'RestResult') + @mock.patch.object(client.urllib.request, 'Request') + @mock.patch.object(client.urllib.request, 'urlopen') + @mock.patch.object(client, 'ssl', new_callable=object) + def test_ssl_no_context(self, _ssl, _urlopen, _Request, _RestResult): + """Verify the PEP476 fix backward compatibility. """ + path = '/api/storage/v1/pools' + _urlopen.return_value = mock.Mock() + self.client.request(path, mock.ANY) + _urlopen.assert_called_once_with(mock.ANY, timeout=self.timeout) diff --git a/cinder/volume/drivers/zfssa/zfssarest.py b/cinder/volume/drivers/zfssa/zfssarest.py index 63a8ab068..1c7dd4a00 100644 --- a/cinder/volume/drivers/zfssa/zfssarest.py +++ b/cinder/volume/drivers/zfssa/zfssarest.py @@ -26,6 +26,10 @@ from cinder.volume.drivers.zfssa import webdavclient LOG = log.getLogger(__name__) +def factory_restclient(url, **kwargs): + return restclient.RestClientURL(url, **kwargs) + + class ZFSSAApi(object): """ZFSSA API proxy class""" @@ -60,7 +64,7 @@ class ZFSSAApi(object): def set_host(self, host, timeout=None): self.host = host self.url = "https://" + self.host + ":215" - self.rclient = restclient.RestClientURL(self.url, timeout=timeout) + self.rclient = factory_restclient(self.url, timeout=timeout) def login(self, auth_str): """Login to the appliance"""