cinder/cinder/tests/unit/test_cmd.py

2298 lines
104 KiB
Python

# 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 iso8601
import sys
import time
import ddt
import fixtures
import mock
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 import test
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 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)
c2 = mock.call(binary=constants.BACKUP_BINARY,
coordination=True,
process_number=2)
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)
expected = """\
5 rows matched query mock_mig_1, 4 migrated
6 rows matched query mock_mig_2, 6 migrated
+------------+--------------+-----------+
| Migration | Total Needed | Completed |
+------------+--------------+-----------+
| mock_mig_1 | 5 | 4 |
| mock_mig_2 | 6 | 6 |
+------------+--------------+-----------+
"""
command.online_migrations[0].assert_has_calls([mock.call(ctxt,
10)])
command.online_migrations[1].assert_has_calls([mock.call(ctxt,
6)])
self.assertEqual(expected, sys.stdout.getvalue())
@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)
expected = """\
Running batches of 50 until complete.
120 rows matched query fake_migration, 50 migrated
120 rows matched query fake_migration, 50 migrated
120 rows matched query fake_migration, 20 migrated
120 rows matched query fake_migration, 0 migrated
+----------------+--------------+-----------+
| Migration | Total Needed | Completed |
+----------------+--------------+-----------+
| fake_migration | 120 | 120 |
+----------------+--------------+-----------+
"""
self.assertEqual(expected, sys.stdout.getvalue())
@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_module, \
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_module.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_module.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_module, \
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_module.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_module.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.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.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.utils.notify_about_snapshot_usage')
@mock.patch('cinder.objects.snapshot.SnapshotList.'
'get_all_active_by_window')
@mock.patch('cinder.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.utils.notify_about_backup_usage')
@mock.patch('cinder.objects.backup.BackupList.get_all_active_by_window')
@mock.patch('cinder.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.utils.notify_about_backup_usage')
@mock.patch('cinder.objects.backup.BackupList.get_all_active_by_window')
@mock.patch('cinder.volume.utils.notify_about_snapshot_usage')
@mock.patch('cinder.objects.snapshot.SnapshotList.'
'get_all_active_by_window')
@mock.patch('cinder.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'})
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
@mock.patch('cinder.objects.Service.get_minimum_obj_version',
return_value='1.8')
def test_shared_targets_migrations(self, mock_version):
"""Ensure we can update the column."""
# Run the migration and verify that we updated 1 entry
with mock.patch('cinder.volume.rpcapi.VolumeAPI.get_capabilities',
return_value={'connection_protocol': 'iSCSI',
'shared_targets': False}):
total, updated = (
cinder_manage.shared_targets_online_data_migration(
self.ctxt, 10))
self.assertEqual(3, total)
self.assertEqual(3, updated)
@mock.patch('cinder.objects.Service.get_minimum_obj_version',
return_value='1.8')
def test_shared_targets_migrations_non_iscsi(self, mock_version):
"""Ensure we can update the column."""
# Run the migration and verify that we updated 1 entry
with mock.patch('cinder.volume.rpcapi.VolumeAPI.get_capabilities',
return_value={'connection_protocol': 'RBD'}):
total, updated = (
cinder_manage.shared_targets_online_data_migration(
self.ctxt, 10))
self.assertEqual(3, total)
self.assertEqual(3, updated)
@mock.patch('cinder.objects.Service.get_minimum_obj_version',
return_value='1.8')
def test_shared_targets_migrations_with_limit(self, mock_version):
"""Ensure we update in batches."""
# Run the migration and verify that we updated 1 entry
with mock.patch('cinder.volume.rpcapi.VolumeAPI.get_capabilities',
return_value={'connection_protocol': 'iSCSI',
'shared_targets': False}):
total, updated = (
cinder_manage.shared_targets_online_data_migration(
self.ctxt, 2))
self.assertEqual(3, total)
self.assertEqual(2, updated)
total, updated = (
cinder_manage.shared_targets_online_data_migration(
self.ctxt, 2))
self.assertEqual(1, total)
self.assertEqual(1, updated)