From 6eae8a0fbb154e48699c595a265afc576d9eb1ec Mon Sep 17 00:00:00 2001 From: David Ames Date: Wed, 11 Apr 2018 16:28:55 -0700 Subject: [PATCH] Use libjuju unit.run for juju_run Use libjuju for juju run commands Add unit test for model.run_on_unit --- unit_tests/test_zaza_model.py | 31 ++++++++++++++++++++++++++++++ zaza/model.py | 33 ++++++++++++++++++++++++-------- zaza/utilities/_local_utils.py | 35 +++++++++++++++------------------- 3 files changed, 71 insertions(+), 28 deletions(-) diff --git a/unit_tests/test_zaza_model.py b/unit_tests/test_zaza_model.py index 012a55e..4333443 100644 --- a/unit_tests/test_zaza_model.py +++ b/unit_tests/test_zaza_model.py @@ -12,6 +12,25 @@ class TestModel(ut_utils.BaseTestCase): async def _scp_to(source, destination, user, proxy, scp_opts): return + + async def _run(command, timeout=None): + return self.action + + self.action = mock.MagicMock() + self.action.data = { + 'model-uuid': '1a035018-71ff-473e-8aab-d1a8d6b6cda7', + 'id': 'e26ffb69-6626-4e93-8840-07f7e041e99d', + 'receiver': 'glance/0', + 'name': 'juju-run', + 'parameters': { + 'command': 'somecommand someargument', 'timeout': 0}, + 'status': 'completed', + 'message': '', + 'results': {'Code': '0', 'Stderr': '', 'Stdout': 'RESULT'}, + 'enqueued': '2018-04-11T23:13:42Z', + 'started': '2018-04-11T23:13:42Z', + 'completed': '2018-04-11T23:13:43Z'} + self.unit1 = mock.MagicMock() self.unit1.public_address = 'ip1' self.unit1.name = 'app/2' @@ -22,6 +41,7 @@ class TestModel(ut_utils.BaseTestCase): self.unit2.name = 'app/4' self.unit2.entity_id = 'app/4' self.unit2.machine = 'machine7' + self.unit1.run.side_effect = _run self.unit1.scp_to.side_effect = _scp_to self.unit2.scp_to.side_effect = _scp_to self.units = [self.unit1, self.unit2] @@ -144,3 +164,14 @@ class TestModel(ut_utils.BaseTestCase): self.patch_object(model, 'get_units') self.get_units.return_value = self.units self.assertEqual(model.get_app_ips('model', 'app'), ['ip1', 'ip2']) + + def test_run_on_unit(self): + expected = {'Code': '0', 'Stderr': '', 'Stdout': 'RESULT'} + self.cmd = cmd = 'somecommand someargument' + self.patch_object(model, 'Model') + self.patch_object(model, 'get_unit_from_name') + self.get_unit_from_name.return_value = self.unit1 + self.Model.return_value = self.Model_mock + self.assertEqual(model.run_on_unit('app/2', 'modelname', cmd), + expected) + self.unit1.run.assert_called_once_with(cmd, timeout=None) diff --git a/zaza/model.py b/zaza/model.py index 4a38ae6..bae8e0a 100644 --- a/zaza/model.py +++ b/zaza/model.py @@ -178,23 +178,40 @@ def scp_from_unit(unit_name, model_name, source, destination, user='ubuntu', run_in_model(model_name, scp_func, add_model_arg=True, awaitable=True)) -def run_on_unit(unit, model_name, command): +def run_on_unit(unit_name, model_name, command, timeout=None): """Juju run on unit - :param unit: Unit object - :type unit: object + :param unit_name: Name of unit to match + :type unit: str :param model_name: Name of model unit is in :type model_name: str :param command: Command to execute :type command: str + :param timeout: DISABLED due to Issue #225 + https://github.com/juju/python-libjuju/issues/225 + :type timeout: int + :returns: action.data['results'] {'Code': '', 'Stderr': '', 'Stdout': ''} + :rtype: dict """ - async def _run_on_unit(unit, command): - return await unit.run(command) + + # Disabling timeout due to Issue #225 + # https://github.com/juju/python-libjuju/issues/225 + if timeout: + timeout = None + + async def _run_on_unit(unit_name, command, model, timeout=None): + unit = get_unit_from_name(unit_name, model) + action = await unit.run(command, timeout=timeout) + if action.data.get('results'): + return action.data.get('results') + else: + return {} run_func = functools.partial( _run_on_unit, - unit, - command) - loop.run( + unit_name, + command, + timeout=timeout) + return loop.run( run_in_model(model_name, run_func, add_model_arg=True, awaitable=True)) diff --git a/zaza/utilities/_local_utils.py b/zaza/utilities/_local_utils.py index bddb444..3a4a1b6 100644 --- a/zaza/utilities/_local_utils.py +++ b/zaza/utilities/_local_utils.py @@ -156,7 +156,7 @@ def parse_arg(options, arg, multiargs=False): return getattr(options, arg) -def remote_run(unit, remote_cmd=None, timeout=None, fatal=None): +def remote_run(unit, remote_cmd, timeout=None, fatal=None): """Run command on unit and return the output NOTE: This function is pre-deprecated. As soon as libjuju unit.run is able @@ -165,31 +165,26 @@ def remote_run(unit, remote_cmd=None, timeout=None, fatal=None): :param remote_cmd: Command to execute on unit :type remote_cmd: string :param timeout: Timeout value for the command - :type arg: string + :type arg: int :param fatal: Command failure condidered fatal or not :type fatal: boolean :returns: Juju run output :rtype: string """ - - logging.warn("Deprecate as soon as possible. Use model.run_on_unit() as " - "soon as libjuju unit.run returns output.") if fatal is None: fatal = True - cmd = ['juju', 'run', '--unit', unit] - if timeout: - cmd.extend(['--timeout', str(timeout)]) - if remote_cmd: - cmd.append(remote_cmd) - else: - cmd.append('uname -a') - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) - output = p.communicate() - if six.PY3: - output = (output[0].decode('utf-8'), output[1]) - if p.returncode != 0 and fatal: - raise Exception('Error running remote command') - return output + result = model.run_on_unit(unit, + lifecycle_utils.get_juju_model(), + remote_cmd, + timeout=timeout) + if result: + if int(result.get('Code')) == 0: + return result.get('Stdout') + else: + if fatal: + raise Exception('Error running remote command: {}' + .format(result.get('Stderr'))) + return result.get('Stderr') def get_pkg_version(application, pkg): @@ -209,7 +204,7 @@ def get_pkg_version(application, pkg): for unit in units: cmd = 'dpkg -l | grep {}'.format(pkg) out = remote_run(unit.entity_id, cmd) - versions.append(out[0].split()[2]) + versions.append(out.split('\n')[0].split()[2]) if len(set(versions)) != 1: raise Exception('Unexpected output from pkg version check') return versions[0]