Use exit codes enum

We have much more, than 2 possible exit codes ant
it's difficult to remember all of it's, so switch to IntEnum (int compatible operations)

Change-Id: I3f3732ba407f20d8397dc385e53519d868afb82f
This commit is contained in:
Alexey Stepanov 2016-08-11 10:05:53 +03:00
parent 0ef3f35079
commit 6ea7050191
8 changed files with 183 additions and 26 deletions

View File

@ -21,6 +21,7 @@ from yaml import safe_load
from devops.error import DevopsError
from devops.error import DevopsNotImplementedError
from devops.helpers.proc_enums import ExitCodes
from devops import logger
@ -42,20 +43,23 @@ class ExecResult(object):
'__lock'
]
def __init__(self, cmd, stdout=None, stderr=None, exit_code=-1):
def __init__(self, cmd, stdout=None, stderr=None,
exit_code=ExitCodes.EX_INVALID):
"""Command execution result read from fifo
:type cmd: str
:type stdout: list
:type stderr: list
:type exit_code: int
:type exit_code: ExitCodes
"""
self.__lock = RLock()
self.__cmd = cmd
self.__stdout = stdout if stdout is not None else []
self.__stderr = stderr if stderr is not None else []
self.__exit_code = exit_code
self.__exit_code = None
self.exit_code = exit_code
# By default is none:
self.__stdout_str = None
@ -217,9 +221,12 @@ class ExecResult(object):
:type new_val: int
"""
if not isinstance(new_val, int):
if not isinstance(new_val, (int, ExitCodes)):
raise TypeError('Exit code is strictly int')
with self.lock:
if isinstance(new_val, int) and \
new_val in ExitCodes.__members__.values():
new_val = ExitCodes(new_val)
self.__exit_code = new_val
def __deserialize(self, fmt):
@ -313,7 +320,7 @@ class ExecResult(object):
def __repr__(self):
return (
'{cls}(cmd={cmd}, stdout={stdout}, stderr={stderr}, '
'exit_code={exit_code})'.format(
'exit_code={exit_code!s})'.format(
cls=self.__class__.__name__,
cmd=self.cmd,
stdout=self.stdout,
@ -326,7 +333,7 @@ class ExecResult(object):
"{cls}(\n\tcmd={cmd},"
"\n\t stdout=\n'{stdout_brief}',"
"\n\tstderr=\n'{stderr_brief}', "
'\n\texit_code={exit_code}\n)'.format(
'\n\texit_code={exit_code!s}\n)'.format(
cls=self.__class__.__name__,
cmd=self.cmd,
stdout_brief=self.stdout_brief,

View File

@ -0,0 +1,125 @@
# Copyright 2016 Mirantis, Inc.
#
# 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.
from enum import IntEnum
from enum import unique
@unique
class SigNum(IntEnum):
SIGHUP = 1 # Hangup (POSIX).
SIGINT = 2 # Interrupt (ANSI).
SIGQUIT = 3 # Quit (POSIX).
SIGILL = 4 # Illegal instruction (ANSI).
SIGTRAP = 5 # Trace trap (POSIX).
SIGABRT = 6 # Abort (ANSI).
SIGBUS = 7 # BUS error (4.2 BSD).
SIGFPE = 8 # Floating-point exception (ANSI).
SIGKILL = 9 # Kill, unblockable (POSIX).
SIGUSR1 = 10 # User-defined signal 1 (POSIX).
SIGSEGV = 11 # Segmentation violation (ANSI).
SIGUSR2 = 12 # User-defined signal 2 (POSIX).
SIGPIPE = 13 # Broken pipe (POSIX).
SIGALRM = 14 # Alarm clock (POSIX).
SIGTERM = 15 # Termination (ANSI).
SIGSTKFLT = 16 # Stack fault.
SIGCHLD = 17 # Child status has changed (POSIX).
SIGCONT = 18 # Continue (POSIX).
SIGSTOP = 19 # Stop, unblockable (POSIX).
SIGTSTP = 20 # Keyboard stop (POSIX).
SIGTTIN = 21 # Background read from tty (POSIX).
SIGTTOU = 22 # Background write to tty (POSIX).
SIGURG = 23 # Urgent condition on socket (4.2 BSD).
SIGXCPU = 24 # CPU limit exceeded (4.2 BSD).
SIGXFSZ = 25 # File size limit exceeded (4.2 BSD).
SIGVTALRM = 26 # Virtual alarm clock (4.2 BSD).
SIGPROF = 27 # Profiling alarm clock (4.2 BSD).
SIGWINCH = 28 # Window size change (4.3 BSD, Sun).
SIGPOLL = 29 # Pollable event occurred (System V)
SIGPWR = 30 # Power failure restart (System V).
SIGSYS = 31 # Bad system call.
def __str__(self):
return "{name}<{value:d}(0x{value:02X})>".format(
name=self.name,
value=self.value
)
@unique
class ExitCodes(IntEnum):
EX_OK = 0 # successful termination
EX_INVALID = 0xDEADBEEF # uint32 debug value. Impossible for POSIX
EX_ERROR = 1 # general failure
EX_BUILTIN = 2 # Misuse of shell builtins (according to Bash)
EX_USAGE = 64 # command line usage error
EX_DATAERR = 65 # data format error
EX_NOINPUT = 66 # cannot open input
EX_NOUSER = 67 # addressee unknown
EX_NOHOST = 68 # host name unknown
EX_UNAVAILABLE = 69 # service unavailable
EX_SOFTWARE = 70 # internal software error
EX_OSERR = 71 # system error (e.g., can't fork)
EX_OSFILE = 72 # critical OS file missing
EX_CANTCREAT = 73 # can't create (user) output file
EX_IOERR = 74 # input/output error
EX_TEMPFAIL = 75 # temp failure; user is invited to retry
EX_PROTOCOL = 76 # remote error in protocol
EX_NOPERM = 77 # permission denied
EX_CONFIG = 78 # configuration error
EX_NOEXEC = 126 # If a command is found but is not executable
EX_NOCMD = 127 # If a command is not found
# Signal exits:
EX_SIGHUP = 128 + SigNum.SIGHUP
EX_SIGINT = 128 + SigNum.SIGINT
EX_SIGQUIT = 128 + SigNum.SIGQUIT
EX_SIGILL = 128 + SigNum.SIGILL
EX_SIGTRAP = 128 + SigNum.SIGTRAP
EX_SIGABRT = 128 + SigNum.SIGABRT
EX_SIGBUS = 128 + SigNum.SIGBUS
EX_SIGFPE = 128 + SigNum.SIGFPE
EX_SIGKILL = 128 + SigNum.SIGKILL
EX_SIGUSR1 = 128 + SigNum.SIGUSR1
EX_SIGSEGV = 128 + SigNum.SIGSEGV
EX_SIGUSR2 = 128 + SigNum.SIGUSR2
EX_SIGPIPE = 128 + SigNum.SIGPIPE
EX_SIGALRM = 128 + SigNum.SIGALRM
EX_SIGTERM = 128 + SigNum.SIGTERM
EX_SIGSTKFLT = 128 + SigNum.SIGSTKFLT
EX_SIGCHLD = 128 + SigNum.SIGCHLD
EX_SIGCONT = 128 + SigNum.SIGCONT
EX_SIGSTOP = 128 + SigNum.SIGSTOP
EX_SIGTSTP = 128 + SigNum.SIGTSTP
EX_SIGTTIN = 128 + SigNum.SIGTTIN
EX_SIGTTOU = 128 + SigNum.SIGTTOU
EX_SIGURG = 128 + SigNum.SIGURG
EX_SIGXCPU = 128 + SigNum.SIGXCPU
EX_SIGXFSZ = 128 + SigNum.SIGXFSZ
EX_SIGVTALRM = 128 + SigNum.SIGVTALRM
EX_SIGPROF = 128 + SigNum.SIGPROF
EX_SIGWINCH = 128 + SigNum.SIGWINCH
EX_SIGPOLL = 128 + SigNum.SIGPOLL
EX_SIGPWR = 128 + SigNum.SIGPWR
EX_SIGSYS = 128 + SigNum.SIGSYS
def __str__(self):
return "{name}<{value:d}(0x{value:02X})>".format(
name=self.name,
value=self.value
)

View File

@ -28,6 +28,7 @@ import six
from devops.error import DevopsCalledProcessError
from devops.error import TimeoutError
from devops.helpers.exec_result import ExecResult
from devops.helpers.proc_enums import ExitCodes
from devops.helpers.retry import retry
from devops import logger
@ -567,12 +568,21 @@ class SSHClient(six.with_metaclass(_MemorizedSSH, object)):
:raises: DevopsCalledProcessError
"""
if expected is None:
expected = [0]
expected = [ExitCodes.EX_OK]
else:
expected = [
ExitCodes(code)
if (
isinstance(code, int) and
code in ExitCodes.__members__.values())
else code
for code in expected
]
ret = self.execute(command, verbose, timeout)
if ret['exit_code'] not in expected:
message = (
"{append}Command '{cmd}' returned exit code {code} while "
"expected {expected}\n"
"{append}Command '{cmd}' returned exit code {code!s} while "
"expected {expected!s}\n"
"\tSTDOUT:\n"
"{stdout}"
"\n\tSTDERR:\n"
@ -614,7 +624,7 @@ class SSHClient(six.with_metaclass(_MemorizedSSH, object)):
if ret['stderr']:
message = (
"{append}Command '{cmd}' STDERR while not expected\n"
"\texit code: {code}\n"
"\texit code: {code!s}\n"
"\tSTDOUT:\n"
"{stdout}"
"\n\tSTDERR:\n"
@ -721,7 +731,7 @@ class SSHClient(six.with_metaclass(_MemorizedSSH, object)):
if verbose:
logger.info(
'{cmd} execution results:\n'
'Exit code: {code}\n'
'Exit code: {code!s}\n'
'STDOUT:\n'
'{stdout}\n'
'STDERR:\n'

View File

@ -27,6 +27,7 @@ from devops.error import TimeoutError
from devops.helpers.decorators import threaded
from devops.helpers.exec_result import ExecResult
from devops.helpers.metaclasses import SingletonMeta
from devops.helpers.proc_enums import ExitCodes
from devops import logger
@ -144,7 +145,7 @@ class Subprocess(with_metaclass(SingletonMeta, object)):
if verbose:
logger.info(
'{cmd} execution results:\n'
'Exit code: {code}\n'
'Exit code: {code!s}\n'
'STDOUT:\n'
'{stdout}\n'
'STDERR:\n'
@ -185,12 +186,21 @@ class Subprocess(with_metaclass(SingletonMeta, object)):
"""
if expected is None:
expected = [0]
expected = [ExitCodes.EX_OK]
else:
expected = [
ExitCodes(code)
if (
isinstance(code, int) and
code in ExitCodes.__members__.values())
else code
for code in expected
]
ret = cls.execute(command, verbose, timeout)
if ret['exit_code'] not in expected:
message = (
"{append}Command '{cmd}' returned exit code {code} while "
"expected {expected}\n"
"{append}Command '{cmd}' returned exit code {code!s} while "
"expected {expected!s}\n"
"\tSTDOUT:\n"
"{stdout}"
"\n\tSTDERR:\n"
@ -235,7 +245,7 @@ class Subprocess(with_metaclass(SingletonMeta, object)):
if ret['stderr']:
message = (
"{append}Command '{cmd}' STDERR while not expected\n"
"\texit code: {code}\n"
"\texit code: {code!s}\n"
"\tSTDOUT:\n"
"{stdout}"
"\n\tSTDERR:\n"

View File

@ -23,6 +23,7 @@ import mock
from devops.helpers.exec_result import DevopsError
from devops.helpers.exec_result import DevopsNotImplementedError
from devops.helpers.exec_result import ExecResult
from devops.helpers.proc_enums import ExitCodes
cmd = 'ls -la'
@ -48,17 +49,17 @@ class TestExecResult(TestCase):
self.assertEqual(exec_result.stdout_brief, exec_result['stdout_brief'])
self.assertEqual(exec_result.stderr_brief, '')
self.assertEqual(exec_result.stderr_brief, exec_result['stderr_brief'])
self.assertEqual(exec_result.exit_code, -1)
self.assertEqual(exec_result.exit_code, ExitCodes.EX_INVALID)
self.assertEqual(exec_result.exit_code, exec_result['exit_code'])
self.assertEqual(
repr(exec_result),
'{cls}(cmd={cmd}, stdout={stdout}, stderr={stderr}, '
'exit_code={exit_code})'.format(
'exit_code={exit_code!s})'.format(
cls=ExecResult.__name__,
cmd=cmd,
stdout=[],
stderr=[],
exit_code=-1
exit_code=ExitCodes.EX_INVALID
)
)
self.assertEqual(
@ -66,12 +67,12 @@ class TestExecResult(TestCase):
"{cls}(\n\tcmd={cmd},"
"\n\t stdout=\n'{stdout_brief}',"
"\n\tstderr=\n'{stderr_brief}', "
'\n\texit_code={exit_code}\n)'.format(
'\n\texit_code={exit_code!s}\n)'.format(
cls=ExecResult.__name__,
cmd=cmd,
stdout_brief='',
stderr_brief='',
exit_code=-1
exit_code=ExitCodes.EX_INVALID
)
)
@ -91,7 +92,7 @@ class TestExecResult(TestCase):
self.assertEqual(
hash(exec_result),
hash((ExecResult, cmd, '', '', -1))
hash((ExecResult, cmd, '', '', ExitCodes.EX_INVALID))
)
self.assertEqual(exec_result.stdout_len, 0)
@ -112,7 +113,7 @@ class TestExecResult(TestCase):
def test_setters(self):
exec_result = ExecResult(cmd=cmd)
self.assertEqual(exec_result.exit_code, -1)
self.assertEqual(exec_result.exit_code, ExitCodes.EX_INVALID)
exec_result.exit_code = 0
self.assertEqual(exec_result.exit_code, 0)
self.assertEqual(exec_result.exit_code, exec_result['exit_code'])

View File

@ -1120,7 +1120,7 @@ class TestExecute(TestCase):
logger.assert_has_calls((
mock.call.info(
'{cmd} execution results:\n'
'Exit code: {code}\n'
'Exit code: {code!s}\n'
'STDOUT:\n'
'{stdout}\n'
'STDERR:\n'
@ -1173,7 +1173,7 @@ class TestExecute(TestCase):
logger.assert_has_calls((
mock.call.info(
'{cmd} execution results:\n'
'Exit code: {code}\n'
'Exit code: {code!s}\n'
'STDOUT:\n'
'{stdout}\n'
'STDERR:\n'

View File

@ -120,7 +120,7 @@ class TestSubprocessRunner(TestCase):
call.debug("Executing command: '{}'".format(command.rstrip())),
call.info(
'{cmd} execution results:\n'
'Exit code: {code}\n'
'Exit code: {code!s}\n'
'STDOUT:\n'
'{stdout}\n'
'STDERR:\n'

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from sys import version_info
from setuptools import find_packages
from setuptools import setup
@ -37,6 +39,7 @@ setup(
'bin/dos_check_db.sh',
],
data_files=[('bin', ['bin/dos_functions.sh'])],
# Use magic in install_requires due to risk of old setuptools
install_requires=[
'keystoneauth1>=2.1.0',
'netaddr>=0.7.12,!=0.7.16',
@ -48,6 +51,7 @@ setup(
'tabulate',
'six>=1.9.0',
'python-dateutil>=2.4.2',
'enum34' if version_info.major == 2 else ''
],
tests_require=[
'pytest>=2.7.1',