# 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 datetime import re import sys import time from unittest import mock import ddt import fixtures import iso8601 from oslo_config import cfg from oslo_db import exception as oslo_exception from oslo_utils import timeutils import six from six.moves import StringIO # Prevent load failures on macOS if sys.platform == 'darwin': rtslib_fb = mock.MagicMock() cinder_rtstool = mock.MagicMock() else: import rtslib_fb from cinder.cmd import api as cinder_api from cinder.cmd import backup as cinder_backup from cinder.cmd import manage as cinder_manage if sys.platform != 'darwin': from cinder.cmd import rtstool as cinder_rtstool from cinder.cmd import scheduler as cinder_scheduler from cinder.cmd import volume as cinder_volume from cinder.cmd import volume_usage_audit from cinder.common import constants from cinder import context from cinder.db.sqlalchemy import api as sqlalchemy_api from cinder import exception from cinder.objects import fields from cinder.tests.unit import fake_cluster from cinder.tests.unit import fake_constants as fake from cinder.tests.unit import fake_service from cinder.tests.unit import fake_volume from cinder.tests.unit import test from cinder.tests.unit import utils from cinder import version from cinder.volume import rpcapi CONF = cfg.CONF class TestCinderApiCmd(test.TestCase): """Unit test cases for python modules under cinder/cmd.""" def setUp(self): super(TestCinderApiCmd, self).setUp() sys.argv = ['cinder-api'] @mock.patch('cinder.service.WSGIService') @mock.patch('cinder.service.process_launcher') @mock.patch('cinder.rpc.init') @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') def test_main(self, log_setup, monkey_patch, rpc_init, process_launcher, wsgi_service): launcher = process_launcher.return_value server = wsgi_service.return_value server.workers = mock.sentinel.worker_count cinder_api.main() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") monkey_patch.assert_called_once_with() rpc_init.assert_called_once_with(CONF) process_launcher.assert_called_once_with() wsgi_service.assert_called_once_with('osapi_volume') launcher.launch_service.assert_called_once_with( server, workers=server.workers) launcher.wait.assert_called_once_with() class TestCinderBackupCmd(test.TestCase): def setUp(self): super(TestCinderBackupCmd, self).setUp() sys.argv = ['cinder-backup'] @mock.patch('cinder.service.get_launcher') @mock.patch('cinder.service.Service.create') @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') def test_main_multiprocess(self, log_setup, monkey_patch, service_create, get_launcher): CONF.set_override('backup_workers', 2) cinder_backup.main() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) c1 = mock.call(binary=constants.BACKUP_BINARY, coordination=True, process_number=1, service_name='backup') c2 = mock.call(binary=constants.BACKUP_BINARY, coordination=True, process_number=2, service_name='backup') service_create.assert_has_calls([c1, c2]) launcher = get_launcher.return_value self.assertEqual(2, launcher.launch_service.call_count) launcher.wait.assert_called_once_with() class TestCinderSchedulerCmd(test.TestCase): def setUp(self): super(TestCinderSchedulerCmd, self).setUp() sys.argv = ['cinder-scheduler'] @mock.patch('cinder.service.wait') @mock.patch('cinder.service.serve') @mock.patch('cinder.service.Service.create') @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') def test_main(self, log_setup, monkey_patch, service_create, service_serve, service_wait): server = service_create.return_value cinder_scheduler.main() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") monkey_patch.assert_called_once_with() service_create.assert_called_once_with(binary='cinder-scheduler') service_serve.assert_called_once_with(server) service_wait.assert_called_once_with() class TestCinderVolumeCmdPosix(test.TestCase): def setUp(self): super(TestCinderVolumeCmdPosix, self).setUp() sys.argv = ['cinder-volume'] self.patch('os.name', 'posix') @mock.patch('cinder.service.get_launcher') @mock.patch('cinder.service.Service.create') @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') def test_main(self, log_setup, monkey_patch, service_create, get_launcher): CONF.set_override('enabled_backends', None) self.assertRaises(SystemExit, cinder_volume.main) self.assertFalse(service_create.called) @mock.patch('cinder.service.get_launcher') @mock.patch('cinder.service.Service.create') @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') def test_main_with_backends(self, log_setup, monkey_patch, service_create, get_launcher): backends = ['', 'backend1', 'backend2', ''] CONF.set_override('enabled_backends', backends) CONF.set_override('host', 'host') CONF.set_override('cluster', None) launcher = get_launcher.return_value cinder_volume.main() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") monkey_patch.assert_called_once_with() get_launcher.assert_called_once_with() c1 = mock.call(binary=constants.VOLUME_BINARY, host='host@backend1', service_name='backend1', coordination=True, cluster=None) c2 = mock.call(binary=constants.VOLUME_BINARY, host='host@backend2', service_name='backend2', coordination=True, cluster=None) service_create.assert_has_calls([c1, c2]) self.assertEqual(2, launcher.launch_service.call_count) launcher.wait.assert_called_once_with() @ddt.ddt @test.testtools.skipIf(sys.platform == 'darwin', 'Not supported on macOS') class TestCinderVolumeCmdWin32(test.TestCase): def setUp(self): super(TestCinderVolumeCmdWin32, self).setUp() sys.argv = ['cinder-volume'] self._mock_win32_proc_launcher = mock.Mock() self.patch('os.name', 'nt') self.patch('cinder.service.WindowsProcessLauncher', lambda *args, **kwargs: self._mock_win32_proc_launcher) @mock.patch('cinder.service.get_launcher') @mock.patch('cinder.service.Service.create') @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') def test_main(self, log_setup, monkey_patch, service_create, get_launcher): CONF.set_override('enabled_backends', None) self.assertRaises(SystemExit, cinder_volume.main) self.assertFalse(service_create.called) self.assertFalse(self._mock_win32_proc_launcher.called) @mock.patch('cinder.service.get_launcher') @mock.patch('cinder.service.Service.create') @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') def test_main_invalid_backend(self, log_setup, monkey_patch, service_create, get_launcher): CONF.set_override('enabled_backends', 'backend1') CONF.set_override('backend_name', 'backend2') self.assertRaises(exception.InvalidInput, cinder_volume.main) self.assertFalse(service_create.called) self.assertFalse(self._mock_win32_proc_launcher.called) @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') @ddt.data({}, {'binary_path': 'cinder-volume-script.py', 'exp_py_executable': True}) @ddt.unpack def test_main_with_multiple_backends(self, log_setup, monkey_patch, binary_path='cinder-volume', exp_py_executable=False): # If multiple backends are used, we expect the Windows process # launcher to be used in order to create the child processes. backends = ['', 'backend1', 'backend2', ''] CONF.set_override('enabled_backends', backends) CONF.set_override('host', 'host') launcher = self._mock_win32_proc_launcher # Depending on the setuptools version, '-script.py' and '.exe' # binary path extensions may be trimmed. We need to take this # into consideration when building the command that will be # used to spawn child subprocesses. sys.argv = [binary_path] cinder_volume.main() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") monkey_patch.assert_called_once_with() exp_cmd_prefix = [sys.executable] if exp_py_executable else [] exp_cmds = [ exp_cmd_prefix + sys.argv + ['--backend_name=%s' % backend_name] for backend_name in ['backend1', 'backend2']] launcher.add_process.assert_has_calls( [mock.call(exp_cmd) for exp_cmd in exp_cmds]) launcher.wait.assert_called_once_with() @mock.patch('cinder.service.get_launcher') @mock.patch('cinder.service.Service.create') @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') def test_main_with_multiple_backends_child( self, log_setup, monkey_patch, service_create, get_launcher): # We're testing the code expected to be run within child processes. backends = ['', 'backend1', 'backend2', ''] CONF.set_override('enabled_backends', backends) CONF.set_override('host', 'host') CONF.set_override('cluster', None) launcher = get_launcher.return_value sys.argv += ['--backend_name', 'backend2'] cinder_volume.main() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") monkey_patch.assert_called_once_with() service_create.assert_called_once_with( binary=constants.VOLUME_BINARY, host='host@backend2', service_name='backend2', coordination=True, cluster=None) launcher.launch_service.assert_called_once_with( service_create.return_value) @mock.patch('cinder.service.get_launcher') @mock.patch('cinder.service.Service.create') @mock.patch('cinder.utils.monkey_patch') @mock.patch('oslo_log.log.setup') def test_main_with_single_backend( self, log_setup, monkey_patch, service_create, get_launcher): # We're expecting the service to be run within the same process. CONF.set_override('enabled_backends', ['backend2']) CONF.set_override('host', 'host') CONF.set_override('cluster', None) launcher = get_launcher.return_value cinder_volume.main() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") monkey_patch.assert_called_once_with() service_create.assert_called_once_with( binary=constants.VOLUME_BINARY, host='host@backend2', service_name='backend2', coordination=True, cluster=None) launcher.launch_service.assert_called_once_with( service_create.return_value) @ddt.ddt class TestCinderManageCmd(test.TestCase): def setUp(self): super(TestCinderManageCmd, self).setUp() sys.argv = ['cinder-manage'] def _test_purge_invalid_age_in_days(self, age_in_days): db_cmds = cinder_manage.DbCommands() ex = self.assertRaises(SystemExit, db_cmds.purge, age_in_days) self.assertEqual(1, ex.code) @mock.patch('cinder.objects.ServiceList.get_all') @mock.patch('cinder.db.migration.db_sync') def test_db_commands_sync(self, db_sync, service_get_mock): version = 11 db_cmds = cinder_manage.DbCommands() db_cmds.sync(version=version) db_sync.assert_called_once_with(version) service_get_mock.assert_not_called() @mock.patch('cinder.objects.Service.save') @mock.patch('cinder.objects.ServiceList.get_all') @mock.patch('cinder.db.migration.db_sync') def test_db_commands_sync_bump_versions(self, db_sync, service_get_mock, service_save): ctxt = context.get_admin_context() services = [fake_service.fake_service_obj(ctxt, binary='cinder-' + binary, rpc_current_version='0.1', object_current_version='0.2') for binary in ('volume', 'scheduler', 'backup')] service_get_mock.return_value = services version = 11 db_cmds = cinder_manage.DbCommands() db_cmds.sync(version=version, bump_versions=True) db_sync.assert_called_once_with(version) self.assertEqual(3, service_save.call_count) for service in services: self.assertEqual(cinder_manage.RPC_VERSIONS[service.binary], service.rpc_current_version) self.assertEqual(cinder_manage.OVO_VERSION, service.object_current_version) @mock.patch('oslo_db.sqlalchemy.migration.db_version') def test_db_commands_version(self, db_version): db_cmds = cinder_manage.DbCommands() with mock.patch('sys.stdout', new=six.StringIO()): db_cmds.version() self.assertEqual(1, db_version.call_count) def test_db_commands_upgrade_out_of_range(self): version = 2147483647 db_cmds = cinder_manage.DbCommands() exit = self.assertRaises(SystemExit, db_cmds.sync, version + 1) self.assertEqual(1, exit.code) @mock.patch("oslo_db.sqlalchemy.migration.db_sync") def test_db_commands_script_not_present(self, db_sync): db_sync.side_effect = oslo_exception.DBMigrationError(None) db_cmds = cinder_manage.DbCommands() exit = self.assertRaises(SystemExit, db_cmds.sync, 101) self.assertEqual(1, exit.code) @mock.patch('cinder.cmd.manage.DbCommands.online_migrations', (mock.Mock(side_effect=((2, 2), (0, 0)), __name__='foo'),)) def test_db_commands_online_data_migrations(self): db_cmds = cinder_manage.DbCommands() exit = self.assertRaises(SystemExit, db_cmds.online_data_migrations) self.assertEqual(0, exit.code) cinder_manage.DbCommands.online_migrations[0].assert_has_calls( (mock.call(mock.ANY, 50),) * 2) def _fake_db_command(self, migrations=None): if migrations is None: mock_mig_1 = mock.MagicMock(__name__="mock_mig_1") mock_mig_2 = mock.MagicMock(__name__="mock_mig_2") mock_mig_1.return_value = (5, 4) mock_mig_2.return_value = (6, 6) migrations = (mock_mig_1, mock_mig_2) class _CommandSub(cinder_manage.DbCommands): online_migrations = migrations return _CommandSub @mock.patch('cinder.context.get_admin_context') def test_online_migrations(self, mock_get_context): self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO())) ctxt = mock_get_context.return_value db_cmds = self._fake_db_command() command = db_cmds() exit = self.assertRaises(SystemExit, command.online_data_migrations, 10) self.assertEqual(1, exit.code) command.online_migrations[0].assert_has_calls([mock.call(ctxt, 10)]) command.online_migrations[1].assert_has_calls([mock.call(ctxt, 6)]) output = sys.stdout.getvalue() matches = re.findall( '5 rows matched query mock_mig_1, 4 migrated', output, re.MULTILINE) self.assertEqual(len(matches), 1) matches = re.findall( '6 rows matched query mock_mig_2, 6 migrated', output, re.MULTILINE) self.assertEqual(len(matches), 1) matches = re.findall( 'mock_mig_1 .* 5 .* 4', output, re.MULTILINE) self.assertEqual(len(matches), 1) matches = re.findall( 'mock_mig_2 .* 6 .* 6', output, re.MULTILINE) self.assertEqual(len(matches), 1) @mock.patch('cinder.context.get_admin_context') def test_online_migrations_no_max_count(self, mock_get_context): self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO())) fake_remaining = [120] def fake_migration(context, count): self.assertEqual(mock_get_context.return_value, context) found = 120 done = min(fake_remaining[0], count) fake_remaining[0] -= done return found, done command_cls = self._fake_db_command((fake_migration,)) command = command_cls() exit = self.assertRaises(SystemExit, command.online_data_migrations, None) self.assertEqual(0, exit.code) output = sys.stdout.getvalue() self.assertIn('Running batches of 50 until complete.', output) matches = re.findall( '120 rows matched query fake_migration, 50 migrated', output, re.MULTILINE) self.assertEqual(len(matches), 2) matches = re.findall( '120 rows matched query fake_migration, 20 migrated', output, re.MULTILINE) self.assertEqual(len(matches), 1) matches = re.findall( '120 rows matched query fake_migration, 0 migrated', output, re.MULTILINE) self.assertEqual(len(matches), 1) matches = re.findall( 'fake_migration .* 120 .* 120', output, re.MULTILINE) self.assertEqual(len(matches), 1) @mock.patch('cinder.context.get_admin_context') def test_online_migrations_error(self, mock_get_context): self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO())) good_remaining = [50] def good_migration(context, count): self.assertEqual(mock_get_context.return_value, context) found = 50 done = min(good_remaining[0], count) good_remaining[0] -= done return found, done bad_migration = mock.MagicMock() bad_migration.side_effect = test.TestingException bad_migration.__name__ = 'bad_migration' command_cls = self._fake_db_command((bad_migration, good_migration)) command = command_cls() # bad_migration raises an exception, but it could be because # good_migration had not completed yet. We should get 1 in this case, # because some work was done, and the command should be reiterated. exit = self.assertRaises(SystemExit, command.online_data_migrations, max_count=50) self.assertEqual(1, exit.code) # When running this for the second time, there's no work left for # good_migration to do, but bad_migration still fails - should # get 2 this time. exit = self.assertRaises(SystemExit, command.online_data_migrations, max_count=50) self.assertEqual(2, exit.code) # When --max_count is not used, we should get 2 if all possible # migrations completed but some raise exceptions good_remaining = [50] exit = self.assertRaises(SystemExit, command.online_data_migrations, None) self.assertEqual(2, exit.code) @mock.patch('cinder.cmd.manage.DbCommands.online_migrations', (mock.Mock(side_effect=((2, 2), (0, 0)), __name__='foo'),)) def test_db_commands_online_data_migrations_ignore_state_and_max(self): db_cmds = cinder_manage.DbCommands() exit = self.assertRaises(SystemExit, db_cmds.online_data_migrations, 2) self.assertEqual(1, exit.code) cinder_manage.DbCommands.online_migrations[0].assert_called_once_with( mock.ANY, 2) @mock.patch('cinder.cmd.manage.DbCommands.online_migrations', (mock.Mock(side_effect=((2, 2), (0, 0)), __name__='foo'),)) def test_db_commands_online_data_migrations_max_negative(self): db_cmds = cinder_manage.DbCommands() exit = self.assertRaises(SystemExit, db_cmds.online_data_migrations, -1) self.assertEqual(127, exit.code) cinder_manage.DbCommands.online_migrations[0].assert_not_called() @mock.patch('cinder.db.reset_active_backend') @mock.patch('cinder.context.get_admin_context') def test_db_commands_reset_active_backend(self, admin_ctxt_mock, reset_backend_mock): db_cmds = cinder_manage.DbCommands() db_cmds.reset_active_backend(True, 'fake-backend-id', 'fake-host') reset_backend_mock.assert_called_with(admin_ctxt_mock.return_value, True, 'fake-backend-id', 'fake-host') @mock.patch('cinder.version.version_string') def test_versions_commands_list(self, version_string): version_cmds = cinder_manage.VersionCommands() with mock.patch('sys.stdout', new=six.StringIO()): version_cmds.list() version_string.assert_called_once_with() @mock.patch('cinder.version.version_string') def test_versions_commands_call(self, version_string): version_cmds = cinder_manage.VersionCommands() with mock.patch('sys.stdout', new=six.StringIO()): version_cmds.__call__() version_string.assert_called_once_with() def test_purge_with_negative_age_in_days(self): age_in_days = -1 self._test_purge_invalid_age_in_days(age_in_days) def test_purge_exceeded_age_in_days_limit(self): age_in_days = int(time.time() / 86400) + 1 self._test_purge_invalid_age_in_days(age_in_days) @mock.patch('cinder.db.sqlalchemy.api.purge_deleted_rows') @mock.patch('cinder.context.get_admin_context') def test_purge_less_than_age_in_days_limit(self, get_admin_context, purge_deleted_rows): age_in_days = int(time.time() / 86400) - 1 ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, is_admin=True) get_admin_context.return_value = ctxt purge_deleted_rows.return_value = None db_cmds = cinder_manage.DbCommands() db_cmds.purge(age_in_days) get_admin_context.assert_called_once_with() purge_deleted_rows.assert_called_once_with( ctxt, age_in_days=age_in_days) @mock.patch('cinder.db.service_get_all') @mock.patch('cinder.context.get_admin_context') def test_host_commands_list(self, get_admin_context, service_get_all): get_admin_context.return_value = mock.sentinel.ctxt service_get_all.return_value = [ {'host': 'fake-host', 'availability_zone': 'fake-az', 'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'}] with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: expected_out = ("%(host)-25s\t%(zone)-15s\n" % {'host': 'host', 'zone': 'zone'}) expected_out += ("%(host)-25s\t%(availability_zone)-15s\n" % {'host': 'fake-host', 'availability_zone': 'fake-az'}) host_cmds = cinder_manage.HostCommands() host_cmds.list() get_admin_context.assert_called_once_with() service_get_all.assert_called_once_with(mock.sentinel.ctxt) self.assertEqual(expected_out, fake_out.getvalue()) @mock.patch('cinder.db.service_get_all') @mock.patch('cinder.context.get_admin_context') def test_host_commands_list_with_zone(self, get_admin_context, service_get_all): get_admin_context.return_value = mock.sentinel.ctxt service_get_all.return_value = [ {'host': 'fake-host', 'availability_zone': 'fake-az1', 'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'}, {'host': 'fake-host', 'availability_zone': 'fake-az2', 'uuid': '4200b32b-0bf9-436c-86b2-0675f6ac218e'}] with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: expected_out = ("%(host)-25s\t%(zone)-15s\n" % {'host': 'host', 'zone': 'zone'}) expected_out += ("%(host)-25s\t%(availability_zone)-15s\n" % {'host': 'fake-host', 'availability_zone': 'fake-az1'}) host_cmds = cinder_manage.HostCommands() host_cmds.list(zone='fake-az1') get_admin_context.assert_called_once_with() service_get_all.assert_called_once_with(mock.sentinel.ctxt) self.assertEqual(expected_out, fake_out.getvalue()) @mock.patch('cinder.db.sqlalchemy.api.volume_get') @mock.patch('cinder.context.get_admin_context') @mock.patch('cinder.rpc.get_client') @mock.patch('cinder.rpc.init') def test_volume_commands_delete(self, rpc_init, get_client, get_admin_context, volume_get): ctxt = context.RequestContext('admin', 'fake', True) get_admin_context.return_value = ctxt mock_client = mock.MagicMock() cctxt = mock.MagicMock() mock_client.prepare.return_value = cctxt get_client.return_value = mock_client host = 'fake@host' db_volume = {'host': host + '#pool1'} volume = fake_volume.fake_db_volume(**db_volume) volume_obj = fake_volume.fake_volume_obj(ctxt, **volume) volume_id = volume['id'] volume_get.return_value = volume volume_cmds = cinder_manage.VolumeCommands() volume_cmds._client = mock_client volume_cmds.delete(volume_id) volume_get.assert_called_once_with(ctxt, volume_id) mock_client.prepare.assert_called_once_with( server="fake", topic="cinder-volume.fake@host", version="3.0") cctxt.cast.assert_called_once_with( ctxt, 'delete_volume', cascade=False, unmanage_only=False, volume=volume_obj) @mock.patch('cinder.db.volume_destroy') @mock.patch('cinder.db.sqlalchemy.api.volume_get') @mock.patch('cinder.context.get_admin_context') @mock.patch('cinder.rpc.init') def test_volume_commands_delete_no_host(self, rpc_init, get_admin_context, volume_get, volume_destroy): ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, is_admin=True) get_admin_context.return_value = ctxt volume = fake_volume.fake_db_volume() volume_id = volume['id'] volume_get.return_value = volume with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: expected_out = ('Volume not yet assigned to host.\n' 'Deleting volume from database and skipping' ' rpc.\n') volume_cmds = cinder_manage.VolumeCommands() volume_cmds.delete(volume_id) get_admin_context.assert_called_once_with() volume_get.assert_called_once_with(ctxt, volume_id) self.assertTrue(volume_destroy.called) admin_context = volume_destroy.call_args[0][0] self.assertTrue(admin_context.is_admin) self.assertEqual(expected_out, fake_out.getvalue()) @mock.patch('cinder.db.volume_destroy') @mock.patch('cinder.db.sqlalchemy.api.volume_get') @mock.patch('cinder.context.get_admin_context') @mock.patch('cinder.rpc.init') def test_volume_commands_delete_volume_in_use(self, rpc_init, get_admin_context, volume_get, volume_destroy): ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_context.return_value = ctxt db_volume = {'status': 'in-use', 'host': 'fake-host'} volume = fake_volume.fake_db_volume(**db_volume) volume_id = volume['id'] volume_get.return_value = volume with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: expected_out = ('Volume is in-use.\n' 'Detach volume from instance and then try' ' again.\n') volume_cmds = cinder_manage.VolumeCommands() volume_cmds.delete(volume_id) volume_get.assert_called_once_with(ctxt, volume_id) self.assertEqual(expected_out, fake_out.getvalue()) def test_config_commands_list(self): with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: expected_out = '' for key, value in CONF.items(): expected_out += '%s = %s' % (key, value) + '\n' config_cmds = cinder_manage.ConfigCommands() config_cmds.list() self.assertEqual(expected_out, fake_out.getvalue()) def test_config_commands_list_param(self): with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: CONF.set_override('host', 'fake') expected_out = 'host = fake\n' config_cmds = cinder_manage.ConfigCommands() config_cmds.list(param='host') self.assertEqual(expected_out, fake_out.getvalue()) @mock.patch('cinder.db.backup_get_all') @mock.patch('cinder.context.get_admin_context') def test_backup_commands_list(self, get_admin_context, backup_get_all): ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_context.return_value = ctxt backup = {'id': fake.BACKUP_ID, 'user_id': fake.USER_ID, 'project_id': fake.PROJECT_ID, 'host': 'fake-host', 'display_name': 'fake-display-name', 'container': 'fake-container', 'status': fields.BackupStatus.AVAILABLE, 'size': 123, 'object_count': 1, 'volume_id': fake.VOLUME_ID, 'backup_metadata': {}, } backup_get_all.return_value = [backup] with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: hdr = ('%-32s\t%-32s\t%-32s\t%-24s\t%-24s\t%-12s\t%-12s\t%-12s' '\t%-12s') header = hdr % ('ID', 'User ID', 'Project ID', 'Host', 'Name', 'Container', 'Status', 'Size', 'Object Count') res = ('%-32s\t%-32s\t%-32s\t%-24s\t%-24s\t%-12s\t%-12s\t%-12d' '\t%-12s') resource = res % (backup['id'], backup['user_id'], backup['project_id'], backup['host'], backup['display_name'], backup['container'], backup['status'], backup['size'], 1) expected_out = header + '\n' + resource + '\n' backup_cmds = cinder_manage.BackupCommands() backup_cmds.list() get_admin_context.assert_called_once_with() backup_get_all.assert_called_once_with(ctxt, None, None, None, None, None, None) self.assertEqual(expected_out, fake_out.getvalue()) @mock.patch('cinder.db.backup_update') @mock.patch('cinder.db.backup_get_all_by_host') @mock.patch('cinder.context.get_admin_context') def test_update_backup_host(self, get_admin_context, backup_get_by_host, backup_update): ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_context.return_value = ctxt backup = {'id': fake.BACKUP_ID, 'user_id': fake.USER_ID, 'project_id': fake.PROJECT_ID, 'host': 'fake-host', 'display_name': 'fake-display-name', 'container': 'fake-container', 'status': fields.BackupStatus.AVAILABLE, 'size': 123, 'object_count': 1, 'volume_id': fake.VOLUME_ID, 'backup_metadata': {}, } backup_get_by_host.return_value = [backup] backup_cmds = cinder_manage.BackupCommands() backup_cmds.update_backup_host('fake_host', 'fake_host2') get_admin_context.assert_called_once_with() backup_get_by_host.assert_called_once_with(ctxt, 'fake_host') backup_update.assert_called_once_with(ctxt, fake.BACKUP_ID, {'host': 'fake_host2'}) @mock.patch('cinder.db.consistencygroup_update') @mock.patch('cinder.db.consistencygroup_get_all') @mock.patch('cinder.context.get_admin_context') def test_update_consisgroup_host(self, get_admin_context, consisgroup_get_all, consisgroup_update): ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_context.return_value = ctxt consisgroup = {'id': fake.CONSISTENCY_GROUP_ID, 'user_id': fake.USER_ID, 'project_id': fake.PROJECT_ID, 'host': 'fake-host', 'status': fields.ConsistencyGroupStatus.AVAILABLE } consisgroup_get_all.return_value = [consisgroup] consisgrup_cmds = cinder_manage.ConsistencyGroupCommands() consisgrup_cmds.update_cg_host('fake_host', 'fake_host2') get_admin_context.assert_called_once_with() consisgroup_get_all.assert_called_once_with( ctxt, filters={'host': 'fake_host'}, limit=None, marker=None, offset=None, sort_dirs=None, sort_keys=None) consisgroup_update.assert_called_once_with( ctxt, fake.CONSISTENCY_GROUP_ID, {'host': 'fake_host2'}) @mock.patch('cinder.objects.service.Service.is_up', new_callable=mock.PropertyMock) @mock.patch('cinder.db.service_get_all') @mock.patch('cinder.context.get_admin_context') def _test_service_commands_list(self, service, get_admin_context, service_get_all, service_is_up): ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_context.return_value = ctxt service_get_all.return_value = [service] service_is_up.return_value = True with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: format = "%-16s %-36s %-16s %-10s %-5s %-20s %-12s %-15s %-36s" print_format = format % ('Binary', 'Host', 'Zone', 'Status', 'State', 'Updated At', 'RPC Version', 'Object Version', 'Cluster') rpc_version = service['rpc_current_version'] object_version = service['object_current_version'] cluster = service.get('cluster_name', '') service_format = format % (service['binary'], service['host'], service['availability_zone'], 'enabled', ':-)', service['updated_at'], rpc_version, object_version, cluster) expected_out = print_format + '\n' + service_format + '\n' service_cmds = cinder_manage.ServiceCommands() service_cmds.list() self.assertEqual(expected_out, fake_out.getvalue()) get_admin_context.assert_called_with() service_get_all.assert_called_with(ctxt) def test_service_commands_list(self): service = {'binary': 'cinder-binary', 'host': 'fake-host.fake-domain', 'availability_zone': 'fake-zone', 'updated_at': '2014-06-30 11:22:33', 'disabled': False, 'rpc_current_version': '1.1', 'object_current_version': '1.1', 'cluster_name': 'my_cluster', 'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'} for binary in ('volume', 'scheduler', 'backup'): service['binary'] = 'cinder-%s' % binary self._test_service_commands_list(service) def test_service_commands_list_no_updated_at_or_cluster(self): service = {'binary': 'cinder-binary', 'host': 'fake-host.fake-domain', 'availability_zone': 'fake-zone', 'updated_at': None, 'disabled': False, 'rpc_current_version': '1.1', 'object_current_version': '1.1', 'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'} for binary in ('volume', 'scheduler', 'backup'): service['binary'] = 'cinder-%s' % binary self._test_service_commands_list(service) @ddt.data(('foobar', 'foobar'), ('-foo bar', 'foo bar'), ('--foo bar', 'foo bar'), ('--foo-bar', 'foo_bar'), ('---foo-bar', '_foo_bar')) @ddt.unpack def test_get_arg_string(self, arg, expected): self.assertEqual(expected, cinder_manage.get_arg_string(arg)) def test_fetch_func_args(self): @cinder_manage.args('--full-rename') @cinder_manage.args('--different-dest', dest='my_dest') @cinder_manage.args('current') def my_func(): pass expected = {'full_rename': mock.sentinel.full_rename, 'my_dest': mock.sentinel.my_dest, 'current': mock.sentinel.current} with mock.patch.object(cinder_manage, 'CONF') as mock_conf: mock_conf.category = mock.Mock(**expected) self.assertDictEqual(expected, cinder_manage.fetch_func_args(my_func)) @mock.patch('cinder.context.get_admin_context') @mock.patch('cinder.db.cluster_get_all') def tests_cluster_commands_list(self, get_all_mock, get_admin_mock, ): now = timeutils.utcnow() cluster = fake_cluster.fake_cluster_orm(num_hosts=4, num_down_hosts=2, created_at=now, last_heartbeat=now) get_all_mock.return_value = [cluster] ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_mock.return_value = ctxt with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: format_ = "%-36s %-16s %-10s %-5s %-20s %-7s %-12s %-20s" print_format = format_ % ('Name', 'Binary', 'Status', 'State', 'Heartbeat', 'Hosts', 'Down Hosts', 'Updated At') cluster_format = format_ % (cluster.name, cluster.binary, 'enabled', ':-)', cluster.last_heartbeat, cluster.num_hosts, cluster.num_down_hosts, None) expected_out = print_format + '\n' + cluster_format + '\n' cluster_cmds = cinder_manage.ClusterCommands() cluster_cmds.list() self.assertEqual(expected_out, fake_out.getvalue()) get_admin_mock.assert_called_with() get_all_mock.assert_called_with(ctxt, is_up=None, get_services=False, services_summary=True, read_deleted='no') @mock.patch('cinder.db.sqlalchemy.api.cluster_get', auto_specs=True) @mock.patch('cinder.context.get_admin_context') def test_cluster_commands_remove_not_found(self, admin_ctxt_mock, cluster_get_mock): cluster_get_mock.side_effect = exception.ClusterNotFound(id=1) cluster_commands = cinder_manage.ClusterCommands() exit = cluster_commands.remove(False, 'abinary', 'acluster') self.assertEqual(2, exit) cluster_get_mock.assert_called_once_with(admin_ctxt_mock.return_value, None, name='acluster', binary='abinary', get_services=False) @mock.patch('cinder.db.sqlalchemy.api.service_destroy', auto_specs=True) @mock.patch('cinder.db.sqlalchemy.api.cluster_destroy', auto_specs=True) @mock.patch('cinder.db.sqlalchemy.api.cluster_get', auto_specs=True) @mock.patch('cinder.context.get_admin_context') def test_cluster_commands_remove_fail_has_hosts(self, admin_ctxt_mock, cluster_get_mock, cluster_destroy_mock, service_destroy_mock): cluster = fake_cluster.fake_cluster_ovo(mock.Mock()) cluster_get_mock.return_value = cluster cluster_destroy_mock.side_effect = exception.ClusterHasHosts(id=1) cluster_commands = cinder_manage.ClusterCommands() exit = cluster_commands.remove(False, 'abinary', 'acluster') self.assertEqual(2, exit) cluster_get_mock.assert_called_once_with(admin_ctxt_mock.return_value, None, name='acluster', binary='abinary', get_services=False) cluster_destroy_mock.assert_called_once_with( admin_ctxt_mock.return_value.elevated.return_value, cluster.id) service_destroy_mock.assert_not_called() @mock.patch('cinder.db.sqlalchemy.api.service_destroy', auto_specs=True) @mock.patch('cinder.db.sqlalchemy.api.cluster_destroy', auto_specs=True) @mock.patch('cinder.db.sqlalchemy.api.cluster_get', auto_specs=True) @mock.patch('cinder.context.get_admin_context') def test_cluster_commands_remove_success_no_hosts(self, admin_ctxt_mock, cluster_get_mock, cluster_destroy_mock, service_destroy_mock): cluster = fake_cluster.fake_cluster_orm() cluster_get_mock.return_value = cluster cluster_commands = cinder_manage.ClusterCommands() exit = cluster_commands.remove(False, 'abinary', 'acluster') self.assertIsNone(exit) cluster_get_mock.assert_called_once_with(admin_ctxt_mock.return_value, None, name='acluster', binary='abinary', get_services=False) cluster_destroy_mock.assert_called_once_with( admin_ctxt_mock.return_value.elevated.return_value, cluster.id) service_destroy_mock.assert_not_called() @mock.patch('cinder.db.sqlalchemy.api.service_destroy', auto_specs=True) @mock.patch('cinder.db.sqlalchemy.api.cluster_destroy', auto_specs=True) @mock.patch('cinder.db.sqlalchemy.api.cluster_get', auto_specs=True) @mock.patch('cinder.context.get_admin_context') def test_cluster_commands_remove_recursive(self, admin_ctxt_mock, cluster_get_mock, cluster_destroy_mock, service_destroy_mock): cluster = fake_cluster.fake_cluster_orm() cluster.services = [fake_service.fake_service_orm()] cluster_get_mock.return_value = cluster cluster_commands = cinder_manage.ClusterCommands() exit = cluster_commands.remove(True, 'abinary', 'acluster') self.assertIsNone(exit) cluster_get_mock.assert_called_once_with(admin_ctxt_mock.return_value, None, name='acluster', binary='abinary', get_services=True) cluster_destroy_mock.assert_called_once_with( admin_ctxt_mock.return_value.elevated.return_value, cluster.id) service_destroy_mock.assert_called_once_with( admin_ctxt_mock.return_value.elevated.return_value, cluster.services[0]['id']) @mock.patch('cinder.db.sqlalchemy.api.volume_include_in_cluster', auto_specs=True, return_value=1) @mock.patch('cinder.db.sqlalchemy.api.consistencygroup_include_in_cluster', auto_specs=True, return_value=2) @mock.patch('cinder.context.get_admin_context') def test_cluster_commands_rename(self, admin_ctxt_mock, volume_include_mock, cg_include_mock): """Test that cluster rename changes volumes and cgs.""" current_cluster_name = mock.sentinel.old_cluster_name new_cluster_name = mock.sentinel.new_cluster_name partial = mock.sentinel.partial cluster_commands = cinder_manage.ClusterCommands() exit = cluster_commands.rename(partial, current_cluster_name, new_cluster_name) self.assertIsNone(exit) volume_include_mock.assert_called_once_with( admin_ctxt_mock.return_value, new_cluster_name, partial, cluster_name=current_cluster_name) cg_include_mock.assert_called_once_with( admin_ctxt_mock.return_value, new_cluster_name, partial, cluster_name=current_cluster_name) @mock.patch('cinder.db.sqlalchemy.api.volume_include_in_cluster', auto_specs=True, return_value=0) @mock.patch('cinder.db.sqlalchemy.api.consistencygroup_include_in_cluster', auto_specs=True, return_value=0) @mock.patch('cinder.context.get_admin_context') def test_cluster_commands_rename_no_changes(self, admin_ctxt_mock, volume_include_mock, cg_include_mock): """Test that we return an error when cluster rename has no effect.""" cluster_commands = cinder_manage.ClusterCommands() exit = cluster_commands.rename(False, 'cluster', 'new_cluster') self.assertEqual(2, exit) @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt') def test_main_argv_lt_2(self, register_cli_opt): script_name = 'cinder-manage' sys.argv = [script_name] CONF(sys.argv[1:], project='cinder', version=version.version_string()) with mock.patch('sys.stdout', new=six.StringIO()): exit = self.assertRaises(SystemExit, cinder_manage.main) self.assertTrue(register_cli_opt.called) self.assertEqual(2, exit.code) @mock.patch('oslo_config.cfg.ConfigOpts.__call__') @mock.patch('oslo_log.log.setup') @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt') def test_main_sudo_failed(self, register_cli_opt, log_setup, config_opts_call): script_name = 'cinder-manage' sys.argv = [script_name, 'fake_category', 'fake_action'] config_opts_call.side_effect = cfg.ConfigFilesNotFoundError( mock.sentinel._namespace) with mock.patch('sys.stdout', new=six.StringIO()): exit = self.assertRaises(SystemExit, cinder_manage.main) self.assertTrue(register_cli_opt.called) config_opts_call.assert_called_once_with( sys.argv[1:], project='cinder', version=version.version_string()) self.assertFalse(log_setup.called) self.assertEqual(2, exit.code) @mock.patch('oslo_config.cfg.ConfigOpts.__call__') @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt') def test_main(self, register_cli_opt, config_opts_call): script_name = 'cinder-manage' sys.argv = [script_name, 'config', 'list'] action_fn = mock.MagicMock() CONF.category = mock.MagicMock(action_fn=action_fn) cinder_manage.main() self.assertTrue(register_cli_opt.called) config_opts_call.assert_called_once_with( sys.argv[1:], project='cinder', version=version.version_string()) self.assertTrue(action_fn.called) @mock.patch('oslo_config.cfg.ConfigOpts.__call__') @mock.patch('oslo_log.log.setup') @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt') def test_main_invalid_dir(self, register_cli_opt, log_setup, config_opts_call): script_name = 'cinder-manage' fake_dir = 'fake-dir' invalid_dir = 'Invalid directory:' sys.argv = [script_name, '--config-dir', fake_dir] config_opts_call.side_effect = cfg.ConfigDirNotFoundError(fake_dir) with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: exit = self.assertRaises(SystemExit, cinder_manage.main) self.assertTrue(register_cli_opt.called) config_opts_call.assert_called_once_with( sys.argv[1:], project='cinder', version=version.version_string()) self.assertIn(invalid_dir, fake_out.getvalue()) self.assertIn(fake_dir, fake_out.getvalue()) self.assertFalse(log_setup.called) self.assertEqual(2, exit.code) @mock.patch('cinder.db') def test_remove_service_failure(self, mock_db): mock_db.service_destroy.side_effect = SystemExit(1) service_commands = cinder_manage.ServiceCommands() exit = service_commands.remove('abinary', 'ahost') self.assertEqual(2, exit) @mock.patch('cinder.db.service_destroy') @mock.patch( 'cinder.db.service_get', return_value = {'id': '12', 'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'}) def test_remove_service_success(self, mock_get_by_args, mock_service_destroy): service_commands = cinder_manage.ServiceCommands() self.assertIsNone(service_commands.remove('abinary', 'ahost')) @test.testtools.skipIf(sys.platform == 'darwin', 'Not supported on macOS') class TestCinderRtstoolCmd(test.TestCase): def setUp(self): super(TestCinderRtstoolCmd, self).setUp() sys.argv = ['cinder-rtstool'] self.INITIATOR_IQN = 'iqn.2015.12.com.example.openstack.i:UNIT1' self.TARGET_IQN = 'iqn.2015.12.com.example.openstack.i:TARGET1' @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_create_rtslib_error(self, rtsroot): rtsroot.side_effect = rtslib_fb.utils.RTSLibError() with mock.patch('sys.stdout', new=six.StringIO()): self.assertRaises(rtslib_fb.utils.RTSLibError, cinder_rtstool.create, mock.sentinel.backing_device, mock.sentinel.name, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.iser_enabled) def _test_create_rtslib_error_network_portal(self, ip): with mock.patch.object(rtslib_fb, 'NetworkPortal') as network_portal, \ mock.patch.object(rtslib_fb, 'LUN') as lun, \ mock.patch.object(rtslib_fb, 'TPG') as tpg, \ mock.patch.object(rtslib_fb, 'FabricModule') as fabric_mod, \ mock.patch.object(rtslib_fb, 'Target') as target, \ mock.patch.object(rtslib_fb, 'BlockStorageObject') as \ block_storage_object, \ mock.patch.object(rtslib_fb.root, 'RTSRoot') as rts_root: root_new = mock.MagicMock(storage_objects=mock.MagicMock()) rts_root.return_value = root_new block_storage_object.return_value = mock.sentinel.so_new target.return_value = mock.sentinel.target_new fabric_mod.return_value = mock.sentinel.fabric_new tpg_new = tpg.return_value lun.return_value = mock.sentinel.lun_new if ip == '0.0.0.0': network_portal.side_effect = rtslib_fb.utils.RTSLibError() self.assertRaises(rtslib_fb.utils.RTSLibError, cinder_rtstool.create, mock.sentinel.backing_device, mock.sentinel.name, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.iser_enabled) else: cinder_rtstool.create(mock.sentinel.backing_device, mock.sentinel.name, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.iser_enabled) rts_root.assert_called_once_with() block_storage_object.assert_called_once_with( name=mock.sentinel.name, dev=mock.sentinel.backing_device) target.assert_called_once_with(mock.sentinel.fabric_new, mock.sentinel.name, 'create') fabric_mod.assert_called_once_with('iscsi') tpg.assert_called_once_with(mock.sentinel.target_new, mode='create') tpg_new.set_attribute.assert_called_once_with('authentication', '1') lun.assert_called_once_with(tpg_new, storage_object=mock.sentinel.so_new) self.assertEqual(1, tpg_new.enable) if ip == '::0': ip = '[::0]' network_portal.assert_any_call(tpg_new, ip, 3260, mode='any') def test_create_rtslib_error_network_portal_ipv4(self): with mock.patch('sys.stdout', new=six.StringIO()): self._test_create_rtslib_error_network_portal('0.0.0.0') def test_create_rtslib_error_network_portal_ipv6(self): with mock.patch('sys.stdout', new=six.StringIO()): self._test_create_rtslib_error_network_portal('::0') def _test_create(self, ip): with mock.patch.object(rtslib_fb, 'NetworkPortal') as network_portal, \ mock.patch.object(rtslib_fb, 'LUN') as lun, \ mock.patch.object(rtslib_fb, 'TPG') as tpg, \ mock.patch.object(rtslib_fb, 'FabricModule') as fabric_mod, \ mock.patch.object(rtslib_fb, 'Target') as target, \ mock.patch.object(rtslib_fb, 'BlockStorageObject') as \ block_storage_object, \ mock.patch.object(rtslib_fb.root, 'RTSRoot') as rts_root: root_new = mock.MagicMock(storage_objects=mock.MagicMock()) rts_root.return_value = root_new block_storage_object.return_value = mock.sentinel.so_new target.return_value = mock.sentinel.target_new fabric_mod.return_value = mock.sentinel.fabric_new tpg_new = tpg.return_value lun.return_value = mock.sentinel.lun_new cinder_rtstool.create(mock.sentinel.backing_device, mock.sentinel.name, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.iser_enabled) rts_root.assert_called_once_with() block_storage_object.assert_called_once_with( name=mock.sentinel.name, dev=mock.sentinel.backing_device) target.assert_called_once_with(mock.sentinel.fabric_new, mock.sentinel.name, 'create') fabric_mod.assert_called_once_with('iscsi') tpg.assert_called_once_with(mock.sentinel.target_new, mode='create') tpg_new.set_attribute.assert_called_once_with('authentication', '1') lun.assert_called_once_with(tpg_new, storage_object=mock.sentinel.so_new) self.assertEqual(1, tpg_new.enable) if ip == '::0': ip = '[::0]' network_portal.assert_any_call(tpg_new, ip, 3260, mode='any') def test_create_ipv4(self): self._test_create('0.0.0.0') def test_create_ipv6(self): self._test_create('::0') def _test_create_ips_and_port(self, mock_rtslib, port, ips, expected_ips): mock_rtslib.BlockStorageObject.return_value = mock.sentinel.bso mock_rtslib.Target.return_value = mock.sentinel.target_new mock_rtslib.FabricModule.return_value = mock.sentinel.iscsi_fabric tpg_new = mock_rtslib.TPG.return_value cinder_rtstool.create(mock.sentinel.backing_device, mock.sentinel.name, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.iser_enabled, portals_ips=ips, portals_port=port) mock_rtslib.Target.assert_called_once_with(mock.sentinel.iscsi_fabric, mock.sentinel.name, 'create') mock_rtslib.TPG.assert_called_once_with(mock.sentinel.target_new, mode='create') mock_rtslib.LUN.assert_called_once_with( tpg_new, storage_object=mock.sentinel.bso) mock_rtslib.NetworkPortal.assert_has_calls( map(lambda ip: mock.call(tpg_new, ip, port, mode='any'), expected_ips), any_order=True ) @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True) def test_create_ips_and_port_ipv4(self, mock_rtslib): ips = ['10.0.0.2', '10.0.0.3', '10.0.0.4'] port = 3261 self._test_create_ips_and_port(mock_rtslib, port, ips, ips) @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True) def test_create_ips_and_port_ipv6(self, mock_rtslib): ips = ['fe80::fc16:3eff:fecb:ad2f'] expected_ips = ['[fe80::fc16:3eff:fecb:ad2f]'] port = 3261 self._test_create_ips_and_port(mock_rtslib, port, ips, expected_ips) @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_add_initiator_rtslib_error(self, rtsroot): rtsroot.side_effect = rtslib_fb.utils.RTSLibError() with mock.patch('sys.stdout', new=six.StringIO()): self.assertRaises(rtslib_fb.utils.RTSLibError, cinder_rtstool.add_initiator, mock.sentinel.target_iqn, self.INITIATOR_IQN, mock.sentinel.userid, mock.sentinel.password) @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_add_initiator_rtstool_error(self, rtsroot): rtsroot.targets.return_value = {} self.assertRaises(cinder_rtstool.RtstoolError, cinder_rtstool.add_initiator, mock.sentinel.target_iqn, self.INITIATOR_IQN, mock.sentinel.userid, mock.sentinel.password) @mock.patch.object(rtslib_fb, 'MappedLUN') @mock.patch.object(rtslib_fb, 'NodeACL') @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_add_initiator_acl_exists(self, rtsroot, node_acl, mapped_lun): target_iqn = mock.MagicMock() target_iqn.tpgs.return_value = \ [{'node_acls': self.INITIATOR_IQN}] acl = mock.MagicMock(node_wwn=self.INITIATOR_IQN) tpg = mock.MagicMock(node_acls=[acl]) tpgs = iter([tpg]) target = mock.MagicMock(tpgs=tpgs, wwn=self.TARGET_IQN) rtsroot.return_value = mock.MagicMock(targets=[target]) cinder_rtstool.add_initiator(self.TARGET_IQN, self.INITIATOR_IQN, mock.sentinel.userid, mock.sentinel.password) self.assertFalse(node_acl.called) self.assertFalse(mapped_lun.called) @mock.patch.object(rtslib_fb, 'MappedLUN') @mock.patch.object(rtslib_fb, 'NodeACL') @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_add_initiator_acl_exists_case_1(self, rtsroot, node_acl, mapped_lun): """Ensure initiator iqns are handled in a case-insensitive manner.""" target_iqn = mock.MagicMock() target_iqn.tpgs.return_value = \ [{'node_acls': self.INITIATOR_IQN.lower()}] acl = mock.MagicMock(node_wwn=self.INITIATOR_IQN) tpg = mock.MagicMock(node_acls=[acl]) tpgs = iter([tpg]) target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn) rtsroot.return_value = mock.MagicMock(targets=[target]) cinder_rtstool.add_initiator(target_iqn, self.INITIATOR_IQN, mock.sentinel.userid, mock.sentinel.password) self.assertFalse(node_acl.called) self.assertFalse(mapped_lun.called) @mock.patch.object(rtslib_fb, 'MappedLUN') @mock.patch.object(rtslib_fb, 'NodeACL') @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_add_initiator_acl_exists_case_2(self, rtsroot, node_acl, mapped_lun): """Ensure initiator iqns are handled in a case-insensitive manner.""" iqn_lower = self.INITIATOR_IQN.lower() target_iqn = mock.MagicMock() target_iqn.tpgs.return_value = \ [{'node_acls': self.INITIATOR_IQN}] acl = mock.MagicMock(node_wwn=iqn_lower) tpg = mock.MagicMock(node_acls=[acl]) tpgs = iter([tpg]) target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn) rtsroot.return_value = mock.MagicMock(targets=[target]) cinder_rtstool.add_initiator(target_iqn, self.INITIATOR_IQN, mock.sentinel.userid, mock.sentinel.password) self.assertFalse(node_acl.called) self.assertFalse(mapped_lun.called) @mock.patch.object(rtslib_fb, 'MappedLUN') @mock.patch.object(rtslib_fb, 'NodeACL') @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_add_initiator(self, rtsroot, node_acl, mapped_lun): target_iqn = mock.MagicMock() target_iqn.tpgs.return_value = \ [{'node_acls': self.INITIATOR_IQN}] tpg = mock.MagicMock() tpgs = iter([tpg]) target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn) rtsroot.return_value = mock.MagicMock(targets=[target]) acl_new = mock.MagicMock(chap_userid=mock.sentinel.userid, chap_password=mock.sentinel.password) node_acl.return_value = acl_new cinder_rtstool.add_initiator(target_iqn, self.INITIATOR_IQN, mock.sentinel.userid, mock.sentinel.password) node_acl.assert_called_once_with(tpg, self.INITIATOR_IQN, mode='create') mapped_lun.assert_called_once_with(acl_new, 0, tpg_lun=0) @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_get_targets(self, rtsroot): target = mock.MagicMock() target.dump.return_value = {'wwn': 'fake-wwn'} rtsroot.return_value = mock.MagicMock(targets=[target]) with mock.patch('sys.stdout', new=six.StringIO()) as fake_out: cinder_rtstool.get_targets() self.assertEqual(str(target.wwn), fake_out.getvalue().strip()) @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_delete(self, rtsroot): target = mock.MagicMock(wwn=mock.sentinel.iqn) storage_object = mock.MagicMock() name = mock.PropertyMock(return_value=mock.sentinel.iqn) type(storage_object).name = name rtsroot.return_value = mock.MagicMock( targets=[target], storage_objects=[storage_object]) cinder_rtstool.delete(mock.sentinel.iqn) target.delete.assert_called_once_with() storage_object.delete.assert_called_once_with() @mock.patch.object(rtslib_fb, 'MappedLUN') @mock.patch.object(rtslib_fb, 'NodeACL') @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_delete_initiator(self, rtsroot, node_acl, mapped_lun): target_iqn = mock.MagicMock() target_iqn.tpgs.return_value = \ [{'node_acls': self.INITIATOR_IQN}] acl = mock.MagicMock(node_wwn=self.INITIATOR_IQN) tpg = mock.MagicMock(node_acls=[acl]) tpgs = iter([tpg]) target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn) rtsroot.return_value = mock.MagicMock(targets=[target]) cinder_rtstool.delete_initiator(target_iqn, self.INITIATOR_IQN) @mock.patch.object(rtslib_fb, 'MappedLUN') @mock.patch.object(rtslib_fb, 'NodeACL') @mock.patch.object(rtslib_fb.root, 'RTSRoot') def test_delete_initiator_case(self, rtsroot, node_acl, mapped_lun): """Ensure iqns are handled in a case-insensitive manner.""" initiator_iqn_lower = self.INITIATOR_IQN.lower() target_iqn = mock.MagicMock() target_iqn.tpgs.return_value = \ [{'node_acls': initiator_iqn_lower}] acl = mock.MagicMock(node_wwn=self.INITIATOR_IQN) tpg = mock.MagicMock(node_acls=[acl]) tpgs = iter([tpg]) target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn) rtsroot.return_value = mock.MagicMock(targets=[target]) cinder_rtstool.delete_initiator(target_iqn, self.INITIATOR_IQN) @mock.patch.object(cinder_rtstool, 'os', autospec=True) @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True) def test_save_with_filename(self, mock_rtslib, mock_os): filename = mock.sentinel.filename cinder_rtstool.save_to_file(filename) rtsroot = mock_rtslib.root.RTSRoot rtsroot.assert_called_once_with() self.assertEqual(0, mock_os.path.dirname.call_count) self.assertEqual(0, mock_os.path.exists.call_count) self.assertEqual(0, mock_os.makedirs.call_count) rtsroot.return_value.save_to_file.assert_called_once_with(filename) @mock.patch.object(cinder_rtstool, 'os', **{'path.exists.return_value': True, 'path.dirname.return_value': mock.sentinel.dirname}) @mock.patch.object(cinder_rtstool, 'rtslib_fb', **{'root.default_save_file': mock.sentinel.filename}) def test_save(self, mock_rtslib, mock_os): """Test that we check path exists with default file.""" cinder_rtstool.save_to_file(None) rtsroot = mock_rtslib.root.RTSRoot rtsroot.assert_called_once_with() rtsroot.return_value.save_to_file.assert_called_once_with( mock.sentinel.filename) mock_os.path.dirname.assert_called_once_with(mock.sentinel.filename) mock_os.path.exists.assert_called_once_with(mock.sentinel.dirname) self.assertEqual(0, mock_os.makedirs.call_count) @mock.patch.object(cinder_rtstool, 'os', **{'path.exists.return_value': False, 'path.dirname.return_value': mock.sentinel.dirname}) @mock.patch.object(cinder_rtstool, 'rtslib_fb', **{'root.default_save_file': mock.sentinel.filename}) def test_save_no_targetcli(self, mock_rtslib, mock_os): """Test that we create path if it doesn't exist with default file.""" cinder_rtstool.save_to_file(None) rtsroot = mock_rtslib.root.RTSRoot rtsroot.assert_called_once_with() rtsroot.return_value.save_to_file.assert_called_once_with( mock.sentinel.filename) mock_os.path.dirname.assert_called_once_with(mock.sentinel.filename) mock_os.path.exists.assert_called_once_with(mock.sentinel.dirname) mock_os.makedirs.assert_called_once_with(mock.sentinel.dirname, 0o755) @mock.patch.object(cinder_rtstool, 'os', autospec=True) @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True) def test_save_error_creating_dir(self, mock_rtslib, mock_os): mock_os.path.dirname.return_value = 'dirname' mock_os.path.exists.return_value = False mock_os.makedirs.side_effect = OSError('error') regexp = (r'targetcli not installed and could not create default ' r'directory \(dirname\): error$') self.assertRaisesRegex(cinder_rtstool.RtstoolError, regexp, cinder_rtstool.save_to_file, None) @mock.patch.object(cinder_rtstool, 'os', autospec=True) @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True) def test_save_error_saving(self, mock_rtslib, mock_os): save = mock_rtslib.root.RTSRoot.return_value.save_to_file save.side_effect = OSError('error') regexp = r'Could not save configuration to myfile: error' self.assertRaisesRegex(cinder_rtstool.RtstoolError, regexp, cinder_rtstool.save_to_file, 'myfile') @mock.patch.object(cinder_rtstool, 'rtslib_fb', **{'root.default_save_file': mock.sentinel.filename}) def test_restore(self, mock_rtslib): """Test that we restore target configuration with default file.""" cinder_rtstool.restore_from_file(None) rtsroot = mock_rtslib.root.RTSRoot rtsroot.assert_called_once_with() rtsroot.return_value.restore_from_file.assert_called_once_with( mock.sentinel.filename) @mock.patch.object(cinder_rtstool, 'rtslib_fb') def test_restore_with_file(self, mock_rtslib): """Test that we restore target configuration with specified file.""" cinder_rtstool.restore_from_file('saved_file') rtsroot = mock_rtslib.root.RTSRoot rtsroot.return_value.restore_from_file.assert_called_once_with( 'saved_file') @mock.patch('cinder.cmd.rtstool.restore_from_file') def test_restore_error(self, restore_from_file): """Test that we fail to restore target configuration.""" restore_from_file.side_effect = OSError self.assertRaises(OSError, cinder_rtstool.restore_from_file, mock.sentinel.filename) def test_usage(self): with mock.patch('sys.stdout', new=six.StringIO()): exit = self.assertRaises(SystemExit, cinder_rtstool.usage) self.assertEqual(1, exit.code) @mock.patch('cinder.cmd.rtstool.usage') def test_main_argc_lt_2(self, usage): usage.side_effect = SystemExit(1) sys.argv = ['cinder-rtstool'] exit = self.assertRaises(SystemExit, cinder_rtstool.usage) self.assertTrue(usage.called) self.assertEqual(1, exit.code) def test_main_create_argv_lt_6(self): sys.argv = ['cinder-rtstool', 'create'] self._test_main_check_argv() def test_main_create_argv_gt_7(self): sys.argv = ['cinder-rtstool', 'create', 'fake-arg1', 'fake-arg2', 'fake-arg3', 'fake-arg4', 'fake-arg5', 'fake-arg6'] self._test_main_check_argv() def test_main_add_initiator_argv_lt_6(self): sys.argv = ['cinder-rtstool', 'add-initiator'] self._test_main_check_argv() def test_main_delete_argv_lt_3(self): sys.argv = ['cinder-rtstool', 'delete'] self._test_main_check_argv() def test_main_no_action(self): sys.argv = ['cinder-rtstool'] self._test_main_check_argv() def _test_main_check_argv(self): with mock.patch('cinder.cmd.rtstool.usage') as usage: usage.side_effect = SystemExit(1) sys.argv = ['cinder-rtstool', 'create'] exit = self.assertRaises(SystemExit, cinder_rtstool.main) self.assertTrue(usage.called) self.assertEqual(1, exit.code) @mock.patch('cinder.cmd.rtstool.save_to_file') def test_main_save(self, mock_save): sys.argv = ['cinder-rtstool', 'save'] rc = cinder_rtstool.main() mock_save.assert_called_once_with(None) self.assertEqual(0, rc) @mock.patch('cinder.cmd.rtstool.save_to_file') def test_main_save_with_file(self, mock_save): sys.argv = ['cinder-rtstool', 'save', mock.sentinel.filename] rc = cinder_rtstool.main() mock_save.assert_called_once_with(mock.sentinel.filename) self.assertEqual(0, rc) def test_main_create(self): with mock.patch('cinder.cmd.rtstool.create') as create: sys.argv = ['cinder-rtstool', 'create', mock.sentinel.backing_device, mock.sentinel.name, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.iser_enabled, str(mock.sentinel.initiator_iqns)] rc = cinder_rtstool.main() create.assert_called_once_with( mock.sentinel.backing_device, mock.sentinel.name, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.iser_enabled, initiator_iqns=str(mock.sentinel.initiator_iqns)) self.assertEqual(0, rc) @mock.patch('cinder.cmd.rtstool.create') def test_main_create_ips_and_port(self, mock_create): sys.argv = ['cinder-rtstool', 'create', mock.sentinel.backing_device, mock.sentinel.name, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.iser_enabled, str(mock.sentinel.initiator_iqns), '-p3261', '-aip1,ip2,ip3'] rc = cinder_rtstool.main() mock_create.assert_called_once_with( mock.sentinel.backing_device, mock.sentinel.name, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.iser_enabled, initiator_iqns=str(mock.sentinel.initiator_iqns), portals_ips=['ip1', 'ip2', 'ip3'], portals_port=3261) self.assertEqual(0, rc) def test_main_add_initiator(self): with mock.patch('cinder.cmd.rtstool.add_initiator') as add_initiator: sys.argv = ['cinder-rtstool', 'add-initiator', mock.sentinel.target_iqn, mock.sentinel.userid, mock.sentinel.password, mock.sentinel.initiator_iqns] rc = cinder_rtstool.main() add_initiator.assert_called_once_with( mock.sentinel.target_iqn, mock.sentinel.initiator_iqns, mock.sentinel.userid, mock.sentinel.password) self.assertEqual(0, rc) def test_main_get_targets(self): with mock.patch('cinder.cmd.rtstool.get_targets') as get_targets: sys.argv = ['cinder-rtstool', 'get-targets'] rc = cinder_rtstool.main() get_targets.assert_called_once_with() self.assertEqual(0, rc) def test_main_delete(self): with mock.patch('cinder.cmd.rtstool.delete') as delete: sys.argv = ['cinder-rtstool', 'delete', mock.sentinel.iqn] rc = cinder_rtstool.main() delete.assert_called_once_with(mock.sentinel.iqn) self.assertEqual(0, rc) @mock.patch.object(cinder_rtstool, 'verify_rtslib') def test_main_verify(self, mock_verify_rtslib): sys.argv = ['cinder-rtstool', 'verify'] rc = cinder_rtstool.main() mock_verify_rtslib.assert_called_once_with() self.assertEqual(0, rc) class TestCinderVolumeUsageAuditCmd(test.TestCase): def setUp(self): super(TestCinderVolumeUsageAuditCmd, self).setUp() sys.argv = ['cinder-volume-usage-audit'] @mock.patch('cinder.utils.last_completed_audit_period') @mock.patch('cinder.rpc.init') @mock.patch('cinder.version.version_string') @mock.patch('oslo_log.log.getLogger') @mock.patch('oslo_log.log.setup') @mock.patch('cinder.context.get_admin_context') def test_main_time_error(self, get_admin_context, log_setup, get_logger, version_string, rpc_init, last_completed_audit_period): CONF.set_override('start_time', '2014-01-01 01:00:00') CONF.set_override('end_time', '2013-01-01 01:00:00') last_completed_audit_period.return_value = (mock.sentinel.begin, mock.sentinel.end) exit = self.assertRaises(SystemExit, volume_usage_audit.main) get_admin_context.assert_called_once_with() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") get_logger.assert_called_once_with('cinder') self.assertEqual(-1, exit.code) rpc_init.assert_called_once_with(CONF) last_completed_audit_period.assert_called_once_with() @mock.patch('cinder.volume.volume_utils.notify_about_volume_usage') @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window') @mock.patch('cinder.utils.last_completed_audit_period') @mock.patch('cinder.rpc.init') @mock.patch('cinder.version.version_string') @mock.patch('oslo_log.log.getLogger') @mock.patch('oslo_log.log.setup') @mock.patch('cinder.context.get_admin_context') def test_main_send_create_volume_error(self, get_admin_context, log_setup, get_logger, version_string, rpc_init, last_completed_audit_period, volume_get_all_active_by_window, notify_about_volume_usage): CONF.set_override('send_actions', True) CONF.set_override('start_time', '2014-01-01 01:00:00') CONF.set_override('end_time', '2014-02-02 02:00:00') begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC) end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC) ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_context.return_value = ctxt last_completed_audit_period.return_value = (begin, end) volume1_created = datetime.datetime(2014, 1, 1, 2, 0, tzinfo=iso8601.UTC) volume1_deleted = datetime.datetime(2014, 1, 1, 3, 0, tzinfo=iso8601.UTC) volume1 = mock.MagicMock(id=fake.VOLUME_ID, project_id=fake.PROJECT_ID, created_at=volume1_created, deleted_at=volume1_deleted) volume_get_all_active_by_window.return_value = [volume1] extra_info = { 'audit_period_beginning': str(begin), 'audit_period_ending': str(end), } local_extra_info = { 'audit_period_beginning': str(volume1.created_at), 'audit_period_ending': str(volume1.created_at), } def _notify_about_volume_usage(*args, **kwargs): if 'create.end' in args: raise Exception() else: pass notify_about_volume_usage.side_effect = _notify_about_volume_usage volume_usage_audit.main() get_admin_context.assert_called_once_with() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") get_logger.assert_called_once_with('cinder') rpc_init.assert_called_once_with(CONF) last_completed_audit_period.assert_called_once_with() volume_get_all_active_by_window.assert_called_once_with(ctxt, begin, end) notify_about_volume_usage.assert_has_calls([ mock.call(ctxt, volume1, 'exists', extra_usage_info=extra_info), mock.call(ctxt, volume1, 'create.start', extra_usage_info=local_extra_info), mock.call(ctxt, volume1, 'create.end', extra_usage_info=local_extra_info) ]) @mock.patch('cinder.volume.volume_utils.notify_about_volume_usage') @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window') @mock.patch('cinder.utils.last_completed_audit_period') @mock.patch('cinder.rpc.init') @mock.patch('cinder.version.version_string') @mock.patch('oslo_log.log.getLogger') @mock.patch('oslo_log.log.setup') @mock.patch('cinder.context.get_admin_context') def test_main_send_delete_volume_error(self, get_admin_context, log_setup, get_logger, version_string, rpc_init, last_completed_audit_period, volume_get_all_active_by_window, notify_about_volume_usage): CONF.set_override('send_actions', True) CONF.set_override('start_time', '2014-01-01 01:00:00') CONF.set_override('end_time', '2014-02-02 02:00:00') begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC) end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC) ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_context.return_value = ctxt last_completed_audit_period.return_value = (begin, end) volume1_created = datetime.datetime(2014, 1, 1, 2, 0, tzinfo=iso8601.UTC) volume1_deleted = datetime.datetime(2014, 1, 1, 3, 0, tzinfo=iso8601.UTC) volume1 = mock.MagicMock(id=fake.VOLUME_ID, project_id=fake.PROJECT_ID, created_at=volume1_created, deleted_at=volume1_deleted) volume_get_all_active_by_window.return_value = [volume1] extra_info = { 'audit_period_beginning': str(begin), 'audit_period_ending': str(end), } local_extra_info_create = { 'audit_period_beginning': str(volume1.created_at), 'audit_period_ending': str(volume1.created_at), } local_extra_info_delete = { 'audit_period_beginning': str(volume1.deleted_at), 'audit_period_ending': str(volume1.deleted_at), } def _notify_about_volume_usage(*args, **kwargs): if 'delete.end' in args: raise Exception() else: pass notify_about_volume_usage.side_effect = _notify_about_volume_usage volume_usage_audit.main() get_admin_context.assert_called_once_with() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") get_logger.assert_called_once_with('cinder') rpc_init.assert_called_once_with(CONF) last_completed_audit_period.assert_called_once_with() volume_get_all_active_by_window.assert_called_once_with(ctxt, begin, end) notify_about_volume_usage.assert_has_calls([ mock.call(ctxt, volume1, 'exists', extra_usage_info=extra_info), mock.call(ctxt, volume1, 'create.start', extra_usage_info=local_extra_info_create), mock.call(ctxt, volume1, 'create.end', extra_usage_info=local_extra_info_create), mock.call(ctxt, volume1, 'delete.start', extra_usage_info=local_extra_info_delete), mock.call(ctxt, volume1, 'delete.end', extra_usage_info=local_extra_info_delete) ]) @mock.patch('cinder.volume.volume_utils.notify_about_snapshot_usage') @mock.patch('cinder.objects.snapshot.SnapshotList.' 'get_all_active_by_window') @mock.patch('cinder.volume.volume_utils.notify_about_volume_usage') @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window') @mock.patch('cinder.utils.last_completed_audit_period') @mock.patch('cinder.rpc.init') @mock.patch('cinder.version.version_string') @mock.patch('oslo_log.log.getLogger') @mock.patch('oslo_log.log.setup') @mock.patch('cinder.context.get_admin_context') def test_main_send_snapshot_error(self, get_admin_context, log_setup, get_logger, version_string, rpc_init, last_completed_audit_period, volume_get_all_active_by_window, notify_about_volume_usage, snapshot_get_all_active_by_window, notify_about_snapshot_usage): CONF.set_override('send_actions', True) CONF.set_override('start_time', '2014-01-01 01:00:00') CONF.set_override('end_time', '2014-02-02 02:00:00') begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC) end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC) ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_context.return_value = ctxt last_completed_audit_period.return_value = (begin, end) snapshot1_created = datetime.datetime(2014, 1, 1, 2, 0, tzinfo=iso8601.UTC) snapshot1_deleted = datetime.datetime(2014, 1, 1, 3, 0, tzinfo=iso8601.UTC) snapshot1 = mock.MagicMock(id=fake.VOLUME_ID, project_id=fake.PROJECT_ID, created_at=snapshot1_created, deleted_at=snapshot1_deleted) volume_get_all_active_by_window.return_value = [] snapshot_get_all_active_by_window.return_value = [snapshot1] extra_info = { 'audit_period_beginning': str(begin), 'audit_period_ending': str(end), } local_extra_info_create = { 'audit_period_beginning': str(snapshot1.created_at), 'audit_period_ending': str(snapshot1.created_at), } local_extra_info_delete = { 'audit_period_beginning': str(snapshot1.deleted_at), 'audit_period_ending': str(snapshot1.deleted_at), } def _notify_about_snapshot_usage(*args, **kwargs): # notify_about_snapshot_usage raises an exception, but does not # block raise Exception() notify_about_snapshot_usage.side_effect = _notify_about_snapshot_usage volume_usage_audit.main() get_admin_context.assert_called_once_with() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") get_logger.assert_called_once_with('cinder') rpc_init.assert_called_once_with(CONF) last_completed_audit_period.assert_called_once_with() volume_get_all_active_by_window.assert_called_once_with(ctxt, begin, end) self.assertFalse(notify_about_volume_usage.called) notify_about_snapshot_usage.assert_has_calls([ mock.call(ctxt, snapshot1, 'exists', extra_info), mock.call(ctxt, snapshot1, 'create.start', extra_usage_info=local_extra_info_create), mock.call(ctxt, snapshot1, 'delete.start', extra_usage_info=local_extra_info_delete) ]) @mock.patch('cinder.volume.volume_utils.notify_about_backup_usage') @mock.patch('cinder.objects.backup.BackupList.get_all_active_by_window') @mock.patch('cinder.volume.volume_utils.notify_about_volume_usage') @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window') @mock.patch('cinder.utils.last_completed_audit_period') @mock.patch('cinder.rpc.init') @mock.patch('cinder.version.version_string') @mock.patch('cinder.context.get_admin_context') def test_main_send_backup_error(self, get_admin_context, version_string, rpc_init, last_completed_audit_period, volume_get_all_active_by_window, notify_about_volume_usage, backup_get_all_active_by_window, notify_about_backup_usage): CONF.set_override('send_actions', True) CONF.set_override('start_time', '2014-01-01 01:00:00') CONF.set_override('end_time', '2014-02-02 02:00:00') begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC) end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC) ctxt = context.RequestContext('fake-user', 'fake-project') get_admin_context.return_value = ctxt last_completed_audit_period.return_value = (begin, end) backup1_created = datetime.datetime(2014, 1, 1, 2, 0, tzinfo=iso8601.UTC) backup1_deleted = datetime.datetime(2014, 1, 1, 3, 0, tzinfo=iso8601.UTC) backup1 = mock.MagicMock(id=fake.BACKUP_ID, project_id=fake.PROJECT_ID, created_at=backup1_created, deleted_at=backup1_deleted) volume_get_all_active_by_window.return_value = [] backup_get_all_active_by_window.return_value = [backup1] extra_info = { 'audit_period_beginning': str(begin), 'audit_period_ending': str(end), } local_extra_info_create = { 'audit_period_beginning': str(backup1.created_at), 'audit_period_ending': str(backup1.created_at), } local_extra_info_delete = { 'audit_period_beginning': str(backup1.deleted_at), 'audit_period_ending': str(backup1.deleted_at), } notify_about_backup_usage.side_effect = Exception() volume_usage_audit.main() get_admin_context.assert_called_once_with() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) rpc_init.assert_called_once_with(CONF) last_completed_audit_period.assert_called_once_with() volume_get_all_active_by_window.assert_called_once_with(ctxt, begin, end) self.assertFalse(notify_about_volume_usage.called) notify_about_backup_usage.assert_any_call(ctxt, backup1, 'exists', extra_info) notify_about_backup_usage.assert_any_call( ctxt, backup1, 'create.start', extra_usage_info=local_extra_info_create) notify_about_backup_usage.assert_any_call( ctxt, backup1, 'delete.start', extra_usage_info=local_extra_info_delete) @mock.patch('cinder.volume.volume_utils.notify_about_backup_usage') @mock.patch('cinder.objects.backup.BackupList.get_all_active_by_window') @mock.patch('cinder.volume.volume_utils.notify_about_snapshot_usage') @mock.patch('cinder.objects.snapshot.SnapshotList.' 'get_all_active_by_window') @mock.patch('cinder.volume.volume_utils.notify_about_volume_usage') @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window') @mock.patch('cinder.utils.last_completed_audit_period') @mock.patch('cinder.rpc.init') @mock.patch('cinder.version.version_string') @mock.patch('oslo_log.log.getLogger') @mock.patch('oslo_log.log.setup') @mock.patch('cinder.context.get_admin_context') def test_main(self, get_admin_context, log_setup, get_logger, version_string, rpc_init, last_completed_audit_period, volume_get_all_active_by_window, notify_about_volume_usage, snapshot_get_all_active_by_window, notify_about_snapshot_usage, backup_get_all_active_by_window, notify_about_backup_usage): CONF.set_override('send_actions', True) CONF.set_override('start_time', '2014-01-01 01:00:00') CONF.set_override('end_time', '2014-02-02 02:00:00') begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC) end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC) ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID) get_admin_context.return_value = ctxt last_completed_audit_period.return_value = (begin, end) volume1_created = datetime.datetime(2014, 1, 1, 2, 0, tzinfo=iso8601.UTC) volume1_deleted = datetime.datetime(2014, 1, 1, 3, 0, tzinfo=iso8601.UTC) volume1 = mock.MagicMock(id=fake.VOLUME_ID, project_id=fake.PROJECT_ID, created_at=volume1_created, deleted_at=volume1_deleted) volume_get_all_active_by_window.return_value = [volume1] extra_info = { 'audit_period_beginning': str(begin), 'audit_period_ending': str(end), } extra_info_volume_create = { 'audit_period_beginning': str(volume1.created_at), 'audit_period_ending': str(volume1.created_at), } extra_info_volume_delete = { 'audit_period_beginning': str(volume1.deleted_at), 'audit_period_ending': str(volume1.deleted_at), } snapshot1_created = datetime.datetime(2014, 1, 1, 2, 0, tzinfo=iso8601.UTC) snapshot1_deleted = datetime.datetime(2014, 1, 1, 3, 0, tzinfo=iso8601.UTC) snapshot1 = mock.MagicMock(id=fake.VOLUME_ID, project_id=fake.PROJECT_ID, created_at=snapshot1_created, deleted_at=snapshot1_deleted) snapshot_get_all_active_by_window.return_value = [snapshot1] extra_info_snapshot_create = { 'audit_period_beginning': str(snapshot1.created_at), 'audit_period_ending': str(snapshot1.created_at), } extra_info_snapshot_delete = { 'audit_period_beginning': str(snapshot1.deleted_at), 'audit_period_ending': str(snapshot1.deleted_at), } backup1_created = datetime.datetime(2014, 1, 1, 2, 0, tzinfo=iso8601.UTC) backup1_deleted = datetime.datetime(2014, 1, 1, 3, 0, tzinfo=iso8601.UTC) backup1 = mock.MagicMock(id=fake.BACKUP_ID, project_id=fake.PROJECT_ID, created_at=backup1_created, deleted_at=backup1_deleted) backup_get_all_active_by_window.return_value = [backup1] extra_info_backup_create = { 'audit_period_beginning': str(backup1.created_at), 'audit_period_ending': str(backup1.created_at), } extra_info_backup_delete = { 'audit_period_beginning': str(backup1.deleted_at), 'audit_period_ending': str(backup1.deleted_at), } volume_usage_audit.main() get_admin_context.assert_called_once_with() self.assertEqual('cinder', CONF.project) self.assertEqual(CONF.version, version.version_string()) log_setup.assert_called_once_with(CONF, "cinder") get_logger.assert_called_once_with('cinder') rpc_init.assert_called_once_with(CONF) last_completed_audit_period.assert_called_once_with() volume_get_all_active_by_window.assert_called_once_with(ctxt, begin, end) notify_about_volume_usage.assert_has_calls([ mock.call(ctxt, volume1, 'exists', extra_usage_info=extra_info), mock.call(ctxt, volume1, 'create.start', extra_usage_info=extra_info_volume_create), mock.call(ctxt, volume1, 'create.end', extra_usage_info=extra_info_volume_create), mock.call(ctxt, volume1, 'delete.start', extra_usage_info=extra_info_volume_delete), mock.call(ctxt, volume1, 'delete.end', extra_usage_info=extra_info_volume_delete) ]) notify_about_snapshot_usage.assert_has_calls([ mock.call(ctxt, snapshot1, 'exists', extra_info), mock.call(ctxt, snapshot1, 'create.start', extra_usage_info=extra_info_snapshot_create), mock.call(ctxt, snapshot1, 'create.end', extra_usage_info=extra_info_snapshot_create), mock.call(ctxt, snapshot1, 'delete.start', extra_usage_info=extra_info_snapshot_delete), mock.call(ctxt, snapshot1, 'delete.end', extra_usage_info=extra_info_snapshot_delete) ]) notify_about_backup_usage.assert_has_calls([ mock.call(ctxt, backup1, 'exists', extra_info), mock.call(ctxt, backup1, 'create.start', extra_usage_info=extra_info_backup_create), mock.call(ctxt, backup1, 'create.end', extra_usage_info=extra_info_backup_create), mock.call(ctxt, backup1, 'delete.start', extra_usage_info=extra_info_backup_delete), mock.call(ctxt, backup1, 'delete.end', extra_usage_info=extra_info_backup_delete) ]) class TestVolumeSharedTargetsOnlineMigration(test.TestCase): """Unit tests for cinder.db.api.service_*.""" def setUp(self): super(TestVolumeSharedTargetsOnlineMigration, self).setUp() def _get_minimum_rpc_version_mock(ctxt, binary): binary_map = { 'cinder-volume': rpcapi.VolumeAPI, } return binary_map[binary].RPC_API_VERSION self.patch('cinder.objects.Service.get_minimum_rpc_version', side_effect=_get_minimum_rpc_version_mock) ctxt = context.get_admin_context() # default value in db for shared_targets on a volume # is True, so don't need to set it here explicitly for i in range(3): sqlalchemy_api.volume_create( ctxt, {'host': 'host1@lvm-driver1#lvm-driver1', 'service_uuid': 'f080f895-cff2-4eb3-9c61-050c060b59ad', 'volume_type_id': fake.VOLUME_TYPE_ID}) values = { 'host': 'host1@lvm-driver1', 'binary': constants.VOLUME_BINARY, 'topic': constants.VOLUME_TOPIC, 'uuid': 'f080f895-cff2-4eb3-9c61-050c060b59ad'} utils.create_service(ctxt, values) self.ctxt = ctxt