1#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2#    not use this file except in compliance with the License. You may obtain
3#    a copy of the License at
4#
5#         http://www.apache.org/licenses/LICENSE-2.0
6#
7#    Unless required by applicable law or agreed to in writing, software
8#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10#    License for the specific language governing permissions and limitations
11#    under the License.
12
13import datetime
14import iso8601
15import sys
16import time
17
18import ddt
19import fixtures
20import mock
21from oslo_config import cfg
22from oslo_db import exception as oslo_exception
23from oslo_utils import timeutils
24import six
25from six.moves import StringIO
26
27try:
28    import rtslib_fb
29except ImportError:
30    import rtslib as rtslib_fb
31
32
33from cinder.cmd import api as cinder_api
34from cinder.cmd import backup as cinder_backup
35from cinder.cmd import manage as cinder_manage
36from cinder.cmd import rtstool as cinder_rtstool
37from cinder.cmd import scheduler as cinder_scheduler
38from cinder.cmd import volume as cinder_volume
39from cinder.cmd import volume_usage_audit
40from cinder.common import constants
41from cinder import context
42from cinder.db.sqlalchemy import api as sqlalchemy_api
43from cinder import exception
44from cinder.objects import fields
45from cinder import test
46from cinder.tests.unit import fake_cluster
47from cinder.tests.unit import fake_constants as fake
48from cinder.tests.unit import fake_service
49from cinder.tests.unit import fake_volume
50from cinder.tests.unit import utils
51from cinder import version
52from cinder.volume import rpcapi
53
54CONF = cfg.CONF
55
56
57class TestCinderApiCmd(test.TestCase):
58    """Unit test cases for python modules under cinder/cmd."""
59
60    def setUp(self):
61        super(TestCinderApiCmd, self).setUp()
62        sys.argv = ['cinder-api']
63
64    @mock.patch('cinder.service.WSGIService')
65    @mock.patch('cinder.service.process_launcher')
66    @mock.patch('cinder.rpc.init')
67    @mock.patch('cinder.utils.monkey_patch')
68    @mock.patch('oslo_log.log.setup')
69    def test_main(self, log_setup, monkey_patch, rpc_init, process_launcher,
70                  wsgi_service):
71        launcher = process_launcher.return_value
72        server = wsgi_service.return_value
73        server.workers = mock.sentinel.worker_count
74
75        cinder_api.main()
76
77        self.assertEqual('cinder', CONF.project)
78        self.assertEqual(CONF.version, version.version_string())
79        log_setup.assert_called_once_with(CONF, "cinder")
80        monkey_patch.assert_called_once_with()
81        rpc_init.assert_called_once_with(CONF)
82        process_launcher.assert_called_once_with()
83        wsgi_service.assert_called_once_with('osapi_volume')
84        launcher.launch_service.assert_called_once_with(
85            server,
86            workers=server.workers)
87        launcher.wait.assert_called_once_with()
88
89
90class TestCinderBackupCmd(test.TestCase):
91
92    def setUp(self):
93        super(TestCinderBackupCmd, self).setUp()
94        sys.argv = ['cinder-backup']
95
96    @mock.patch('cinder.service.wait')
97    @mock.patch('cinder.service.serve')
98    @mock.patch('cinder.service.Service.create')
99    @mock.patch('cinder.utils.monkey_patch')
100    @mock.patch('oslo_log.log.setup')
101    def test_main(self, log_setup, monkey_patch, service_create, service_serve,
102                  service_wait):
103        server = service_create.return_value
104
105        cinder_backup.main()
106
107        self.assertEqual('cinder', CONF.project)
108        self.assertEqual(CONF.version, version.version_string())
109        log_setup.assert_called_once_with(CONF, "cinder")
110        monkey_patch.assert_called_once_with()
111        service_create.assert_called_once_with(binary='cinder-backup',
112                                               coordination=True)
113        service_serve.assert_called_once_with(server)
114        service_wait.assert_called_once_with()
115
116
117class TestCinderSchedulerCmd(test.TestCase):
118
119    def setUp(self):
120        super(TestCinderSchedulerCmd, self).setUp()
121        sys.argv = ['cinder-scheduler']
122
123    @mock.patch('cinder.service.wait')
124    @mock.patch('cinder.service.serve')
125    @mock.patch('cinder.service.Service.create')
126    @mock.patch('cinder.utils.monkey_patch')
127    @mock.patch('oslo_log.log.setup')
128    def test_main(self, log_setup, monkey_patch, service_create,
129                  service_serve, service_wait):
130        server = service_create.return_value
131
132        cinder_scheduler.main()
133
134        self.assertEqual('cinder', CONF.project)
135        self.assertEqual(CONF.version, version.version_string())
136        log_setup.assert_called_once_with(CONF, "cinder")
137        monkey_patch.assert_called_once_with()
138        service_create.assert_called_once_with(binary='cinder-scheduler')
139        service_serve.assert_called_once_with(server)
140        service_wait.assert_called_once_with()
141
142
143class TestCinderVolumeCmdPosix(test.TestCase):
144
145    def setUp(self):
146        super(TestCinderVolumeCmdPosix, self).setUp()
147        sys.argv = ['cinder-volume']
148
149        self.patch('os.name', 'posix')
150
151    @mock.patch('cinder.service.get_launcher')
152    @mock.patch('cinder.service.Service.create')
153    @mock.patch('cinder.utils.monkey_patch')
154    @mock.patch('oslo_log.log.setup')
155    def test_main(self, log_setup, monkey_patch, service_create,
156                  get_launcher):
157        CONF.set_override('enabled_backends', None)
158        self.assertRaises(SystemExit, cinder_volume.main)
159        self.assertFalse(service_create.called)
160
161    @mock.patch('cinder.service.get_launcher')
162    @mock.patch('cinder.service.Service.create')
163    @mock.patch('cinder.utils.monkey_patch')
164    @mock.patch('oslo_log.log.setup')
165    def test_main_with_backends(self, log_setup, monkey_patch, service_create,
166                                get_launcher):
167        backends = ['', 'backend1', 'backend2', '']
168        CONF.set_override('enabled_backends', backends)
169        CONF.set_override('host', 'host')
170        launcher = get_launcher.return_value
171
172        cinder_volume.main()
173
174        self.assertEqual('cinder', CONF.project)
175        self.assertEqual(CONF.version, version.version_string())
176        log_setup.assert_called_once_with(CONF, "cinder")
177        monkey_patch.assert_called_once_with()
178        get_launcher.assert_called_once_with()
179        c1 = mock.call(binary=constants.VOLUME_BINARY, host='host@backend1',
180                       service_name='backend1', coordination=True,
181                       cluster=None)
182        c2 = mock.call(binary=constants.VOLUME_BINARY, host='host@backend2',
183                       service_name='backend2', coordination=True,
184                       cluster=None)
185        service_create.assert_has_calls([c1, c2])
186        self.assertEqual(2, launcher.launch_service.call_count)
187        launcher.wait.assert_called_once_with()
188
189
190@ddt.ddt
191class TestCinderVolumeCmdWin32(test.TestCase):
192
193    def setUp(self):
194        super(TestCinderVolumeCmdWin32, self).setUp()
195        sys.argv = ['cinder-volume']
196
197        self._mock_win32_proc_launcher = mock.Mock()
198
199        self.patch('os.name', 'nt')
200        self.patch('cinder.service.WindowsProcessLauncher',
201                   lambda *args, **kwargs: self._mock_win32_proc_launcher)
202
203    @mock.patch('cinder.service.get_launcher')
204    @mock.patch('cinder.service.Service.create')
205    @mock.patch('cinder.utils.monkey_patch')
206    @mock.patch('oslo_log.log.setup')
207    def test_main(self, log_setup, monkey_patch, service_create,
208                  get_launcher):
209        CONF.set_override('enabled_backends', None)
210        self.assertRaises(SystemExit, cinder_volume.main)
211        self.assertFalse(service_create.called)
212        self.assertFalse(self._mock_win32_proc_launcher.called)
213
214    @mock.patch('cinder.service.get_launcher')
215    @mock.patch('cinder.service.Service.create')
216    @mock.patch('cinder.utils.monkey_patch')
217    @mock.patch('oslo_log.log.setup')
218    def test_main_invalid_backend(self, log_setup, monkey_patch,
219                                  service_create, get_launcher):
220        CONF.set_override('enabled_backends', 'backend1')
221        CONF.set_override('backend_name', 'backend2')
222        self.assertRaises(exception.InvalidInput, cinder_volume.main)
223        self.assertFalse(service_create.called)
224        self.assertFalse(self._mock_win32_proc_launcher.called)
225
226    @mock.patch('cinder.utils.monkey_patch')
227    @mock.patch('oslo_log.log.setup')
228    @ddt.data({},
229              {'binary_path': 'cinder-volume-script.py',
230               'exp_py_executable': True})
231    @ddt.unpack
232    def test_main_with_multiple_backends(self, log_setup, monkey_patch,
233                                         binary_path='cinder-volume',
234                                         exp_py_executable=False):
235        # If multiple backends are used, we expect the Windows process
236        # launcher to be used in order to create the child processes.
237        backends = ['', 'backend1', 'backend2', '']
238        CONF.set_override('enabled_backends', backends)
239        CONF.set_override('host', 'host')
240        launcher = self._mock_win32_proc_launcher
241
242        # Depending on the setuptools version, '-script.py' and '.exe'
243        # binary path extensions may be trimmed. We need to take this
244        # into consideration when building the command that will be
245        # used to spawn child subprocesses.
246        sys.argv = [binary_path]
247
248        cinder_volume.main()
249
250        self.assertEqual('cinder', CONF.project)
251        self.assertEqual(CONF.version, version.version_string())
252        log_setup.assert_called_once_with(CONF, "cinder")
253        monkey_patch.assert_called_once_with()
254
255        exp_cmd_prefix = [sys.executable] if exp_py_executable else []
256        exp_cmds = [
257            exp_cmd_prefix + sys.argv + ['--backend_name=%s' % backend_name]
258            for backend_name in ['backend1', 'backend2']]
259        launcher.add_process.assert_has_calls(
260            [mock.call(exp_cmd) for exp_cmd in exp_cmds])
261        launcher.wait.assert_called_once_with()
262
263    @mock.patch('cinder.service.get_launcher')
264    @mock.patch('cinder.service.Service.create')
265    @mock.patch('cinder.utils.monkey_patch')
266    @mock.patch('oslo_log.log.setup')
267    def test_main_with_multiple_backends_child(
268            self, log_setup, monkey_patch, service_create, get_launcher):
269        # We're testing the code expected to be run within child processes.
270        backends = ['', 'backend1', 'backend2', '']
271        CONF.set_override('enabled_backends', backends)
272        CONF.set_override('host', 'host')
273        launcher = get_launcher.return_value
274
275        sys.argv += ['--backend_name', 'backend2']
276
277        cinder_volume.main()
278
279        self.assertEqual('cinder', CONF.project)
280        self.assertEqual(CONF.version, version.version_string())
281        log_setup.assert_called_once_with(CONF, "cinder")
282        monkey_patch.assert_called_once_with()
283
284        service_create.assert_called_once_with(
285            binary=constants.VOLUME_BINARY, host='host@backend2',
286            service_name='backend2', coordination=True,
287            cluster=None)
288        launcher.launch_service.assert_called_once_with(
289            service_create.return_value)
290
291    @mock.patch('cinder.service.get_launcher')
292    @mock.patch('cinder.service.Service.create')
293    @mock.patch('cinder.utils.monkey_patch')
294    @mock.patch('oslo_log.log.setup')
295    def test_main_with_single_backend(
296            self, log_setup, monkey_patch, service_create, get_launcher):
297        # We're expecting the service to be run within the same process.
298        CONF.set_override('enabled_backends', ['backend2'])
299        CONF.set_override('host', 'host')
300        launcher = get_launcher.return_value
301
302        cinder_volume.main()
303
304        self.assertEqual('cinder', CONF.project)
305        self.assertEqual(CONF.version, version.version_string())
306        log_setup.assert_called_once_with(CONF, "cinder")
307        monkey_patch.assert_called_once_with()
308
309        service_create.assert_called_once_with(
310            binary=constants.VOLUME_BINARY, host='host@backend2',
311            service_name='backend2', coordination=True,
312            cluster=None)
313        launcher.launch_service.assert_called_once_with(
314            service_create.return_value)
315
316
317@ddt.ddt
318class TestCinderManageCmd(test.TestCase):
319
320    def setUp(self):
321        super(TestCinderManageCmd, self).setUp()
322        sys.argv = ['cinder-manage']
323
324    def _test_purge_invalid_age_in_days(self, age_in_days):
325        db_cmds = cinder_manage.DbCommands()
326        ex = self.assertRaises(SystemExit, db_cmds.purge, age_in_days)
327        self.assertEqual(1, ex.code)
328
329    @mock.patch('cinder.objects.ServiceList.get_all')
330    @mock.patch('cinder.db.migration.db_sync')
331    def test_db_commands_sync(self, db_sync, service_get_mock):
332        version = 11
333        db_cmds = cinder_manage.DbCommands()
334        db_cmds.sync(version=version)
335        db_sync.assert_called_once_with(version)
336        service_get_mock.assert_not_called()
337
338    @mock.patch('cinder.objects.Service.save')
339    @mock.patch('cinder.objects.ServiceList.get_all')
340    @mock.patch('cinder.db.migration.db_sync')
341    def test_db_commands_sync_bump_versions(self, db_sync, service_get_mock,
342                                            service_save):
343        ctxt = context.get_admin_context()
344        services = [fake_service.fake_service_obj(ctxt,
345                                                  binary='cinder-' + binary,
346                                                  rpc_current_version='0.1',
347                                                  object_current_version='0.2')
348                    for binary in ('volume', 'scheduler', 'backup')]
349        service_get_mock.return_value = services
350
351        version = 11
352        db_cmds = cinder_manage.DbCommands()
353        db_cmds.sync(version=version, bump_versions=True)
354        db_sync.assert_called_once_with(version)
355
356        self.assertEqual(3, service_save.call_count)
357        for service in services:
358            self.assertEqual(cinder_manage.RPC_VERSIONS[service.binary],
359                             service.rpc_current_version)
360            self.assertEqual(cinder_manage.OVO_VERSION,
361                             service.object_current_version)
362
363    @mock.patch('oslo_db.sqlalchemy.migration.db_version')
364    def test_db_commands_version(self, db_version):
365        db_cmds = cinder_manage.DbCommands()
366        with mock.patch('sys.stdout', new=six.StringIO()):
367            db_cmds.version()
368            self.assertEqual(1, db_version.call_count)
369
370    def test_db_commands_upgrade_out_of_range(self):
371        version = 2147483647
372        db_cmds = cinder_manage.DbCommands()
373        exit = self.assertRaises(SystemExit, db_cmds.sync, version + 1)
374        self.assertEqual(1, exit.code)
375
376    @mock.patch("oslo_db.sqlalchemy.migration.db_sync")
377    def test_db_commands_script_not_present(self, db_sync):
378        db_sync.side_effect = oslo_exception.DBMigrationError(None)
379        db_cmds = cinder_manage.DbCommands()
380        exit = self.assertRaises(SystemExit, db_cmds.sync, 101)
381        self.assertEqual(1, exit.code)
382
383    @mock.patch('cinder.cmd.manage.DbCommands.online_migrations',
384                (mock.Mock(side_effect=((2, 2), (0, 0)), __name__='foo'),))
385    def test_db_commands_online_data_migrations(self):
386        db_cmds = cinder_manage.DbCommands()
387        exit = self.assertRaises(SystemExit, db_cmds.online_data_migrations)
388        self.assertEqual(0, exit.code)
389        cinder_manage.DbCommands.online_migrations[0].assert_has_calls(
390            (mock.call(mock.ANY, 50),) * 2)
391
392    def _fake_db_command(self, migrations=None):
393        if migrations is None:
394            mock_mig_1 = mock.MagicMock(__name__="mock_mig_1")
395            mock_mig_2 = mock.MagicMock(__name__="mock_mig_2")
396            mock_mig_1.return_value = (5, 4)
397            mock_mig_2.return_value = (6, 6)
398            migrations = (mock_mig_1, mock_mig_2)
399
400        class _CommandSub(cinder_manage.DbCommands):
401            online_migrations = migrations
402
403        return _CommandSub
404
405    @mock.patch('cinder.context.get_admin_context')
406    def test_online_migrations(self, mock_get_context):
407        self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO()))
408        ctxt = mock_get_context.return_value
409        db_cmds = self._fake_db_command()
410        command = db_cmds()
411        exit = self.assertRaises(SystemExit,
412                                 command.online_data_migrations, 10)
413        self.assertEqual(1, exit.code)
414        expected = """\
4155 rows matched query mock_mig_1, 4 migrated, 1 remaining
4166 rows matched query mock_mig_2, 6 migrated, 0 remaining
417+------------+--------------+-----------+
418| Migration  | Total Needed | Completed |
419+------------+--------------+-----------+
420| mock_mig_1 |      5       |     4     |
421| mock_mig_2 |      6       |     6     |
422+------------+--------------+-----------+
423"""
424        command.online_migrations[0].assert_has_calls([mock.call(ctxt,
425                                                                 10)])
426        command.online_migrations[1].assert_has_calls([mock.call(ctxt,
427                                                                 6)])
428
429        self.assertEqual(expected, sys.stdout.getvalue())
430
431    @mock.patch('cinder.cmd.manage.DbCommands.online_migrations',
432                (mock.Mock(side_effect=((2, 2), (0, 0)), __name__='foo'),))
433    def test_db_commands_online_data_migrations_ignore_state_and_max(self):
434        db_cmds = cinder_manage.DbCommands()
435        exit = self.assertRaises(SystemExit, db_cmds.online_data_migrations,
436                                 2)
437        self.assertEqual(1, exit.code)
438        cinder_manage.DbCommands.online_migrations[0].assert_called_once_with(
439            mock.ANY, 2)
440
441    @mock.patch('cinder.cmd.manage.DbCommands.online_migrations',
442                (mock.Mock(side_effect=((2, 2), (0, 0)), __name__='foo'),))
443    def test_db_commands_online_data_migrations_max_negative(self):
444        db_cmds = cinder_manage.DbCommands()
445        exit = self.assertRaises(SystemExit, db_cmds.online_data_migrations,
446                                 -1)
447        self.assertEqual(127, exit.code)
448        cinder_manage.DbCommands.online_migrations[0].assert_not_called()
449
450    @mock.patch('cinder.version.version_string')
451    def test_versions_commands_list(self, version_string):
452        version_cmds = cinder_manage.VersionCommands()
453        with mock.patch('sys.stdout', new=six.StringIO()):
454            version_cmds.list()
455            version_string.assert_called_once_with()
456
457    @mock.patch('cinder.version.version_string')
458    def test_versions_commands_call(self, version_string):
459        version_cmds = cinder_manage.VersionCommands()
460        with mock.patch('sys.stdout', new=six.StringIO()):
461            version_cmds.__call__()
462            version_string.assert_called_once_with()
463
464    def test_purge_with_negative_age_in_days(self):
465        age_in_days = -1
466        self._test_purge_invalid_age_in_days(age_in_days)
467
468    def test_purge_exceeded_age_in_days_limit(self):
469        age_in_days = int(time.time() / 86400) + 1
470        self._test_purge_invalid_age_in_days(age_in_days)
471
472    @mock.patch('cinder.db.sqlalchemy.api.purge_deleted_rows')
473    @mock.patch('cinder.context.get_admin_context')
474    def test_purge_less_than_age_in_days_limit(self, get_admin_context,
475                                               purge_deleted_rows):
476        age_in_days = int(time.time() / 86400) - 1
477        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
478                                      is_admin=True)
479        get_admin_context.return_value = ctxt
480
481        purge_deleted_rows.return_value = None
482
483        db_cmds = cinder_manage.DbCommands()
484        db_cmds.purge(age_in_days)
485
486        get_admin_context.assert_called_once_with()
487        purge_deleted_rows.assert_called_once_with(
488            ctxt, age_in_days=age_in_days)
489
490    @mock.patch('cinder.db.service_get_all')
491    @mock.patch('cinder.context.get_admin_context')
492    def test_host_commands_list(self, get_admin_context, service_get_all):
493        get_admin_context.return_value = mock.sentinel.ctxt
494        service_get_all.return_value = [
495            {'host': 'fake-host',
496             'availability_zone': 'fake-az',
497             'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'}]
498
499        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
500            expected_out = ("%(host)-25s\t%(zone)-15s\n" %
501                            {'host': 'host', 'zone': 'zone'})
502            expected_out += ("%(host)-25s\t%(availability_zone)-15s\n" %
503                             {'host': 'fake-host',
504                              'availability_zone': 'fake-az'})
505            host_cmds = cinder_manage.HostCommands()
506            host_cmds.list()
507
508            get_admin_context.assert_called_once_with()
509            service_get_all.assert_called_once_with(mock.sentinel.ctxt)
510            self.assertEqual(expected_out, fake_out.getvalue())
511
512    @mock.patch('cinder.db.service_get_all')
513    @mock.patch('cinder.context.get_admin_context')
514    def test_host_commands_list_with_zone(self, get_admin_context,
515                                          service_get_all):
516        get_admin_context.return_value = mock.sentinel.ctxt
517        service_get_all.return_value = [
518            {'host': 'fake-host',
519             'availability_zone': 'fake-az1',
520             'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'},
521            {'host': 'fake-host',
522             'availability_zone': 'fake-az2',
523             'uuid': '4200b32b-0bf9-436c-86b2-0675f6ac218e'}]
524
525        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
526            expected_out = ("%(host)-25s\t%(zone)-15s\n" %
527                            {'host': 'host', 'zone': 'zone'})
528            expected_out += ("%(host)-25s\t%(availability_zone)-15s\n" %
529                             {'host': 'fake-host',
530                              'availability_zone': 'fake-az1'})
531            host_cmds = cinder_manage.HostCommands()
532            host_cmds.list(zone='fake-az1')
533
534            get_admin_context.assert_called_once_with()
535            service_get_all.assert_called_once_with(mock.sentinel.ctxt)
536            self.assertEqual(expected_out, fake_out.getvalue())
537
538    @mock.patch('cinder.db.sqlalchemy.api.volume_get')
539    @mock.patch('cinder.context.get_admin_context')
540    @mock.patch('cinder.rpc.get_client')
541    @mock.patch('cinder.rpc.init')
542    def test_volume_commands_delete(self, rpc_init, get_client,
543                                    get_admin_context, volume_get):
544        ctxt = context.RequestContext('admin', 'fake', True)
545        get_admin_context.return_value = ctxt
546        mock_client = mock.MagicMock()
547        cctxt = mock.MagicMock()
548        mock_client.prepare.return_value = cctxt
549        get_client.return_value = mock_client
550        host = 'fake@host'
551        db_volume = {'host': host + '#pool1'}
552        volume = fake_volume.fake_db_volume(**db_volume)
553        volume_obj = fake_volume.fake_volume_obj(ctxt, **volume)
554        volume_id = volume['id']
555        volume_get.return_value = volume
556
557        volume_cmds = cinder_manage.VolumeCommands()
558        volume_cmds._client = mock_client
559        volume_cmds.delete(volume_id)
560
561        volume_get.assert_called_once_with(ctxt, volume_id)
562        mock_client.prepare.assert_called_once_with(
563            server="fake",
564            topic="cinder-volume.fake@host",
565            version="3.0")
566
567        cctxt.cast.assert_called_once_with(
568            ctxt, 'delete_volume',
569            cascade=False,
570            unmanage_only=False,
571            volume=volume_obj)
572
573    @mock.patch('cinder.db.volume_destroy')
574    @mock.patch('cinder.db.sqlalchemy.api.volume_get')
575    @mock.patch('cinder.context.get_admin_context')
576    @mock.patch('cinder.rpc.init')
577    def test_volume_commands_delete_no_host(self, rpc_init, get_admin_context,
578                                            volume_get, volume_destroy):
579        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
580                                      is_admin=True)
581        get_admin_context.return_value = ctxt
582        volume = fake_volume.fake_db_volume()
583        volume_id = volume['id']
584        volume_get.return_value = volume
585
586        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
587            expected_out = ('Volume not yet assigned to host.\n'
588                            'Deleting volume from database and skipping'
589                            ' rpc.\n')
590            volume_cmds = cinder_manage.VolumeCommands()
591            volume_cmds.delete(volume_id)
592
593            get_admin_context.assert_called_once_with()
594            volume_get.assert_called_once_with(ctxt, volume_id)
595            self.assertTrue(volume_destroy.called)
596            admin_context = volume_destroy.call_args[0][0]
597            self.assertTrue(admin_context.is_admin)
598            self.assertEqual(expected_out, fake_out.getvalue())
599
600    @mock.patch('cinder.db.volume_destroy')
601    @mock.patch('cinder.db.sqlalchemy.api.volume_get')
602    @mock.patch('cinder.context.get_admin_context')
603    @mock.patch('cinder.rpc.init')
604    def test_volume_commands_delete_volume_in_use(self, rpc_init,
605                                                  get_admin_context,
606                                                  volume_get, volume_destroy):
607        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
608        get_admin_context.return_value = ctxt
609        db_volume = {'status': 'in-use', 'host': 'fake-host'}
610        volume = fake_volume.fake_db_volume(**db_volume)
611        volume_id = volume['id']
612        volume_get.return_value = volume
613
614        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
615            expected_out = ('Volume is in-use.\n'
616                            'Detach volume from instance and then try'
617                            ' again.\n')
618            volume_cmds = cinder_manage.VolumeCommands()
619            volume_cmds.delete(volume_id)
620
621            volume_get.assert_called_once_with(ctxt, volume_id)
622            self.assertEqual(expected_out, fake_out.getvalue())
623
624    def test_config_commands_list(self):
625        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
626            expected_out = ''
627            for key, value in CONF.items():
628                expected_out += '%s = %s' % (key, value) + '\n'
629
630            config_cmds = cinder_manage.ConfigCommands()
631            config_cmds.list()
632
633            self.assertEqual(expected_out, fake_out.getvalue())
634
635    def test_config_commands_list_param(self):
636        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
637            CONF.set_override('host', 'fake')
638            expected_out = 'host = fake\n'
639
640            config_cmds = cinder_manage.ConfigCommands()
641            config_cmds.list(param='host')
642
643            self.assertEqual(expected_out, fake_out.getvalue())
644
645    def test_get_log_commands_no_errors(self):
646        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
647            CONF.set_override('log_dir', None)
648            expected_out = 'No errors in logfiles!\n'
649
650            get_log_cmds = cinder_manage.GetLogCommands()
651            get_log_cmds.errors()
652
653            out_lines = fake_out.getvalue().splitlines(True)
654
655            self.assertTrue(out_lines[0].startswith('DEPRECATED'))
656            self.assertEqual(expected_out, out_lines[1])
657
658    @mock.patch('six.moves.builtins.open')
659    @mock.patch('os.listdir')
660    def test_get_log_commands_errors(self, listdir, open):
661        CONF.set_override('log_dir', 'fake-dir')
662        listdir.return_value = ['fake-error.log']
663
664        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
665            open.return_value = six.StringIO(
666                '[ ERROR ] fake-error-message')
667            expected_out = ['fake-dir/fake-error.log:-\n',
668                            'Line 1 : [ ERROR ] fake-error-message\n']
669
670            get_log_cmds = cinder_manage.GetLogCommands()
671            get_log_cmds.errors()
672
673            out_lines = fake_out.getvalue().splitlines(True)
674
675            self.assertTrue(out_lines[0].startswith('DEPRECATED'))
676            self.assertEqual(expected_out[0], out_lines[1])
677            self.assertEqual(expected_out[1], out_lines[2])
678
679            open.assert_called_once_with('fake-dir/fake-error.log', 'r')
680            listdir.assert_called_once_with(CONF.log_dir)
681
682    @mock.patch('six.moves.builtins.open')
683    @mock.patch('os.path.exists')
684    def test_get_log_commands_syslog_no_log_file(self, path_exists, open):
685        path_exists.return_value = False
686
687        get_log_cmds = cinder_manage.GetLogCommands()
688        with mock.patch('sys.stdout', new=six.StringIO()):
689            exit = self.assertRaises(SystemExit, get_log_cmds.syslog)
690            self.assertEqual(1, exit.code)
691
692            path_exists.assert_any_call('/var/log/syslog')
693            path_exists.assert_any_call('/var/log/messages')
694
695    @mock.patch('cinder.db.backup_get_all')
696    @mock.patch('cinder.context.get_admin_context')
697    def test_backup_commands_list(self, get_admin_context, backup_get_all):
698        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
699        get_admin_context.return_value = ctxt
700        backup = {'id': fake.BACKUP_ID,
701                  'user_id': fake.USER_ID,
702                  'project_id': fake.PROJECT_ID,
703                  'host': 'fake-host',
704                  'display_name': 'fake-display-name',
705                  'container': 'fake-container',
706                  'status': fields.BackupStatus.AVAILABLE,
707                  'size': 123,
708                  'object_count': 1,
709                  'volume_id': fake.VOLUME_ID,
710                  'backup_metadata': {},
711                  }
712        backup_get_all.return_value = [backup]
713        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
714            hdr = ('%-32s\t%-32s\t%-32s\t%-24s\t%-24s\t%-12s\t%-12s\t%-12s'
715                   '\t%-12s')
716            header = hdr % ('ID',
717                            'User ID',
718                            'Project ID',
719                            'Host',
720                            'Name',
721                            'Container',
722                            'Status',
723                            'Size',
724                            'Object Count')
725            res = ('%-32s\t%-32s\t%-32s\t%-24s\t%-24s\t%-12s\t%-12s\t%-12d'
726                   '\t%-12s')
727            resource = res % (backup['id'],
728                              backup['user_id'],
729                              backup['project_id'],
730                              backup['host'],
731                              backup['display_name'],
732                              backup['container'],
733                              backup['status'],
734                              backup['size'],
735                              1)
736            expected_out = header + '\n' + resource + '\n'
737
738            backup_cmds = cinder_manage.BackupCommands()
739            backup_cmds.list()
740
741            get_admin_context.assert_called_once_with()
742            backup_get_all.assert_called_once_with(ctxt, None, None, None,
743                                                   None, None, None)
744            self.assertEqual(expected_out, fake_out.getvalue())
745
746    @mock.patch('cinder.db.backup_update')
747    @mock.patch('cinder.db.backup_get_all_by_host')
748    @mock.patch('cinder.context.get_admin_context')
749    def test_update_backup_host(self, get_admin_context,
750                                backup_get_by_host,
751                                backup_update):
752        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
753        get_admin_context.return_value = ctxt
754        backup = {'id': fake.BACKUP_ID,
755                  'user_id': fake.USER_ID,
756                  'project_id': fake.PROJECT_ID,
757                  'host': 'fake-host',
758                  'display_name': 'fake-display-name',
759                  'container': 'fake-container',
760                  'status': fields.BackupStatus.AVAILABLE,
761                  'size': 123,
762                  'object_count': 1,
763                  'volume_id': fake.VOLUME_ID,
764                  'backup_metadata': {},
765                  }
766        backup_get_by_host.return_value = [backup]
767        backup_cmds = cinder_manage.BackupCommands()
768        backup_cmds.update_backup_host('fake_host', 'fake_host2')
769
770        get_admin_context.assert_called_once_with()
771        backup_get_by_host.assert_called_once_with(ctxt, 'fake_host')
772        backup_update.assert_called_once_with(ctxt, fake.BACKUP_ID,
773                                              {'host': 'fake_host2'})
774
775    @mock.patch('cinder.db.consistencygroup_update')
776    @mock.patch('cinder.db.consistencygroup_get_all')
777    @mock.patch('cinder.context.get_admin_context')
778    def test_update_consisgroup_host(self, get_admin_context,
779                                     consisgroup_get_all,
780                                     consisgroup_update):
781        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
782        get_admin_context.return_value = ctxt
783        consisgroup = {'id': fake.CONSISTENCY_GROUP_ID,
784                       'user_id': fake.USER_ID,
785                       'project_id': fake.PROJECT_ID,
786                       'host': 'fake-host',
787                       'status': fields.ConsistencyGroupStatus.AVAILABLE
788                       }
789        consisgroup_get_all.return_value = [consisgroup]
790        consisgrup_cmds = cinder_manage.ConsistencyGroupCommands()
791        consisgrup_cmds.update_cg_host('fake_host', 'fake_host2')
792
793        get_admin_context.assert_called_once_with()
794        consisgroup_get_all.assert_called_once_with(
795            ctxt, filters={'host': 'fake_host'}, limit=None, marker=None,
796            offset=None, sort_dirs=None, sort_keys=None)
797        consisgroup_update.assert_called_once_with(
798            ctxt, fake.CONSISTENCY_GROUP_ID, {'host': 'fake_host2'})
799
800    @mock.patch('cinder.objects.service.Service.is_up',
801                new_callable=mock.PropertyMock)
802    @mock.patch('cinder.db.service_get_all')
803    @mock.patch('cinder.context.get_admin_context')
804    def _test_service_commands_list(self, service, get_admin_context,
805                                    service_get_all, service_is_up):
806        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
807        get_admin_context.return_value = ctxt
808        service_get_all.return_value = [service]
809        service_is_up.return_value = True
810        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
811            format = "%-16s %-36s %-16s %-10s %-5s %-20s %-12s %-15s %-36s"
812            print_format = format % ('Binary',
813                                     'Host',
814                                     'Zone',
815                                     'Status',
816                                     'State',
817                                     'Updated At',
818                                     'RPC Version',
819                                     'Object Version',
820                                     'Cluster')
821            rpc_version = service['rpc_current_version']
822            object_version = service['object_current_version']
823            cluster = service.get('cluster_name', '')
824            service_format = format % (service['binary'],
825                                       service['host'],
826                                       service['availability_zone'],
827                                       'enabled',
828                                       ':-)',
829                                       service['updated_at'],
830                                       rpc_version,
831                                       object_version,
832                                       cluster)
833            expected_out = print_format + '\n' + service_format + '\n'
834
835            service_cmds = cinder_manage.ServiceCommands()
836            service_cmds.list()
837
838            self.assertEqual(expected_out, fake_out.getvalue())
839            get_admin_context.assert_called_with()
840            service_get_all.assert_called_with(ctxt)
841
842    def test_service_commands_list(self):
843        service = {'binary': 'cinder-binary',
844                   'host': 'fake-host.fake-domain',
845                   'availability_zone': 'fake-zone',
846                   'updated_at': '2014-06-30 11:22:33',
847                   'disabled': False,
848                   'rpc_current_version': '1.1',
849                   'object_current_version': '1.1',
850                   'cluster_name': 'my_cluster',
851                   'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'}
852        for binary in ('volume', 'scheduler', 'backup'):
853            service['binary'] = 'cinder-%s' % binary
854            self._test_service_commands_list(service)
855
856    def test_service_commands_list_no_updated_at_or_cluster(self):
857        service = {'binary': 'cinder-binary',
858                   'host': 'fake-host.fake-domain',
859                   'availability_zone': 'fake-zone',
860                   'updated_at': None,
861                   'disabled': False,
862                   'rpc_current_version': '1.1',
863                   'object_current_version': '1.1',
864                   'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'}
865        for binary in ('volume', 'scheduler', 'backup'):
866            service['binary'] = 'cinder-%s' % binary
867            self._test_service_commands_list(service)
868
869    @ddt.data(('foobar', 'foobar'), ('-foo bar', 'foo bar'),
870              ('--foo bar', 'foo bar'), ('--foo-bar', 'foo_bar'),
871              ('---foo-bar', '_foo_bar'))
872    @ddt.unpack
873    def test_get_arg_string(self, arg, expected):
874        self.assertEqual(expected, cinder_manage.get_arg_string(arg))
875
876    def test_fetch_func_args(self):
877        @cinder_manage.args('--full-rename')
878        @cinder_manage.args('--different-dest', dest='my_dest')
879        @cinder_manage.args('current')
880        def my_func():
881            pass
882
883        expected = {'full_rename': mock.sentinel.full_rename,
884                    'my_dest': mock.sentinel.my_dest,
885                    'current': mock.sentinel.current}
886
887        with mock.patch.object(cinder_manage, 'CONF') as mock_conf:
888            mock_conf.category = mock.Mock(**expected)
889            self.assertDictEqual(expected,
890                                 cinder_manage.fetch_func_args(my_func))
891
892    @mock.patch('cinder.context.get_admin_context')
893    @mock.patch('cinder.db.cluster_get_all')
894    def tests_cluster_commands_list(self, get_all_mock, get_admin_mock,
895                                    ):
896        now = timeutils.utcnow()
897        cluster = fake_cluster.fake_cluster_orm(num_hosts=4, num_down_hosts=2,
898                                                created_at=now,
899                                                last_heartbeat=now)
900        get_all_mock.return_value = [cluster]
901
902        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
903        get_admin_mock.return_value = ctxt
904
905        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
906            format_ = "%-36s %-16s %-10s %-5s %-20s %-7s %-12s %-20s"
907            print_format = format_ % ('Name',
908                                      'Binary',
909                                      'Status',
910                                      'State',
911                                      'Heartbeat',
912                                      'Hosts',
913                                      'Down Hosts',
914                                      'Updated At')
915            cluster_format = format_ % (cluster.name, cluster.binary,
916                                        'enabled', ':-)',
917                                        cluster.last_heartbeat,
918                                        cluster.num_hosts,
919                                        cluster.num_down_hosts,
920                                        None)
921            expected_out = print_format + '\n' + cluster_format + '\n'
922
923            cluster_cmds = cinder_manage.ClusterCommands()
924            cluster_cmds.list()
925
926            self.assertEqual(expected_out, fake_out.getvalue())
927            get_admin_mock.assert_called_with()
928            get_all_mock.assert_called_with(ctxt, is_up=None,
929                                            get_services=False,
930                                            services_summary=True,
931                                            read_deleted='no')
932
933    @mock.patch('cinder.db.sqlalchemy.api.cluster_get', auto_specs=True)
934    @mock.patch('cinder.context.get_admin_context')
935    def test_cluster_commands_remove_not_found(self, admin_ctxt_mock,
936                                               cluster_get_mock):
937        cluster_get_mock.side_effect = exception.ClusterNotFound(id=1)
938        cluster_commands = cinder_manage.ClusterCommands()
939        exit = cluster_commands.remove(False, 'abinary', 'acluster')
940        self.assertEqual(2, exit)
941        cluster_get_mock.assert_called_once_with(admin_ctxt_mock.return_value,
942                                                 None, name='acluster',
943                                                 binary='abinary',
944                                                 get_services=False)
945
946    @mock.patch('cinder.db.sqlalchemy.api.service_destroy', auto_specs=True)
947    @mock.patch('cinder.db.sqlalchemy.api.cluster_destroy', auto_specs=True)
948    @mock.patch('cinder.db.sqlalchemy.api.cluster_get', auto_specs=True)
949    @mock.patch('cinder.context.get_admin_context')
950    def test_cluster_commands_remove_fail_has_hosts(self, admin_ctxt_mock,
951                                                    cluster_get_mock,
952                                                    cluster_destroy_mock,
953                                                    service_destroy_mock):
954        cluster = fake_cluster.fake_cluster_ovo(mock.Mock())
955        cluster_get_mock.return_value = cluster
956        cluster_destroy_mock.side_effect = exception.ClusterHasHosts(id=1)
957        cluster_commands = cinder_manage.ClusterCommands()
958        exit = cluster_commands.remove(False, 'abinary', 'acluster')
959        self.assertEqual(2, exit)
960        cluster_get_mock.assert_called_once_with(admin_ctxt_mock.return_value,
961                                                 None, name='acluster',
962                                                 binary='abinary',
963                                                 get_services=False)
964        cluster_destroy_mock.assert_called_once_with(
965            admin_ctxt_mock.return_value.elevated.return_value, cluster.id)
966        service_destroy_mock.assert_not_called()
967
968    @mock.patch('cinder.db.sqlalchemy.api.service_destroy', auto_specs=True)
969    @mock.patch('cinder.db.sqlalchemy.api.cluster_destroy', auto_specs=True)
970    @mock.patch('cinder.db.sqlalchemy.api.cluster_get', auto_specs=True)
971    @mock.patch('cinder.context.get_admin_context')
972    def test_cluster_commands_remove_success_no_hosts(self, admin_ctxt_mock,
973                                                      cluster_get_mock,
974                                                      cluster_destroy_mock,
975                                                      service_destroy_mock):
976        cluster = fake_cluster.fake_cluster_orm()
977        cluster_get_mock.return_value = cluster
978        cluster_commands = cinder_manage.ClusterCommands()
979        exit = cluster_commands.remove(False, 'abinary', 'acluster')
980        self.assertIsNone(exit)
981        cluster_get_mock.assert_called_once_with(admin_ctxt_mock.return_value,
982                                                 None, name='acluster',
983                                                 binary='abinary',
984                                                 get_services=False)
985        cluster_destroy_mock.assert_called_once_with(
986            admin_ctxt_mock.return_value.elevated.return_value, cluster.id)
987        service_destroy_mock.assert_not_called()
988
989    @mock.patch('cinder.db.sqlalchemy.api.service_destroy', auto_specs=True)
990    @mock.patch('cinder.db.sqlalchemy.api.cluster_destroy', auto_specs=True)
991    @mock.patch('cinder.db.sqlalchemy.api.cluster_get', auto_specs=True)
992    @mock.patch('cinder.context.get_admin_context')
993    def test_cluster_commands_remove_recursive(self, admin_ctxt_mock,
994                                               cluster_get_mock,
995                                               cluster_destroy_mock,
996                                               service_destroy_mock):
997        cluster = fake_cluster.fake_cluster_orm()
998        cluster.services = [fake_service.fake_service_orm()]
999        cluster_get_mock.return_value = cluster
1000        cluster_commands = cinder_manage.ClusterCommands()
1001        exit = cluster_commands.remove(True, 'abinary', 'acluster')
1002        self.assertIsNone(exit)
1003        cluster_get_mock.assert_called_once_with(admin_ctxt_mock.return_value,
1004                                                 None, name='acluster',
1005                                                 binary='abinary',
1006                                                 get_services=True)
1007        cluster_destroy_mock.assert_called_once_with(
1008            admin_ctxt_mock.return_value.elevated.return_value, cluster.id)
1009        service_destroy_mock.assert_called_once_with(
1010            admin_ctxt_mock.return_value.elevated.return_value,
1011            cluster.services[0]['id'])
1012
1013    @mock.patch('cinder.db.sqlalchemy.api.volume_include_in_cluster',
1014                auto_specs=True, return_value=1)
1015    @mock.patch('cinder.db.sqlalchemy.api.consistencygroup_include_in_cluster',
1016                auto_specs=True, return_value=2)
1017    @mock.patch('cinder.context.get_admin_context')
1018    def test_cluster_commands_rename(self, admin_ctxt_mock,
1019                                     volume_include_mock, cg_include_mock):
1020        """Test that cluster rename changes volumes and cgs."""
1021        current_cluster_name = mock.sentinel.old_cluster_name
1022        new_cluster_name = mock.sentinel.new_cluster_name
1023        partial = mock.sentinel.partial
1024        cluster_commands = cinder_manage.ClusterCommands()
1025        exit = cluster_commands.rename(partial, current_cluster_name,
1026                                       new_cluster_name)
1027
1028        self.assertIsNone(exit)
1029        volume_include_mock.assert_called_once_with(
1030            admin_ctxt_mock.return_value, new_cluster_name, partial,
1031            cluster_name=current_cluster_name)
1032        cg_include_mock.assert_called_once_with(
1033            admin_ctxt_mock.return_value, new_cluster_name, partial,
1034            cluster_name=current_cluster_name)
1035
1036    @mock.patch('cinder.db.sqlalchemy.api.volume_include_in_cluster',
1037                auto_specs=True, return_value=0)
1038    @mock.patch('cinder.db.sqlalchemy.api.consistencygroup_include_in_cluster',
1039                auto_specs=True, return_value=0)
1040    @mock.patch('cinder.context.get_admin_context')
1041    def test_cluster_commands_rename_no_changes(self, admin_ctxt_mock,
1042                                                volume_include_mock,
1043                                                cg_include_mock):
1044        """Test that we return an error when cluster rename has no effect."""
1045        cluster_commands = cinder_manage.ClusterCommands()
1046        exit = cluster_commands.rename(False, 'cluster', 'new_cluster')
1047        self.assertEqual(2, exit)
1048
1049    @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt')
1050    def test_main_argv_lt_2(self, register_cli_opt):
1051        script_name = 'cinder-manage'
1052        sys.argv = [script_name]
1053        CONF(sys.argv[1:], project='cinder', version=version.version_string())
1054
1055        with mock.patch('sys.stdout', new=six.StringIO()):
1056            exit = self.assertRaises(SystemExit, cinder_manage.main)
1057            self.assertTrue(register_cli_opt.called)
1058            self.assertEqual(2, exit.code)
1059
1060    @mock.patch('oslo_config.cfg.ConfigOpts.__call__')
1061    @mock.patch('oslo_log.log.setup')
1062    @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt')
1063    def test_main_sudo_failed(self, register_cli_opt, log_setup,
1064                              config_opts_call):
1065        script_name = 'cinder-manage'
1066        sys.argv = [script_name, 'fake_category', 'fake_action']
1067        config_opts_call.side_effect = cfg.ConfigFilesNotFoundError(
1068            mock.sentinel._namespace)
1069
1070        with mock.patch('sys.stdout', new=six.StringIO()):
1071            exit = self.assertRaises(SystemExit, cinder_manage.main)
1072
1073            self.assertTrue(register_cli_opt.called)
1074            config_opts_call.assert_called_once_with(
1075                sys.argv[1:], project='cinder',
1076                version=version.version_string())
1077            self.assertFalse(log_setup.called)
1078            self.assertEqual(2, exit.code)
1079
1080    @mock.patch('oslo_config.cfg.ConfigOpts.__call__')
1081    @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt')
1082    def test_main(self, register_cli_opt, config_opts_call):
1083        script_name = 'cinder-manage'
1084        sys.argv = [script_name, 'config', 'list']
1085        action_fn = mock.MagicMock()
1086        CONF.category = mock.MagicMock(action_fn=action_fn)
1087
1088        cinder_manage.main()
1089
1090        self.assertTrue(register_cli_opt.called)
1091        config_opts_call.assert_called_once_with(
1092            sys.argv[1:], project='cinder', version=version.version_string())
1093        self.assertTrue(action_fn.called)
1094
1095    @mock.patch('oslo_config.cfg.ConfigOpts.__call__')
1096    @mock.patch('oslo_log.log.setup')
1097    @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt')
1098    def test_main_invalid_dir(self, register_cli_opt, log_setup,
1099                              config_opts_call):
1100        script_name = 'cinder-manage'
1101        fake_dir = 'fake-dir'
1102        invalid_dir = 'Invalid directory:'
1103        sys.argv = [script_name, '--config-dir', fake_dir]
1104        config_opts_call.side_effect = cfg.ConfigDirNotFoundError(fake_dir)
1105
1106        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
1107            exit = self.assertRaises(SystemExit, cinder_manage.main)
1108            self.assertTrue(register_cli_opt.called)
1109            config_opts_call.assert_called_once_with(
1110                sys.argv[1:], project='cinder',
1111                version=version.version_string())
1112            self.assertIn(invalid_dir, fake_out.getvalue())
1113            self.assertIn(fake_dir, fake_out.getvalue())
1114            self.assertFalse(log_setup.called)
1115            self.assertEqual(2, exit.code)
1116
1117    @mock.patch('cinder.db')
1118    def test_remove_service_failure(self, mock_db):
1119        mock_db.service_destroy.side_effect = SystemExit(1)
1120        service_commands = cinder_manage.ServiceCommands()
1121        exit = service_commands.remove('abinary', 'ahost')
1122        self.assertEqual(2, exit)
1123
1124    @mock.patch('cinder.db.service_destroy')
1125    @mock.patch(
1126        'cinder.db.service_get',
1127        return_value = {'id': '12',
1128                        'uuid': 'a3a593da-7f8d-4bb7-8b4c-f2bc1e0b4824'})
1129    def test_remove_service_success(self, mock_get_by_args,
1130                                    mock_service_destroy):
1131        service_commands = cinder_manage.ServiceCommands()
1132        self.assertIsNone(service_commands.remove('abinary', 'ahost'))
1133
1134
1135class TestCinderRtstoolCmd(test.TestCase):
1136
1137    def setUp(self):
1138        super(TestCinderRtstoolCmd, self).setUp()
1139        sys.argv = ['cinder-rtstool']
1140
1141        self.INITIATOR_IQN = 'iqn.2015.12.com.example.openstack.i:UNIT1'
1142        self.TARGET_IQN = 'iqn.2015.12.com.example.openstack.i:TARGET1'
1143
1144    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1145    def test_create_rtslib_error(self, rtsroot):
1146        rtsroot.side_effect = rtslib_fb.utils.RTSLibError()
1147
1148        with mock.patch('sys.stdout', new=six.StringIO()):
1149            self.assertRaises(rtslib_fb.utils.RTSLibError,
1150                              cinder_rtstool.create,
1151                              mock.sentinel.backing_device,
1152                              mock.sentinel.name,
1153                              mock.sentinel.userid,
1154                              mock.sentinel.password,
1155                              mock.sentinel.iser_enabled)
1156
1157    def _test_create_rtslib_error_network_portal(self, ip):
1158        with mock.patch.object(rtslib_fb, 'NetworkPortal') as network_portal, \
1159                mock.patch.object(rtslib_fb, 'LUN') as lun, \
1160                mock.patch.object(rtslib_fb, 'TPG') as tpg, \
1161                mock.patch.object(rtslib_fb, 'FabricModule') as fabric_module, \
1162                mock.patch.object(rtslib_fb, 'Target') as target, \
1163                mock.patch.object(rtslib_fb, 'BlockStorageObject') as \
1164                block_storage_object, \
1165                mock.patch.object(rtslib_fb.root, 'RTSRoot') as rts_root:
1166            root_new = mock.MagicMock(storage_objects=mock.MagicMock())
1167            rts_root.return_value = root_new
1168            block_storage_object.return_value = mock.sentinel.so_new
1169            target.return_value = mock.sentinel.target_new
1170            fabric_module.return_value = mock.sentinel.fabric_new
1171            tpg_new = tpg.return_value
1172            lun.return_value = mock.sentinel.lun_new
1173
1174            if ip == '0.0.0.0':
1175                network_portal.side_effect = rtslib_fb.utils.RTSLibError()
1176                self.assertRaises(rtslib_fb.utils.RTSLibError,
1177                                  cinder_rtstool.create,
1178                                  mock.sentinel.backing_device,
1179                                  mock.sentinel.name,
1180                                  mock.sentinel.userid,
1181                                  mock.sentinel.password,
1182                                  mock.sentinel.iser_enabled)
1183            else:
1184                cinder_rtstool.create(mock.sentinel.backing_device,
1185                                      mock.sentinel.name,
1186                                      mock.sentinel.userid,
1187                                      mock.sentinel.password,
1188                                      mock.sentinel.iser_enabled)
1189
1190            rts_root.assert_called_once_with()
1191            block_storage_object.assert_called_once_with(
1192                name=mock.sentinel.name, dev=mock.sentinel.backing_device)
1193            target.assert_called_once_with(mock.sentinel.fabric_new,
1194                                           mock.sentinel.name, 'create')
1195            fabric_module.assert_called_once_with('iscsi')
1196            tpg.assert_called_once_with(mock.sentinel.target_new,
1197                                        mode='create')
1198            tpg_new.set_attribute.assert_called_once_with('authentication',
1199                                                          '1')
1200            lun.assert_called_once_with(tpg_new,
1201                                        storage_object=mock.sentinel.so_new)
1202            self.assertEqual(1, tpg_new.enable)
1203
1204            if ip == '::0':
1205                ip = '[::0]'
1206
1207            network_portal.assert_any_call(tpg_new, ip, 3260, mode='any')
1208
1209    def test_create_rtslib_error_network_portal_ipv4(self):
1210        with mock.patch('sys.stdout', new=six.StringIO()):
1211            self._test_create_rtslib_error_network_portal('0.0.0.0')
1212
1213    def test_create_rtslib_error_network_portal_ipv6(self):
1214        with mock.patch('sys.stdout', new=six.StringIO()):
1215            self._test_create_rtslib_error_network_portal('::0')
1216
1217    def _test_create(self, ip):
1218        with mock.patch.object(rtslib_fb, 'NetworkPortal') as network_portal, \
1219                mock.patch.object(rtslib_fb, 'LUN') as lun, \
1220                mock.patch.object(rtslib_fb, 'TPG') as tpg, \
1221                mock.patch.object(rtslib_fb, 'FabricModule') as fabric_module, \
1222                mock.patch.object(rtslib_fb, 'Target') as target, \
1223                mock.patch.object(rtslib_fb, 'BlockStorageObject') as \
1224                block_storage_object, \
1225                mock.patch.object(rtslib_fb.root, 'RTSRoot') as rts_root:
1226            root_new = mock.MagicMock(storage_objects=mock.MagicMock())
1227            rts_root.return_value = root_new
1228            block_storage_object.return_value = mock.sentinel.so_new
1229            target.return_value = mock.sentinel.target_new
1230            fabric_module.return_value = mock.sentinel.fabric_new
1231            tpg_new = tpg.return_value
1232            lun.return_value = mock.sentinel.lun_new
1233
1234            cinder_rtstool.create(mock.sentinel.backing_device,
1235                                  mock.sentinel.name,
1236                                  mock.sentinel.userid,
1237                                  mock.sentinel.password,
1238                                  mock.sentinel.iser_enabled)
1239
1240            rts_root.assert_called_once_with()
1241            block_storage_object.assert_called_once_with(
1242                name=mock.sentinel.name, dev=mock.sentinel.backing_device)
1243            target.assert_called_once_with(mock.sentinel.fabric_new,
1244                                           mock.sentinel.name, 'create')
1245            fabric_module.assert_called_once_with('iscsi')
1246            tpg.assert_called_once_with(mock.sentinel.target_new,
1247                                        mode='create')
1248            tpg_new.set_attribute.assert_called_once_with('authentication',
1249                                                          '1')
1250            lun.assert_called_once_with(tpg_new,
1251                                        storage_object=mock.sentinel.so_new)
1252            self.assertEqual(1, tpg_new.enable)
1253
1254            if ip == '::0':
1255                ip = '[::0]'
1256
1257            network_portal.assert_any_call(tpg_new, ip, 3260, mode='any')
1258
1259    def test_create_ipv4(self):
1260        self._test_create('0.0.0.0')
1261
1262    def test_create_ipv6(self):
1263        self._test_create('::0')
1264
1265    def _test_create_ips_and_port(self, mock_rtslib, port, ips, expected_ips):
1266        mock_rtslib.BlockStorageObject.return_value = mock.sentinel.bso
1267        mock_rtslib.Target.return_value = mock.sentinel.target_new
1268        mock_rtslib.FabricModule.return_value = mock.sentinel.iscsi_fabric
1269        tpg_new = mock_rtslib.TPG.return_value
1270
1271        cinder_rtstool.create(mock.sentinel.backing_device,
1272                              mock.sentinel.name,
1273                              mock.sentinel.userid,
1274                              mock.sentinel.password,
1275                              mock.sentinel.iser_enabled,
1276                              portals_ips=ips,
1277                              portals_port=port)
1278
1279        mock_rtslib.Target.assert_called_once_with(mock.sentinel.iscsi_fabric,
1280                                                   mock.sentinel.name,
1281                                                   'create')
1282        mock_rtslib.TPG.assert_called_once_with(mock.sentinel.target_new,
1283                                                mode='create')
1284        mock_rtslib.LUN.assert_called_once_with(
1285            tpg_new,
1286            storage_object=mock.sentinel.bso)
1287
1288        mock_rtslib.NetworkPortal.assert_has_calls(
1289            map(lambda ip: mock.call(tpg_new, ip, port, mode='any'),
1290                expected_ips), any_order=True
1291        )
1292
1293    @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True)
1294    def test_create_ips_and_port_ipv4(self, mock_rtslib):
1295        ips = ['10.0.0.2', '10.0.0.3', '10.0.0.4']
1296        port = 3261
1297        self._test_create_ips_and_port(mock_rtslib, port, ips, ips)
1298
1299    @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True)
1300    def test_create_ips_and_port_ipv6(self, mock_rtslib):
1301        ips = ['fe80::fc16:3eff:fecb:ad2f']
1302        expected_ips = ['[fe80::fc16:3eff:fecb:ad2f]']
1303        port = 3261
1304        self._test_create_ips_and_port(mock_rtslib, port, ips,
1305                                       expected_ips)
1306
1307    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1308    def test_add_initiator_rtslib_error(self, rtsroot):
1309        rtsroot.side_effect = rtslib_fb.utils.RTSLibError()
1310
1311        with mock.patch('sys.stdout', new=six.StringIO()):
1312            self.assertRaises(rtslib_fb.utils.RTSLibError,
1313                              cinder_rtstool.add_initiator,
1314                              mock.sentinel.target_iqn,
1315                              self.INITIATOR_IQN,
1316                              mock.sentinel.userid,
1317                              mock.sentinel.password)
1318
1319    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1320    def test_add_initiator_rtstool_error(self, rtsroot):
1321        rtsroot.targets.return_value = {}
1322
1323        self.assertRaises(cinder_rtstool.RtstoolError,
1324                          cinder_rtstool.add_initiator,
1325                          mock.sentinel.target_iqn,
1326                          self.INITIATOR_IQN,
1327                          mock.sentinel.userid,
1328                          mock.sentinel.password)
1329
1330    @mock.patch.object(rtslib_fb, 'MappedLUN')
1331    @mock.patch.object(rtslib_fb, 'NodeACL')
1332    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1333    def test_add_initiator_acl_exists(self, rtsroot, node_acl, mapped_lun):
1334        target_iqn = mock.MagicMock()
1335        target_iqn.tpgs.return_value = \
1336            [{'node_acls': self.INITIATOR_IQN}]
1337        acl = mock.MagicMock(node_wwn=self.INITIATOR_IQN)
1338        tpg = mock.MagicMock(node_acls=[acl])
1339        tpgs = iter([tpg])
1340        target = mock.MagicMock(tpgs=tpgs, wwn=self.TARGET_IQN)
1341        rtsroot.return_value = mock.MagicMock(targets=[target])
1342
1343        cinder_rtstool.add_initiator(self.TARGET_IQN,
1344                                     self.INITIATOR_IQN,
1345                                     mock.sentinel.userid,
1346                                     mock.sentinel.password)
1347        self.assertFalse(node_acl.called)
1348        self.assertFalse(mapped_lun.called)
1349
1350    @mock.patch.object(rtslib_fb, 'MappedLUN')
1351    @mock.patch.object(rtslib_fb, 'NodeACL')
1352    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1353    def test_add_initiator_acl_exists_case_1(self,
1354                                             rtsroot,
1355                                             node_acl,
1356                                             mapped_lun):
1357        """Ensure initiator iqns are handled in a case-insensitive manner."""
1358        target_iqn = mock.MagicMock()
1359        target_iqn.tpgs.return_value = \
1360            [{'node_acls': self.INITIATOR_IQN.lower()}]
1361        acl = mock.MagicMock(node_wwn=self.INITIATOR_IQN)
1362        tpg = mock.MagicMock(node_acls=[acl])
1363        tpgs = iter([tpg])
1364        target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn)
1365        rtsroot.return_value = mock.MagicMock(targets=[target])
1366
1367        cinder_rtstool.add_initiator(target_iqn,
1368                                     self.INITIATOR_IQN,
1369                                     mock.sentinel.userid,
1370                                     mock.sentinel.password)
1371        self.assertFalse(node_acl.called)
1372        self.assertFalse(mapped_lun.called)
1373
1374    @mock.patch.object(rtslib_fb, 'MappedLUN')
1375    @mock.patch.object(rtslib_fb, 'NodeACL')
1376    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1377    def test_add_initiator_acl_exists_case_2(self,
1378                                             rtsroot,
1379                                             node_acl,
1380                                             mapped_lun):
1381        """Ensure initiator iqns are handled in a case-insensitive manner."""
1382        iqn_lower = self.INITIATOR_IQN.lower()
1383        target_iqn = mock.MagicMock()
1384        target_iqn.tpgs.return_value = \
1385            [{'node_acls': self.INITIATOR_IQN}]
1386        acl = mock.MagicMock(node_wwn=iqn_lower)
1387        tpg = mock.MagicMock(node_acls=[acl])
1388        tpgs = iter([tpg])
1389        target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn)
1390        rtsroot.return_value = mock.MagicMock(targets=[target])
1391
1392        cinder_rtstool.add_initiator(target_iqn,
1393                                     self.INITIATOR_IQN,
1394                                     mock.sentinel.userid,
1395                                     mock.sentinel.password)
1396        self.assertFalse(node_acl.called)
1397        self.assertFalse(mapped_lun.called)
1398
1399    @mock.patch.object(rtslib_fb, 'MappedLUN')
1400    @mock.patch.object(rtslib_fb, 'NodeACL')
1401    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1402    def test_add_initiator(self, rtsroot, node_acl, mapped_lun):
1403        target_iqn = mock.MagicMock()
1404        target_iqn.tpgs.return_value = \
1405            [{'node_acls': self.INITIATOR_IQN}]
1406        tpg = mock.MagicMock()
1407        tpgs = iter([tpg])
1408        target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn)
1409        rtsroot.return_value = mock.MagicMock(targets=[target])
1410
1411        acl_new = mock.MagicMock(chap_userid=mock.sentinel.userid,
1412                                 chap_password=mock.sentinel.password)
1413        node_acl.return_value = acl_new
1414
1415        cinder_rtstool.add_initiator(target_iqn,
1416                                     self.INITIATOR_IQN,
1417                                     mock.sentinel.userid,
1418                                     mock.sentinel.password)
1419        node_acl.assert_called_once_with(tpg,
1420                                         self.INITIATOR_IQN,
1421                                         mode='create')
1422        mapped_lun.assert_called_once_with(acl_new, 0, tpg_lun=0)
1423
1424    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1425    def test_get_targets(self, rtsroot):
1426        target = mock.MagicMock()
1427        target.dump.return_value = {'wwn': 'fake-wwn'}
1428        rtsroot.return_value = mock.MagicMock(targets=[target])
1429
1430        with mock.patch('sys.stdout', new=six.StringIO()) as fake_out:
1431            cinder_rtstool.get_targets()
1432
1433            self.assertEqual(str(target.wwn), fake_out.getvalue().strip())
1434
1435    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1436    def test_delete(self, rtsroot):
1437        target = mock.MagicMock(wwn=mock.sentinel.iqn)
1438        storage_object = mock.MagicMock()
1439        name = mock.PropertyMock(return_value=mock.sentinel.iqn)
1440        type(storage_object).name = name
1441        rtsroot.return_value = mock.MagicMock(
1442            targets=[target], storage_objects=[storage_object])
1443
1444        cinder_rtstool.delete(mock.sentinel.iqn)
1445
1446        target.delete.assert_called_once_with()
1447        storage_object.delete.assert_called_once_with()
1448
1449    @mock.patch.object(rtslib_fb, 'MappedLUN')
1450    @mock.patch.object(rtslib_fb, 'NodeACL')
1451    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1452    def test_delete_initiator(self, rtsroot, node_acl, mapped_lun):
1453        target_iqn = mock.MagicMock()
1454        target_iqn.tpgs.return_value = \
1455            [{'node_acls': self.INITIATOR_IQN}]
1456        acl = mock.MagicMock(node_wwn=self.INITIATOR_IQN)
1457        tpg = mock.MagicMock(node_acls=[acl])
1458        tpgs = iter([tpg])
1459        target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn)
1460        rtsroot.return_value = mock.MagicMock(targets=[target])
1461
1462        cinder_rtstool.delete_initiator(target_iqn,
1463                                        self.INITIATOR_IQN)
1464
1465    @mock.patch.object(rtslib_fb, 'MappedLUN')
1466    @mock.patch.object(rtslib_fb, 'NodeACL')
1467    @mock.patch.object(rtslib_fb.root, 'RTSRoot')
1468    def test_delete_initiator_case(self, rtsroot, node_acl, mapped_lun):
1469        """Ensure iqns are handled in a case-insensitive manner."""
1470        initiator_iqn_lower = self.INITIATOR_IQN.lower()
1471        target_iqn = mock.MagicMock()
1472        target_iqn.tpgs.return_value = \
1473            [{'node_acls': initiator_iqn_lower}]
1474        acl = mock.MagicMock(node_wwn=self.INITIATOR_IQN)
1475        tpg = mock.MagicMock(node_acls=[acl])
1476        tpgs = iter([tpg])
1477        target = mock.MagicMock(tpgs=tpgs, wwn=target_iqn)
1478        rtsroot.return_value = mock.MagicMock(targets=[target])
1479
1480        cinder_rtstool.delete_initiator(target_iqn,
1481                                        self.INITIATOR_IQN)
1482
1483    @mock.patch.object(cinder_rtstool, 'os', autospec=True)
1484    @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True)
1485    def test_save_with_filename(self, mock_rtslib, mock_os):
1486        filename = mock.sentinel.filename
1487        cinder_rtstool.save_to_file(filename)
1488        rtsroot = mock_rtslib.root.RTSRoot
1489        rtsroot.assert_called_once_with()
1490        self.assertEqual(0, mock_os.path.dirname.call_count)
1491        self.assertEqual(0, mock_os.path.exists.call_count)
1492        self.assertEqual(0, mock_os.makedirs.call_count)
1493        rtsroot.return_value.save_to_file.assert_called_once_with(filename)
1494
1495    @mock.patch.object(cinder_rtstool, 'os',
1496                       **{'path.exists.return_value': True,
1497                          'path.dirname.return_value': mock.sentinel.dirname})
1498    @mock.patch.object(cinder_rtstool, 'rtslib_fb',
1499                       **{'root.default_save_file': mock.sentinel.filename})
1500    def test_save(self, mock_rtslib, mock_os):
1501        """Test that we check path exists with default file."""
1502        cinder_rtstool.save_to_file(None)
1503        rtsroot = mock_rtslib.root.RTSRoot
1504        rtsroot.assert_called_once_with()
1505        rtsroot.return_value.save_to_file.assert_called_once_with(
1506            mock.sentinel.filename)
1507        mock_os.path.dirname.assert_called_once_with(mock.sentinel.filename)
1508        mock_os.path.exists.assert_called_once_with(mock.sentinel.dirname)
1509        self.assertEqual(0, mock_os.makedirs.call_count)
1510
1511    @mock.patch.object(cinder_rtstool, 'os',
1512                       **{'path.exists.return_value': False,
1513                          'path.dirname.return_value': mock.sentinel.dirname})
1514    @mock.patch.object(cinder_rtstool, 'rtslib_fb',
1515                       **{'root.default_save_file': mock.sentinel.filename})
1516    def test_save_no_targetcli(self, mock_rtslib, mock_os):
1517        """Test that we create path if it doesn't exist with default file."""
1518        cinder_rtstool.save_to_file(None)
1519        rtsroot = mock_rtslib.root.RTSRoot
1520        rtsroot.assert_called_once_with()
1521        rtsroot.return_value.save_to_file.assert_called_once_with(
1522            mock.sentinel.filename)
1523        mock_os.path.dirname.assert_called_once_with(mock.sentinel.filename)
1524        mock_os.path.exists.assert_called_once_with(mock.sentinel.dirname)
1525        mock_os.makedirs.assert_called_once_with(mock.sentinel.dirname, 0o755)
1526
1527    @mock.patch.object(cinder_rtstool, 'os', autospec=True)
1528    @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True)
1529    def test_save_error_creating_dir(self, mock_rtslib, mock_os):
1530        mock_os.path.dirname.return_value = 'dirname'
1531        mock_os.path.exists.return_value = False
1532        mock_os.makedirs.side_effect = OSError('error')
1533
1534        regexp = (r'targetcli not installed and could not create default '
1535                  r'directory \(dirname\): error$')
1536        self.assertRaisesRegexp(cinder_rtstool.RtstoolError, regexp,
1537                                cinder_rtstool.save_to_file, None)
1538
1539    @mock.patch.object(cinder_rtstool, 'os', autospec=True)
1540    @mock.patch.object(cinder_rtstool, 'rtslib_fb', autospec=True)
1541    def test_save_error_saving(self, mock_rtslib, mock_os):
1542        save = mock_rtslib.root.RTSRoot.return_value.save_to_file
1543        save.side_effect = OSError('error')
1544        regexp = r'Could not save configuration to myfile: error'
1545        self.assertRaisesRegexp(cinder_rtstool.RtstoolError, regexp,
1546                                cinder_rtstool.save_to_file, 'myfile')
1547
1548    @mock.patch.object(cinder_rtstool, 'rtslib_fb',
1549                       **{'root.default_save_file': mock.sentinel.filename})
1550    def test_restore(self, mock_rtslib):
1551        """Test that we restore target configuration with default file."""
1552        cinder_rtstool.restore_from_file(None)
1553        rtsroot = mock_rtslib.root.RTSRoot
1554        rtsroot.assert_called_once_with()
1555        rtsroot.return_value.restore_from_file.assert_called_once_with(
1556            mock.sentinel.filename)
1557
1558    @mock.patch.object(cinder_rtstool, 'rtslib_fb')
1559    def test_restore_with_file(self, mock_rtslib):
1560        """Test that we restore target configuration with specified file."""
1561        cinder_rtstool.restore_from_file('saved_file')
1562        rtsroot = mock_rtslib.root.RTSRoot
1563        rtsroot.return_value.restore_from_file.assert_called_once_with(
1564            'saved_file')
1565
1566    @mock.patch('cinder.cmd.rtstool.restore_from_file')
1567    def test_restore_error(self, restore_from_file):
1568        """Test that we fail to restore target configuration."""
1569        restore_from_file.side_effect = OSError
1570        self.assertRaises(OSError,
1571                          cinder_rtstool.restore_from_file,
1572                          mock.sentinel.filename)
1573
1574    def test_usage(self):
1575        with mock.patch('sys.stdout', new=six.StringIO()):
1576            exit = self.assertRaises(SystemExit, cinder_rtstool.usage)
1577            self.assertEqual(1, exit.code)
1578
1579    @mock.patch('cinder.cmd.rtstool.usage')
1580    def test_main_argc_lt_2(self, usage):
1581        usage.side_effect = SystemExit(1)
1582        sys.argv = ['cinder-rtstool']
1583
1584        exit = self.assertRaises(SystemExit, cinder_rtstool.usage)
1585
1586        self.assertTrue(usage.called)
1587        self.assertEqual(1, exit.code)
1588
1589    def test_main_create_argv_lt_6(self):
1590        sys.argv = ['cinder-rtstool', 'create']
1591        self._test_main_check_argv()
1592
1593    def test_main_create_argv_gt_7(self):
1594        sys.argv = ['cinder-rtstool', 'create', 'fake-arg1', 'fake-arg2',
1595                    'fake-arg3', 'fake-arg4', 'fake-arg5', 'fake-arg6']
1596        self._test_main_check_argv()
1597
1598    def test_main_add_initiator_argv_lt_6(self):
1599        sys.argv = ['cinder-rtstool', 'add-initiator']
1600        self._test_main_check_argv()
1601
1602    def test_main_delete_argv_lt_3(self):
1603        sys.argv = ['cinder-rtstool', 'delete']
1604        self._test_main_check_argv()
1605
1606    def test_main_no_action(self):
1607        sys.argv = ['cinder-rtstool']
1608        self._test_main_check_argv()
1609
1610    def _test_main_check_argv(self):
1611        with mock.patch('cinder.cmd.rtstool.usage') as usage:
1612            usage.side_effect = SystemExit(1)
1613            sys.argv = ['cinder-rtstool', 'create']
1614
1615            exit = self.assertRaises(SystemExit, cinder_rtstool.main)
1616
1617            self.assertTrue(usage.called)
1618            self.assertEqual(1, exit.code)
1619
1620    @mock.patch('cinder.cmd.rtstool.save_to_file')
1621    def test_main_save(self, mock_save):
1622        sys.argv = ['cinder-rtstool',
1623                    'save']
1624        rc = cinder_rtstool.main()
1625        mock_save.assert_called_once_with(None)
1626        self.assertEqual(0, rc)
1627
1628    @mock.patch('cinder.cmd.rtstool.save_to_file')
1629    def test_main_save_with_file(self, mock_save):
1630        sys.argv = ['cinder-rtstool',
1631                    'save',
1632                    mock.sentinel.filename]
1633        rc = cinder_rtstool.main()
1634        mock_save.assert_called_once_with(mock.sentinel.filename)
1635        self.assertEqual(0, rc)
1636
1637    def test_main_create(self):
1638        with mock.patch('cinder.cmd.rtstool.create') as create:
1639            sys.argv = ['cinder-rtstool',
1640                        'create',
1641                        mock.sentinel.backing_device,
1642                        mock.sentinel.name,
1643                        mock.sentinel.userid,
1644                        mock.sentinel.password,
1645                        mock.sentinel.iser_enabled,
1646                        str(mock.sentinel.initiator_iqns)]
1647
1648            rc = cinder_rtstool.main()
1649
1650            create.assert_called_once_with(
1651                mock.sentinel.backing_device,
1652                mock.sentinel.name,
1653                mock.sentinel.userid,
1654                mock.sentinel.password,
1655                mock.sentinel.iser_enabled,
1656                initiator_iqns=str(mock.sentinel.initiator_iqns))
1657            self.assertEqual(0, rc)
1658
1659    @mock.patch('cinder.cmd.rtstool.create')
1660    def test_main_create_ips_and_port(self, mock_create):
1661        sys.argv = ['cinder-rtstool',
1662                    'create',
1663                    mock.sentinel.backing_device,
1664                    mock.sentinel.name,
1665                    mock.sentinel.userid,
1666                    mock.sentinel.password,
1667                    mock.sentinel.iser_enabled,
1668                    str(mock.sentinel.initiator_iqns),
1669                    '-p3261',
1670                    '-aip1,ip2,ip3']
1671
1672        rc = cinder_rtstool.main()
1673
1674        mock_create.assert_called_once_with(
1675            mock.sentinel.backing_device,
1676            mock.sentinel.name,
1677            mock.sentinel.userid,
1678            mock.sentinel.password,
1679            mock.sentinel.iser_enabled,
1680            initiator_iqns=str(mock.sentinel.initiator_iqns),
1681            portals_ips=['ip1', 'ip2', 'ip3'],
1682            portals_port=3261)
1683        self.assertEqual(0, rc)
1684
1685    def test_main_add_initiator(self):
1686        with mock.patch('cinder.cmd.rtstool.add_initiator') as add_initiator:
1687            sys.argv = ['cinder-rtstool',
1688                        'add-initiator',
1689                        mock.sentinel.target_iqn,
1690                        mock.sentinel.userid,
1691                        mock.sentinel.password,
1692                        mock.sentinel.initiator_iqns]
1693
1694            rc = cinder_rtstool.main()
1695
1696            add_initiator.assert_called_once_with(
1697                mock.sentinel.target_iqn, mock.sentinel.initiator_iqns,
1698                mock.sentinel.userid, mock.sentinel.password)
1699            self.assertEqual(0, rc)
1700
1701    def test_main_get_targets(self):
1702        with mock.patch('cinder.cmd.rtstool.get_targets') as get_targets:
1703            sys.argv = ['cinder-rtstool', 'get-targets']
1704
1705            rc = cinder_rtstool.main()
1706
1707            get_targets.assert_called_once_with()
1708            self.assertEqual(0, rc)
1709
1710    def test_main_delete(self):
1711        with mock.patch('cinder.cmd.rtstool.delete') as delete:
1712            sys.argv = ['cinder-rtstool', 'delete', mock.sentinel.iqn]
1713
1714            rc = cinder_rtstool.main()
1715
1716            delete.assert_called_once_with(mock.sentinel.iqn)
1717            self.assertEqual(0, rc)
1718
1719    @mock.patch.object(cinder_rtstool, 'verify_rtslib')
1720    def test_main_verify(self, mock_verify_rtslib):
1721        sys.argv = ['cinder-rtstool', 'verify']
1722
1723        rc = cinder_rtstool.main()
1724
1725        mock_verify_rtslib.assert_called_once_with()
1726        self.assertEqual(0, rc)
1727
1728
1729class TestCinderVolumeUsageAuditCmd(test.TestCase):
1730
1731    def setUp(self):
1732        super(TestCinderVolumeUsageAuditCmd, self).setUp()
1733        sys.argv = ['cinder-volume-usage-audit']
1734
1735    @mock.patch('cinder.utils.last_completed_audit_period')
1736    @mock.patch('cinder.rpc.init')
1737    @mock.patch('cinder.version.version_string')
1738    @mock.patch('oslo_log.log.getLogger')
1739    @mock.patch('oslo_log.log.setup')
1740    @mock.patch('cinder.context.get_admin_context')
1741    def test_main_time_error(self, get_admin_context, log_setup, get_logger,
1742                             version_string, rpc_init,
1743                             last_completed_audit_period):
1744        CONF.set_override('start_time', '2014-01-01 01:00:00')
1745        CONF.set_override('end_time', '2013-01-01 01:00:00')
1746        last_completed_audit_period.return_value = (mock.sentinel.begin,
1747                                                    mock.sentinel.end)
1748
1749        exit = self.assertRaises(SystemExit, volume_usage_audit.main)
1750
1751        get_admin_context.assert_called_once_with()
1752        self.assertEqual('cinder', CONF.project)
1753        self.assertEqual(CONF.version, version.version_string())
1754        log_setup.assert_called_once_with(CONF, "cinder")
1755        get_logger.assert_called_once_with('cinder')
1756        self.assertEqual(-1, exit.code)
1757        rpc_init.assert_called_once_with(CONF)
1758        last_completed_audit_period.assert_called_once_with()
1759
1760    @mock.patch('cinder.volume.utils.notify_about_volume_usage')
1761    @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window')
1762    @mock.patch('cinder.utils.last_completed_audit_period')
1763    @mock.patch('cinder.rpc.init')
1764    @mock.patch('cinder.version.version_string')
1765    @mock.patch('oslo_log.log.getLogger')
1766    @mock.patch('oslo_log.log.setup')
1767    @mock.patch('cinder.context.get_admin_context')
1768    def test_main_send_create_volume_error(self, get_admin_context, log_setup,
1769                                           get_logger, version_string,
1770                                           rpc_init,
1771                                           last_completed_audit_period,
1772                                           volume_get_all_active_by_window,
1773                                           notify_about_volume_usage):
1774        CONF.set_override('send_actions', True)
1775        CONF.set_override('start_time', '2014-01-01 01:00:00')
1776        CONF.set_override('end_time', '2014-02-02 02:00:00')
1777        begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC)
1778        end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC)
1779        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
1780        get_admin_context.return_value = ctxt
1781        last_completed_audit_period.return_value = (begin, end)
1782        volume1_created = datetime.datetime(2014, 1, 1, 2, 0,
1783                                            tzinfo=iso8601.UTC)
1784        volume1_deleted = datetime.datetime(2014, 1, 1, 3, 0,
1785                                            tzinfo=iso8601.UTC)
1786        volume1 = mock.MagicMock(id=fake.VOLUME_ID, project_id=fake.PROJECT_ID,
1787                                 created_at=volume1_created,
1788                                 deleted_at=volume1_deleted)
1789        volume_get_all_active_by_window.return_value = [volume1]
1790        extra_info = {
1791            'audit_period_beginning': str(begin),
1792            'audit_period_ending': str(end),
1793        }
1794        local_extra_info = {
1795            'audit_period_beginning': str(volume1.created_at),
1796            'audit_period_ending': str(volume1.created_at),
1797        }
1798
1799        def _notify_about_volume_usage(*args, **kwargs):
1800            if 'create.end' in args:
1801                raise Exception()
1802            else:
1803                pass
1804
1805        notify_about_volume_usage.side_effect = _notify_about_volume_usage
1806
1807        volume_usage_audit.main()
1808
1809        get_admin_context.assert_called_once_with()
1810        self.assertEqual('cinder', CONF.project)
1811        self.assertEqual(CONF.version, version.version_string())
1812        log_setup.assert_called_once_with(CONF, "cinder")
1813        get_logger.assert_called_once_with('cinder')
1814        rpc_init.assert_called_once_with(CONF)
1815        last_completed_audit_period.assert_called_once_with()
1816        volume_get_all_active_by_window.assert_called_once_with(ctxt, begin,
1817                                                                end)
1818        notify_about_volume_usage.assert_has_calls([
1819            mock.call(ctxt, volume1, 'exists', extra_usage_info=extra_info),
1820            mock.call(ctxt, volume1, 'create.start',
1821                      extra_usage_info=local_extra_info),
1822            mock.call(ctxt, volume1, 'create.end',
1823                      extra_usage_info=local_extra_info)
1824        ])
1825
1826    @mock.patch('cinder.volume.utils.notify_about_volume_usage')
1827    @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window')
1828    @mock.patch('cinder.utils.last_completed_audit_period')
1829    @mock.patch('cinder.rpc.init')
1830    @mock.patch('cinder.version.version_string')
1831    @mock.patch('oslo_log.log.getLogger')
1832    @mock.patch('oslo_log.log.setup')
1833    @mock.patch('cinder.context.get_admin_context')
1834    def test_main_send_delete_volume_error(self, get_admin_context, log_setup,
1835                                           get_logger, version_string,
1836                                           rpc_init,
1837                                           last_completed_audit_period,
1838                                           volume_get_all_active_by_window,
1839                                           notify_about_volume_usage):
1840        CONF.set_override('send_actions', True)
1841        CONF.set_override('start_time', '2014-01-01 01:00:00')
1842        CONF.set_override('end_time', '2014-02-02 02:00:00')
1843        begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC)
1844        end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC)
1845        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
1846        get_admin_context.return_value = ctxt
1847        last_completed_audit_period.return_value = (begin, end)
1848        volume1_created = datetime.datetime(2014, 1, 1, 2, 0,
1849                                            tzinfo=iso8601.UTC)
1850        volume1_deleted = datetime.datetime(2014, 1, 1, 3, 0,
1851                                            tzinfo=iso8601.UTC)
1852        volume1 = mock.MagicMock(id=fake.VOLUME_ID, project_id=fake.PROJECT_ID,
1853                                 created_at=volume1_created,
1854                                 deleted_at=volume1_deleted)
1855        volume_get_all_active_by_window.return_value = [volume1]
1856        extra_info = {
1857            'audit_period_beginning': str(begin),
1858            'audit_period_ending': str(end),
1859        }
1860        local_extra_info_create = {
1861            'audit_period_beginning': str(volume1.created_at),
1862            'audit_period_ending': str(volume1.created_at),
1863        }
1864        local_extra_info_delete = {
1865            'audit_period_beginning': str(volume1.deleted_at),
1866            'audit_period_ending': str(volume1.deleted_at),
1867        }
1868
1869        def _notify_about_volume_usage(*args, **kwargs):
1870            if 'delete.end' in args:
1871                raise Exception()
1872            else:
1873                pass
1874
1875        notify_about_volume_usage.side_effect = _notify_about_volume_usage
1876
1877        volume_usage_audit.main()
1878
1879        get_admin_context.assert_called_once_with()
1880        self.assertEqual('cinder', CONF.project)
1881        self.assertEqual(CONF.version, version.version_string())
1882        log_setup.assert_called_once_with(CONF, "cinder")
1883        get_logger.assert_called_once_with('cinder')
1884        rpc_init.assert_called_once_with(CONF)
1885        last_completed_audit_period.assert_called_once_with()
1886        volume_get_all_active_by_window.assert_called_once_with(ctxt, begin,
1887                                                                end)
1888        notify_about_volume_usage.assert_has_calls([
1889            mock.call(ctxt, volume1, 'exists', extra_usage_info=extra_info),
1890            mock.call(ctxt, volume1, 'create.start',
1891                      extra_usage_info=local_extra_info_create),
1892            mock.call(ctxt, volume1, 'create.end',
1893                      extra_usage_info=local_extra_info_create),
1894            mock.call(ctxt, volume1, 'delete.start',
1895                      extra_usage_info=local_extra_info_delete),
1896            mock.call(ctxt, volume1, 'delete.end',
1897                      extra_usage_info=local_extra_info_delete)
1898        ])
1899
1900    @mock.patch('cinder.volume.utils.notify_about_snapshot_usage')
1901    @mock.patch('cinder.objects.snapshot.SnapshotList.'
1902                'get_all_active_by_window')
1903    @mock.patch('cinder.volume.utils.notify_about_volume_usage')
1904    @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window')
1905    @mock.patch('cinder.utils.last_completed_audit_period')
1906    @mock.patch('cinder.rpc.init')
1907    @mock.patch('cinder.version.version_string')
1908    @mock.patch('oslo_log.log.getLogger')
1909    @mock.patch('oslo_log.log.setup')
1910    @mock.patch('cinder.context.get_admin_context')
1911    def test_main_send_snapshot_error(self, get_admin_context,
1912                                      log_setup, get_logger,
1913                                      version_string, rpc_init,
1914                                      last_completed_audit_period,
1915                                      volume_get_all_active_by_window,
1916                                      notify_about_volume_usage,
1917                                      snapshot_get_all_active_by_window,
1918                                      notify_about_snapshot_usage):
1919        CONF.set_override('send_actions', True)
1920        CONF.set_override('start_time', '2014-01-01 01:00:00')
1921        CONF.set_override('end_time', '2014-02-02 02:00:00')
1922        begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC)
1923        end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC)
1924        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
1925        get_admin_context.return_value = ctxt
1926        last_completed_audit_period.return_value = (begin, end)
1927        snapshot1_created = datetime.datetime(2014, 1, 1, 2, 0,
1928                                              tzinfo=iso8601.UTC)
1929        snapshot1_deleted = datetime.datetime(2014, 1, 1, 3, 0,
1930                                              tzinfo=iso8601.UTC)
1931        snapshot1 = mock.MagicMock(id=fake.VOLUME_ID,
1932                                   project_id=fake.PROJECT_ID,
1933                                   created_at=snapshot1_created,
1934                                   deleted_at=snapshot1_deleted)
1935        volume_get_all_active_by_window.return_value = []
1936        snapshot_get_all_active_by_window.return_value = [snapshot1]
1937        extra_info = {
1938            'audit_period_beginning': str(begin),
1939            'audit_period_ending': str(end),
1940        }
1941        local_extra_info_create = {
1942            'audit_period_beginning': str(snapshot1.created_at),
1943            'audit_period_ending': str(snapshot1.created_at),
1944        }
1945        local_extra_info_delete = {
1946            'audit_period_beginning': str(snapshot1.deleted_at),
1947            'audit_period_ending': str(snapshot1.deleted_at),
1948        }
1949
1950        def _notify_about_snapshot_usage(*args, **kwargs):
1951            # notify_about_snapshot_usage raises an exception, but does not
1952            # block
1953            raise Exception()
1954
1955        notify_about_snapshot_usage.side_effect = _notify_about_snapshot_usage
1956
1957        volume_usage_audit.main()
1958
1959        get_admin_context.assert_called_once_with()
1960        self.assertEqual('cinder', CONF.project)
1961        self.assertEqual(CONF.version, version.version_string())
1962        log_setup.assert_called_once_with(CONF, "cinder")
1963        get_logger.assert_called_once_with('cinder')
1964        rpc_init.assert_called_once_with(CONF)
1965        last_completed_audit_period.assert_called_once_with()
1966        volume_get_all_active_by_window.assert_called_once_with(ctxt, begin,
1967                                                                end)
1968        self.assertFalse(notify_about_volume_usage.called)
1969        notify_about_snapshot_usage.assert_has_calls([
1970            mock.call(ctxt, snapshot1, 'exists', extra_info),
1971            mock.call(ctxt, snapshot1, 'create.start',
1972                      extra_usage_info=local_extra_info_create),
1973            mock.call(ctxt, snapshot1, 'delete.start',
1974                      extra_usage_info=local_extra_info_delete)
1975        ])
1976
1977    @mock.patch('cinder.volume.utils.notify_about_backup_usage')
1978    @mock.patch('cinder.objects.backup.BackupList.get_all_active_by_window')
1979    @mock.patch('cinder.volume.utils.notify_about_volume_usage')
1980    @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window')
1981    @mock.patch('cinder.utils.last_completed_audit_period')
1982    @mock.patch('cinder.rpc.init')
1983    @mock.patch('cinder.version.version_string')
1984    @mock.patch('cinder.context.get_admin_context')
1985    def test_main_send_backup_error(self, get_admin_context,
1986                                    version_string, rpc_init,
1987                                    last_completed_audit_period,
1988                                    volume_get_all_active_by_window,
1989                                    notify_about_volume_usage,
1990                                    backup_get_all_active_by_window,
1991                                    notify_about_backup_usage):
1992        CONF.set_override('send_actions', True)
1993        CONF.set_override('start_time', '2014-01-01 01:00:00')
1994        CONF.set_override('end_time', '2014-02-02 02:00:00')
1995        begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC)
1996        end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC)
1997        ctxt = context.RequestContext('fake-user', 'fake-project')
1998        get_admin_context.return_value = ctxt
1999        last_completed_audit_period.return_value = (begin, end)
2000        backup1_created = datetime.datetime(2014, 1, 1, 2, 0,
2001                                            tzinfo=iso8601.UTC)
2002        backup1_deleted = datetime.datetime(2014, 1, 1, 3, 0,
2003                                            tzinfo=iso8601.UTC)
2004        backup1 = mock.MagicMock(id=fake.BACKUP_ID,
2005                                 project_id=fake.PROJECT_ID,
2006                                 created_at=backup1_created,
2007                                 deleted_at=backup1_deleted)
2008        volume_get_all_active_by_window.return_value = []
2009        backup_get_all_active_by_window.return_value = [backup1]
2010        extra_info = {
2011            'audit_period_beginning': str(begin),
2012            'audit_period_ending': str(end),
2013        }
2014        local_extra_info_create = {
2015            'audit_period_beginning': str(backup1.created_at),
2016            'audit_period_ending': str(backup1.created_at),
2017        }
2018        local_extra_info_delete = {
2019            'audit_period_beginning': str(backup1.deleted_at),
2020            'audit_period_ending': str(backup1.deleted_at),
2021        }
2022
2023        notify_about_backup_usage.side_effect = Exception()
2024
2025        volume_usage_audit.main()
2026
2027        get_admin_context.assert_called_once_with()
2028        self.assertEqual('cinder', CONF.project)
2029        self.assertEqual(CONF.version, version.version_string())
2030        rpc_init.assert_called_once_with(CONF)
2031        last_completed_audit_period.assert_called_once_with()
2032        volume_get_all_active_by_window.assert_called_once_with(ctxt,
2033                                                                begin, end)
2034        self.assertFalse(notify_about_volume_usage.called)
2035        notify_about_backup_usage.assert_any_call(ctxt, backup1, 'exists',
2036                                                  extra_info)
2037        notify_about_backup_usage.assert_any_call(
2038            ctxt, backup1, 'create.start',
2039            extra_usage_info=local_extra_info_create)
2040        notify_about_backup_usage.assert_any_call(
2041            ctxt, backup1, 'delete.start',
2042            extra_usage_info=local_extra_info_delete)
2043
2044    @mock.patch('cinder.volume.utils.notify_about_backup_usage')
2045    @mock.patch('cinder.objects.backup.BackupList.get_all_active_by_window')
2046    @mock.patch('cinder.volume.utils.notify_about_snapshot_usage')
2047    @mock.patch('cinder.objects.snapshot.SnapshotList.'
2048                'get_all_active_by_window')
2049    @mock.patch('cinder.volume.utils.notify_about_volume_usage')
2050    @mock.patch('cinder.objects.volume.VolumeList.get_all_active_by_window')
2051    @mock.patch('cinder.utils.last_completed_audit_period')
2052    @mock.patch('cinder.rpc.init')
2053    @mock.patch('cinder.version.version_string')
2054    @mock.patch('oslo_log.log.getLogger')
2055    @mock.patch('oslo_log.log.setup')
2056    @mock.patch('cinder.context.get_admin_context')
2057    def test_main(self, get_admin_context, log_setup, get_logger,
2058                  version_string, rpc_init, last_completed_audit_period,
2059                  volume_get_all_active_by_window, notify_about_volume_usage,
2060                  snapshot_get_all_active_by_window,
2061                  notify_about_snapshot_usage, backup_get_all_active_by_window,
2062                  notify_about_backup_usage):
2063        CONF.set_override('send_actions', True)
2064        CONF.set_override('start_time', '2014-01-01 01:00:00')
2065        CONF.set_override('end_time', '2014-02-02 02:00:00')
2066        begin = datetime.datetime(2014, 1, 1, 1, 0, tzinfo=iso8601.UTC)
2067        end = datetime.datetime(2014, 2, 2, 2, 0, tzinfo=iso8601.UTC)
2068        ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID)
2069        get_admin_context.return_value = ctxt
2070        last_completed_audit_period.return_value = (begin, end)
2071
2072        volume1_created = datetime.datetime(2014, 1, 1, 2, 0,
2073                                            tzinfo=iso8601.UTC)
2074        volume1_deleted = datetime.datetime(2014, 1, 1, 3, 0,
2075                                            tzinfo=iso8601.UTC)
2076        volume1 = mock.MagicMock(id=fake.VOLUME_ID, project_id=fake.PROJECT_ID,
2077                                 created_at=volume1_created,
2078                                 deleted_at=volume1_deleted)
2079        volume_get_all_active_by_window.return_value = [volume1]
2080        extra_info = {
2081            'audit_period_beginning': str(begin),
2082            'audit_period_ending': str(end),
2083        }
2084        extra_info_volume_create = {
2085            'audit_period_beginning': str(volume1.created_at),
2086            'audit_period_ending': str(volume1.created_at),
2087        }
2088        extra_info_volume_delete = {
2089            'audit_period_beginning': str(volume1.deleted_at),
2090            'audit_period_ending': str(volume1.deleted_at),
2091        }
2092
2093        snapshot1_created = datetime.datetime(2014, 1, 1, 2, 0,
2094                                              tzinfo=iso8601.UTC)
2095        snapshot1_deleted = datetime.datetime(2014, 1, 1, 3, 0,
2096                                              tzinfo=iso8601.UTC)
2097        snapshot1 = mock.MagicMock(id=fake.VOLUME_ID,
2098                                   project_id=fake.PROJECT_ID,
2099                                   created_at=snapshot1_created,
2100                                   deleted_at=snapshot1_deleted)
2101        snapshot_get_all_active_by_window.return_value = [snapshot1]
2102        extra_info_snapshot_create = {
2103            'audit_period_beginning': str(snapshot1.created_at),
2104            'audit_period_ending': str(snapshot1.created_at),
2105        }
2106        extra_info_snapshot_delete = {
2107            'audit_period_beginning': str(snapshot1.deleted_at),
2108            'audit_period_ending': str(snapshot1.deleted_at),
2109        }
2110
2111        backup1_created = datetime.datetime(2014, 1, 1, 2, 0,
2112                                            tzinfo=iso8601.UTC)
2113        backup1_deleted = datetime.datetime(2014, 1, 1, 3, 0,
2114                                            tzinfo=iso8601.UTC)
2115        backup1 = mock.MagicMock(id=fake.BACKUP_ID,
2116                                 project_id=fake.PROJECT_ID,
2117                                 created_at=backup1_created,
2118                                 deleted_at=backup1_deleted)
2119        backup_get_all_active_by_window.return_value = [backup1]
2120        extra_info_backup_create = {
2121            'audit_period_beginning': str(backup1.created_at),
2122            'audit_period_ending': str(backup1.created_at),
2123        }
2124        extra_info_backup_delete = {
2125            'audit_period_beginning': str(backup1.deleted_at),
2126            'audit_period_ending': str(backup1.deleted_at),
2127        }
2128
2129        volume_usage_audit.main()
2130
2131        get_admin_context.assert_called_once_with()
2132        self.assertEqual('cinder', CONF.project)
2133        self.assertEqual(CONF.version, version.version_string())
2134        log_setup.assert_called_once_with(CONF, "cinder")
2135        get_logger.assert_called_once_with('cinder')
2136        rpc_init.assert_called_once_with(CONF)
2137        last_completed_audit_period.assert_called_once_with()
2138        volume_get_all_active_by_window.assert_called_once_with(ctxt,
2139                                                                begin, end)
2140        notify_about_volume_usage.assert_has_calls([
2141            mock.call(ctxt, volume1, 'exists', extra_usage_info=extra_info),
2142            mock.call(ctxt, volume1, 'create.start',
2143                      extra_usage_info=extra_info_volume_create),
2144            mock.call(ctxt, volume1, 'create.end',
2145                      extra_usage_info=extra_info_volume_create),
2146            mock.call(ctxt, volume1, 'delete.start',
2147                      extra_usage_info=extra_info_volume_delete),
2148            mock.call(ctxt, volume1, 'delete.end',
2149                      extra_usage_info=extra_info_volume_delete)
2150        ])
2151
2152        notify_about_snapshot_usage.assert_has_calls([
2153            mock.call(ctxt, snapshot1, 'exists', extra_info),
2154            mock.call(ctxt, snapshot1, 'create.start',
2155                      extra_usage_info=extra_info_snapshot_create),
2156            mock.call(ctxt, snapshot1, 'create.end',
2157                      extra_usage_info=extra_info_snapshot_create),
2158            mock.call(ctxt, snapshot1, 'delete.start',
2159                      extra_usage_info=extra_info_snapshot_delete),
2160            mock.call(ctxt, snapshot1, 'delete.end',
2161                      extra_usage_info=extra_info_snapshot_delete)
2162        ])
2163
2164        notify_about_backup_usage.assert_has_calls([
2165            mock.call(ctxt, backup1, 'exists', extra_info),
2166            mock.call(ctxt, backup1, 'create.start',
2167                      extra_usage_info=extra_info_backup_create),
2168            mock.call(ctxt, backup1, 'create.end',
2169                      extra_usage_info=extra_info_backup_create),
2170            mock.call(ctxt, backup1, 'delete.start',
2171                      extra_usage_info=extra_info_backup_delete),
2172            mock.call(ctxt, backup1, 'delete.end',
2173                      extra_usage_info=extra_info_backup_delete)
2174        ])
2175
2176
2177class TestVolumeSharedTargetsOnlineMigration(test.TestCase):
2178    """Unit tests for cinder.db.api.service_*."""
2179
2180    def setUp(self):
2181        super(TestVolumeSharedTargetsOnlineMigration, self).setUp()
2182
2183        def _get_minimum_rpc_version_mock(ctxt, binary):
2184            binary_map = {
2185                'cinder-volume': rpcapi.VolumeAPI,
2186            }
2187            return binary_map[binary].RPC_API_VERSION
2188
2189        self.patch('cinder.objects.Service.get_minimum_rpc_version',
2190                   side_effect=_get_minimum_rpc_version_mock)
2191
2192        ctxt = context.get_admin_context()
2193        # default value in db for shared_targets on a volume
2194        # is True, so don't need to set it here explicitly
2195        for i in range(3):
2196            sqlalchemy_api.volume_create(
2197                ctxt,
2198                {'host': 'host1@lvm-driver1#lvm-driver1',
2199                 'service_uuid': 'f080f895-cff2-4eb3-9c61-050c060b59ad'})
2200
2201        values = {
2202            'host': 'host1@lvm-driver1',
2203            'binary': constants.VOLUME_BINARY,
2204            'topic': constants.VOLUME_TOPIC,
2205            'uuid': 'f080f895-cff2-4eb3-9c61-050c060b59ad'}
2206        utils.create_service(ctxt, values)
2207        self.ctxt = ctxt
2208
2209    @mock.patch('cinder.objects.Service.get_minimum_obj_version',
2210                return_value='1.8')
2211    def test_shared_targets_migrations(self, mock_version):
2212        """Ensure we can update the column."""
2213        # Run the migration and verify that we updated 1 entry
2214        with mock.patch('cinder.volume.rpcapi.VolumeAPI.get_capabilities',
2215                        return_value={'connection_protocol': 'iSCSI',
2216                                      'shared_targets': False}):
2217            total, updated = (
2218                cinder_manage.shared_targets_online_data_migration(
2219                    self.ctxt, 10))
2220            self.assertEqual(3, total)
2221            self.assertEqual(3, updated)
2222
2223    @mock.patch('cinder.objects.Service.get_minimum_obj_version',
2224                return_value='1.8')
2225    def test_shared_targets_migrations_non_iscsi(self, mock_version):
2226        """Ensure we can update the column."""
2227        # Run the migration and verify that we updated 1 entry
2228        with mock.patch('cinder.volume.rpcapi.VolumeAPI.get_capabilities',
2229                        return_value={'connection_protocol': 'RBD'}):
2230            total, updated = (
2231                cinder_manage.shared_targets_online_data_migration(
2232                    self.ctxt, 10))
2233            self.assertEqual(3, total)
2234            self.assertEqual(3, updated)
2235
2236    @mock.patch('cinder.objects.Service.get_minimum_obj_version',
2237                return_value='1.8')
2238    def test_shared_targets_migrations_with_limit(self, mock_version):
2239        """Ensure we update in batches."""
2240        # Run the migration and verify that we updated 1 entry
2241        with mock.patch('cinder.volume.rpcapi.VolumeAPI.get_capabilities',
2242                        return_value={'connection_protocol': 'iSCSI',
2243                                      'shared_targets': False}):
2244            total, updated = (
2245                cinder_manage.shared_targets_online_data_migration(
2246                    self.ctxt, 2))
2247            self.assertEqual(3, total)
2248            self.assertEqual(2, updated)
2249
2250            total, updated = (
2251                cinder_manage.shared_targets_online_data_migration(
2252                    self.ctxt, 2))
2253            self.assertEqual(1, total)
2254            self.assertEqual(1, updated)
2255