From a633b48299967efa9356682861b4f810f817bd85 Mon Sep 17 00:00:00 2001 From: nir0s Date: Mon, 25 Apr 2016 13:24:23 +0300 Subject: [PATCH] add-missing-env-var-and-limit-handling --- .travis.yml | 1 - serv/init/base.py | 38 +++--- serv/init/systemd.py | 20 +-- serv/init/sysv.py | 27 ++-- serv/init/templates/supervisor_default.conf | 129 -------------------- serv/init/templates/systemd_default.service | 20 +++ serv/init/upstart.py | 20 +-- serv/serv.py | 64 +++++----- serv/tests/test_serv.py | 116 +++++++++++++----- setup.py | 9 +- 10 files changed, 210 insertions(+), 234 deletions(-) delete mode 100644 serv/init/templates/supervisor_default.conf diff --git a/.travis.yml b/.travis.yml index 1bfa428..a420bbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,3 @@ install: script: - tox -e $TOX_ENV - sudo tox -e deploy - diff --git a/serv/init/base.py b/serv/init/base.py index d83ab67..50333c9 100644 --- a/serv/init/base.py +++ b/serv/init/base.py @@ -38,12 +38,13 @@ def __init__(self, lgr=None, **params): self._validate_service_params() def _set_default_parameter_values(self): - p = self.params - p['description'] = p.get('description', 'no description given') - p['chdir'] = p.get('chdir', '/') - p['chroot'] = p.get('chroot', '/') - p['user'] = p.get('user', 'root') - p['group'] = p.get('group', 'root') + params = self.params + params['description'] = params.get( + 'description', 'no description given') + params['chdir'] = params.get('chdir', '/') + params['chroot'] = params.get('chroot', '/') + params['user'] = params.get('user', 'root') + params['group'] = params.get('group', 'root') def _validate_service_params(self): niceness = self.params.get('nice') @@ -63,21 +64,21 @@ def _validate_service_params(self): 'limit_stack_size', ] - def _raise_limit_error(): + def _raise_limit_error(limit_type, limit): self.lgr.error('All limits must be integers greater than 0 or ' 'ulimited. You provided a {0} with value ' - '{1}.'.format('limit_coredump', limit)) + '{1}.'.format(limit_type, limit)) sys.exit(1) - for l in limit_params: - limit = self.params.get(l) + for limit_type in limit_params: + limit = self.params.get(limit_type) if limit not in (None, 'ulimited'): try: value = int(limit) except (ValueError, TypeError): - _raise_limit_error() + _raise_limit_error(limit_type, limit) if value < 1: - _raise_limit_error() + _raise_limit_error(limit_type, limit) def generate(self, overwrite): """Generates service files. @@ -234,13 +235,12 @@ def deploy_service_file(self, source, destination, create_directory=False): def generate_service_files(self): files = [] - for s in const.TEMPLATES[self.init_sys][self.init_sys_ver].keys(): - # remove j2 suffix and then, for instance for: - # systemd['default']['service'] - pfx = '_'.join([self.init_sys, self.init_sys_ver]) - sfx = s or '' - template = pfx + sfx - self.destination = os.path.join(self.tmp, self.name + sfx) + for file_type in \ + const.TEMPLATES[self.init_sys][self.init_sys_ver].keys(): + prefix = '_'.join([self.init_sys, self.init_sys_ver]) + suffix = file_type or '' + template = prefix + suffix + self.destination = os.path.join(self.tmp, self.name + suffix) files.append(self.destination) self.generate_file_from_template(template, self.destination) return files diff --git a/serv/init/systemd.py b/serv/init/systemd.py index 982ff52..adcd899 100644 --- a/serv/init/systemd.py +++ b/serv/init/systemd.py @@ -145,16 +145,14 @@ def _parse_service_info(svc): description=svc_info[4] ) - def is_system_exists(self): + @staticmethod + def is_system_exists(): """Returns True if the init system exists and False if not. """ - try: - sh.systemctl('--version') - return True - except: - return False + return is_system_exists() - def get_system_version(self): + @staticmethod + def get_system_version(): """Returns the init system's version if it exists. """ try: @@ -180,3 +178,11 @@ def validate_platform(self): self.lgr.error( 'Cannot install SysVinit service on non-Linux systems.') sys.exit() + + +def is_system_exists(): + try: + sh.systemctl('--version') + return True + except: + return False diff --git a/serv/init/sysv.py b/serv/init/sysv.py index 6188721..7cc2cf6 100644 --- a/serv/init/sysv.py +++ b/serv/init/sysv.py @@ -1,5 +1,6 @@ import os import sys +import subprocess from serv import utils from serv.init.base import Base @@ -42,7 +43,9 @@ def install(self): def start(self): try: - sh.service(self.name, 'start', _bg=True) + subprocess.check_call( + 'service {0} start'.format(self.name), + shell=True, stdout=subprocess.PIPE) except sh.CommandNotFound: # TODO: cleanup generated files if not found. self.lgr.warning('service command unavailable. Trying to run ' @@ -58,7 +61,9 @@ def start(self): def stop(self): try: - sh.service(self.name, 'stop', _bg=True) + subprocess.check_call( + 'service {0} stop'.format(self.name), + shell=True, stdout=subprocess.PIPE) except sh.CommandNotFound: self.lgr.warning('service command unavailable. Trying to run ' 'script directly.') @@ -67,7 +72,7 @@ def stop(self): service.stop(_bg=True) except sh.CommandNotFound as ex: self.lgr.error('Command not found: {0}'.format(str(ex))) - sys.exit() + sys.exit(1) except: self.lgr.info('Service already stopped.') @@ -113,11 +118,12 @@ def _parse_service_info(svc): pid=pid ) - def is_system_exists(self): - # maybe a safer way would be to check if /etc/init.d is not empty. - return os.path.isdir('/etc/init.d') + @staticmethod + def is_system_exists(): + return is_system_exists() - def get_system_version(self): + @staticmethod + def get_system_version(): return 'lsb-3.1' def is_service_exists(self): @@ -156,4 +162,9 @@ def validate_platform(self): if utils.IS_WIN or utils.IS_DARWIN: self.lgr.error( 'Cannot install SysVinit service on non-Linux systems.') - sys.exit() + sys.exit(1) + + +def is_system_exists(): + # TODO: maybe a safer way would be to check if /etc/init.d is not empty. + return os.path.isdir('/etc/init.d') diff --git a/serv/init/templates/supervisor_default.conf b/serv/init/templates/supervisor_default.conf deleted file mode 100644 index 10bbbcb..0000000 --- a/serv/init/templates/supervisor_default.conf +++ /dev/null @@ -1,129 +0,0 @@ -; Sample supervisor config file. - -[unix_http_server] -file=/tmp/supervisor.sock ; (the path to the socket file) -;chmod=0700 ; socket file mode (default 0700) -;chown=nobody:nogroup ; socket file uid:gid owner -;username=user ; (default is no username (open server)) -;password=123 ; (default is no password (open server)) - -;[inet_http_server] ; inet (TCP) server disabled by default -;port=127.0.0.1:9001 ; (ip_address:port specifier, *:port for all iface) -;username=user ; (default is no username (open server)) -;password=123 ; (default is no password (open server)) - -[supervisord] -logfile=/var/log/supervisord.log ; (main log file;default $CWD/supervisord.log) -logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) -logfile_backups=10 ; (num of main logfile rotation backups;default 10) -loglevel=info ; (log level;default info; others: debug,warn,trace) -pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) -nodaemon=false ; (start in foreground if true;default false) -minfds=1024 ; (min. avail startup file descriptors;default 1024) -minprocs=200 ; (min. avail process descriptors;default 200) -;umask=022 ; (process file creation umask;default 022) -;user=chrism ; (default is current user, required if root) -;identifier=supervisor ; (supervisord identifier, default is 'supervisor') -;directory=/tmp ; (default is not to cd during start) -;nocleanup=true ; (don't clean up tempfiles at start;default false) -;childlogdir=/tmp ; ('AUTO' child log dir, default $TEMP) -;environment=KEY=value ; (key value pairs to add to environment) -;strip_ansi=false ; (strip ansi escape codes in logs; def. false) - -; the below section must remain in the config file for RPC -; (supervisorctl/web interface) to work, additional interfaces may be -; added by defining them in separate rpcinterface: sections -[rpcinterface:supervisor] -supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface - -[supervisorctl] -serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket -;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket -;username=chris ; should be same as http_username if set -;password=123 ; should be same as http_password if set -;prompt=mysupervisor ; cmd line prompt (default "supervisor") -;history_file=~/.sc_history ; use readline history if available - -; The below sample program section shows all possible program subsection values, -; create one or more 'real' program: sections to be able to control them under -; supervisor. - -;[program:theprogramname] -;command=/bin/cat ; the program (relative uses PATH, can take args) -;process_name=%(program_name)s ; process_name expr (default %(program_name)s) -;numprocs=1 ; number of processes copies to start (def 1) -;directory=/tmp ; directory to cwd to before exec (def no cwd) -;umask=022 ; umask for process (default None) -;priority=999 ; the relative start priority (default 999) -;autostart=true ; start at supervisord start (default: true) -;autorestart=true ; retstart at unexpected quit (default: true) -;startsecs=10 ; number of secs prog must stay running (def. 1) -;startretries=3 ; max # of serial start failures (default 3) -;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2) -;stopsignal=QUIT ; signal used to kill process (default TERM) -;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) -;user=chrism ; setuid to this UNIX account to run the program -;redirect_stderr=true ; redirect proc stderr to stdout (default false) -;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO -;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) -;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) -;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) -;stdout_events_enabled=false ; emit events on stdout writes (default false) -;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO -;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) -;stderr_logfile_backups=10 ; # of stderr logfile backups (default 10) -;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) -;stderr_events_enabled=false ; emit events on stderr writes (default false) -;environment=A=1,B=2 ; process environment additions (def no adds) -;serverurl=AUTO ; override serverurl computation (childutils) - -; The below sample eventlistener section shows all possible -; eventlistener subsection values, create one or more 'real' -; eventlistener: sections to be able to handle event notifications -; sent by supervisor. - -;[eventlistener:theeventlistenername] -;command=/bin/eventlistener ; the program (relative uses PATH, can take args) -;process_name=%(program_name)s ; process_name expr (default %(program_name)s) -;numprocs=1 ; number of processes copies to start (def 1) -;events=EVENT ; event notif. types to subscribe to (req'd) -;buffer_size=10 ; event buffer queue size (default 10) -;directory=/tmp ; directory to cwd to before exec (def no cwd) -;umask=022 ; umask for process (default None) -;priority=-1 ; the relative start priority (default -1) -;autostart=true ; start at supervisord start (default: true) -;autorestart=unexpected ; restart at unexpected quit (default: unexpected) -;startsecs=10 ; number of secs prog must stay running (def. 1) -;startretries=3 ; max # of serial start failures (default 3) -;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2) -;stopsignal=QUIT ; signal used to kill process (default TERM) -;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) -;user=chrism ; setuid to this UNIX account to run the program -;redirect_stderr=true ; redirect proc stderr to stdout (default false) -;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO -;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) -;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) -;stdout_events_enabled=false ; emit events on stdout writes (default false) -;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO -;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) -;stderr_logfile_backups ; # of stderr logfile backups (default 10) -;stderr_events_enabled=false ; emit events on stderr writes (default false) -;environment=A=1,B=2 ; process environment additions -;serverurl=AUTO ; override serverurl computation (childutils) - -; The below sample group section shows all possible group values, -; create one or more 'real' group: sections to create "heterogeneous" -; process groups. - -;[group:thegroupname] -;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions -;priority=999 ; the relative start priority (default 999) - -; The [include] section can just contain the "files" setting. This -; setting can list multiple files (separated by whitespace or -; newlines). It can also contain wildcards. The filenames are -; interpreted as relative to this file. Included files *cannot* -; include files themselves. - -[include] -files = /etc/supervisor/* diff --git a/serv/init/templates/systemd_default.service b/serv/init/templates/systemd_default.service index 866af57..3ee0b17 100644 --- a/serv/init/templates/systemd_default.service +++ b/serv/init/templates/systemd_default.service @@ -11,5 +11,25 @@ ExecStart={{ cmd }} {{ args }} Restart={{ always or 'restart' }} WorkingDirectory={{ chdir or '/' }} +{% if nice %} +LimitNICE={{ nice }}{% endif %}{% if limit_coredump %} +LimitCORE={{ limit_coredump }}{% endif %}{% if limit_cputime %} +LimitCPU={{ limit_cputime }}{% endif %}{% if limit_data %} +LimitDATA={{ limit_data }}{% endif %}{% if limit_file_size %} +LimitFSIZE={{ limit_file_size }}{% endif %}{% if limit_locked_memory %} +LimitMEMLOCK={{ limit_locked_memory }}{% endif %}{% if limit_open_files %} +LimitNOFILE={{ limit_open_files }}{% endif %}{% if limit_user_processes %} +LimitNPROC={{ limit_user_processes }}{% endif %}{% if limit_physical_memory %} +LimitRSS={{ limit_physical_memory }}{% endif %}{% if limit_stack_size %} +LimitSTACK={{ limit_stack_size }}{% endif %} + +#Unsupported by Serv just yet +#LimitAS= +#LimitLOCKS= +#LimitSIGPENDING= +#LimitMSGQUEUE= +#LimitRTPRIO= +#LimitRTTIME= + [Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/serv/init/upstart.py b/serv/init/upstart.py index 928809f..0c0f52c 100644 --- a/serv/init/upstart.py +++ b/serv/init/upstart.py @@ -79,14 +79,12 @@ def _parse_service_info(svc): pid=pid ) - def is_system_exists(self): - try: - sh.initctl.version() - return True - except: - return False + @staticmethod + def is_system_exists(): + return is_system_exists() - def get_system_version(self): + @staticmethod + def get_system_version(): try: output = sh.initctl.version() except: @@ -104,3 +102,11 @@ def validate_platform(self): self.lgr.error( 'Cannot install SysVinit service on non-Linux systems.') sys.exit() + + +def is_system_exists(): + try: + sh.initctl.version() + return True + except: + return False diff --git a/serv/serv.py b/serv/serv.py index 947c2d4..a2df726 100644 --- a/serv/serv.py +++ b/serv/serv.py @@ -6,6 +6,7 @@ import logging try: + import distro import sh except ImportError: pass @@ -46,19 +47,19 @@ def __init__(self, init_system=None, init_system_version=None, init_sys=self.init_sys, init_sys_ver=self.init_sys_ver) # all implementation objects - imps = self._find_all_implementations() - # lowercase names of all implementations (e.g. [sysv, systemd]) - self.implementations = \ - [i.__name__.lower() for i in imps if i.__name__.lower() != 'base'] + implementations = self._find_all_implementations() + self.implementation_names = \ + [implementation.__name__.lower() for implementation in + implementations if implementation.__name__.lower() != 'base'] - if self.init_sys not in self.implementations: + if self.init_sys not in self.implementation_names: lgr.error('init system {0} not supported'.format(self.init_sys)) sys.exit(1) # a class object which can be instantiated to control # a service. # this is instantiated with the relevant parameters (self.params) # in each scenario. - self.implementation = self._get_init_system(imps) + self.implementation = self._get_init_system(implementations) def _get_init_system(self, init_systems): for system in init_systems: @@ -249,7 +250,7 @@ def lookup(self): return [('launchd', 'default')] lgr.debug('Looking up init method...') return self._lookup_by_mapping() \ - or self._auto_lookup() + or self._init_sys_auto_lookup() # TODO: both this and _get_systemctl_version should be under their # corresponding implementations @@ -279,7 +280,7 @@ def _get_systemctl_version(): return str(version.group()) return None - def _auto_lookup(self): + def _init_sys_auto_lookup(self): """Returns a list of tuples of available init systems on the current machine. @@ -315,19 +316,18 @@ def _lookup_by_mapping(): for Arch where the distro's ID changes (Manjaro, Antergos, etc...) But the "ID_LIKE" field is always (?) `arch`. """ - import distro like = distro.like().lower() distribution_id = distro.id().lower() version = distro.major_version() # init (upstart 1.12.1) if distribution_id in ('arch'): version = 'any' - elif like in ('arch'): + if like in ('arch'): version = 'any' - d = const.DIST_TO_INITSYS.get( + init_sys = const.DIST_TO_INITSYS.get( distribution_id, const.DIST_TO_INITSYS.get(like)) - if d: - return [d.get(version)] or [] + if init_sys: + return [init_sys.get(version)] or [] @click.group() @@ -347,7 +347,7 @@ def main(): @click.option('-s', '--start', default=False, is_flag=True, help='Start the service after deploying it.') @click.option('--init-system', required=False, - type=click.Choice(Serv().implementations), + type=click.Choice(Serv().implementation_names), help='Init system to use. (If omitted, will attempt to ' 'automatically identify it.)') @click.option('--init-system-version', required=False, default='default', @@ -371,36 +371,38 @@ def main(): help='Directory to change to before executing `cmd`. ' '[Default: /]') @click.option('--nice', required=False, type=click.IntRange(-20, 19), - help='process\'s `niceness` level. [-20 >< 19]') + help="process's `niceness` level. [-20 >< 19]" + ) # TODO: add validation that valid umask. @click.option('--umask', required=False, type=int, - help='process\'s `niceness` level. [e.g. 755]') + help="process's `niceness` level. [e.g. 755]" + ) @click.option('--limit-coredump', required=False, default=None, - help='process\'s `limit-coredump` level. ' + help="process's `limit-coredump` level. " '[`ulimited` || > 0 ]') @click.option('--limit-cputime', required=False, default=None, - help='process\'s `limit-cputime` level. ' + help="process's `limit-cputime` level. " '[`ulimited` || > 0 ]') @click.option('--limit-data', required=False, default=None, - help='process\'s `limit-data` level. ' + help="process's `limit-data` level. " '[`ulimited` || > 0 ]') -@click.option('--limit-file_size', required=False, default=None, - help='process\'s `limit-file-size` level. ' +@click.option('--limit-file-size', required=False, default=None, + help="process's `limit-file-size` level. " '[`ulimited` || > 0 ]') @click.option('--limit-locked-memory', required=False, default=None, - help='process\'s `limit-locked-memory` level. ' + help="process's `limit-locked-memory` level. " '[`ulimited` || > 0 ]') @click.option('--limit-open-files', required=False, default=None, - help='process\'s `limit-open-files` level. ' + help="process's `limit-open-files` level. " '[`ulimited` || > 0 ]') @click.option('--limit-user-processes', required=False, default=None, - help='process\'s `limit-user-processes` level. ' + help="process's `limit-user-processes` level. " '[`ulimited` || > 0 ]') @click.option('--limit-physical-memory', required=False, default=None, - help='process\'s `limit-physical-memory` level. ' + help="process's `limit-physical-memory` level. " '[`ulimited` || > 0 ]') @click.option('--limit-stack-size', required=False, default=None, - help='process\'s `limit-stack-size` level. ' + help="process's `limit-stack-size` level. " '[`ulimited` || > 0 ]') @click.option('-v', '--verbose', default=False, is_flag=True) def generate(cmd, name, init_system, init_system_version, overwrite, @@ -415,7 +417,7 @@ def generate(cmd, name, init_system, init_system_version, overwrite, @click.command() @click.argument('name') @click.option('--init-system', required=False, - type=click.Choice(Serv().implementations), + type=click.Choice(Serv().implementation_names), help='Init system to use.') @click.option('-v', '--verbose', default=False, is_flag=True) def remove(name, init_system, verbose): @@ -428,7 +430,7 @@ def remove(name, init_system, verbose): @click.command() @click.argument('name', required=False) @click.option('--init-system', required=False, - type=click.Choice(Serv().implementations), + type=click.Choice(Serv().implementation_names), help='Init system to use.') @click.option('-v', '--verbose', default=False, is_flag=True) def status(name, init_system, verbose): @@ -442,7 +444,7 @@ def status(name, init_system, verbose): @click.command() @click.argument('name') @click.option('--init-system', required=False, - type=click.Choice(Serv().implementations), + type=click.Choice(Serv().implementation_names), help='Init system to use.') @click.option('-v', '--verbose', default=False, is_flag=True) def stop(name, init_system, verbose): @@ -455,7 +457,7 @@ def stop(name, init_system, verbose): @click.command() @click.argument('name') @click.option('--init-system', required=False, - type=click.Choice(Serv().implementations), + type=click.Choice(Serv().implementation_names), help='Init system to use.') @click.option('-v', '--verbose', default=False, is_flag=True) def start(name, init_system, verbose): @@ -468,7 +470,7 @@ def start(name, init_system, verbose): @click.command() @click.argument('name') @click.option('--init-system', required=False, - type=click.Choice(Serv().implementations), + type=click.Choice(Serv().implementation_names), help='Init system to use.') @click.option('-v', '--verbose', default=False, is_flag=True) def restart(name, init_system, verbose): diff --git a/serv/tests/test_serv.py b/serv/tests/test_serv.py index c916896..70c60be 100644 --- a/serv/tests/test_serv.py +++ b/serv/tests/test_serv.py @@ -1,15 +1,17 @@ -import socket -import time import os +import time +import socket import shutil -import getpass +import subprocess from distutils.spawn import find_executable -import click.testing as clicktest import testtools +import click.testing as clicktest -import serv.serv as serv from serv import utils +import serv.serv as serv + +from serv import init def _invoke_click(func, args=None, opts=None): @@ -36,6 +38,14 @@ def setUp(self): self.upstart = self.service + '.conf' self.sysv = self.service + def tearDown(self): + super(TestGenerate, self).tearDown() + # TODO: ignore_errors? + try: + shutil.rmtree(os.path.dirname(self.init_script)) + except: + pass + def _get_file_for_system(self, system): return os.path.join( utils.get_tmp_dir(system, self.service), getattr(self, system)) @@ -53,33 +63,60 @@ def _test_generate(self, sys): '--overwrite': None, '--init-system=': sys } - try: - _invoke_click('generate', [self.cmd], opts) - f = self._get_file_for_system(sys) - self.assertTrue(f) - with open(f) as generated_file: - self.content = generated_file.read() - finally: - shutil.rmtree(os.path.dirname(f)) + additional_opts = { + '--nice=': '5', + '--limit-coredump=': '10', + '--limit-physical-memory=': '20', + '--var=': 'KEY1=VALUE1' + } + opts.update(additional_opts) + self.init_script = self._get_file_for_system(sys) + _invoke_click('generate', [self.cmd], opts) + self.assertTrue(self.init_script) + with open(self.init_script) as generated_file: + self.content = generated_file.read() def test_systemd(self): self._test_generate('systemd') self.assertIn(self.cmd + ' ' + self.args, self.content) + self.assertIn('LimitNICE=5', self.content) + self.assertIn('LimitCORE=10', self.content) + self.assertIn('LimitRSS=20', self.content) + env_vars_file = os.path.join( + utils.get_tmp_dir('systemd', self.service), self.service) + with open(env_vars_file) as vars_file: + content = vars_file.read() + self.assertIn('KEY1=VALUE1', content) + def test_upstart(self): self._test_generate('upstart') self.assertIn(self.cmd + ' ' + self.args, self.content) + self.assertIn('nice 5', self.content) + self.assertIn('limit core 10 10', self.content) + self.assertIn('limit rss 20 20', self.content) + self.assertIn('env KEY1=VALUE1', self.content) + def test_sysv(self): self._test_generate('sysv') self.assertIn('program={0}'.format(self.cmd), self.content) self.assertIn('args="{0}"'.format(self.args), self.content) + self.assertIn('nice -n "$nice"', self.content) + self.assertIn('ulimit -d 10 -m 20', self.content) + env_vars_file = os.path.join( + utils.get_tmp_dir('sysv', self.service), + self.service + '.defaults') + with open(env_vars_file) as vars_file: + content = vars_file.read() + self.assertIn('nice="5"', content) def test_nssm(self): self._test_generate('nssm') self.assertIn( '"{0}" "{1}" "{2}"'.format(self.service, self.cmd, self.args), self.content) + self.assertIn('KEY1=VALUE1 ^', self.content) def test_generate_no_overwrite(self): sys = 'systemd' @@ -128,26 +165,42 @@ class TestDeploy(testtools.TestCase): @classmethod def setUpClass(cls): - cls.service_name = 'test' + cls.service_name = 'testservice' cls.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + def setUp(self): + super(TestDeploy, self).setUp() + def tearDown(self): super(TestDeploy, self).tearDown() - _invoke_click('remove', args=[self.service_name]) def _verify_port_open(self): - time.sleep(3) - self.assertEqual(self.sock.connect_ex(('127.0.0.1', 8000)), 0) + # for some reason, socket does a bad job identifying opened + # and closed ports here. weird. + time.sleep(1) + if utils.IS_WIN: + self.assertEqual(self.sock.connect_ex(('127.0.0.1', 8000)), 0) + else: + subprocess.check_call( + 'ss -lnpt | grep 8000', shell=True, stdout=subprocess.PIPE) def _verify_port_closed(self): - self.assertEqual(self.sock.connect_ex(('127.0.0.1', 8000)), - 10056 if utils.IS_WIN else 106) + time.sleep(1) + if utils.IS_WIN: + self.assertEqual(self.sock.connect_ex(('127.0.0.1', 8000)), 10056) + else: + try: + subprocess.check_call( + 'ss -lnpt | grep 8000', shell=True, stdout=subprocess.PIPE) + except subprocess.CalledProcessError as ex: + self.assertIn('returned non-zero exit status 1', str(ex)) def _test_deploy_remove(self, system): if system == 'nssm': args = find_executable('python') or 'c:\\python27\\python' else: args = find_executable('python2') or '/usr/bin/python2' + init_system = {'--init-system=': system} opts = { '-n': self.service_name, '-a': '-m SimpleHTTPServer', @@ -155,35 +208,40 @@ def _test_deploy_remove(self, system): '-s': None, '-v': None, '--overwrite': None, - '--init-system=': system } + opts.update(init_system) _invoke_click('generate', [args], opts) self._verify_port_open() - _invoke_click('remove', args=[self.service_name]) + if not utils.IS_WIN: + _invoke_click('stop', [self.service_name], init_system) + self._verify_port_closed() + _invoke_click('start', [self.service_name], init_system) + self._verify_port_open() + _invoke_click('restart', [self.service_name], init_system) + self._verify_port_open() + _invoke_click('remove', [self.service_name], init_system) self._verify_port_closed() - # TODO: these should all use init.is_system_exists to check whether - # a test can run or not. this is just silly. def test_systemd(self): if utils.IS_WIN: self.skipTest('Irrelevant on Windows.') - if getpass.getuser() != 'travis': - self.skipTest('Cannot run on Travis.') + if not init.systemd.is_system_exists(): + self.skipTest('Systemd not found on this system.') self._test_deploy_remove('systemd') def test_upstart(self): if utils.IS_WIN: self.skipTest('Irrelevant on Windows.') - if getpass.getuser() != 'travis': - self.skipTest('Should run on Travis.') + if not init.upstart.is_system_exists(): + self.skipTest('Upstart not found on this system.') self._test_deploy_remove('upstart') def test_sysv(self): if utils.IS_WIN: self.skipTest('Irrelevant on Windows.') - if getpass.getuser() == 'travis': - self.skipTest('Should run on Travis.') + if not init.sysv.is_system_exists(): + self.skipTest('SysVinit not found on this system.') self._test_deploy_remove('sysv') def test_nssm(self): diff --git a/setup.py b/setup.py index 4498272..4f6d72b 100644 --- a/setup.py +++ b/setup.py @@ -27,15 +27,18 @@ def _get_package_data(): IS_WIN = (os.name == 'nt') install_requires = [ "click==6.2", - "distro==0.6.0", "jinja2==2.8" ] if not IS_WIN: - install_requires.append("sh==1.11") + non_win_requirements = [ + "distro==0.6.0", + "sh==1.11" + ] + install_requires.extend(non_win_requirements) setup( name='Serv', - version="0.1.4", + version="0.2.0", url='https://github.com/nir0s/serv', author='nir0s', author_email='nir36g@gmail.com',