diff --git a/masakariclient/cliargs.py b/masakariclient/cliargs.py index f776d63..e6666ef 100644 --- a/masakariclient/cliargs.py +++ b/masakariclient/cliargs.py @@ -30,7 +30,9 @@ def add_global_args(parser, version): help=_('Version number for Masakari API to use, Default to "1".')) parser.add_argument( - '--debug', default=False, action='store_true', + '-d', '--debug', + action='store_true', + default=False, help=_('Print debugging output.')) diff --git a/masakariclient/shell.py b/masakariclient/shell.py index 7642687..fef890c 100644 --- a/masakariclient/shell.py +++ b/masakariclient/shell.py @@ -13,6 +13,7 @@ # limitations under the License. import argparse +import logging import sys from oslo_utils import encodeutils @@ -26,6 +27,7 @@ from masakariclient.common.i18n import _ from masakariclient.common import utils USER_AGENT = 'python-masakariclient' +LOG = logging.getLogger(__name__) class MasakariShell(object): @@ -113,6 +115,16 @@ class MasakariShell(object): return masakari_client.Client(api_ver, user_agent=USER_AGENT, **kwargs) + def _setup_logging(self, debug): + if debug: + log_level = logging.DEBUG + else: + log_level = logging.WARNING + log_format = "%(levelname)s (%(module)s) %(message)s" + logging.basicConfig(format=log_format, level=log_level) + logging.getLogger('iso8601').setLevel(logging.WARNING) + logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING) + def main(self, argv): parser = argparse.ArgumentParser( @@ -129,6 +141,8 @@ class MasakariShell(object): # parse main arguments (options, args) = parser.parse_known_args(argv) + self._setup_logging(options.debug) + base_parser = parser api_ver = options.masakari_api_version @@ -177,7 +191,7 @@ def main(args=None): print(_("KeyboardInterrupt masakari client"), sys.stderr) return 130 except Exception as e: - if '--debug' in args: + if '--debug' in args or '-d' in args: raise else: print(encodeutils.safe_encode(six.text_type(e)), sys.stderr) diff --git a/masakariclient/tests/unit/__init__.py b/masakariclient/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/masakariclient/tests/unit/test_cliargs.py b/masakariclient/tests/unit/test_cliargs.py new file mode 100644 index 0000000..e96551a --- /dev/null +++ b/masakariclient/tests/unit/test_cliargs.py @@ -0,0 +1,29 @@ +# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock +import testtools + +from masakariclient import cliargs + + +class TestCliArgs(testtools.TestCase): + + def test_add_global_identity_args(self): + parser = mock.Mock() + cliargs.add_global_identity_args(parser) + + def test_add_global_args(self): + parser = mock.Mock() + cliargs.add_global_args(parser, '1') diff --git a/masakariclient/tests/test_masakariclient.py b/masakariclient/tests/unit/test_client.py similarity index 50% rename from masakariclient/tests/test_masakariclient.py rename to masakariclient/tests/unit/test_client.py index 5c81284..578a317 100644 --- a/masakariclient/tests/test_masakariclient.py +++ b/masakariclient/tests/unit/test_client.py @@ -1,3 +1,5 @@ +# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation +# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -16,11 +18,30 @@ test_masakariclient Tests for `masakariclient` module. """ +import mock +from masakariclient import client as mc +from masakariclient.common import utils from masakariclient.tests import base +class FakeClient(object): + + def __init__(self, session): + super(FakeClient, self).__init__() + self.session = session + + class TestMasakariclient(base.TestCase): - def test_something(self): - pass + @mock.patch.object(utils, 'import_versioned_module') + def test_client_init(self, mock_import): + the_module = mock.Mock() + the_module.Client = FakeClient + mock_import.return_value = the_module + session = mock.Mock() + + res = mc.Client('FAKE_VER', session) + + mock_import.assert_called_once_with('FAKE_VER', 'client') + self.assertIsInstance(res, FakeClient) diff --git a/masakariclient/tests/unit/test_shell.py b/masakariclient/tests/unit/test_shell.py new file mode 100644 index 0000000..6f3ddb1 --- /dev/null +++ b/masakariclient/tests/unit/test_shell.py @@ -0,0 +1,128 @@ +# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +test_shell +---------------------------------- + +Tests for `masakariclient` module. +""" +import logging +import mock +import six +import sys +import testtools + +from masakariclient import shell +from masakariclient.tests import base + + +class FakeClient(object): + + def __init__(self): + super(FakeClient, self).__init__() + self.service = FakeService() + return self.service + + +class FakeService(object): + + def __init__(self): + super(FakeService, self).__init__() + + def do_notification_list(self): + pass + + +class HelpFormatterTest(testtools.TestCase): + + def test_start_section(self): + formatter = shell.HelpFormatter('masakari') + res = formatter.start_section(('dummyheading', 'dummy', 'dummy')) + self.assertIsNone(res) + heading = formatter._current_section.heading + self.assertEqual("DUMMYHEADING('dummy', 'dummy')", heading) + + +class TestMasakariShell(base.TestCase): + def setUp(self): + super(TestMasakariShell, self).setUp() + + def _shell(self, func, *args, **kwargs): + orig_out = sys.stdout + sys.stdout = six.StringIO() + func(*args, **kwargs) + output = sys.stdout.getvalue() + sys.stdout.close() + sys.stdout = orig_out + + return output + + def test_do_bash_completion(self): + sh = shell.MasakariShell() + sc1 = mock.Mock() + sc2 = mock.Mock() + sc1._optionals._option_string_actions = ('A1', 'A2', 'C') + sc2._optionals._option_string_actions = ('B1', 'B2', 'C') + sh.subcommands = { + 'command-foo': sc1, + 'command-bar': sc2, + 'bash-completion': None, + 'bash_completion': None, + } + + output = self._shell(sh.do_bash_completion, None) + + output = output.split('\n')[0] + output_list = output.split(' ') + for option in ('A1', 'A2', 'C', 'B1', 'B2', + 'command-foo', 'command-bar'): + self.assertIn(option, output_list) + + @mock.patch.object(logging, 'basicConfig') + @mock.patch.object(logging, 'getLogger') + def test_setup_logging_debug_true(self, moc_getLogger, + moc_basicConfig): + sh = shell.MasakariShell() + sh._setup_logging(True) + + moc_basicConfig.assert_called_once_with( + format="%(levelname)s (%(module)s) %(message)s", + level=logging.DEBUG) + mock_calls = [ + mock.call('iso8601'), + mock.call().setLevel(logging.WARNING), + mock.call('urllib3.connectionpool'), + mock.call().setLevel(logging.WARNING), + ] + moc_getLogger.assert_has_calls(mock_calls) + + @mock.patch.object(logging, 'basicConfig') + @mock.patch.object(logging, 'getLogger') + def test_setup_logging_debug_false(self, + moc_getLogger, + moc_basicConfig): + sh = shell.MasakariShell() + sh._setup_logging(False) + + moc_basicConfig.assert_called_once_with( + format="%(levelname)s (%(module)s) %(message)s", + level=logging.WARNING) + mock_calls = [ + mock.call('iso8601'), + mock.call().setLevel(logging.WARNING), + mock.call('urllib3.connectionpool'), + mock.call().setLevel(logging.WARNING), + ] + moc_getLogger.assert_has_calls(mock_calls) diff --git a/masakariclient/tests/unit/v1/__init__.py b/masakariclient/tests/unit/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/masakariclient/tests/unit/v1/test_client.py b/masakariclient/tests/unit/v1/test_client.py new file mode 100644 index 0000000..5c13e7d --- /dev/null +++ b/masakariclient/tests/unit/v1/test_client.py @@ -0,0 +1,50 @@ +# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +test_masakariclient +---------------------------------- + +Tests for `masakariclient` module. +""" +import mock + +from masakariclient.sdk.ha import connection +from masakariclient.tests import base +import masakariclient.v1.client as mc + + +class FakeConnection(object): + + def __init__(self, prof=None, user_agent=None, **kwargs): + super(FakeConnection, self).__init__() + self.vmha = None + + +class TestV1Client(base.TestCase): + + def setUp(self): + super(TestV1Client, self).setUp() + self.conn = mock.Mock() + self.service = mock.Mock() + self.conn.vmha = self.service + + def test_client_init(self): + with mock.patch.object(connection, + 'create_connection') as mock_connection: + mock_connection.return_value = self.conn + + res = mc.Client() + + self.assertEqual(self.conn.ha, res.service) + mock_connection.assert_called_once_with(prof=None, user_agent=None) diff --git a/masakariclient/tests/unit/v1/test_shell.py b/masakariclient/tests/unit/v1/test_shell.py new file mode 100644 index 0000000..4951af0 --- /dev/null +++ b/masakariclient/tests/unit/v1/test_shell.py @@ -0,0 +1,57 @@ +# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +test_masakariclient +---------------------------------- + +Tests for `masakariclient` module. +""" +import mock + +from masakariclient.common import utils +from masakariclient.tests import base +import masakariclient.v1.shell as ms + + +class TestV1Shell(base.TestCase): + + def setUp(self): + super(TestV1Shell, self).setUp() + self.vals = { + 'notification_uuid': 'b3bf75d7-c2e9-4023-a10b-e5b464b9b539', + 'source_host_uuid': '68fa7386-983e-4497-b5c4-3780f774d302', + 'created_at': '2016-11-15T12:24:39.000000', + 'updated_at': None, + 'payload': {'event': 'STOPPED'}, + 'generated_time': '2016-10-10T10:00:00.000000', + 'type': 'VM', + 'id': '27'} + + @mock.patch.object(utils, 'print_list') + def test_do_notification_list(self, mock_print_list): + service = mock.Mock() + service.notifications.return_value = self.vals + args = mock.Mock() + columns = [ + 'notification_uuid', + 'generated_time', + 'status', + 'source_host_uuid', + 'type'] + + ms.do_notification_list(service, args) + + mock_print_list.assert_called_once_with( + self.vals, + columns) diff --git a/masakariclient/v1/shell.py b/masakariclient/v1/shell.py index e45b1d0..5c4b609 100644 --- a/masakariclient/v1/shell.py +++ b/masakariclient/v1/shell.py @@ -12,4 +12,61 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Implement sub commands in this file after that.""" + +from oslo_serialization import jsonutils + +from masakariclient.common import utils + + +def do_notification_list(service, args): + """List notifications. + + :param service: service object. + :param args: API args. + """ + try: + notifications = service.notifications() + columns = [ + 'notification_uuid', 'generated_time', 'status', + 'source_host_uuid', 'type'] + utils.print_list(notifications, columns) + + except Exception as e: + print(e) + + +@utils.arg('--id', metavar='', required=True, + help='Notification to display (name or ID)') +def do_notification_show(service, args): + """Show a notification details.""" + try: + notification = service.get_notification(args.id) + utils.print_dict(notification.to_dict()) + + except Exception as e: + print(e) + + +@utils.arg('--type', metavar='', required=True, + help='Type of failure.') +@utils.arg('--hostname', metavar='', required=True, + help='Hostname of notification.') +@utils.arg('--generated-time', metavar='', required=True, + help='Timestamp for notification. e.g. 2016-01-01T01:00:00.000') +@utils.arg('--payload', metavar='', required=True, + help='JSON string about failure event.') +def do_notification_create(service, args): + """Create a notification.""" + try: + payload = jsonutils.loads(args.payload) + attrs = { + 'type': args.type, + 'hostname': args.hostname, + 'generated_time': args.generated_time, + 'payload': payload, + } + notification = service.create_notification(**attrs) + utils.print_dict(notification.to_dict()) + + except Exception as e: + print(e)