Coverage for manila/tests/cmd/test_manage.py: 92%
352 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2015 Mirantis Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16import code
17import io
18import readline
19import sys
20from unittest import mock
21import yaml
23import ddt
24from oslo_config import cfg
25from oslo_serialization import jsonutils
27from manila.cmd import manage as manila_manage
28from manila import context
29from manila import db
30from manila.db import migration
31from manila import test
32from manila import utils
33from manila import version
35CONF = cfg.CONF
38@ddt.ddt
39class ManilaCmdManageTestCase(test.TestCase):
40 def setUp(self):
41 super(ManilaCmdManageTestCase, self).setUp()
42 sys.argv = ['manila-share']
43 CONF(sys.argv[1:], project='manila', version=version.version_string())
44 self.shell_commands = manila_manage.ShellCommands()
45 self.host_commands = manila_manage.HostCommands()
46 self.db_commands = manila_manage.DbCommands()
47 self.version_commands = manila_manage.VersionCommands()
48 self.config_commands = manila_manage.ConfigCommands()
49 self.get_log_cmds = manila_manage.GetLogCommands()
50 self.service_cmds = manila_manage.ServiceCommands()
51 self.share_cmds = manila_manage.ShareCommands()
52 self.server_cmds = manila_manage.ShareServerCommands()
53 self.list_commands = manila_manage.ListCommand()
55 @mock.patch.object(manila_manage.ShellCommands, 'run', mock.Mock())
56 def test_shell_commands_bpython(self):
57 self.shell_commands.bpython()
58 manila_manage.ShellCommands.run.assert_called_once_with('bpython')
60 @mock.patch.object(manila_manage.ShellCommands, 'run', mock.Mock())
61 def test_shell_commands_ipython(self):
62 self.shell_commands.ipython()
63 manila_manage.ShellCommands.run.assert_called_once_with('ipython')
65 @mock.patch.object(manila_manage.ShellCommands, 'run', mock.Mock())
66 def test_shell_commands_python(self):
67 self.shell_commands.python()
68 manila_manage.ShellCommands.run.assert_called_once_with('python')
70 @ddt.data({}, {'shell': 'bpython'})
71 def test_run_bpython(self, kwargs):
72 try:
73 import bpython
74 except ImportError as e:
75 self.skipTest(str(e))
76 self.mock_object(bpython, 'embed')
77 self.shell_commands.run(**kwargs)
78 bpython.embed.assert_called_once_with()
80 def test_run_bpython_import_error(self):
81 try:
82 import bpython
83 import IPython
84 except ImportError as e:
85 self.skipTest(str(e))
86 self.mock_object(bpython, 'embed',
87 mock.Mock(side_effect=ImportError()))
88 self.mock_object(IPython, 'embed')
90 self.shell_commands.run(shell='bpython')
92 IPython.embed.assert_called_once_with()
94 def test_run(self):
95 try:
96 import bpython
97 except ImportError as e:
98 self.skipTest(str(e))
99 self.mock_object(bpython, 'embed')
101 self.shell_commands.run()
103 bpython.embed.assert_called_once_with()
105 def test_run_ipython(self):
106 try:
107 import IPython
108 except ImportError as e:
109 self.skipTest(str(e))
110 self.mock_object(IPython, 'embed')
112 self.shell_commands.run(shell='ipython')
114 IPython.embed.assert_called_once_with()
116 def test_run_ipython_import_error(self):
117 try:
118 import IPython
119 if not hasattr(IPython, 'Shell'):
120 setattr(IPython, 'Shell', mock.Mock())
121 setattr(IPython.Shell, 'IPShell',
122 mock.Mock(side_effect=ImportError()))
123 except ImportError as e:
124 self.skipTest(str(e))
125 self.mock_object(IPython, 'embed',
126 mock.Mock(side_effect=ImportError()))
127 self.mock_object(readline, 'parse_and_bind')
128 self.mock_object(code, 'interact')
129 shell = IPython.embed.return_value
131 self.shell_commands.run(shell='ipython')
132 IPython.Shell.IPShell.assert_called_once_with(argv=[])
133 self.assertFalse(shell.mainloop.called)
134 self.assertTrue(readline.parse_and_bind.called)
135 code.interact.assert_called_once_with()
137 def test_run_python(self):
138 self.mock_object(readline, 'parse_and_bind')
139 self.mock_object(code, 'interact')
141 self.shell_commands.run(shell='python')
143 readline.parse_and_bind.assert_called_once_with("tab:complete")
144 code.interact.assert_called_once_with()
146 def test_run_python_import_error(self):
147 self.mock_object(readline, 'parse_and_bind')
148 self.mock_object(code, 'interact')
150 self.shell_commands.run(shell='python')
152 readline.parse_and_bind.assert_called_once_with("tab:complete")
153 code.interact.assert_called_once_with()
155 @mock.patch('builtins.print')
156 def test_list(self, print_mock):
157 serv_1 = {
158 'host': 'fake_host1',
159 'availability_zone': {'name': 'avail_zone1'},
160 }
161 serv_2 = {
162 'host': 'fake_host2',
163 'availability_zone': {'name': 'avail_zone2'},
164 }
165 self.mock_object(db, 'service_get_all',
166 mock.Mock(return_value=[serv_1, serv_2]))
167 self.mock_object(context, 'get_admin_context',
168 mock.Mock(return_value='admin_ctxt'))
170 self.host_commands.list(zone='avail_zone1')
171 context.get_admin_context.assert_called_once_with()
172 db.service_get_all.assert_called_once_with('admin_ctxt')
173 print_mock.assert_has_calls([
174 mock.call(u'host \tzone '),
175 mock.call('fake_host1 \tavail_zone1 ')])
177 @mock.patch('builtins.print')
178 def test_list_zone_is_none(self, print_mock):
179 serv_1 = {
180 'host': 'fake_host1',
181 'availability_zone': {'name': 'avail_zone1'},
182 }
183 serv_2 = {
184 'host': 'fake_host2',
185 'availability_zone': {'name': 'avail_zone2'},
186 }
187 self.mock_object(db, 'service_get_all',
188 mock.Mock(return_value=[serv_1, serv_2]))
189 self.mock_object(context, 'get_admin_context',
190 mock.Mock(return_value='admin_ctxt'))
192 self.host_commands.list()
193 context.get_admin_context.assert_called_once_with()
194 db.service_get_all.assert_called_once_with('admin_ctxt')
195 print_mock.assert_has_calls([
196 mock.call(u'host \tzone '),
197 mock.call('fake_host1 \tavail_zone1 '),
198 mock.call('fake_host2 \tavail_zone2 ')])
200 def test_sync(self):
201 self.mock_object(migration, 'upgrade')
202 self.db_commands.sync(version='123')
203 migration.upgrade.assert_called_once_with('123')
205 def test_version(self):
206 self.mock_object(migration, 'version')
207 self.db_commands.version()
208 migration.version.assert_called_once_with()
210 def test_downgrade(self):
211 self.mock_object(migration, 'downgrade')
212 self.db_commands.downgrade(version='123')
213 migration.downgrade.assert_called_once_with('123')
215 def test_revision(self):
216 self.mock_object(migration, 'revision')
217 self.db_commands.revision('message', True)
218 migration.revision.assert_called_once_with('message', True)
220 def test_stamp(self):
221 self.mock_object(migration, 'stamp')
222 self.db_commands.stamp(version='123')
223 migration.stamp.assert_called_once_with('123')
225 def test_version_commands_list(self):
226 self.mock_object(version, 'version_string',
227 mock.Mock(return_value='123'))
228 with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
229 self.version_commands.list()
230 version.version_string.assert_called_once_with()
231 self.assertEqual('123\n', fake_out.getvalue())
233 def test_version_commands_call(self):
234 self.mock_object(version, 'version_string',
235 mock.Mock(return_value='123'))
236 with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
237 self.version_commands()
238 version.version_string.assert_called_once_with()
239 self.assertEqual('123\n', fake_out.getvalue())
241 def test_get_log_commands_no_errors(self):
242 with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
243 CONF.set_override('log_dir', None)
244 expected_out = 'No errors in logfiles!\n'
246 self.get_log_cmds.errors()
248 self.assertEqual(expected_out, fake_out.getvalue())
250 @mock.patch('builtins.open')
251 @mock.patch('os.listdir')
252 def test_get_log_commands_errors(self, listdir, open):
253 CONF.set_override('log_dir', 'fake-dir')
254 listdir.return_value = ['fake-error.log']
256 with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
257 open.return_value = io.StringIO(
258 '[ ERROR ] fake-error-message')
259 expected_out = ('fake-dir/fake-error.log:-\n'
260 'Line 1 : [ ERROR ] fake-error-message\n')
261 self.get_log_cmds.errors()
263 self.assertEqual(expected_out, fake_out.getvalue())
264 open.assert_called_once_with('fake-dir/fake-error.log', 'r')
265 listdir.assert_called_once_with(CONF.log_dir)
267 @mock.patch('builtins.open')
268 @mock.patch('os.path.exists')
269 def test_get_log_commands_syslog_no_log_file(self, path_exists, open):
270 path_exists.return_value = False
271 exit = self.assertRaises(SystemExit, self.get_log_cmds.syslog)
272 self.assertEqual(1, exit.code)
273 path_exists.assert_any_call('/var/log/syslog')
274 path_exists.assert_any_call('/var/log/messages')
276 @mock.patch('manila.utils.service_is_up')
277 @mock.patch('manila.db.service_get_all')
278 @mock.patch('manila.context.get_admin_context')
279 def test_service_commands_list(self, get_admin_context, service_get_all,
280 service_is_up):
281 ctxt = context.RequestContext('fake-user', 'fake-project')
282 get_admin_context.return_value = ctxt
283 service = {'binary': 'manila-binary',
284 'host': 'fake-host.fake-domain',
285 'availability_zone': {'name': 'fake-zone'},
286 'updated_at': '2014-06-30 11:22:33',
287 'disabled': False}
288 service_get_all.return_value = [service]
289 service_is_up.return_value = True
290 with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
291 format = "%-16s %-36s %-16s %-10s %-5s %-10s"
292 print_format = format % ('Binary',
293 'Host',
294 'Zone',
295 'Status',
296 'State',
297 'Updated at')
298 service_format = format % (service['binary'],
299 service['host'].partition('.')[0],
300 service['availability_zone']['name'],
301 'enabled',
302 ':-)',
303 service['updated_at'])
304 expected_out = print_format + '\n' + service_format + '\n'
305 self.service_cmds.list(format_output='table')
306 self.assertEqual(expected_out, fake_out.getvalue())
307 get_admin_context.assert_called_with()
308 service_get_all.assert_called_with(ctxt)
309 service_is_up.assert_called_with(service)
311 @ddt.data('json', 'yaml')
312 def test_service_commands_list_format(self, format_output):
313 ctxt = context.RequestContext('fake-user', 'fake-project')
314 format_method_name = f'list_{format_output}'
315 mock_list_method = self.mock_object(
316 self.service_cmds, format_method_name)
317 get_admin_context = self.mock_object(context, 'get_admin_context')
318 service_get_all = self.mock_object(db, 'service_get_all')
319 service_is_up = self.mock_object(utils, 'service_is_up')
320 get_admin_context.return_value = ctxt
321 service = {'binary': 'manila-binary',
322 'host': 'fake-host.fake-domain',
323 'availability_zone': {'name': 'fake-zone'},
324 'updated_at': '2014-06-30 11:22:33',
325 'disabled': False}
326 services = [service]
327 service_get_all.return_value = services
328 service_is_up.return_value = True
330 with mock.patch('sys.stdout', new=io.StringIO()):
331 self.service_cmds.list(format_output=format_output)
332 get_admin_context.assert_called_with()
333 service_get_all.assert_called_with(ctxt)
334 service_is_up.assert_called_with(service)
335 service_format = {
336 'binary': service['binary'],
337 'host': service['host'].partition('.')[0],
338 'zone': service['availability_zone']['name'],
339 'status': 'enabled',
340 'state': ':-)',
341 'updated_at': service['updated_at'],
342 }
343 mock_list_method.assert_called_once_with(
344 'services', [service_format])
346 @ddt.data(True, False)
347 def test_service_commands_cleanup(self, service_is_up):
348 ctxt = context.RequestContext('fake-user', 'fake-project')
349 self.mock_object(context, 'get_admin_context',
350 mock.Mock(return_value=ctxt))
351 service = {'id': 17,
352 'binary': 'manila-binary',
353 'host': 'fake-host.fake-domain',
354 'availability_zone': {'name': 'fake-zone'},
355 'updated_at': '2020-06-17 07:22:33',
356 'disabled': False}
357 self.mock_object(db, 'service_get_all',
358 mock.Mock(return_value=[service]))
359 self.mock_object(db, 'service_destroy')
360 self.mock_object(utils, 'service_is_up',
361 mock.Mock(return_value=service_is_up))
363 with mock.patch('sys.stdout', new=io.StringIO()) as fake_out:
364 if not service_is_up:
365 expected_out = "Cleaned up service %s" % service['host']
366 else:
367 expected_out = ''
368 self.service_cmds.cleanup()
370 self.assertEqual(expected_out, fake_out.getvalue().strip())
371 context.get_admin_context.assert_called_with()
372 db.service_get_all.assert_called_with(ctxt)
373 utils.service_is_up.assert_called_with(service)
374 if not service_is_up:
375 db.service_destroy.assert_called_with(ctxt, service['id'])
376 else:
377 self.assertFalse(db.service_destroy.called)
379 def test_methods_of(self):
380 obj = type('Fake', (object,),
381 {name: lambda: 'fake_' for name in ('_a', 'b', 'c')})
382 expected = [('b', obj.b), ('c', obj.c)]
383 self.assertEqual(expected, manila_manage.methods_of(obj))
385 @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt')
386 def test_main_argv_lt_2(self, register_cli_opt):
387 script_name = 'manila-manage'
388 sys.argv = [script_name]
389 CONF(sys.argv[1:], project='manila', version=version.version_string())
390 exit = self.assertRaises(SystemExit, manila_manage.main)
392 self.assertTrue(register_cli_opt.called)
393 self.assertEqual(2, exit.code)
395 @mock.patch('oslo_config.cfg.ConfigOpts.__call__')
396 @mock.patch('oslo_log.log.register_options')
397 @mock.patch('oslo_log.log.setup')
398 @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt')
399 def test_main_sudo_failed(self, register_cli_opt, log_setup,
400 register_log_opts, config_opts_call):
401 script_name = 'manila-manage'
402 sys.argv = [script_name, 'fake_category', 'fake_action']
403 config_opts_call.side_effect = cfg.ConfigFilesNotFoundError(
404 mock.sentinel._namespace)
406 exit = self.assertRaises(SystemExit, manila_manage.main)
408 self.assertTrue(register_cli_opt.called)
409 register_log_opts.assert_called_once_with(CONF)
410 config_opts_call.assert_called_once_with(
411 sys.argv[1:], project='manila',
412 version=version.version_string())
413 self.assertFalse(log_setup.called)
414 self.assertEqual(2, exit.code)
416 @mock.patch('oslo_config.cfg.ConfigOpts.__call__')
417 @mock.patch('oslo_config.cfg.ConfigOpts.register_cli_opt')
418 @mock.patch('oslo_log.log.register_options')
419 def test_main(self, register_log_opts, register_cli_opt, config_opts_call):
420 script_name = 'manila-manage'
421 sys.argv = [script_name, 'config', 'list']
422 action_fn = mock.MagicMock()
423 CONF.category = mock.MagicMock(action_fn=action_fn)
425 manila_manage.main()
427 self.assertTrue(register_cli_opt.called)
428 register_log_opts.assert_called_once_with(CONF)
429 config_opts_call.assert_called_once_with(
430 sys.argv[1:], project='manila', version=version.version_string())
431 self.assertTrue(action_fn.called)
433 @ddt.data('bar', '-bar', '--bar')
434 def test_get_arg_string(self, arg):
435 parsed_arg = manila_manage.get_arg_string(arg)
436 self.assertEqual('bar', parsed_arg)
438 @ddt.data({'current_host': 'controller-0@fancystore01#pool100',
439 'new_host': 'controller-0@fancystore01'},
440 {'current_host': 'controller-0@fancystore01',
441 'new_host': 'controller-0'})
442 @ddt.unpack
443 def test_share_update_host_fail_validation(self, current_host, new_host):
444 self.mock_object(context, 'get_admin_context',
445 mock.Mock(return_value='admin_ctxt'))
446 self.mock_object(db, 'share_resources_host_update')
448 self.assertRaises(SystemExit,
449 self.share_cmds.update_host,
450 current_host, new_host)
452 self.assertFalse(db.share_resources_host_update.called)
454 @ddt.data({'current_host': 'controller-0@fancystore01#pool100',
455 'new_host': 'controller-0@fancystore02#pool0'},
456 {'current_host': 'controller-0@fancystore01',
457 'new_host': 'controller-1@fancystore01'},
458 {'current_host': 'controller-0',
459 'new_host': 'controller-1'},
460 {'current_host': 'controller-0@fancystore01#pool100',
461 'new_host': 'controller-1@fancystore02', 'force': True})
462 @ddt.unpack
463 def test_share_update_host(self, current_host, new_host, force=False):
464 db_op = {'instances': 3, 'groups': 4, 'servers': 2}
465 self.mock_object(context, 'get_admin_context',
466 mock.Mock(return_value='admin_ctxt'))
467 self.mock_object(db, 'share_resources_host_update',
468 mock.Mock(return_value=db_op))
470 with mock.patch('sys.stdout', new=io.StringIO()) as intercepted_op:
471 self.share_cmds.update_host(current_host, new_host, force)
473 expected_op = ("Updated host of 3 share instances, 4 share groups and "
474 "2 share servers on %(chost)s to %(nhost)s." %
475 {'chost': current_host, 'nhost': new_host})
476 self.assertEqual(expected_op, intercepted_op.getvalue().strip())
477 db.share_resources_host_update.assert_called_once_with(
478 'admin_ctxt', current_host, new_host)
480 def test_share_delete(self):
481 share_id = "fake_share_id"
482 share = {
483 'id': share_id,
484 'instances': [
485 {'id': 'instance_id1', 'replica_state': 'active'},
486 {'id': 'instance_id2', 'replica_state': 'error'},
487 {'id': 'instance_id3', 'replica_state': 'active'},
488 ]
489 }
490 self.mock_object(context, 'get_admin_context',
491 mock.Mock(return_value='admin_ctxt'))
492 self.mock_object(db, 'share_get',
493 mock.Mock(return_value=share))
494 self.mock_object(db, 'share_instance_delete',
495 mock.Mock(return_value=None))
497 self.share_cmds.delete(share_id)
499 db.share_instance_delete.assert_has_calls([
500 mock.call('admin_ctxt', 'instance_id2'),
501 mock.call('admin_ctxt', 'instance_id1'),
502 mock.call('admin_ctxt', 'instance_id3'),
503 ])
505 self.assertEqual(3, db.share_instance_delete.call_count)
507 def test_share_server_update_capability(self):
508 self.mock_object(context, 'get_admin_context',
509 mock.Mock(return_value='admin_ctxt'))
510 self.mock_object(db, 'share_servers_update')
511 share_servers = 'server_id_a,server_id_b'
512 share_server_list = [server.strip()
513 for server in share_servers.split(",")]
514 capabilities = "security_service_update_support" \
515 ",network_allocation_update_support"
516 capabilities_list = capabilities.split(",")
517 values_to_update = [
518 {capabilities_list[0]: True,
519 capabilities_list[1]: True}]
521 with mock.patch('sys.stdout', new=io.StringIO()) as output:
522 self.server_cmds.update_share_server_capabilities(
523 share_servers, capabilities, True)
525 expected_op = ("The capability(ies) %(cap)s of the following share "
526 "server(s) %(servers)s was(were) updated to "
527 "%(value)s.") % {
528 'cap': capabilities_list,
529 'servers': share_server_list,
530 'value': True,
531 }
533 self.assertEqual(expected_op, output.getvalue().strip())
534 db.share_servers_update.assert_called_once_with(
535 'admin_ctxt', share_server_list, values_to_update[0])
537 def test_share_server_update_capability_not_supported(self):
538 share_servers = 'server_id_a'
539 capabilities = 'invalid_capability'
541 exit = self.assertRaises(
542 SystemExit,
543 self.server_cmds.update_share_server_capabilities,
544 share_servers,
545 capabilities,
546 True)
548 self.assertEqual(1, exit.code)
550 @mock.patch('builtins.print')
551 def test_list_commands_json(self, mock_print):
552 resource_name = 'service'
553 service_format = [{
554 'binary': 'manila-binary',
555 'host': 'fake-host',
556 'availability_zone': 'fakeaz',
557 'status': 'enabled',
558 'state': ':-)',
559 'updated_at': '13 04:57:49 PM -03 2023'
560 }]
562 mock_json_dumps = self.mock_object(
563 jsonutils, 'dumps', mock.Mock(return_value=service_format[0]))
564 services = {resource_name: service_format}
566 self.list_commands.list_json('service', service_format)
568 mock_json_dumps.assert_called_once_with(
569 services, indent=4)
570 mock_print.assert_called_once_with(service_format[0])
572 @mock.patch('builtins.print')
573 def test_list_commands_yaml(self, mock_print):
574 resource_name = 'service'
575 service_format = [{
576 'binary': 'manila-binary',
577 'host': 'fake-host',
578 'availability_zone': 'fakeaz',
579 'status': 'enabled',
580 'state': ':-)',
581 'updated_at': '13 04:57:49 PM -03 2023'
582 }]
584 mock_yaml_dump = self.mock_object(
585 yaml, 'dump', mock.Mock(return_value=service_format[0]))
586 services = {resource_name: service_format}
588 self.list_commands.list_yaml('service', service_format)
590 mock_yaml_dump.assert_called_once_with(
591 services)
592 mock_print.assert_called_once_with(service_format[0])