diff --git a/synergy/client/command.py b/synergy/client/command.py index 48593c8..d5fa88c 100644 --- a/synergy/client/command.py +++ b/synergy/client/command.py @@ -80,19 +80,18 @@ class ManagerCommand(HTTPCommand): "status", add_help=True, help="show the managers status") status_parser.add_argument( - "manager", nargs='*', help="one or more manager name") + "manager", nargs='?', default=None, help="at most one manager") start_parser = manager_parsers.add_parser( "start", add_help=True, help="start the manager") - start_parser.add_argument( - "manager", nargs='+', help="one or more manager name") + start_parser.add_argument("manager", help="the manager to start") stop_parser = manager_parsers.add_parser( "stop", add_help=True, help="stop the manager") stop_parser.add_argument( - "manager", nargs='+', help="one or more manager name") + "manager", help="the manager to stop") def execute(self, synergy_url, args=None): table = [] @@ -113,21 +112,19 @@ class ManagerCommand(HTTPCommand): headers.append("rate (min)") url += "/synergy/" + args.command - managers = super(ManagerCommand, self).execute( + result = super(ManagerCommand, self).execute( url, {"manager": args.manager}) if args.command == "status": - for manager in managers: + for manager in result: table.append([manager.getName(), manager.getStatus(), manager.getRate()]) else: - for manager in managers: - msg = manager.get("message") - - table.append([manager.getName(), - manager.getStatus() + " (%s)" % msg, - manager.getRate()]) + msg = result.get("message") + table.append([result.getName(), + result.getStatus() + " (%s)" % msg, + result.getRate()]) print(tabulate(table, headers, tablefmt="fancy_grid")) diff --git a/synergy/client/shell.py b/synergy/client/shell.py index 627a1c4..41ba896 100644 --- a/synergy/client/shell.py +++ b/synergy/client/shell.py @@ -1,10 +1,12 @@ import os import os.path -import requests import sys from argparse import ArgumentParser from pkg_resources import iter_entry_points +from requests.exceptions import ConnectionError +from requests.exceptions import HTTPError +from requests.exceptions import RequestException from synergy.client import keystone_v3 __author__ = "Lisa Zangrando" @@ -167,14 +169,14 @@ def main(): commands[command_name].setToken(token) commands[command_name].execute(synergy_url, args) - except KeyboardInterrupt as e: + except KeyboardInterrupt: print("Shutting down synergyclient") sys.exit(1) - except requests.exceptions.HTTPError as e: - print("HTTPError: %s" % e.response._content) + except (RequestException, ConnectionError, HTTPError) as ex: + print("connection to %s failed!" % synergy_url) sys.exit(1) - except Exception as e: - print("ERROR: %s" % e) + except Exception as ex: + print(ex.message) sys.exit(1) diff --git a/synergy/service.py b/synergy/service.py index e5a1b55..92b8dba 100644 --- a/synergy/service.py +++ b/synergy/service.py @@ -153,15 +153,58 @@ class Synergy(Service): self.saved_args, self.saved_kwargs = args, kwargs - def authorizationRequired(f): + def parseParameters(f): + def wrapper(self, *args, **kw): + context = args[0] + + query = context.get("QUERY_STRING", None) + + if query: + parameters = parse_qs(query) + + for key in parameters: + value = escape(parameters[key][0]) + value = value.replace("'", "\"") + + try: + value = json.loads(value) + except ValueError: + pass + + context[key] = value + + return f(self, *args, **kw) + + return wrapper + + def checkParameters(paremeters): + def check(f): + def wrapper(self, *args, **kw): + context = args[0] + start_response = args[1] + + for parameter in paremeters: + value = context.get(parameter, None) + + if not value: + start_response("400 BAD REQUEST", + [("Content-Type", "text/plain")]) + return "parameter %s not found!" % parameter + + if parameter == "manager" and value not in self.managers: + start_response("404 NOT FOUND", + [("Content-Type", "text/plain")]) + return "manager %s not found!" % value + + return f(self, *args, **kw) + return wrapper + return check + + def authorize(f): def wrapper(self, *args, **kw): if self.auth_plugin: context = args[0] context["managers"] = self.managers - query = context.get("QUERY_STRING", None) - - if query: - context.update(parse_qs(query)) try: self.auth_plugin.authorize(context) @@ -174,7 +217,7 @@ class Synergy(Service): return wrapper - @authorizationRequired + @authorize def listManagers(self, environ, start_response): result = [] @@ -188,23 +231,21 @@ class Synergy(Service): start_response("200 OK", [("Content-Type", "text/html")]) return ["%s" % json.dumps(result, cls=SynergyEncoder)] - @authorizationRequired + @parseParameters + @authorize def getManagerStatus(self, environ, start_response): + manager_name = environ.get("manager", None) + manager_list = None result = [] - query = environ.get("QUERY_STRING", None) + if manager_name: + if manager_name not in self.managers: + start_response("404 NOT FOUND", + [("Content-Type", "text/plain")]) + return "manager %s not found!" % manager_name - if query: - parameters = parse_qs(query) - - if "manager" in parameters: - if isinstance(parameters['manager'], (list, tuple)): - manager_list = parameters['manager'] - else: - manager_list = [parameters['manager']] - else: - manager_list = self.managers.keys() + manager_list = [manager_name] else: manager_list = self.managers.keys() @@ -220,184 +261,94 @@ class Synergy(Service): result.append(m) - if len(manager_list) == 1 and len(result) == 0: - start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) - return ["manager %s not found!" % manager_list[0]] - start_response("200 OK", [("Content-Type", "text/html")]) - return ["%s" % json.dumps(result, cls=SynergyEncoder)] + return [json.dumps(result, cls=SynergyEncoder)] - @authorizationRequired + @parseParameters + @checkParameters(["manager", "command", "args"]) + @authorize def executeCommand(self, environ, start_response): - manager_name = None - command = None - query = environ.get("QUERY_STRING", None) - - if not query: - start_response("400 BAD REQUEST", [("Content-Type", "text/plain")]) - return ["bad request"] - - parameters = parse_qs(query) - LOG.debug("execute command: parameters=%s" % parameters) - - if "manager" not in parameters: - start_response("400 BAD REQUEST", [("Content-Type", "text/plain")]) - return ["manager not specified!"] - - manager_name = escape(parameters['manager'][0]) - - if manager_name not in self.managers: - start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) - return ["manager %s not found!" % manager_name] - - if "command" not in parameters: - start_response("400 BAD REQUEST", [("Content-Type", "text/plain")]) - return ["bad request"] - - command = escape(parameters['command'][0]) - - if "args" in parameters: - manager_args = escape(parameters['args'][0]) - manager_args = manager_args.replace("'", "\"") - manager_args = json.loads(manager_args) - else: - manager_args = {} - + manager_name = environ["manager"] manager = self.managers[manager_name] + manager_args = environ["args"] + command = environ["command"] try: result = manager.execute(command=command, **manager_args) start_response("200 OK", [("Content-Type", "text/html")]) - return ["%s" % json.dumps(result, cls=SynergyEncoder)] + return [json.dumps(result, cls=SynergyEncoder)] except NotImplementedError: message = "execute() not implemented!" LOG.error(message) start_response("500 INTERNAL SERVER ERROR", [("Content-Type", "text/plain")]) - return ["error: %s" % message] + return message except SynergyError as ex: LOG.debug("execute command: error=%s" % ex) + start_response("500 INTERNAL SERVER ERROR", [("Content-Type", "text/plain")]) - return ["error: %s" % ex] + return "%s" % ex - @authorizationRequired + @parseParameters + @checkParameters(["manager"]) + @authorize def startManager(self, environ, start_response): - manager_list = None - result = [] + manager_name = environ["manager"] + manager = self.managers[manager_name] + result = Manager(manager_name) + result.setRate(manager.getRate()) - query = environ.get("QUERY_STRING", None) + if manager.getStatus() == "ACTIVE": + LOG.info("starting the %s manager" % (manager_name)) - if not query: - start_response("400 BAD REQUEST", [("Content-Type", "text/plain")]) - return ["bad request"] + manager.resume() - parameters = parse_qs(query) + LOG.info("%s manager started! (rate=%s min)" + % (manager_name, manager.getRate())) - if "manager" not in parameters: - start_response("400 BAD REQUEST", [("Content-Type", "text/plain")]) - return ["manager not specified!"] - - if isinstance(parameters['manager'], (list, tuple)): - manager_list = parameters['manager'] - else: - manager_list = [parameters['manager']] - - for manager_name in manager_list: - manager_name = escape(manager_name) - - if manager_name not in self.managers: - continue - - manager = self.managers[manager_name] - m = Manager(manager_name) - m.setRate(manager.getRate()) - - result.append(m) - - if manager.getStatus() == "ACTIVE": - LOG.info("starting the %s manager" % (manager_name)) - - manager.resume() - - LOG.info("%s manager started! (rate=%s min)" - % (manager_name, manager.getRate())) - - m.setStatus("RUNNING") - m.set("message", "started successfully") - elif manager.getStatus() == "RUNNING": - m.setStatus("RUNNING") - m.set("message", "WARN: already started") - elif manager.getStatus() == "ERROR": - m.setStatus("ERROR") - m.set("message", "wrong state") - - if len(manager_list) == 1 and len(result) == 0: - start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) - return ["manager %r not found!" % manager_list[0]] + result.setStatus("RUNNING") + result.set("message", "started successfully") + elif manager.getStatus() == "RUNNING": + result.setStatus("RUNNING") + result.set("message", "WARN: already started") + elif manager.getStatus() == "ERROR": + result.setStatus("ERROR") + result.set("message", "wrong state") start_response("200 OK", [("Content-Type", "text/html")]) - return ["%s" % json.dumps(result, cls=SynergyEncoder)] + return json.dumps(result, cls=SynergyEncoder) - @authorizationRequired + @parseParameters + @checkParameters(["manager"]) + @authorize def stopManager(self, environ, start_response): - manager_list = None - result = [] - query = environ.get("QUERY_STRING", None) + manager_name = environ["manager"] + manager = self.managers[manager_name] + result = Manager(manager_name) + result.setRate(manager.getRate()) - if not query: - start_response("400 BAD REQUEST", [("Content-Type", "text/plain")]) - return ["bad request"] + if manager.getStatus() == "RUNNING": + LOG.info("stopping the %s manager" % (manager_name)) - parameters = parse_qs(query) + manager.pause() - if "manager" not in parameters: - start_response("400 BAD REQUEST", [("Content-Type", "text/plain")]) - return ["manager not specified!"] + LOG.info("%s manager stopped!" % (manager_name)) - if isinstance(parameters['manager'], (list, tuple)): - manager_list = parameters['manager'] - else: - manager_list = [parameters['manager']] - - for manager_name in manager_list: - manager_name = escape(manager_name) - - if manager_name not in self.managers: - continue - - manager = self.managers[manager_name] - - m = Manager(manager_name) - m.setRate(manager.getRate()) - - result.append(m) - - if manager.getStatus() == "RUNNING": - LOG.info("stopping the %s manager" % (manager_name)) - - manager.pause() - - LOG.info("%s manager stopped!" % (manager_name)) - - m.setStatus("ACTIVE") - m.set("message", "stopped successfully") - elif manager.getStatus() == "ACTIVE": - m.setStatus("ACTIVE") - m.set("message", "WARN: already stopped") - elif manager.getStatus() == "ERROR": - m.setStatus("ERROR") - m.set("message", "wrong state") - - if len(manager_list) == 1 and len(result) == 0: - start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) - return ["manager %r not found!" % manager_list[0]] + result.setStatus("ACTIVE") + result.set("message", "stopped successfully") + elif manager.getStatus() == "ACTIVE": + result.setStatus("ACTIVE") + result.set("message", "WARN: already stopped") + elif manager.getStatus() == "ERROR": + result.setStatus("ERROR") + result.set("message", "wrong state") start_response("200 OK", [("Content-Type", "text/html")]) - return ["%s" % json.dumps(result, cls=SynergyEncoder)] + return json.dumps(result, cls=SynergyEncoder) def start(self): self.model_disconnected = False diff --git a/synergy/tests/functional/test_synergy.py b/synergy/tests/functional/test_synergy.py index 4ab1859..32e8efa 100644 --- a/synergy/tests/functional/test_synergy.py +++ b/synergy/tests/functional/test_synergy.py @@ -75,7 +75,17 @@ class SynergyTests(unittest.TestCase): @mock.patch('synergy.service.LOG', LOG) def test_getManagerStatus(self): start_response = Mock() - result = self.synergy.getManagerStatus(environ={}, + environ = {} + result = self.synergy.getManagerStatus(environ, + start_response=start_response) + + result = json.loads(result[0], object_hook=objectHookHandler) + + self.assertEqual(result[0].getStatus(), 'ACTIVE') + + environ = {'QUERY_STRING': 'manager=TimerManager'} + + result = self.synergy.getManagerStatus(environ, start_response=start_response) result = json.loads(result[0], object_hook=objectHookHandler) @@ -89,23 +99,23 @@ class SynergyTests(unittest.TestCase): result = self.synergy.startManager(environ, start_response) - self.assertEqual(result[0], "manager 'NONE' not found!") + self.assertEqual(result, "manager NONE not found!") environ = {'QUERY_STRING': 'manager=TimerManager'} result = self.synergy.startManager(environ, start_response) - result = json.loads(result[0], object_hook=objectHookHandler) + result = json.loads(result, object_hook=objectHookHandler) - self.assertEqual(result[0].getStatus(), 'RUNNING') - self.assertEqual(result[0].get("message"), 'started successfully') + self.assertEqual(result.getStatus(), 'RUNNING') + self.assertEqual(result.get("message"), 'started successfully') time.sleep(0.5) result = self.synergy.startManager(environ, start_response) - result = json.loads(result[0], object_hook=objectHookHandler) + result = json.loads(result, object_hook=objectHookHandler) - self.assertEqual(result[0].getStatus(), 'RUNNING') - self.assertEqual(result[0].get("message"), 'WARN: already started') + self.assertEqual(result.getStatus(), 'RUNNING') + self.assertEqual(result.get("message"), 'WARN: already started') @mock.patch('synergy.service.LOG', LOG) def test_stopManager(self): @@ -114,23 +124,25 @@ class SynergyTests(unittest.TestCase): result = self.synergy.startManager(environ, stop_response) - self.assertEqual(result[0], "manager 'NONE' not found!") + self.assertEqual(result, "manager NONE not found!") environ = {'QUERY_STRING': 'manager=TimerManager'} result = self.synergy.startManager(environ, stop_response) - result = json.loads(result[0], object_hook=objectHookHandler) + result = json.loads(result, object_hook=objectHookHandler) time.sleep(0.5) result = self.synergy.stopManager(environ, stop_response) - result = json.loads(result[0], object_hook=objectHookHandler) + result = json.loads(result, object_hook=objectHookHandler) - self.assertEqual(result[0].getStatus(), 'ACTIVE') + self.assertEqual(result.getStatus(), 'ACTIVE') @mock.patch('synergy.service.LOG', LOG) def test_executeCommand(self): - environ = {'QUERY_STRING': 'manager=TimerManager&command=GET_TIME'} + environ = {'QUERY_STRING': + 'manager=TimerManager&args=%7B%22id%22%3A' + '+null%2C+%22name%22%3A+%22prj_a%22%7D&command=GET_TIME'} start_response = Mock() result = self.synergy.executeCommand(environ, start_response) diff --git a/synergy/tests/unit/test_client_command_managercommand.py b/synergy/tests/unit/test_client_command_managercommand.py index daa9b68..fe6f0d2 100644 --- a/synergy/tests/unit/test_client_command_managercommand.py +++ b/synergy/tests/unit/test_client_command_managercommand.py @@ -49,22 +49,22 @@ class TestManagerCommand(base.TestCase): # manager status res = root_parser.parse_args(["manager", "status"]) - ns = Namespace(command_name="manager", command="status", manager=[]) + ns = Namespace(command_name="manager", command="status", manager=None) self.assertEqual(ns, res) res = root_parser.parse_args(["manager", "status", "TestManager"]) ns = Namespace( command_name="manager", command="status", - manager=["TestManager"]) + manager="TestManager") self.assertEqual(ns, res) res = root_parser.parse_args( - ["manager", "status", "Test1", "Test2", "Test3"]) + ["manager", "status", "Test1"]) ns = Namespace( command_name="manager", command="status", - manager=["Test1", "Test2", "Test3"]) + manager="Test1") self.assertEqual(ns, res) # manager start @@ -77,15 +77,15 @@ class TestManagerCommand(base.TestCase): ns = Namespace( command_name="manager", command="start", - manager=["TestManager"]) + manager="TestManager") self.assertEqual(ns, res) res = root_parser.parse_args( - ["manager", "start", "Test1", "Test2"]) + ["manager", "start", "Test1"]) ns = Namespace( command_name="manager", command="start", - manager=["Test1", "Test2"]) + manager="Test1") self.assertEqual(ns, res) # manager stop @@ -98,15 +98,15 @@ class TestManagerCommand(base.TestCase): ns = Namespace( command_name="manager", command="stop", - manager=["TestManager"]) + manager="TestManager") self.assertEqual(ns, res) res = root_parser.parse_args( - ["manager", "stop", "Test1", "Test2"]) + ["manager", "stop", "Test1"]) ns = Namespace( command_name="manager", command="stop", - manager=["Test1", "Test2"]) + manager="Test1") self.assertEqual(ns, res) @mock.patch('synergy.client.command.tabulate') @@ -204,50 +204,12 @@ class TestManagerCommand(base.TestCase): tablefmt="fancy_grid") @mock.patch('synergy.client.command.tabulate') - def test_execute_status_two_managers(self, mock_tabulate): - """Check the CLI output of "manager status ManagerA ManagerB".""" - # Mock the parser call - mock_parser = mock.Mock() - mock_parser.args.command = "status" - mock_parser.args.manager = ["ManagerA", "ManagerB"] - - # Mock 2 managers and their statuses - manager_a = mock.Mock() - manager_a.getName.return_value = "ManagerA" - manager_a.getStatus.return_value = "UP" - manager_a.getRate.return_value = 5 - manager_b = mock.Mock() - manager_b.getName.return_value = "ManagerB" - manager_b.getStatus.return_value = "DOWN" - manager_b.getRate.return_value = 10 - mgrs = [manager_a, manager_b] - - # Execute "manager status ManagerA ManagerB" - with mock.patch.object(HTTPCommand, 'execute', return_value=mgrs) as m: - self.manager_command.execute(synergy_url="", args=mock_parser.args) - - # Check the executed call - m.assert_called_once_with( - "/synergy/status", - {"manager": ["ManagerA", "ManagerB"]}) - - # Check the data when we call tabulate - headers = ["manager", "status", "rate (min)"] - table = [ - ["ManagerA", "UP", 5], - ["ManagerB", "DOWN", 10]] - mock_tabulate.assert_called_once_with( - table, - headers, - tablefmt="fancy_grid") - - @mock.patch('synergy.client.command.tabulate') - def test_execute_start_one_manager(self, mock_tabulate): + def test_execute_start_manager(self, mock_tabulate): """Check the CLI output of "manager start ManagerA".""" # Mock the parser call mock_parser = mock.Mock() mock_parser.args.command = "start" - mock_parser.args.manager = ["ManagerA"] + mock_parser.args.manager = "ManagerA" # Mock a manager manager_a = mock.Mock() @@ -255,7 +217,7 @@ class TestManagerCommand(base.TestCase): manager_a.getStatus.return_value = "RUNNING" manager_a.get.return_value = "started successfully" manager_a.getRate.return_value = 1 - mgrs = [manager_a] + mgrs = manager_a # Execute "manager start ManagerA" with mock.patch.object(HTTPCommand, 'execute', return_value=mgrs) as m: @@ -264,7 +226,7 @@ class TestManagerCommand(base.TestCase): # Check the executed call to "manager start ManagerA" m.assert_called_once_with( "/synergy/start", - {"manager": ["ManagerA"]}) + {"manager": "ManagerA"}) # Check the data when we call tabulate headers = ["manager", "status", "rate (min)"] @@ -275,52 +237,12 @@ class TestManagerCommand(base.TestCase): tablefmt="fancy_grid") @mock.patch('synergy.client.command.tabulate') - def test_execute_start_two_managers(self, mock_tabulate): - """Check the CLI output of "manager start ManagerA ManagerB".""" - # Mock the parser call - mock_parser = mock.Mock() - mock_parser.args.command = "start" - mock_parser.args.manager = ["ManagerA", "ManagerB"] - - # Mock 2 managers - manager_a = mock.Mock() - manager_a.getName.return_value = "ManagerA" - manager_a.getStatus.return_value = "RUNNING" - manager_a.get.return_value = "started successfully" - manager_a.getRate.return_value = 1 - manager_b = mock.Mock() - manager_b.getName.return_value = "ManagerB" - manager_b.getStatus.return_value = "RUNNING" - manager_b.get.return_value = "started successfully" - manager_b.getRate.return_value = 4 - mgrs = [manager_a, manager_b] - - # Execute "manager start ManagerA ManagerB" - with mock.patch.object(HTTPCommand, 'execute', return_value=mgrs) as m: - self.manager_command.execute(synergy_url='', args=mock_parser.args) - - # Check the executed call to "manager start ManagerA ManagerB" - m.assert_called_once_with( - "/synergy/start", - {"manager": ["ManagerA", "ManagerB"]}) - - # Check the data when we call tabulate - headers = ["manager", "status", "rate (min)"] - table = [ - ["ManagerA", "RUNNING (started successfully)", 1], - ["ManagerB", "RUNNING (started successfully)", 4]] - mock_tabulate.assert_called_once_with( - table, - headers, - tablefmt="fancy_grid") - - @mock.patch('synergy.client.command.tabulate') - def test_execute_stop_one_manager(self, mock_tabulate): + def test_execute_stop_manager(self, mock_tabulate): """Check the CLI output of "manager stop ManagerA".""" # Mock the parser call mock_parser = mock.Mock() mock_parser.args.command = "stop" - mock_parser.args.manager = ["ManagerA"] + mock_parser.args.manager = "ManagerA" # Mock a manager manager_a = mock.Mock() @@ -328,7 +250,7 @@ class TestManagerCommand(base.TestCase): manager_a.getStatus.return_value = "ACTIVE" manager_a.get.return_value = "stopped successfully" manager_a.getRate.return_value = 1 - mgrs = [manager_a] + mgrs = manager_a # Execute "manager stop ManagerA" with mock.patch.object(HTTPCommand, 'execute', return_value=mgrs) as m: @@ -337,7 +259,7 @@ class TestManagerCommand(base.TestCase): # Check the executed call to "manager stop ManagerA" m.assert_called_once_with( "/synergy/stop", - {"manager": ["ManagerA"]}) + {"manager": "ManagerA"}) # Check the data when we call tabulate headers = ["manager", "status", "rate (min)"] @@ -346,43 +268,3 @@ class TestManagerCommand(base.TestCase): table, headers, tablefmt="fancy_grid") - - @mock.patch('synergy.client.command.tabulate') - def test_execute_stop_two_managers(self, mock_tabulate): - """Check the CLI output of "manager stop ManagerA ManagerB".""" - # Mock the parser call - mock_parser = mock.Mock() - mock_parser.args.command = "stop" - mock_parser.args.manager = ["ManagerA", "ManagerB"] - - # Mock 2 managers - manager_a = mock.Mock() - manager_a.getName.return_value = "ManagerA" - manager_a.getStatus.return_value = "ACTIVE" - manager_a.get.return_value = "stopped successfully" - manager_a.getRate.return_value = 1 - manager_b = mock.Mock() - manager_b.getName.return_value = "ManagerB" - manager_b.getStatus.return_value = "ACTIVE" - manager_b.get.return_value = "stopped successfully" - manager_b.getRate.return_value = 4 - mgrs = [manager_a, manager_b] - - # Execute "manager stop ManagerA ManagerB" - with mock.patch.object(HTTPCommand, 'execute', return_value=mgrs) as m: - self.manager_command.execute(synergy_url='', args=mock_parser.args) - - # Check the executed call to "manager start ManagerA ManagerB" - m.assert_called_once_with( - "/synergy/stop", - {"manager": ["ManagerA", "ManagerB"]}) - - # Check the data when we call tabulate - headers = ["manager", "status", "rate (min)"] - table = [ - ["ManagerA", "ACTIVE (stopped successfully)", 1], - ["ManagerB", "ACTIVE (stopped successfully)", 4]] - mock_tabulate.assert_called_once_with( - table, - headers, - tablefmt="fancy_grid")