Implement a moulinette lock and revisit extra params a bit

This commit is contained in:
Jerome Lebleu 2014-03-11 15:03:33 +01:00
parent 9c9ccc1271
commit 66f60381e4
3 changed files with 109 additions and 91 deletions

View file

@ -98,26 +98,11 @@ def cli(namespaces, args, use_cache=True):
instead of using the cached one
"""
import os
from .actionsmap import ActionsMap
from .helpers import pretty_print_dict
lock_file = '/var/run/moulinette.lock'
# TODO: Move the lock checking into the ActionsMap class
# Check the lock
if os.path.isfile(lock_file):
raise MoulinetteError(1, _("The moulinette is already running"))
# Create a lock
with open(lock_file, 'w') as f: pass
os.system('chmod 400 '+ lock_file)
try:
amap = ActionsMap('cli', namespaces, use_cache)
pretty_print_dict(amap.process(args))
except KeyboardInterrupt, EOFError:
raise MoulinetteError(125, _("Interrupted"))
finally:
# Remove the lock
os.remove(lock_file)

View file

@ -10,7 +10,7 @@ from collections import OrderedDict
import logging
from . import __version__
from .core import MoulinetteError
from .core import MoulinetteError, MoulinetteLock
## Interfaces' Actions map Parser --------------------------------------
@ -441,10 +441,9 @@ class PatternParameter(_ExtraParameter):
name = 'pattern'
def __call__(self, arguments, arg_name, arg_value):
pattern = arguments[0]
message = arguments[1]
pattern, message = (arguments[0], arguments[1])
if arg_value is not None and not re.match(pattern, arg_value):
if not re.match(pattern, arg_value or ''):
raise MoulinetteError(22, message)
return arg_value
@ -496,12 +495,13 @@ class ExtraArgumentParser(object):
"""
# Iterate over parameters to validate
for p, v in parameters.items():
# Remove unknow parameters
if p not in self.extra.keys():
klass = self.extra.get(p, None)
if not klass:
# Remove unknown parameters
del parameters[p]
else:
# Validate parameter value
parameters[p] = self.extra[p].validate(v, arg_name)
parameters[p] = klass.validate(v, arg_name)
return parameters
@ -595,25 +595,28 @@ class ActionsMap(object):
self.extraparser = ExtraArgumentParser(interface)
self.parser = self._construct_parser(actionsmaps)
def process(self, args, **kwargs):
def process(self, args, timeout=0, **kwargs):
"""
Parse arguments and process the proper action
Keyword arguments:
- args -- The arguments to parse
- timeout -- The time period before failing if the lock
cannot be acquired for the action
- **kwargs -- Additional interface arguments
"""
# Check moulinette status
# Parse arguments
arguments = vars(self.parser.parse_args(args, **kwargs))
arguments = self._parse_extra_parameters(arguments)
for an, parameters in (arguments.pop('_extra', {})).items():
arguments[an] = self.extraparser.parse(an, arguments[an], parameters)
# Retrieve action information
namespace, category, action = arguments.pop('_info')
func_name = '%s_%s' % (category, action)
# Lock the moulinette for the namespace
with MoulinetteLock(namespace, timeout):
try:
mod = __import__('%s.%s' % (namespace, category),
globals=globals(), level=0,
@ -624,7 +627,6 @@ class ActionsMap(object):
else:
# Process the action
return func(**arguments)
return {}
@staticmethod
def get_namespaces():
@ -674,57 +676,7 @@ class ActionsMap(object):
return actionsmaps
## Private class and methods
def _store_extra_parameters(self, parser, arg_name, arg_extra):
"""
Store extra parameters for a given argument
Keyword arguments:
- parser -- Parser object for the arguments
- arg_name -- Argument name
- arg_extra -- Argument extra parameters
Returns:
The parser object
"""
if arg_extra:
# Retrieve current extra parameters dict
extra = parser.get_default('_extra')
if not extra or not isinstance(extra, dict):
extra = {}
if not self.use_cache:
# Validate extra parameters for the argument
extra[arg_name] = self.extraparser.validate(arg_name, arg_extra)
else:
extra[arg_name] = arg_extra
parser.set_defaults(_extra=extra)
return parser
def _parse_extra_parameters(self, args):
"""
Parse arguments with their extra parameters
Keyword arguments:
- args -- A dict of all arguments
Return:
The parsed arguments dict
"""
# Retrieve extra parameters for the arguments
extra = args.pop('_extra', None)
if not extra:
return args
# Validate extra parameters for each arguments
for an, parameters in extra.items():
args[an] = self.extraparser.parse(an, args[an], parameters)
return args
## Private methods
def _construct_parser(self, actionsmaps):
"""
@ -738,6 +690,12 @@ class ActionsMap(object):
An interface relevant's parser object
"""
# Define setter for extra parameters
if not self.use_cache:
_set_extra = lambda an, e: self.extraparser.validate(an, e)
else:
_set_extra = lambda an, e: e
# Instantiate parser
top_parser = self._parser_class()
@ -787,6 +745,12 @@ class ActionsMap(object):
extra = argp.pop('extra', None)
arg = parser.add_argument(*name, **argp)
parser = self._store_extra_parameters(parser, arg.dest, extra)
if not extra:
continue
# Store extra parameters
extras = parser.get_default('_extra') or {}
extras[arg.dest] = _set_extra(arg.dest, extra)
parser.set_defaults(_extra=extras)
return top_parser

View file

@ -2,7 +2,9 @@
import os
import sys
import time
import gettext
from .helpers import colorize
# Package manipulation -------------------------------------------------
@ -164,3 +166,70 @@ class MoulinetteError(Exception):
def colorize(self):
return self.__str__(colorized=True)
class MoulinetteLock(object):
"""Locker for a moulinette instance
It provides a lock mechanism for a given moulinette instance. It can
be used in a with statement as it has a context-manager support.
Keyword arguments:
- namespace -- The namespace to lock
- timeout -- The time period before failing if the lock cannot
be acquired
- interval -- The time period before trying again to acquire the
lock
"""
def __init__(self, namespace, timeout=0, interval=.5):
self.namespace = namespace
self.timeout = timeout
self.interval = interval
self._lockfile = '/var/run/moulinette_%s.lock' % namespace
self._locked = False
def acquire(self):
"""Attempt to acquire the lock for the moulinette instance
It will try to write to the lock file only if it doesn't exist.
Otherwise, it will wait and try again until the timeout expires
or the lock file doesn't exist.
"""
start_time = time.time()
while True:
if not os.path.isfile(self._lockfile):
# Create the lock file
(open(self._lockfile, 'w')).close()
break
if (time.time() - start_time) > self.timeout:
raise MoulinetteError(1, _("An instance is already running for '%s'") \
% self.namespace)
# Wait before checking again
time.sleep(self.interval)
self._locked = True
def release(self):
"""Release the lock of the moulinette instance
It will delete the lock file if the lock has been acquired.
"""
if self._locked:
os.unlink(self._lockfile)
self._locked = False
def __enter__(self):
if not self._locked:
self.acquire()
return self
def __exit__(self, *args):
self.release()
def __del__(self):
self.release()