From 388e52ce23b881f95f68f40458011433aee2808b Mon Sep 17 00:00:00 2001 From: Chuck Fouts Date: Sun, 12 Jun 2016 17:05:24 -0400 Subject: [PATCH] NetApp: Fix issue with busy snapshot deletion. This fixes the issue of deleting temporary snapshots created during the consistency group creation process. These temporary snapshots may not be deleted if the system is under load and the temporary snapshot remains in a "busy" state after the consistency group creation process is otherwise complete. This change also reduces lines of code by implementing a manager for the creation of FixedIntervalLoopingCall instances in the ONTAP drivers. This looping call manager also provides the ability to start all registered looping calls after the driver has been properly initialized. The looping call manager also makes it easy to ensure that FixedIntervalLoopingCall instances are not instantiated in Unit Tests. Closes-Bug: #1596679 Change-Id: I13096a8c94a32e68814f81900032dbcc6a4a9806 --- .../drivers/netapp/dataontap/client/fakes.py | 34 ++++ .../dataontap/client/test_client_7mode.py | 39 ++++ .../dataontap/client/test_client_base.py | 17 ++ .../dataontap/client/test_client_cmode.py | 67 +++++++ .../netapp/dataontap/test_block_7mode.py | 18 ++ .../netapp/dataontap/test_block_base.py | 98 +++++++--- .../netapp/dataontap/test_block_cmode.py | 59 +++--- .../netapp/dataontap/test_nfs_7mode.py | 31 +++- .../drivers/netapp/dataontap/test_nfs_base.py | 172 ++++++++++++------ .../netapp/dataontap/test_nfs_cmode.py | 58 +++--- .../dataontap/utils/test_loopingcalls.py | 63 +++++++ .../drivers/netapp/dataontap/block_7mode.py | 9 + .../drivers/netapp/dataontap/block_base.py | 47 +++-- .../drivers/netapp/dataontap/block_cmode.py | 40 ++-- .../netapp/dataontap/client/client_7mode.py | 37 ++++ .../netapp/dataontap/client/client_base.py | 16 ++ .../netapp/dataontap/client/client_cmode.py | 50 ++++- .../drivers/netapp/dataontap/nfs_7mode.py | 17 +- .../drivers/netapp/dataontap/nfs_base.py | 63 +++++-- .../drivers/netapp/dataontap/nfs_cmode.py | 44 +++-- .../netapp/dataontap/utils/loopingcalls.py | 43 +++++ 21 files changed, 831 insertions(+), 191 deletions(-) create mode 100644 cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_loopingcalls.py create mode 100644 cinder/volume/drivers/netapp/dataontap/utils/loopingcalls.py diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py index 2d64e62fd..dfce922ec 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py @@ -251,6 +251,7 @@ SNAPSHOT_INFO_FOR_PRESENT_NOT_BUSY_SNAPSHOT_CMODE = etree.XML(""" %(snapshot_name)s False %(vol_name)s + abcd-ef01-2345-6789 1 @@ -283,6 +284,39 @@ SNAPSHOT_INFO_FOR_PRESENT_NOT_BUSY_SNAPSHOT_7MODE = etree.XML(""" %(snapshot_name)s False %(vol_name)s + abcd-ef01-2345-6789 + + + +""" % { + 'snapshot_name': fake.SNAPSHOT['name'], + 'vol_name': fake.SNAPSHOT['volume_id'], +}) + +SNAPSHOT_INFO_MARKED_FOR_DELETE_SNAPSHOT_7MODE = etree.XML(""" + + + + deleted_cinder_%(snapshot_name)s + False + %(vol_name)s + abcd-ef01-2345-6789 + + + +""" % { + 'snapshot_name': fake.SNAPSHOT['name'], + 'vol_name': fake.SNAPSHOT['volume_id'], +}) + +SNAPSHOT_INFO_MARKED_FOR_DELETE_SNAPSHOT_7MODE_BUSY = etree.XML(""" + + + + deleted_cinder_busy_snapshot + True + %(vol_name)s + abcd-ef01-2345-6789 diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_7mode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_7mode.py index 0323da072..aa5a7a6a5 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_7mode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_7mode.py @@ -17,6 +17,7 @@ import uuid +import ddt from lxml import etree import mock import paramiko @@ -30,6 +31,7 @@ from cinder.tests.unit.volume.drivers.netapp.dataontap.client import ( from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api from cinder.volume.drivers.netapp.dataontap.client import client_7mode +from cinder.volume.drivers.netapp.dataontap.client import client_base from cinder.volume.drivers.netapp import utils as netapp_utils CONNECTION_INFO = {'hostname': 'hostname', @@ -39,6 +41,7 @@ CONNECTION_INFO = {'hostname': 'hostname', 'password': 'passw0rd'} +@ddt.ddt class NetApp7modeClientTestCase(test.TestCase): def setUp(self): @@ -816,3 +819,39 @@ class NetApp7modeClientTestCase(test.TestCase): self.assertRaises(exception.SnapshotNotFound, self.client.get_snapshot, expected_vol_name, expected_snapshot_name) + + @ddt.data({ + 'mock_return': + fake_client.SNAPSHOT_INFO_MARKED_FOR_DELETE_SNAPSHOT_7MODE, + 'expected': [{ + 'name': client_base.DELETED_PREFIX + fake.SNAPSHOT_NAME, + 'instance_id': 'abcd-ef01-2345-6789', + 'volume_name': fake.SNAPSHOT['volume_id'], + }] + }, { + 'mock_return': fake_client.NO_RECORDS_RESPONSE, + 'expected': [], + }, { + 'mock_return': + fake_client.SNAPSHOT_INFO_MARKED_FOR_DELETE_SNAPSHOT_7MODE_BUSY, + 'expected': [], + }) + @ddt.unpack + def test_get_snapshots_marked_for_deletion(self, mock_return, expected): + api_response = netapp_api.NaElement(mock_return) + volume_list = [fake.SNAPSHOT['volume_id']] + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + + result = self.client.get_snapshots_marked_for_deletion(volume_list) + + api_args = { + 'target-name': fake.SNAPSHOT['volume_id'], + 'target-type': 'volume', + 'terse': 'true', + } + + self.client.send_request.assert_called_once_with( + 'snapshot-list-info', api_args) + self.assertListEqual(expected, result) diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_base.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_base.py index 4fefb8075..8e31cf409 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_base.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_base.py @@ -591,3 +591,20 @@ class NetAppBaseClientTestCase(test.TestCase): mock.call(fake.FLEXVOL, fake.SNAPSHOT_NAME), ] mock_get_snapshot.assert_has_calls(calls) + + def test_rename_snapshot(self): + self.mock_object(self.client, 'send_request') + + self.client.rename_snapshot( + fake.SNAPSHOT['volume_id'], fake.SNAPSHOT_NAME, + client_base.DELETED_PREFIX + fake.SNAPSHOT_NAME) + + api_args = { + 'volume': fake.SNAPSHOT['volume_id'], + 'current-name': fake.SNAPSHOT_NAME, + 'new-name': + client_base.DELETED_PREFIX + fake.SNAPSHOT_NAME, + } + + self.client.send_request.assert_called_once_with( + 'snapshot-rename', api_args) diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py index e9fe65d10..396c3110c 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py @@ -23,6 +23,7 @@ from lxml import etree import mock import paramiko import six +import time from cinder import exception from cinder import ssh_utils @@ -31,6 +32,7 @@ from cinder.tests.unit.volume.drivers.netapp.dataontap.client import ( fakes as fake_client) from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api +from cinder.volume.drivers.netapp.dataontap.client import client_base from cinder.volume.drivers.netapp.dataontap.client import client_cmode from cinder.volume.drivers.netapp import utils as netapp_utils @@ -3153,6 +3155,8 @@ class NetAppCmodeClientTestCase(test.TestCase): self.assertEqual(expected_prov_opts, actual_prov_opts) def test_wait_for_busy_snapshot(self): + # Need to mock sleep as it is called by @utils.retry + self.mock_object(time, 'sleep') mock_get_snapshot = self.mock_object( self.client, 'get_snapshot', mock.Mock(return_value=fake.SNAPSHOT) @@ -3162,3 +3166,66 @@ class NetAppCmodeClientTestCase(test.TestCase): mock_get_snapshot.assert_called_once_with(fake.FLEXVOL, fake.SNAPSHOT_NAME) + + def test_wait_for_busy_snapshot_raise_exception(self): + # Need to mock sleep as it is called by @utils.retry + self.mock_object(time, 'sleep') + BUSY_SNAPSHOT = dict(fake.SNAPSHOT) + BUSY_SNAPSHOT['busy'] = True + mock_get_snapshot = self.mock_object( + self.client, 'get_snapshot', + mock.Mock(return_value=BUSY_SNAPSHOT) + ) + + self.assertRaises(exception.SnapshotIsBusy, + self.client.wait_for_busy_snapshot, + fake.FLEXVOL, fake.SNAPSHOT_NAME) + + calls = [ + mock.call(fake.FLEXVOL, fake.SNAPSHOT_NAME), + mock.call(fake.FLEXVOL, fake.SNAPSHOT_NAME), + mock.call(fake.FLEXVOL, fake.SNAPSHOT_NAME), + ] + mock_get_snapshot.assert_has_calls(calls) + + @ddt.data({ + 'mock_return': + fake_client.SNAPSHOT_INFO_FOR_PRESENT_NOT_BUSY_SNAPSHOT_CMODE, + 'expected': [{ + 'name': fake.SNAPSHOT_NAME, + 'instance_id': 'abcd-ef01-2345-6789', + 'volume_name': fake.SNAPSHOT['volume_id'], + }] + }, { + 'mock_return': fake_client.NO_RECORDS_RESPONSE, + 'expected': [], + }) + @ddt.unpack + def test_get_snapshots_marked_for_deletion(self, mock_return, expected): + api_response = netapp_api.NaElement(mock_return) + self.mock_object(self.client, + 'send_request', + mock.Mock(return_value=api_response)) + + result = self.client.get_snapshots_marked_for_deletion() + + api_args = { + 'query': { + 'snapshot-info': { + 'name': client_base.DELETED_PREFIX + '*', + 'vserver': self.vserver, + 'busy': 'false' + }, + }, + 'desired-attributes': { + 'snapshot-info': { + 'name': None, + 'volume': None, + 'snapshot-instance-uuid': None, + } + }, + } + + self.client.send_request.assert_called_once_with( + 'snapshot-get-iter', api_args) + self.assertListEqual(expected, result) diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_7mode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_7mode.py index d8c9eee93..31cb2261f 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_7mode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_7mode.py @@ -119,9 +119,12 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase): self.zapi_client.get_ontapi_version.return_value = (1, 9) self.mock_object(self.library, '_refresh_volume_info') self.library.volume_list = ['open1', 'open2'] + mock_add_looping_tasks = self.mock_object( + self.library, '_add_looping_tasks') self.library.check_for_setup_error() + mock_add_looping_tasks.assert_called_once_with() super_check_for_setup_error.assert_called_once_with() def test_check_for_setup_error_no_filtered_pools(self): @@ -746,3 +749,18 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase): mock_super_delete_snapshot.assert_called_once_with(fake.SNAPSHOT) self.assertTrue(self.library.vol_refresh_voluntary) + + def test_add_looping_tasks(self): + mock_super_add_looping_tasks = self.mock_object( + block_base.NetAppBlockStorageLibrary, '_add_looping_tasks') + + self.library._add_looping_tasks() + + mock_super_add_looping_tasks.assert_called_once_with() + + def test_get_backing_flexvol_names(self): + self.library.volume_list = ['vol0', 'vol1', 'vol2'] + + result = self.library._get_backing_flexvol_names() + + self.assertEqual('vol2', result[2]) diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_base.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_base.py index 5ac3f94bb..157477f7e 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_base.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_base.py @@ -28,7 +28,6 @@ import uuid import ddt import mock from oslo_log import versionutils -from oslo_service import loopingcall from oslo_utils import units import six @@ -39,6 +38,7 @@ from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes from cinder.volume.drivers.netapp.dataontap import block_base from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api +from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls from cinder.volume.drivers.netapp import utils as na_utils from cinder.volume import utils as volume_utils @@ -769,44 +769,31 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase): self.library.do_setup(mock.Mock()) self.zapi_client.get_lun_list.return_value = ['lun1'] self.library._extract_and_populate_luns = mock.Mock() - mock_start_periodic_tasks = self.mock_object( - self.library, '_start_periodic_tasks') + mock_looping_start_tasks = self.mock_object( + self.library.loopingcalls, 'start_tasks') + self.library.check_for_setup_error() self.library._extract_and_populate_luns.assert_called_once_with( ['lun1']) - mock_start_periodic_tasks.assert_called_once_with() + mock_looping_start_tasks.assert_called_once_with() @mock.patch.object(na_utils, 'check_flags', mock.Mock()) def test_check_for_setup_error_no_os_host(self): + mock_start_tasks = self.mock_object( + self.library.loopingcalls, 'start_tasks') self.library.configuration.netapp_lun_ostype = None self.library.configuration.netapp_host_type = None self.library.do_setup(mock.Mock()) self.zapi_client.get_lun_list.return_value = ['lun1'] self.library._extract_and_populate_luns = mock.Mock() - mock_start_periodic_tasks = self.mock_object( - self.library, '_start_periodic_tasks') self.library.check_for_setup_error() + self.library._extract_and_populate_luns.assert_called_once_with( ['lun1']) - mock_start_periodic_tasks.assert_called_once_with() - def test_start_periodic_tasks(self): - - mock_handle_housekeeping_tasks = self.mock_object( - self.library, '_handle_housekeeping_tasks') - - housekeeping_periodic_task = mock.Mock() - mock_loopingcall = self.mock_object( - loopingcall, 'FixedIntervalLoopingCall', - mock.Mock(return_value=housekeeping_periodic_task)) - - self.library._start_periodic_tasks() - - mock_loopingcall.assert_called_once_with( - mock_handle_housekeeping_tasks) - self.assertTrue(housekeeping_periodic_task.start.called) + mock_start_tasks.assert_called_once_with() def test_delete_volume(self): mock_delete_lun = self.mock_object(self.library, '_delete_lun') @@ -1372,6 +1359,8 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase): mock_clone_lun = self.mock_object(self.library, '_clone_lun') mock_busy = self.mock_object( self.zapi_client, 'wait_for_busy_snapshot') + mock_delete_snapshot = self.mock_object( + self.zapi_client, 'delete_snapshot') self.library.create_cgsnapshot(fake.CG_SNAPSHOT, [snapshot]) @@ -1383,6 +1372,37 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase): fake.CG_VOLUME_NAME, fake.CG_SNAPSHOT_NAME, source_snapshot=fake.CG_SNAPSHOT_ID) mock_busy.assert_called_once_with(fake.POOL_NAME, fake.CG_SNAPSHOT_ID) + mock_delete_snapshot.assert_called_once_with( + fake.POOL_NAME, fake.CG_SNAPSHOT_ID) + + def test_create_cgsnapshot_busy_snapshot(self): + snapshot = fake.CG_SNAPSHOT + snapshot['volume'] = fake.CG_VOLUME + + mock_extract_host = self.mock_object( + volume_utils, 'extract_host', + mock.Mock(return_value=fake.POOL_NAME)) + mock_clone_lun = self.mock_object(self.library, '_clone_lun') + mock_busy = self.mock_object( + self.zapi_client, 'wait_for_busy_snapshot') + mock_busy.side_effect = exception.SnapshotIsBusy(snapshot['name']) + mock_delete_snapshot = self.mock_object( + self.zapi_client, 'delete_snapshot') + mock_mark_snapshot_for_deletion = self.mock_object( + self.zapi_client, 'mark_snapshot_for_deletion') + + self.library.create_cgsnapshot(fake.CG_SNAPSHOT, [snapshot]) + + mock_extract_host.assert_called_once_with( + fake.CG_VOLUME['host'], level='pool') + self.zapi_client.create_cg_snapshot.assert_called_once_with( + set([fake.POOL_NAME]), fake.CG_SNAPSHOT_ID) + mock_clone_lun.assert_called_once_with( + fake.CG_VOLUME_NAME, fake.CG_SNAPSHOT_NAME, + source_snapshot=fake.CG_SNAPSHOT_ID) + mock_delete_snapshot.assert_not_called() + mock_mark_snapshot_for_deletion.assert_called_once_with( + fake.POOL_NAME, fake.CG_SNAPSHOT_ID) def test_delete_cgsnapshot(self): @@ -1500,3 +1520,37 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase): } mock_clone_source_to_destination.assert_called_once_with( clone_source_to_destination_args, fake.VOLUME) + + def test_add_looping_tasks(self): + mock_add_task = self.mock_object(self.library.loopingcalls, 'add_task') + mock_call = self.mock_object( + self.library, '_delete_snapshots_marked_for_deletion') + + self.library._add_looping_tasks() + + mock_add_task.assert_called_once_with( + mock_call, + loopingcalls.ONE_MINUTE, + loopingcalls.ONE_MINUTE) + + def test_delete_snapshots_marked_for_deletion(self): + snapshots = [{ + 'name': fake.SNAPSHOT_NAME, + 'volume_name': fake.VOLUME['name'] + }] + mock_get_backing_flexvol_names = self.mock_object( + self.library, '_get_backing_flexvol_names') + mock_get_backing_flexvol_names.return_value = [fake.VOLUME['name']] + mock_get_snapshots_marked = self.mock_object( + self.zapi_client, 'get_snapshots_marked_for_deletion') + mock_get_snapshots_marked.return_value = snapshots + mock_delete_snapshot = self.mock_object( + self.zapi_client, 'delete_snapshot') + + self.library._delete_snapshots_marked_for_deletion() + + mock_get_backing_flexvol_names.assert_called_once_with() + mock_get_snapshots_marked.assert_called_once_with( + [fake.VOLUME['name']]) + mock_delete_snapshot.assert_called_once_with( + fake.VOLUME['name'], fake.SNAPSHOT_NAME) diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py index 5ee7cee76..82a3cdd26 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py @@ -20,7 +20,6 @@ Mock unit tests for the NetApp block storage C-mode library import ddt import mock -from oslo_service import loopingcall from cinder import exception from cinder import test @@ -34,6 +33,7 @@ from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api from cinder.volume.drivers.netapp.dataontap.client import client_base from cinder.volume.drivers.netapp.dataontap.performance import perf_cmode from cinder.volume.drivers.netapp.dataontap.utils import data_motion +from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls from cinder.volume.drivers.netapp.dataontap.utils import utils as config_utils from cinder.volume.drivers.netapp import utils as na_utils @@ -104,21 +104,28 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase): block_base.NetAppBlockStorageLibrary, 'check_for_setup_error') mock_check_api_permissions = self.mock_object( self.library.ssc_library, 'check_api_permissions') + mock_add_looping_tasks = self.mock_object( + self.library, '_add_looping_tasks') mock_get_pool_map = self.mock_object( self.library, '_get_flexvol_to_pool_map', mock.Mock(return_value={'fake_map': None})) + mock_add_looping_tasks = self.mock_object( + self.library, '_add_looping_tasks') self.library.check_for_setup_error() self.assertEqual(1, super_check_for_setup_error.call_count) mock_check_api_permissions.assert_called_once_with() + self.assertEqual(1, mock_add_looping_tasks.call_count) mock_get_pool_map.assert_called_once_with() + mock_add_looping_tasks.assert_called_once_with() def test_check_for_setup_error_no_filtered_pools(self): self.mock_object(block_base.NetAppBlockStorageLibrary, 'check_for_setup_error') mock_check_api_permissions = self.mock_object( self.library.ssc_library, 'check_api_permissions') + self.mock_object(self.library, '_add_looping_tasks') self.mock_object( self.library, '_get_flexvol_to_pool_map', mock.Mock(return_value={})) @@ -128,25 +135,6 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase): mock_check_api_permissions.assert_called_once_with() - def test_start_periodic_tasks(self): - - mock_update_ssc = self.mock_object( - self.library, '_update_ssc') - super_start_periodic_tasks = self.mock_object( - block_base.NetAppBlockStorageLibrary, '_start_periodic_tasks') - - update_ssc_periodic_task = mock.Mock() - mock_loopingcall = self.mock_object( - loopingcall, 'FixedIntervalLoopingCall', - mock.Mock(return_value=update_ssc_periodic_task)) - - self.library._start_periodic_tasks() - - mock_loopingcall.assert_called_once_with(mock_update_ssc) - self.assertTrue(update_ssc_periodic_task.start.called) - mock_update_ssc.assert_called_once_with() - super_start_periodic_tasks.assert_called_once_with() - @ddt.data({'replication_enabled': True, 'failed_over': False}, {'replication_enabled': True, 'failed_over': True}, {'replication_enabled': False, 'failed_over': False}) @@ -158,12 +146,9 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase): mock.Mock(return_value=fake_utils.SSC.keys())) self.library.replication_enabled = replication_enabled self.library.failed_over = failed_over - super_handle_housekeeping_tasks = self.mock_object( - block_base.NetAppBlockStorageLibrary, '_handle_housekeeping_tasks') self.library._handle_housekeeping_tasks() - super_handle_housekeeping_tasks.assert_called_once_with() (self.zapi_client.remove_unused_qos_policy_groups. assert_called_once_with()) if replication_enabled and not failed_over: @@ -706,3 +691,31 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase): self.assertEqual('dev1', self.library.failed_over_backend_name) self.assertEqual('dev1', actual_active) self.assertEqual([], vol_updates) + + def test_add_looping_tasks(self): + mock_update_ssc = self.mock_object(self.library, '_update_ssc') + mock_remove_unused_qos_policy_groups = self.mock_object( + self.zapi_client, 'remove_unused_qos_policy_groups') + mock_add_task = self.mock_object(self.library.loopingcalls, 'add_task') + mock_super_add_looping_tasks = self.mock_object( + block_base.NetAppBlockStorageLibrary, '_add_looping_tasks') + + self.library._add_looping_tasks() + + mock_update_ssc.assert_called_once_with() + mock_add_task.assert_has_calls([ + mock.call(mock_update_ssc, + loopingcalls.ONE_HOUR, + loopingcalls.ONE_HOUR), + mock.call(mock_remove_unused_qos_policy_groups, + loopingcalls.ONE_MINUTE, + loopingcalls.ONE_MINUTE)]) + mock_super_add_looping_tasks.assert_called_once_with() + + def test_get_backing_flexvol_names(self): + mock_ssc_library = self.mock_object( + self.library.ssc_library, 'get_ssc') + + self.library._get_backing_flexvol_names() + + mock_ssc_library.assert_called_once_with() diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_7mode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_7mode.py index a4ed4437a..7bfd097dd 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_7mode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_7mode.py @@ -25,6 +25,7 @@ from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake from cinder.tests.unit.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.dataontap import nfs_base from cinder.volume.drivers.netapp import utils as na_utils @@ -196,9 +197,37 @@ class NetApp7modeNfsDriverTestCase(test.TestCase): ] hosts = [snap['volume']['host'] for snap in snapshots] - flexvols = self.driver._get_backing_flexvol_names(hosts) + flexvols = self.driver._get_flexvol_names_from_hosts(hosts) self.assertEqual(3, len(flexvols)) self.assertIn('volume1', flexvols) self.assertIn('volume2', flexvols) self.assertIn('volume3', flexvols) + + def test_check_for_setup_error(self): + mock_get_ontapi_version = self.mock_object( + self.driver.zapi_client, 'get_ontapi_version') + mock_get_ontapi_version.return_value = ['1', '10'] + mock_add_looping_tasks = self.mock_object( + self.driver, '_add_looping_tasks') + mock_super_check_for_setup_error = self.mock_object( + nfs_base.NetAppNfsDriver, 'check_for_setup_error') + + self.driver.check_for_setup_error() + + mock_get_ontapi_version.assert_called_once_with() + mock_add_looping_tasks.assert_called_once_with() + mock_super_check_for_setup_error.assert_called_once_with() + + def test_add_looping_tasks(self): + mock_super_add_looping_tasks = self.mock_object( + nfs_base.NetAppNfsDriver, '_add_looping_tasks') + + self.driver._add_looping_tasks() + mock_super_add_looping_tasks.assert_called_once_with() + + def test_get_backing_flexvol_names(self): + + result = self.driver._get_backing_flexvol_names() + + self.assertEqual('path', result[0]) diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py index 22b8e9a00..28c30edb3 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py @@ -25,7 +25,6 @@ import ddt import mock from os_brick.remotefs import remotefs as remotefs_brick from oslo_concurrency import processutils -from oslo_service import loopingcall from oslo_utils import units import shutil @@ -38,6 +37,7 @@ from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake from cinder import utils from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api from cinder.volume.drivers.netapp.dataontap import nfs_base +from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls from cinder.volume.drivers.netapp import utils as na_utils from cinder.volume.drivers import nfs from cinder.volume.drivers import remotefs @@ -99,33 +99,6 @@ class NetAppNfsDriverTestCase(test.TestCase): self.assertEqual(expected_reserved_percentage, round(result['reserved_percentage'])) - def test_check_for_setup_error(self): - super_check_for_setup_error = self.mock_object( - nfs.NfsDriver, 'check_for_setup_error') - mock_start_periodic_tasks = self.mock_object( - self.driver, '_start_periodic_tasks') - - self.driver.check_for_setup_error() - - super_check_for_setup_error.assert_called_once_with() - mock_start_periodic_tasks.assert_called_once_with() - - def test_start_periodic_tasks(self): - - mock_handle_housekeeping_tasks = self.mock_object( - self.driver, '_handle_housekeeping_tasks') - - housekeeping_periodic_task = mock.Mock() - mock_loopingcall = self.mock_object( - loopingcall, 'FixedIntervalLoopingCall', - mock.Mock(return_value=housekeeping_periodic_task)) - - self.driver._start_periodic_tasks() - - mock_loopingcall.assert_called_once_with( - mock_handle_housekeeping_tasks) - self.assertTrue(housekeeping_periodic_task.start.called) - def test_get_capacity_info_ipv4_share(self): expected = fake.CAPACITY_VALUES get_capacity = self.driver.zapi_client.get_flexvol_capacity @@ -402,36 +375,47 @@ class NetAppNfsDriverTestCase(test.TestCase): self.driver._update_volume_stats) def test_copy_image_to_volume_base_exception(self): - updates = { - 'name': fake.VOLUME_NAME, - 'id': fake.VOLUME_ID, - 'provider_location': fake.PROVIDER_LOCATION, - } mock_info_log = self.mock_object(nfs_base.LOG, 'info') - fake_vol = fake_volume.fake_volume_obj(self.ctxt, **updates) self.mock_object(remotefs.RemoteFSDriver, 'copy_image_to_volume', mock.Mock(side_effect=exception.NfsException)) self.assertRaises(exception.NfsException, self.driver.copy_image_to_volume, - 'fake_context', fake_vol, + 'fake_context', fake.NFS_VOLUME, 'fake_img_service', fake.IMAGE_FILE_ID) mock_info_log.assert_not_called() - @ddt.data(None, Exception) - def test_copy_image_to_volume(self, exc): + def test_copy_image_to_volume(self): + mock_log = self.mock_object(nfs_base, 'LOG') + mock_copy_image = self.mock_object( + remotefs.RemoteFSDriver, 'copy_image_to_volume') + mock_register_image = self.mock_object( + self.driver, '_register_image_in_cache') + + self.driver.copy_image_to_volume('fake_context', + fake.NFS_VOLUME, + 'fake_img_service', + fake.IMAGE_FILE_ID) + + mock_copy_image.assert_called_once_with( + 'fake_context', fake.NFS_VOLUME, 'fake_img_service', + fake.IMAGE_FILE_ID) + self.assertEqual(1, mock_log.info.call_count) + mock_register_image.assert_called_once_with( + fake.NFS_VOLUME, fake.IMAGE_FILE_ID) + + @ddt.data(None, Exception) + def test__register_image_in_cache(self, exc): mock_log = self.mock_object(nfs_base, 'LOG') - self.mock_object(remotefs.RemoteFSDriver, 'copy_image_to_volume') self.mock_object(self.driver, '_do_clone_rel_img_cache', mock.Mock(side_effect=exc)) - retval = self.driver.copy_image_to_volume( - 'fake_context', fake.NFS_VOLUME, 'fake_img_service', - fake.IMAGE_FILE_ID) + retval = self.driver._register_image_in_cache( + fake.NFS_VOLUME, fake.IMAGE_FILE_ID) self.assertIsNone(retval) self.assertEqual(exc is not None, mock_log.warning.called) - self.assertEqual(2, mock_log.info.call_count) + self.assertEqual(1, mock_log.info.call_count) @ddt.data(True, False) def test_do_clone_rel_img_cache(self, path_exists): @@ -975,7 +959,10 @@ class NetAppNfsDriverTestCase(test.TestCase): def test_create_consistencygroup_from_src(self): mock_create_volume_from_snapshot = self.mock_object( - self.driver, 'create_volume_from_snapshot') + self.driver, 'create_volume_from_snapshot', + mock.Mock(return_value={ + 'provider_location': fake.PROVIDER_LOCATION + })) model_update, volumes_model_update = ( self.driver.create_consistencygroup_from_src( @@ -985,11 +972,15 @@ class NetAppNfsDriverTestCase(test.TestCase): mock_create_volume_from_snapshot.assert_called_once_with( fake.VOLUME, fake.SNAPSHOT) self.assertIsNone(model_update) - self.assertIsNone(volumes_model_update) + expected_update = [{ + 'id': fake.VOLUME['id'], + 'provider_location': fake.PROVIDER_LOCATION, + }] + self.assertEqual(expected_update, volumes_model_update) def test_create_consistencygroup_from_src_source_vols(self): mock_get_snapshot_flexvols = self.mock_object( - self.driver, '_get_backing_flexvol_names') + self.driver, '_get_flexvol_names_from_hosts') mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME])) mock_clone_backing_file = self.mock_object( self.driver, '_clone_backing_file_for_volume') @@ -1001,21 +992,25 @@ class NetAppNfsDriverTestCase(test.TestCase): self.driver.create_consistencygroup_from_src( fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.VOLUME], source_cg=fake.CONSISTENCY_GROUP, - source_vols=[fake.CG_VOLUME])) + source_vols=[fake.NFS_VOLUME])) mock_get_snapshot_flexvols.assert_called_once_with( - [fake.CG_VOLUME['host']]) + [fake.NFS_VOLUME['host']]) self.driver.zapi_client.create_cg_snapshot.assert_called_once_with( set([fake.CG_POOL_NAME]), fake_snapshot_name) mock_clone_backing_file.assert_called_once_with( - fake.CG_VOLUME['name'], fake.VOLUME['name'], fake.CG_VOLUME['id'], - source_snapshot=fake_snapshot_name) + fake.NFS_VOLUME['name'], fake.VOLUME['name'], + fake.NFS_VOLUME['id'], source_snapshot=fake_snapshot_name) mock_busy.assert_called_once_with( fake.CG_POOL_NAME, fake_snapshot_name) self.driver.zapi_client.delete_snapshot.assert_called_once_with( fake.CG_POOL_NAME, fake_snapshot_name) self.assertIsNone(model_update) - self.assertIsNone(volumes_model_update) + expected_update = [{ + 'id': fake.NFS_VOLUME['id'], + 'provider_location': fake.PROVIDER_LOCATION, + }] + self.assertEqual(expected_update, volumes_model_update) def test_create_consistencygroup_from_src_invalid_parms(self): @@ -1029,7 +1024,7 @@ class NetAppNfsDriverTestCase(test.TestCase): snapshot = fake.CG_SNAPSHOT snapshot['volume'] = fake.CG_VOLUME mock_get_snapshot_flexvols = self.mock_object( - self.driver, '_get_backing_flexvol_names') + self.driver, '_get_flexvol_names_from_hosts') mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME])) mock_clone_backing_file = self.mock_object( self.driver, '_clone_backing_file_for_volume') @@ -1051,6 +1046,36 @@ class NetAppNfsDriverTestCase(test.TestCase): self.driver.zapi_client.delete_snapshot.assert_called_once_with( fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID) + def test_create_cgsnapshot_busy_snapshot(self): + snapshot = fake.CG_SNAPSHOT + snapshot['volume'] = fake.CG_VOLUME + mock_get_snapshot_flexvols = self.mock_object( + self.driver, '_get_flexvol_names_from_hosts') + mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME])) + mock_clone_backing_file = self.mock_object( + self.driver, '_clone_backing_file_for_volume') + mock_busy = self.mock_object( + self.driver.zapi_client, 'wait_for_busy_snapshot') + mock_busy.side_effect = exception.SnapshotIsBusy(snapshot['name']) + mock_mark_snapshot_for_deletion = self.mock_object( + self.zapi_client, 'mark_snapshot_for_deletion') + + self.driver.create_cgsnapshot( + fake.CG_CONTEXT, fake.CG_SNAPSHOT, [snapshot]) + + mock_get_snapshot_flexvols.assert_called_once_with( + [snapshot['volume']['host']]) + self.driver.zapi_client.create_cg_snapshot.assert_called_once_with( + set([fake.CG_POOL_NAME]), fake.CG_SNAPSHOT_ID) + mock_clone_backing_file.assert_called_once_with( + snapshot['volume']['name'], snapshot['name'], + snapshot['volume']['id'], source_snapshot=fake.CG_SNAPSHOT_ID) + mock_busy.assert_called_once_with( + fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID) + self.driver.zapi_client.delete_snapshot.assert_not_called() + mock_mark_snapshot_for_deletion.assert_called_once_with( + fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID) + def test_delete_consistencygroup_volume_delete_failure(self): self.mock_object(self.driver, '_delete_file', mock.Mock(side_effect=Exception)) @@ -1072,3 +1097,48 @@ class NetAppNfsDriverTestCase(test.TestCase): self.assertEqual('deleted', volumes[0]['status']) mock_delete_file.assert_called_once_with( fake.CG_VOLUME_ID, fake.CG_VOLUME_NAME) + + def test_check_for_setup_error(self): + super_check_for_setup_error = self.mock_object( + nfs.NfsDriver, 'check_for_setup_error') + mock_start_tasks = self.mock_object( + self.driver.loopingcalls, 'start_tasks') + + self.driver.check_for_setup_error() + + super_check_for_setup_error.assert_called_once_with() + mock_start_tasks.assert_called_once_with() + + def test_add_looping_tasks(self): + mock_add_task = self.mock_object(self.driver.loopingcalls, 'add_task') + mock_call = self.mock_object( + self.driver, '_delete_snapshots_marked_for_deletion') + + self.driver._add_looping_tasks() + + mock_add_task.assert_called_once_with( + mock_call, + loopingcalls.ONE_MINUTE, + loopingcalls.ONE_MINUTE) + + def test_delete_snapshots_marked_for_deletion(self): + snapshots = [{ + 'name': fake.SNAPSHOT_NAME, + 'volume_name': fake.VOLUME['name'] + }] + mock_get_flexvol_names = self.mock_object( + self.driver, '_get_backing_flexvol_names') + mock_get_flexvol_names.return_value = [fake.VOLUME['name']] + mock_get_snapshots_marked = self.mock_object( + self.zapi_client, 'get_snapshots_marked_for_deletion') + mock_get_snapshots_marked.return_value = snapshots + mock_delete_snapshot = self.mock_object( + self.zapi_client, 'delete_snapshot') + + self.driver._delete_snapshots_marked_for_deletion() + + mock_get_flexvol_names.assert_called_once_with() + mock_get_snapshots_marked.assert_called_once_with( + [fake.VOLUME['name']]) + mock_delete_snapshot.assert_called_once_with( + fake.VOLUME['name'], fake.SNAPSHOT_NAME) diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py index 7989cf9b5..31f9aaf08 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py @@ -19,7 +19,6 @@ Mock unit tests for the NetApp cmode nfs storage driver import ddt import mock from os_brick.remotefs import remotefs as remotefs_brick -from oslo_service import loopingcall from oslo_utils import units from cinder import exception @@ -36,6 +35,7 @@ from cinder.volume.drivers.netapp.dataontap import nfs_base from cinder.volume.drivers.netapp.dataontap import nfs_cmode from cinder.volume.drivers.netapp.dataontap.performance import perf_cmode from cinder.volume.drivers.netapp.dataontap.utils import data_motion +from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls from cinder.volume.drivers.netapp.dataontap.utils import utils as config_utils from cinder.volume.drivers.netapp import utils as na_utils from cinder.volume.drivers import nfs @@ -384,30 +384,15 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): nfs_base.NetAppNfsDriver, 'check_for_setup_error') mock_check_api_permissions = self.mock_object( self.driver.ssc_library, 'check_api_permissions') + mock_add_looping_tasks = self.mock_object( + self.driver, '_add_looping_tasks') self.driver.check_for_setup_error() self.assertEqual(1, super_check_for_setup_error.call_count) mock_check_api_permissions.assert_called_once_with() - - def test_start_periodic_tasks(self): - - mock_update_ssc = self.mock_object( - self.driver, '_update_ssc') - super_start_periodic_tasks = self.mock_object( - nfs_base.NetAppNfsDriver, '_start_periodic_tasks') - - update_ssc_periodic_task = mock.Mock() - mock_loopingcall = self.mock_object( - loopingcall, 'FixedIntervalLoopingCall', - mock.Mock(return_value=update_ssc_periodic_task)) - - self.driver._start_periodic_tasks() - - mock_loopingcall.assert_called_once_with(mock_update_ssc) - self.assertTrue(update_ssc_periodic_task.start.called) - mock_update_ssc.assert_called_once_with() - super_start_periodic_tasks.assert_called_once_with() + self.assertEqual(1, mock_add_looping_tasks.call_count) + mock_add_looping_tasks.assert_called_once_with() @ddt.data({'replication_enabled': True, 'failed_over': False}, {'replication_enabled': True, 'failed_over': True}, @@ -420,12 +405,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): mock.Mock(return_value=fake_ssc.SSC.keys())) self.driver.replication_enabled = replication_enabled self.driver.failed_over = failed_over - super_handle_housekeeping_tasks = self.mock_object( - nfs_base.NetAppNfsDriver, '_handle_housekeeping_tasks') self.driver._handle_housekeeping_tasks() - super_handle_housekeeping_tasks.assert_called_once_with() (self.driver.zapi_client.remove_unused_qos_policy_groups. assert_called_once_with()) if replication_enabled and not failed_over: @@ -909,6 +891,26 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): mock_get_info.assert_has_calls([mock.call(fake.NFS_VOLUME)]) super_unmanage.assert_has_calls([mock.call(fake.NFS_VOLUME)]) + def test_add_looping_tasks(self): + mock_update_ssc = self.mock_object(self.driver, '_update_ssc') + mock_remove_unused_qos_policy_groups = self.mock_object( + self.driver.zapi_client, 'remove_unused_qos_policy_groups') + mock_add_task = self.mock_object(self.driver.loopingcalls, 'add_task') + mock_super_add_looping_tasks = self.mock_object( + nfs_base.NetAppNfsDriver, '_add_looping_tasks') + + self.driver._add_looping_tasks() + + mock_update_ssc.assert_called_once_with() + mock_add_task.assert_has_calls([ + mock.call(mock_update_ssc, + loopingcalls.ONE_HOUR, + loopingcalls.ONE_HOUR), + mock.call(mock_remove_unused_qos_policy_groups, + loopingcalls.ONE_MINUTE, + loopingcalls.ONE_MINUTE)]) + mock_super_add_looping_tasks.assert_called_once_with() + @ddt.data({'has_space': True, 'type_match': True, 'expected': True}, {'has_space': True, 'type_match': False, 'expected': False}, {'has_space': False, 'type_match': True, 'expected': False}, @@ -1367,10 +1369,18 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): mock_get_ssc.return_value = ssc hosts = [snap['volume']['host'] for snap in snapshots] - flexvols = self.driver._get_backing_flexvol_names(hosts) + flexvols = self.driver._get_flexvol_names_from_hosts(hosts) mock_get_ssc.assert_called_once_with() self.assertEqual(3, len(flexvols)) self.assertIn('volume1', flexvols) self.assertIn('volume2', flexvols) self.assertIn('volume3', flexvols) + + def test_get_backing_flexvol_names(self): + mock_ssc_library = self.mock_object( + self.driver.ssc_library, 'get_ssc') + + self.driver._get_backing_flexvol_names() + + mock_ssc_library.assert_called_once_with() diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_loopingcalls.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_loopingcalls.py new file mode 100644 index 000000000..045f44cb1 --- /dev/null +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_loopingcalls.py @@ -0,0 +1,63 @@ +# Copyright (c) 2016 Chuck Fouts. 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 mock +from oslo_service import loopingcall + +from cinder import test +from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls + + +class LoopingCallsTestCase(test.TestCase): + + def setUp(self): + super(LoopingCallsTestCase, self).setUp() + self.mock_first_looping_task = mock.Mock() + self.mock_second_looping_task = mock.Mock() + + self.mock_loopingcall = self.mock_object( + loopingcall, + 'FixedIntervalLoopingCall', + mock.Mock(side_effect=[self.mock_first_looping_task, + self.mock_second_looping_task]) + ) + self.loopingcalls = loopingcalls.LoopingCalls() + + def test_add_task(self): + interval = 3600 + initial_delay = 5 + + self.loopingcalls.add_task(self.mock_first_looping_task, interval) + self.loopingcalls.add_task( + self.mock_second_looping_task, interval, initial_delay) + + self.assertEqual(2, len(self.loopingcalls.tasks)) + self.assertEqual(interval, self.loopingcalls.tasks[0].interval) + self.assertEqual(initial_delay, + self.loopingcalls.tasks[1].initial_delay) + + def test_start_tasks(self): + interval = 3600 + initial_delay = 5 + + self.loopingcalls.add_task(self.mock_first_looping_task, interval) + self.loopingcalls.add_task( + self.mock_second_looping_task, interval, initial_delay) + + self.loopingcalls.start_tasks() + + self.mock_first_looping_task.start.assert_called_once_with( + interval, 0) + self.mock_second_looping_task.start.assert_called_once_with( + interval, initial_delay) diff --git a/cinder/volume/drivers/netapp/dataontap/block_7mode.py b/cinder/volume/drivers/netapp/dataontap/block_7mode.py index dc8fed078..2949d5547 100644 --- a/cinder/volume/drivers/netapp/dataontap/block_7mode.py +++ b/cinder/volume/drivers/netapp/dataontap/block_7mode.py @@ -119,8 +119,13 @@ class NetAppBlockStorage7modeLibrary(block_base.NetAppBlockStorageLibrary): 'Ensure that the configuration option ' 'netapp_pool_name_search_pattern is set correctly.') raise exception.NetAppDriverException(msg) + self._add_looping_tasks() super(NetAppBlockStorage7modeLibrary, self).check_for_setup_error() + def _add_looping_tasks(self): + """Add tasks that need to be executed at a fixed interval.""" + super(NetAppBlockStorage7modeLibrary, self)._add_looping_tasks() + def _create_lun(self, volume_name, lun_name, size, metadata, qos_policy_group_name=None): """Creates a LUN, handling Data ONTAP differences as needed.""" @@ -444,3 +449,7 @@ class NetAppBlockStorage7modeLibrary(block_base.NetAppBlockStorageLibrary): return (super(NetAppBlockStorage7modeLibrary, self) ._get_preferred_target_from_list(target_details_list)) + + def _get_backing_flexvol_names(self): + """Returns a list of backing flexvol names.""" + return self.volume_list or [] diff --git a/cinder/volume/drivers/netapp/dataontap/block_base.py b/cinder/volume/drivers/netapp/dataontap/block_base.py index d9d1b3952..8587b0c5a 100644 --- a/cinder/volume/drivers/netapp/dataontap/block_base.py +++ b/cinder/volume/drivers/netapp/dataontap/block_base.py @@ -32,7 +32,6 @@ import uuid from oslo_log import log as logging from oslo_log import versionutils -from oslo_service import loopingcall from oslo_utils import excutils from oslo_utils import units import six @@ -41,13 +40,13 @@ from cinder import exception from cinder.i18n import _, _LE, _LI, _LW from cinder import utils from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api +from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls from cinder.volume.drivers.netapp import options as na_opts from cinder.volume.drivers.netapp import utils as na_utils from cinder.volume import utils as volume_utils from cinder.zonemanager import utils as fczm_utils LOG = logging.getLogger(__name__) -HOUSEKEEPING_INTERVAL_SECONDS = 600 # ten minutes class NetAppLun(object): @@ -118,6 +117,7 @@ class NetAppBlockStorageLibrary(object): self.max_over_subscription_ratio = ( self.configuration.max_over_subscription_ratio) self.reserved_percentage = self._get_reserved_percentage() + self.loopingcalls = loopingcalls.LoopingCalls() def _get_reserved_percentage(self): # If the legacy config option if it is set to the default @@ -170,21 +170,26 @@ class NetAppBlockStorageLibrary(object): lun_list = self.zapi_client.get_lun_list() self._extract_and_populate_luns(lun_list) LOG.debug("Success getting list of LUNs from server.") + self.loopingcalls.start_tasks() - self._start_periodic_tasks() + def _add_looping_tasks(self): + """Add tasks that need to be executed at a fixed interval. - def _start_periodic_tasks(self): - """Start recurring tasks common to all Data ONTAP block drivers.""" + Inheriting class overrides and then explicitly calls this method. + """ + # Add the task that deletes snapshots marked for deletion. + self.loopingcalls.add_task( + self._delete_snapshots_marked_for_deletion, + loopingcalls.ONE_MINUTE, + loopingcalls.ONE_MINUTE) - # Start the task that runs other housekeeping tasks, such as deletion - # of previously soft-deleted storage artifacts. - housekeeping_periodic_task = loopingcall.FixedIntervalLoopingCall( - self._handle_housekeeping_tasks) - housekeeping_periodic_task.start( - interval=HOUSEKEEPING_INTERVAL_SECONDS, initial_delay=0) - - def _handle_housekeeping_tasks(self): - """Handle various cleanup activities.""" + def _delete_snapshots_marked_for_deletion(self): + volume_list = self._get_backing_flexvol_names() + snapshots = self.zapi_client.get_snapshots_marked_for_deletion( + volume_list) + for snapshot in snapshots: + self.zapi_client.delete_snapshot( + snapshot['volume_name'], snapshot['name']) def get_pool(self, volume): """Return pool name where volume resides. @@ -1081,8 +1086,14 @@ class NetAppBlockStorageLibrary(object): source_snapshot=cgsnapshot['id']) for flexvol in flexvols: - self.zapi_client.wait_for_busy_snapshot(flexvol, cgsnapshot['id']) - self.zapi_client.delete_snapshot(flexvol, cgsnapshot['id']) + try: + self.zapi_client.wait_for_busy_snapshot( + flexvol, cgsnapshot['id']) + self.zapi_client.delete_snapshot( + flexvol, cgsnapshot['id']) + except exception.SnapshotIsBusy: + self.zapi_client.mark_snapshot_for_deletion( + flexvol, cgsnapshot['id']) return None, None @@ -1127,3 +1138,7 @@ class NetAppBlockStorageLibrary(object): self._clone_source_to_destination(source, volume) return None, None + + def _get_backing_flexvol_names(self): + """Returns a list of backing flexvol names.""" + raise NotImplementedError() diff --git a/cinder/volume/drivers/netapp/dataontap/block_cmode.py b/cinder/volume/drivers/netapp/dataontap/block_cmode.py index b97739b04..2cc73f7a1 100644 --- a/cinder/volume/drivers/netapp/dataontap/block_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/block_cmode.py @@ -25,7 +25,6 @@ Volume driver library for NetApp C-mode block storage systems. """ from oslo_log import log as logging -from oslo_service import loopingcall from oslo_utils import units import six @@ -36,13 +35,13 @@ from cinder.volume.drivers.netapp.dataontap import block_base from cinder.volume.drivers.netapp.dataontap.performance import perf_cmode from cinder.volume.drivers.netapp.dataontap.utils import capabilities from cinder.volume.drivers.netapp.dataontap.utils import data_motion +from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls from cinder.volume.drivers.netapp.dataontap.utils import utils as cmode_utils from cinder.volume.drivers.netapp import options as na_opts from cinder.volume.drivers.netapp import utils as na_utils LOG = logging.getLogger(__name__) -SSC_UPDATE_INTERVAL_SECONDS = 3600 # hourly @six.add_metaclass(utils.TraceWrapperMetaclass) @@ -103,29 +102,36 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary, 'Ensure that the configuration option ' 'netapp_pool_name_search_pattern is set correctly.') raise exception.NetAppDriverException(msg) - + self._add_looping_tasks() super(NetAppBlockStorageCmodeLibrary, self).check_for_setup_error() - def _start_periodic_tasks(self): - """Start recurring tasks for NetApp cDOT block drivers.""" + def _add_looping_tasks(self): + """Add tasks that need to be executed at a fixed interval.""" - # Note(cknight): Run the task once in the current thread to prevent a + # Note(cknight): Run the update once in the current thread to prevent a # race with the first invocation of _update_volume_stats. self._update_ssc() - # Start the task that updates the slow-changing storage service catalog - ssc_periodic_task = loopingcall.FixedIntervalLoopingCall( - self._update_ssc) - ssc_periodic_task.start( - interval=SSC_UPDATE_INTERVAL_SECONDS, - initial_delay=SSC_UPDATE_INTERVAL_SECONDS) + # Add the task that updates the slow-changing storage service catalog + self.loopingcalls.add_task(self._update_ssc, + loopingcalls.ONE_HOUR, + loopingcalls.ONE_HOUR) - super(NetAppBlockStorageCmodeLibrary, self)._start_periodic_tasks() + # Add the task that harvests soft-deleted QoS policy groups. + self.loopingcalls.add_task( + self.zapi_client.remove_unused_qos_policy_groups, + loopingcalls.ONE_MINUTE, + loopingcalls.ONE_MINUTE) + + self.loopingcalls.add_task( + self._handle_housekeeping_tasks, + loopingcalls.TEN_MINUTES, + 0) + + super(NetAppBlockStorageCmodeLibrary, self)._add_looping_tasks() def _handle_housekeeping_tasks(self): """Handle various cleanup activities.""" - (super(NetAppBlockStorageCmodeLibrary, self). - _handle_housekeeping_tasks()) # Harvest soft-deleted QoS policy groups self.zapi_client.remove_unused_qos_policy_groups() @@ -412,3 +418,7 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary, """Failover a backend to a secondary replication target.""" return self._failover_host(volumes, secondary_id=secondary_id) + + def _get_backing_flexvol_names(self): + """Returns a list of backing flexvol names.""" + return self.ssc_library.get_ssc().keys() diff --git a/cinder/volume/drivers/netapp/dataontap/client/client_7mode.py b/cinder/volume/drivers/netapp/dataontap/client/client_7mode.py index 90f7cbae0..80d18c8b3 100644 --- a/cinder/volume/drivers/netapp/dataontap/client/client_7mode.py +++ b/cinder/volume/drivers/netapp/dataontap/client/client_7mode.py @@ -549,3 +549,40 @@ class Client(client_base.Client): raise exception.SnapshotNotFound(snapshot_id=snapshot_name) return snapshot + + def get_snapshots_marked_for_deletion(self, volume_list=None): + """Get a list of snapshots marked for deletion.""" + snapshots = [] + + for volume_name in volume_list: + api_args = { + 'target-name': volume_name, + 'target-type': 'volume', + 'terse': 'true', + } + result = self.send_request('snapshot-list-info', api_args) + snapshots.extend( + self._parse_snapshot_list_info_result(result, volume_name)) + + return snapshots + + def _parse_snapshot_list_info_result(self, result, volume_name): + snapshots = [] + snapshots_elem = result.get_child_by_name( + 'snapshots') or netapp_api.NaElement('none') + snapshot_info_list = snapshots_elem.get_children() + for snapshot_info in snapshot_info_list: + snapshot_name = snapshot_info.get_child_content('name') + snapshot_busy = strutils.bool_from_string( + snapshot_info.get_child_content('busy')) + snapshot_id = snapshot_info.get_child_content( + 'snapshot-instance-uuid') + if (not snapshot_busy and + snapshot_name.startswith(client_base.DELETED_PREFIX)): + snapshots.append({ + 'name': snapshot_name, + 'instance_id': snapshot_id, + 'volume_name': volume_name, + }) + + return snapshots diff --git a/cinder/volume/drivers/netapp/dataontap/client/client_base.py b/cinder/volume/drivers/netapp/dataontap/client/client_base.py index 858fc3e2f..5855c5d8e 100644 --- a/cinder/volume/drivers/netapp/dataontap/client/client_base.py +++ b/cinder/volume/drivers/netapp/dataontap/client/client_base.py @@ -34,6 +34,8 @@ from cinder.volume.drivers.netapp import utils as na_utils LOG = logging.getLogger(__name__) +DELETED_PREFIX = 'deleted_cinder_' + @six.add_metaclass(utils.TraceWrapperMetaclass) class Client(object): @@ -457,3 +459,17 @@ class Client(object): "for volume clone dependency to clear.", {"snap": snapshot_name, "vol": flexvol}) raise exception.SnapshotIsBusy(snapshot_name=snapshot_name) + + def mark_snapshot_for_deletion(self, volume, snapshot_name): + """Mark snapshot for deletion by renaming snapshot.""" + return self.rename_snapshot( + volume, snapshot_name, DELETED_PREFIX + snapshot_name) + + def rename_snapshot(self, volume, current_name, new_name): + """Renames a snapshot.""" + api_args = { + 'volume': volume, + 'current-name': current_name, + 'new-name': new_name, + } + return self.send_request('snapshot-rename', api_args) diff --git a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py index 81acb0df7..eec38e5c6 100644 --- a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py @@ -34,7 +34,6 @@ from oslo_utils import strutils LOG = logging.getLogger(__name__) -DELETED_PREFIX = 'deleted_cinder_' DEFAULT_MAX_PAGE_LENGTH = 50 @@ -546,7 +545,7 @@ class Client(client_base.Client): # matching that pattern. if spec is not None: current_name = spec['policy_name'] - new_name = DELETED_PREFIX + current_name + new_name = client_base.DELETED_PREFIX + current_name try: self.qos_policy_group_rename(current_name, new_name) except netapp_api.NaApiError as ex: @@ -562,7 +561,7 @@ class Client(client_base.Client): api_args = { 'query': { 'qos-policy-group-info': { - 'policy-group': '%s*' % DELETED_PREFIX, + 'policy-group': '%s*' % client_base.DELETED_PREFIX, 'vserver': self.vserver, } }, @@ -1450,6 +1449,51 @@ class Client(client_base.Client): return counter_data + def get_snapshots_marked_for_deletion(self, volume_list=None): + """Get a list of snapshots marked for deletion. + + :param volume_list: placeholder parameter to match 7mode client method + signature. + """ + + api_args = { + 'query': { + 'snapshot-info': { + 'name': client_base.DELETED_PREFIX + '*', + 'vserver': self.vserver, + 'busy': 'false', + }, + }, + 'desired-attributes': { + 'snapshot-info': { + 'name': None, + 'volume': None, + 'snapshot-instance-uuid': None, + } + }, + } + + result = self.send_request('snapshot-get-iter', api_args) + + snapshots = [] + + attributes = result.get_child_by_name( + 'attributes-list') or netapp_api.NaElement('none') + snapshot_info_list = attributes.get_children() + for snapshot_info in snapshot_info_list: + snapshot_name = snapshot_info.get_child_content('name') + snapshot_id = snapshot_info.get_child_content( + 'snapshot-instance-uuid') + snapshot_volume = snapshot_info.get_child_content('volume') + + snapshots.append({ + 'name': snapshot_name, + 'instance_id': snapshot_id, + 'volume_name': snapshot_volume, + }) + + return snapshots + def get_snapshot(self, volume_name, snapshot_name): """Gets a single snapshot.""" api_args = { diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_7mode.py b/cinder/volume/drivers/netapp/dataontap/nfs_7mode.py index 98974d023..d375f0469 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_7mode.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_7mode.py @@ -80,8 +80,13 @@ class NetApp7modeNfsDriver(nfs_base.NetAppNfsDriver): else: msg = _("Data ONTAP API version could not be determined.") raise exception.VolumeBackendAPIException(data=msg) + self._add_looping_tasks() super(NetApp7modeNfsDriver, self).check_for_setup_error() + def _add_looping_tasks(self): + """Add tasks that need to be executed at a fixed interval.""" + super(NetApp7modeNfsDriver, self)._add_looping_tasks() + def _clone_backing_file_for_volume(self, volume_name, clone_name, volume_id, share=None, is_snapshot=False, @@ -223,7 +228,17 @@ class NetApp7modeNfsDriver(nfs_base.NetAppNfsDriver): # 7-mode DOT does not support QoS. return - def _get_backing_flexvol_names(self, hosts): + def _get_backing_flexvol_names(self): + """Returns a list of backing flexvol names.""" + flexvol_names = [] + for nfs_share in self._mounted_shares: + flexvol_name = nfs_share.rsplit('/', 1)[1] + flexvol_names.append(flexvol_name) + LOG.debug("Found flexvol %s", flexvol_name) + + return flexvol_names + + def _get_flexvol_names_from_hosts(self, hosts): """Returns a set of flexvol names.""" flexvols = set() for host in hosts: diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_base.py b/cinder/volume/drivers/netapp/dataontap/nfs_base.py index ac7919e43..c4440c14b 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_base.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_base.py @@ -32,7 +32,6 @@ import time from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log as logging -from oslo_service import loopingcall from oslo_utils import units import six from six.moves import urllib @@ -42,6 +41,7 @@ from cinder.i18n import _, _LE, _LI, _LW from cinder.image import image_utils from cinder import utils from cinder.volume import driver +from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls from cinder.volume.drivers.netapp import options as na_opts from cinder.volume.drivers.netapp import utils as na_utils from cinder.volume.drivers import nfs @@ -83,6 +83,7 @@ class NetAppNfsDriver(driver.ManageableVD, self.configuration.append_config_values(na_opts.netapp_img_cache_opts) self.configuration.append_config_values(na_opts.netapp_nfs_extra_opts) self.backend_name = self.host.split('@')[1] + self.loopingcalls = loopingcalls.LoopingCalls() def do_setup(self, context): super(NetAppNfsDriver, self).do_setup(context) @@ -93,20 +94,26 @@ class NetAppNfsDriver(driver.ManageableVD, def check_for_setup_error(self): """Returns an error if prerequisites aren't met.""" super(NetAppNfsDriver, self).check_for_setup_error() - self._start_periodic_tasks() + self.loopingcalls.start_tasks() - def _start_periodic_tasks(self): - """Start recurring tasks common to all Data ONTAP NFS drivers.""" + def _add_looping_tasks(self): + """Add tasks that need to be executed at a fixed interval. - # Start the task that runs other housekeeping tasks, such as deletion - # of previously soft-deleted storage artifacts. - housekeeping_periodic_task = loopingcall.FixedIntervalLoopingCall( - self._handle_housekeeping_tasks) - housekeeping_periodic_task.start( - interval=HOUSEKEEPING_INTERVAL_SECONDS, initial_delay=0) + Inheriting class overrides and then explicitly calls this method. + """ + # Add the task that deletes snapshots marked for deletion. + self.loopingcalls.add_task( + self._delete_snapshots_marked_for_deletion, + loopingcalls.ONE_MINUTE, + loopingcalls.ONE_MINUTE) - def _handle_housekeeping_tasks(self): - """Handle various cleanup activities.""" + def _delete_snapshots_marked_for_deletion(self): + volume_list = self._get_backing_flexvol_names() + snapshots = self.zapi_client.get_snapshots_marked_for_deletion( + volume_list) + for snapshot in snapshots: + self.zapi_client.delete_snapshot( + snapshot['volume_name'], snapshot['name']) def get_pool(self, volume): """Return pool name where volume resides. @@ -266,7 +273,11 @@ class NetAppNfsDriver(driver.ManageableVD, """Clone backing file for Cinder volume.""" raise NotImplementedError() - def _get_backing_flexvol_names(self, hosts): + def _get_backing_flexvol_names(self): + """Returns backing flexvol names.""" + raise NotImplementedError() + + def _get_flexvol_names_from_hosts(self, hosts): """Returns a set of flexvol names.""" raise NotImplementedError() @@ -1083,7 +1094,7 @@ class NetAppNfsDriver(driver.ManageableVD, """ hosts = [snapshot['volume']['host'] for snapshot in snapshots] - flexvols = self._get_backing_flexvol_names(hosts) + flexvols = self._get_flexvol_names_from_hosts(hosts) # Create snapshot for backing flexvol self.zapi_client.create_cg_snapshot(flexvols, cgsnapshot['id']) @@ -1096,9 +1107,14 @@ class NetAppNfsDriver(driver.ManageableVD, # Delete backing flexvol snapshots for flexvol_name in flexvols: - self.zapi_client.wait_for_busy_snapshot( - flexvol_name, cgsnapshot['id']) - self.zapi_client.delete_snapshot(flexvol_name, cgsnapshot['id']) + try: + self.zapi_client.wait_for_busy_snapshot( + flexvol_name, cgsnapshot['id']) + self.zapi_client.delete_snapshot( + flexvol_name, cgsnapshot['id']) + except exception.SnapshotIsBusy: + self.zapi_client.mark_snapshot_for_deletion( + flexvol_name, cgsnapshot['id']) return None, None @@ -1118,16 +1134,19 @@ class NetAppNfsDriver(driver.ManageableVD, """ LOG.debug("VOLUMES %s ", [dict(vol) for vol in volumes]) model_update = None + volumes_model_update = [] if cgsnapshot: vols = zip(volumes, snapshots) for volume, snapshot in vols: - self.create_volume_from_snapshot(volume, snapshot) + update = self.create_volume_from_snapshot(volume, snapshot) + update['id'] = volume['id'] + volumes_model_update.append(update) elif source_cg and source_vols: hosts = [source_vol['host'] for source_vol in source_vols] - flexvols = self._get_backing_flexvol_names(hosts) + flexvols = self._get_flexvol_names_from_hosts(hosts) # Create snapshot for backing flexvol snapshot_name = 'snapshot-temp-' + source_cg['id'] @@ -1139,6 +1158,10 @@ class NetAppNfsDriver(driver.ManageableVD, self._clone_backing_file_for_volume( source_vol['name'], volume['name'], source_vol['id'], source_snapshot=snapshot_name) + update = {'id': volume['id'], + 'provider_location': source_vol['provider_location'], + } + volumes_model_update.append(update) # Delete backing flexvol snapshots for flexvol_name in flexvols: @@ -1151,4 +1174,4 @@ class NetAppNfsDriver(driver.ManageableVD, model_update = {} model_update['status'] = 'error' - return model_update, None + return model_update, volumes_model_update diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py index 6e5471e06..2e47d21a6 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py @@ -25,7 +25,6 @@ import os import uuid from oslo_log import log as logging -from oslo_service import loopingcall from oslo_utils import excutils import six @@ -38,6 +37,7 @@ from cinder.volume.drivers.netapp.dataontap import nfs_base from cinder.volume.drivers.netapp.dataontap.performance import perf_cmode from cinder.volume.drivers.netapp.dataontap.utils import capabilities from cinder.volume.drivers.netapp.dataontap.utils import data_motion +from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls from cinder.volume.drivers.netapp.dataontap.utils import utils as cmode_utils from cinder.volume.drivers.netapp import options as na_opts from cinder.volume.drivers.netapp import utils as na_utils @@ -45,7 +45,6 @@ from cinder.volume import utils as volume_utils LOG = logging.getLogger(__name__) -SSC_UPDATE_INTERVAL_SECONDS = 3600 # hourly @interface.volumedriver @@ -96,28 +95,39 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver, @utils.trace_method def check_for_setup_error(self): """Check that the driver is working and can communicate.""" - super(NetAppCmodeNfsDriver, self).check_for_setup_error() self.ssc_library.check_api_permissions() + self._add_looping_tasks() + super(NetAppCmodeNfsDriver, self).check_for_setup_error() - def _start_periodic_tasks(self): - """Start recurring tasks for NetApp cDOT NFS driver.""" + def _add_looping_tasks(self): + """Add tasks that need to be executed at a fixed interval.""" - # Note(cknight): Run the task once in the current thread to prevent a + # Note(cknight): Run the update once in the current thread to prevent a # race with the first invocation of _update_volume_stats. self._update_ssc() - # Start the task that updates the slow-changing storage service catalog - ssc_periodic_task = loopingcall.FixedIntervalLoopingCall( - self._update_ssc) - ssc_periodic_task.start( - interval=SSC_UPDATE_INTERVAL_SECONDS, - initial_delay=SSC_UPDATE_INTERVAL_SECONDS) + # Add the task that updates the slow-changing storage service catalog + self.loopingcalls.add_task(self._update_ssc, + loopingcalls.ONE_HOUR, + loopingcalls.ONE_HOUR) - super(NetAppCmodeNfsDriver, self)._start_periodic_tasks() + # Add the task that harvests soft-deleted QoS policy groups. + self.loopingcalls.add_task( + self.zapi_client.remove_unused_qos_policy_groups, + loopingcalls.ONE_MINUTE, + loopingcalls.ONE_MINUTE) + + # Add the task that runs other housekeeping tasks, such as deletion + # of previously soft-deleted storage artifacts. + self.loopingcalls.add_task( + self._handle_housekeeping_tasks, + loopingcalls.TEN_MINUTES, + 0) + + super(NetAppCmodeNfsDriver, self)._add_looping_tasks() def _handle_housekeeping_tasks(self): """Handle various cleanup activities.""" - super(NetAppCmodeNfsDriver, self)._handle_housekeeping_tasks() # Harvest soft-deleted QoS policy groups self.zapi_client.remove_unused_qos_policy_groups() @@ -676,7 +686,11 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver, return self._failover_host(volumes, secondary_id=secondary_id) - def _get_backing_flexvol_names(self, hosts): + def _get_backing_flexvol_names(self): + """Returns a list of backing flexvol names.""" + return self.ssc_library.get_ssc().keys() + + def _get_flexvol_names_from_hosts(self, hosts): """Returns a set of flexvol names.""" flexvols = set() ssc = self.ssc_library.get_ssc() diff --git a/cinder/volume/drivers/netapp/dataontap/utils/loopingcalls.py b/cinder/volume/drivers/netapp/dataontap/utils/loopingcalls.py new file mode 100644 index 000000000..2037e80aa --- /dev/null +++ b/cinder/volume/drivers/netapp/dataontap/utils/loopingcalls.py @@ -0,0 +1,43 @@ +# Copyright (c) 2016 Chuck Fouts. 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. +""" +Collects and starts tasks created from oslo_service.loopingcall. +""" + + +from collections import namedtuple +from oslo_service import loopingcall + +LoopingTask = namedtuple('LoopingTask', + ['looping_call', 'interval', 'initial_delay']) + +# Time intervals in seconds +ONE_MINUTE = 60 +TEN_MINUTES = 600 +ONE_HOUR = 3600 + + +class LoopingCalls(object): + + def __init__(self): + self.tasks = [] + + def add_task(self, call_function, interval, initial_delay=0): + looping_call = loopingcall.FixedIntervalLoopingCall(call_function) + task = LoopingTask(looping_call, interval, initial_delay) + self.tasks.append(task) + + def start_tasks(self): + for task in self.tasks: + task.looping_call.start(task.interval, task.initial_delay)