Move utils.execute to its own module.
Also move the exceptions raised to being local to the executils module. Change-Id: Ibb2445f92840b9ce4b52373b0b09adb3d6a4a897
This commit is contained in:
parent
30f4089461
commit
bdfffca2cb
|
@ -0,0 +1,124 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
System-level utilities and helper functions.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import random
|
||||
import shlex
|
||||
|
||||
from eventlet.green import subprocess
|
||||
from eventlet import greenthread
|
||||
|
||||
from openstack.common.gettextutils import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UnknownArgumentError(Exception):
|
||||
def __init__(self, message=None):
|
||||
super(UnknownArgumentError, self).__init__(message)
|
||||
|
||||
|
||||
class ProcessExecutionError(Exception):
|
||||
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
|
||||
description=None):
|
||||
if description is None:
|
||||
description = "Unexpected error while running command."
|
||||
if exit_code is None:
|
||||
exit_code = '-'
|
||||
message = ("%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r"
|
||||
% (description, cmd, exit_code, stdout, stderr))
|
||||
super(ProcessExecutionError, self).__init__(message)
|
||||
|
||||
|
||||
def execute(*cmd, **kwargs):
|
||||
"""
|
||||
Helper method to execute command with optional retry.
|
||||
|
||||
:cmd Passed to subprocess.Popen.
|
||||
:process_input Send to opened process.
|
||||
:check_exit_code Defaults to 0. Raise executils.ProcessExecutionError
|
||||
unless program exits with this code.
|
||||
:delay_on_retry True | False. Defaults to True. If set to True, wait a
|
||||
short amount of time before retrying.
|
||||
:attempts How many times to retry cmd.
|
||||
:run_as_root True | False. Defaults to False. If set to True,
|
||||
the command is prefixed by the command specified
|
||||
in the root_helper kwarg.
|
||||
:root_helper command to prefix all cmd's with
|
||||
|
||||
:raises executils.UnknownArgumentError on receiving unknown arguments
|
||||
:raises executils.ProcessExecutionError
|
||||
"""
|
||||
|
||||
process_input = kwargs.pop('process_input', None)
|
||||
check_exit_code = kwargs.pop('check_exit_code', 0)
|
||||
delay_on_retry = kwargs.pop('delay_on_retry', True)
|
||||
attempts = kwargs.pop('attempts', 1)
|
||||
run_as_root = kwargs.pop('run_as_root', False)
|
||||
root_helper = kwargs.pop('root_helper', '')
|
||||
if len(kwargs):
|
||||
raise UnknownArgumentError(_('Got unknown keyword args '
|
||||
'to utils.execute: %r') % kwargs)
|
||||
if run_as_root:
|
||||
cmd = shlex.split(root_helper) + list(cmd)
|
||||
cmd = map(str, cmd)
|
||||
|
||||
while attempts > 0:
|
||||
attempts -= 1
|
||||
try:
|
||||
LOG.debug(_('Running cmd (subprocess): %s'), ' '.join(cmd))
|
||||
_PIPE = subprocess.PIPE # pylint: disable=E1101
|
||||
obj = subprocess.Popen(cmd,
|
||||
stdin=_PIPE,
|
||||
stdout=_PIPE,
|
||||
stderr=_PIPE,
|
||||
close_fds=True)
|
||||
result = None
|
||||
if process_input is not None:
|
||||
result = obj.communicate(process_input)
|
||||
else:
|
||||
result = obj.communicate()
|
||||
obj.stdin.close() # pylint: disable=E1101
|
||||
_returncode = obj.returncode # pylint: disable=E1101
|
||||
if _returncode:
|
||||
LOG.debug(_('Result was %s') % _returncode)
|
||||
if (isinstance(check_exit_code, int) and
|
||||
not isinstance(check_exit_code, bool) and
|
||||
_returncode != check_exit_code):
|
||||
(stdout, stderr) = result
|
||||
raise ProcessExecutionError(exit_code=_returncode,
|
||||
stdout=stdout,
|
||||
stderr=stderr,
|
||||
cmd=' '.join(cmd))
|
||||
return result
|
||||
except ProcessExecutionError:
|
||||
if not attempts:
|
||||
raise
|
||||
else:
|
||||
LOG.debug(_('%r failed. Retrying.'), cmd)
|
||||
if delay_on_retry:
|
||||
greenthread.sleep(random.randint(20, 200) / 100.0)
|
||||
finally:
|
||||
# NOTE(termie): this appears to be necessary to let the subprocess
|
||||
# call clean something up in between calls, without
|
||||
# it two execute calls in a row hangs the second one
|
||||
greenthread.sleep(0)
|
|
@ -0,0 +1,84 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 unittest
|
||||
|
||||
import mock
|
||||
|
||||
from openstack.common import processutils
|
||||
|
||||
|
||||
class UtilsTest(unittest.TestCase):
|
||||
# NOTE(jkoelker) Moar tests from nova need to be ported. But they
|
||||
# need to be mock'd out. Currently they requre actually
|
||||
# running code.
|
||||
def test_execute_unknown_kwargs(self):
|
||||
self.assertRaises(processutils.UnknownArgumentError,
|
||||
processutils.execute,
|
||||
hozer=True)
|
||||
|
||||
|
||||
class ProcessExecutionErrorTest(unittest.TestCase):
|
||||
|
||||
def test_defaults(self):
|
||||
err = processutils.ProcessExecutionError()
|
||||
self.assertTrue('None\n' in err.message)
|
||||
self.assertTrue('code: -\n' in err.message)
|
||||
|
||||
def test_with_description(self):
|
||||
description = 'The Narwhal Bacons at Midnight'
|
||||
err = processutils.ProcessExecutionError(description=description)
|
||||
self.assertTrue(description in err.message)
|
||||
|
||||
def test_with_exit_code(self):
|
||||
exit_code = 0
|
||||
err = processutils.ProcessExecutionError(exit_code=exit_code)
|
||||
self.assertTrue(str(exit_code) in err.message)
|
||||
|
||||
def test_with_cmd(self):
|
||||
cmd = 'telinit'
|
||||
err = processutils.ProcessExecutionError(cmd=cmd)
|
||||
self.assertTrue(cmd in err.message)
|
||||
|
||||
def test_with_stdout(self):
|
||||
stdout = """
|
||||
Lo, praise of the prowess of people-kings
|
||||
of spear-armed Danes, in days long sped,
|
||||
we have heard, and what honot the athelings won!
|
||||
Oft Scyld the Scefing from squadroned foes,
|
||||
from many a tribe, the mead-bench tore,
|
||||
awing the earls. Since erse he lay
|
||||
friendless, a foundling, fate repaid him:
|
||||
for he waxed under welkin, in wealth he trove,
|
||||
till before him the folk, both far and near,
|
||||
who house by the whale-path, heard his mandate,
|
||||
gabe him gits: a good king he!
|
||||
To him an heir was afterward born,
|
||||
a son in his halls, whom heaven sent
|
||||
to favor the fol, feeling their woe
|
||||
that erst they had lacked an earl for leader
|
||||
so long a while; the Lord endowed him,
|
||||
the Wielder of Wonder, with world's renown.
|
||||
""".strip()
|
||||
err = processutils.ProcessExecutionError(stdout=stdout)
|
||||
print err.message
|
||||
self.assertTrue('people-kings' in err.message)
|
||||
|
||||
def test_with_stderr(self):
|
||||
stderr = 'Cottonian library'
|
||||
err = processutils.ProcessExecutionError(stderr=stderr)
|
||||
self.assertTrue(stderr in str(err.message))
|
Loading…
Reference in New Issue