# Copyright (c) 2016 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from oslo_utils import timeutils from cinder import context from cinder import db from cinder import manager from cinder import objects from cinder import test from cinder.tests.unit import fake_constants from cinder.tests.unit import utils class FakeManager(manager.CleanableManager): def __init__(self, service_id=None, keep_after_clean=False): if service_id: self.service_id = service_id self.keep_after_clean = keep_after_clean def _do_cleanup(self, ctxt, vo_resource): vo_resource.status += '_cleaned' vo_resource.save() return self.keep_after_clean class TestCleanableManager(test.TestCase): def setUp(self): super(TestCleanableManager, self).setUp() self.user_id = fake_constants.USER_ID self.project_id = fake_constants.PROJECT_ID self.context = context.RequestContext(self.user_id, self.project_id, is_admin=True) self.service = db.service_create(self.context, {}) @mock.patch('cinder.db.workers_init', autospec=True) @mock.patch('cinder.manager.CleanableManager.do_cleanup', autospec=True) def test_init_host_with_service(self, mock_cleanup, mock_workers_init): mngr = FakeManager() self.assertFalse(hasattr(mngr, 'service_id')) mngr.init_host(service_id=self.service.id) self.assertEqual(self.service.id, mngr.service_id) mock_cleanup.assert_called_once_with(mngr, mock.ANY, mock.ANY) clean_req = mock_cleanup.call_args[0][2] self.assertIsInstance(clean_req, objects.CleanupRequest) self.assertEqual(self.service.id, clean_req.service_id) mock_workers_init.assert_called_once_with() def test_do_cleanup(self): """Basic successful cleanup.""" vol = utils.create_volume(self.context, status='creating') db.worker_create(self.context, status='creating', resource_type='Volume', resource_id=vol.id, service_id=self.service.id) clean_req = objects.CleanupRequest(service_id=self.service.id) mngr = FakeManager(self.service.id) mngr.do_cleanup(self.context, clean_req) self.assertListEqual([], db.worker_get_all(self.context)) vol.refresh() self.assertEqual('creating_cleaned', vol.status) def test_do_cleanup_not_cleaning_already_claimed(self): """Basic cleanup that doesn't touch already cleaning works.""" vol = utils.create_volume(self.context, status='creating') worker1 = db.worker_create(self.context, status='creating', resource_type='Volume', resource_id=vol.id, service_id=self.service.id) worker1 = db.worker_get(self.context, id=worker1.id) vol2 = utils.create_volume(self.context, status='deleting') worker2 = db.worker_create(self.context, status='deleting', resource_type='Volume', resource_id=vol2.id, service_id=self.service.id + 1) worker2 = db.worker_get(self.context, id=worker2.id) # Simulate that the change to vol2 worker happened between # worker_get_all and trying to claim a work for cleanup worker2.service_id = self.service.id clean_req = objects.CleanupRequest(service_id=self.service.id) mngr = FakeManager(self.service.id) with mock.patch('cinder.db.worker_get_all') as get_all_mock: get_all_mock.return_value = [worker1, worker2] mngr.do_cleanup(self.context, clean_req) workers = db.worker_get_all(self.context) self.assertEqual(1, len(workers)) self.assertEqual(worker2.id, workers[0].id) vol.refresh() self.assertEqual('creating_cleaned', vol.status) vol2.refresh() self.assertEqual('deleting', vol2.status) def test_do_cleanup_not_cleaning_already_claimed_by_us(self): """Basic cleanup that doesn't touch other thread's claimed works.""" now = timeutils.utcnow() delta = timeutils.datetime.timedelta(seconds=1) original_time = now - delta # Creating the worker in the future, and then changing the in-memory # value of worker2.updated_at to an earlier time, we effectively # simulate that the worker entry was created in the past and that it # has been just updated between worker_get_all and trying # to claim a work for cleanup other_thread_claimed_time = now + delta vol = utils.create_volume(self.context, status='creating') worker1 = db.worker_create(self.context, status='creating', resource_type='Volume', resource_id=vol.id, service_id=self.service.id, updated_at=original_time) worker1 = db.worker_get(self.context, id=worker1.id) vol2 = utils.create_volume(self.context, status='deleting') worker2 = db.worker_create(self.context, status='deleting', resource_type='Volume', resource_id=vol2.id, service_id=self.service.id, updated_at=other_thread_claimed_time) worker2 = db.worker_get(self.context, id=worker2.id) # This with the mock below simulates worker2 was created in the past # and updated right between worker_get_all and worker_claim_for_cleanup worker2.updated_at = original_time clean_req = objects.CleanupRequest(service_id=self.service.id) mngr = FakeManager(self.service.id) with mock.patch('cinder.manager.timeutils.utcnow', return_value=now),\ mock.patch('cinder.db.worker_get_all') as get_all_mock: get_all_mock.return_value = [worker1, worker2] mngr.do_cleanup(self.context, clean_req) workers = db.worker_get_all(self.context) self.assertEqual(1, len(workers)) self.assertEqual(worker2.id, workers[0].id) vol.refresh() self.assertEqual('creating_cleaned', vol.status) vol2.refresh() self.assertEqual('deleting', vol2.status) def test_do_cleanup_resource_deleted(self): """Cleanup on a resource that's been already deleted.""" vol = utils.create_volume(self.context, status='creating') db.worker_create(self.context, status='creating', resource_type='Volume', resource_id=vol.id, service_id=self.service.id) vol.destroy() clean_req = objects.CleanupRequest(service_id=self.service.id) mngr = FakeManager(self.service.id) mngr.do_cleanup(self.context, clean_req) workers = db.worker_get_all(self.context) self.assertListEqual([], workers) def test_do_cleanup_resource_on_another_service(self): """Cleanup on a resource that's been claimed by other service.""" vol = utils.create_volume(self.context, status='deleting') db.worker_create(self.context, status='deleting', resource_type='Volume', resource_id=vol.id, service_id=self.service.id + 1) clean_req = objects.CleanupRequest(service_id=self.service.id) mngr = FakeManager(self.service.id) mngr.do_cleanup(self.context, clean_req) workers = db.worker_get_all(self.context) self.assertEqual(1, len(workers)) vol.refresh() self.assertEqual('deleting', vol.status) def test_do_cleanup_resource_changed_status(self): """Cleanup on a resource that's changed status.""" vol = utils.create_volume(self.context, status='available') db.worker_create(self.context, status='creating', resource_type='Volume', resource_id=vol.id, service_id=self.service.id) clean_req = objects.CleanupRequest(service_id=self.service.id) mngr = FakeManager(self.service.id) mngr.do_cleanup(self.context, clean_req) workers = db.worker_get_all(self.context) self.assertListEqual([], workers) vol.refresh() self.assertEqual('available', vol.status) def test_do_cleanup_keep_worker(self): """Cleanup on a resource that will remove worker when cleaning up.""" vol = utils.create_volume(self.context, status='deleting') db.worker_create(self.context, status='deleting', resource_type='Volume', resource_id=vol.id, service_id=self.service.id) clean_req = objects.CleanupRequest(service_id=self.service.id) mngr = FakeManager(self.service.id, keep_after_clean=True) mngr.do_cleanup(self.context, clean_req) workers = db.worker_get_all(self.context) self.assertEqual(1, len(workers)) vol.refresh() self.assertEqual('deleting_cleaned', vol.status) @mock.patch.object(FakeManager, '_do_cleanup', side_effect=Exception) def test_do_cleanup_revive_on_cleanup_fail(self, mock_clean): """Cleanup will revive a worker if cleanup fails.""" vol = utils.create_volume(self.context, status='creating') db.worker_create(self.context, status='creating', resource_type='Volume', resource_id=vol.id, service_id=self.service.id) clean_req = objects.CleanupRequest(service_id=self.service.id) mngr = FakeManager(self.service.id) mngr.do_cleanup(self.context, clean_req) workers = db.worker_get_all(self.context) self.assertEqual(1, len(workers)) vol.refresh() self.assertEqual('creating', vol.status)