Split the action runner into several subclasses and move the logic

for each action into the appropriate subclass.
This commit is contained in:
Doug Hellmann
2012-03-21 17:30:41 -04:00
parent 1d27275c08
commit 931b896cc1
5 changed files with 277 additions and 191 deletions

View File

@@ -22,6 +22,7 @@ import shlex
import yaml
from devstack.progs import actions
from devstack import decorators
from devstack import importer
from devstack import log as logging
@@ -140,7 +141,9 @@ class Distro(object):
entry_point = component_info[action]
cls = importer.import_entry_point(entry_point)
# Knock all action class info (and any other keys)
key_deletions = [action] + settings.ACTIONS
# FIXME: This module shouldn't need to have knowledge
# of all available actions.
key_deletions = [action] + actions.get_action_names()
for k in key_deletions:
if k in component_info:
del component_info[k]

View File

@@ -17,6 +17,7 @@
from optparse import IndentedHelpFormatter
from optparse import OptionParser, OptionGroup
from devstack.progs import actions
from devstack import log as logging
from devstack import settings
from devstack import version
@@ -64,7 +65,7 @@ def parse():
type="string",
dest="action",
metavar="ACTION",
help="required action to perform: %s" % (_format_list(settings.ACTIONS)))
help="required action to perform: %s" % (_format_list(actions.get_action_names())))
base_group.add_option("-d", "--directory",
action="store",
type="string",

View File

@@ -14,6 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License..
import abc
from devstack import env_rc
from devstack import exceptions as excp
from devstack import log as logging
@@ -22,153 +24,66 @@ from devstack import shell as sh
LOG = logging.getLogger("devstack.progs.actions")
# For actions in this list we will reverse the component order
REVERSE_ACTIONS = [settings.UNINSTALL, settings.STOP]
# For these actions we will attempt to make an rc file if it does not exist
RC_FILE_MAKE_ACTIONS = [settings.INSTALL]
# The order of which uninstalls happen + message of what is happening
# (before and after)
UNINSTALL_ORDERING = [
(
"Unconfiguring {name}.",
(lambda instance: (instance.unconfigure())),
None,
),
(
"Pre-uninstalling {name}.",
(lambda instance: (instance.pre_uninstall())),
None,
),
(
"Uninstalling {name}.",
(lambda instance: (instance.uninstall())),
None,
),
(
"Post-uninstalling {name}.",
(lambda instance: (instance.post_uninstall())),
None,
),
]
# The order of which starts happen + message of what is happening
# (before and after)
STARTS_ORDERING = [
(
"Configuring runner for {name}.",
(lambda instance: (instance.configure())),
None,
),
(
"Pre-starting {name}.",
(lambda instance: (instance.pre_start())),
None,
),
(
"Starting {name}.",
(lambda instance: (instance.start())),
"Started {result} applications.",
),
(
"Post-starting {name}.",
(lambda instance:(instance.post_start())),
None,
),
]
# The order of which stops happen + message of what is happening
# (before and after)
STOPS_ORDERING = [
(
"Stopping {name}.",
(lambda instance:(instance.stop())),
"Stopped {result} items.",
),
]
# The order of which install happen + message of what is happening
# (before and after)
INSTALL_ORDERING = [
(
"Downloading {name}.",
(lambda instance: (instance.download())),
"Performed {result} downloads.",
),
(
"Configuring {name}.",
(lambda instance: (instance.configure())),
"Configured {result} items.",
),
(
"Pre-installing {name}.",
(lambda instance: (instance.pre_install())),
None,
),
(
"Installing {name}.",
(lambda instance: (instance.install())),
"Finished install of {name} - check {result} for traces of what happened.",
),
(
"Post-installing {name}.",
(lambda instance: (instance.post_install())),
None,
),
]
# Map of action name to action order
ACTION_MP = {
settings.START: STARTS_ORDERING,
settings.STOP: STOPS_ORDERING,
settings.INSTALL: INSTALL_ORDERING,
settings.UNINSTALL: UNINSTALL_ORDERING,
}
# These actions must have there prerequisite action accomplished (if
# determined by the boolean lambda to be needed)
PREQ_ACTIONS = {
settings.START: ((lambda instance: (not instance.is_installed())), settings.INSTALL),
settings.UNINSTALL: ((lambda instance: (instance.is_started())), settings.STOP),
}
class ActionRunner(object):
def __init__(self, distro, action,
cfg, pw_gen, pkg_manager,
**kargs):
__meta__ = abc.ABCMeta
PREREQ = None
def __init__(self,
distro,
cfg,
pw_gen,
pkg_manager,
**kargs):
self.distro = distro
self.action = action
self.cfg = cfg
self.pw_gen = pw_gen
self.pkg_manager = pkg_manager
self.keep_old = kargs.get('keep_old', False)
self.force = kargs.get('force', False)
def _apply_reverse(self, action, component_order):
adjusted_order = list(component_order)
if action in REVERSE_ACTIONS:
adjusted_order.reverse()
return adjusted_order
@abc.abstractmethod
def _instance_needs_prereq(self, instance):
"""Determine if the instance will require our prereq to be invoked.
def _construct_instances(self, persona, action, root_dir):
Return boolean where True means invoke the prereq.
"""
return
@abc.abstractmethod
def _run(self, persona, root_dir, component_order, instances):
"""Run the phases of processing for this action.
Subclasses are expected to override this method to
do something useful.
"""
def _order_components(self, components):
"""Returns the components in the order they should be processed.
"""
# Duplicate the list to avoid problems if it is updated later.
return components[:]
def _construct_instances(self, persona, root_dir):
"""Create component objects for each component in the persona.
"""
components = persona.wanted_components
desired_subsystems = persona.wanted_subsystems or dict()
component_opts = persona.component_options or dict()
instances = dict()
for c in components:
(cls, my_info) = self.distro.extract_component(c, action)
(cls, my_info) = self.distro.extract_component(c, self.NAME)
LOG.debug("Constructing class %s" % (cls))
cls_kvs = dict()
cls_kvs['runner'] = self
cls_kvs['component_dir'] = sh.joinpths(root_dir, c)
cls_kvs['subsystem_info'] = my_info.get('subsystems') or dict()
cls_kvs['subsystem_info'] = my_info.get('subsystems', {})
cls_kvs['all_instances'] = instances
cls_kvs['name'] = c
cls_kvs['keep_old'] = self.keep_old
cls_kvs['desired_subsystems'] = desired_subsystems.get(c) or set()
cls_kvs['options'] = component_opts.get(c) or dict()
cls_kvs['desired_subsystems'] = desired_subsystems.get(c, set())
cls_kvs['options'] = component_opts.get(c, {})
# The above is not overrideable...
for (k, v) in my_info.items():
if k not in cls_kvs:
@@ -183,57 +98,239 @@ class ActionRunner(object):
instance.verify()
def _warm_components(self, component_order, instances):
LOG.info("Warming up your component configurations (ie so you won't be prompted later)")
LOG.info("Warming up component configurations")
for c in component_order:
instance = instances[c]
instance.warm_configs()
def _run_phase(self, start_msg, functor, end_msg, component_order, instances):
"""Run a given 'functor' across all of the components, in order.
"""
for c in component_order:
instance = instances[c]
if start_msg:
LOG.info(start_msg.format(name=c))
try:
result = functor(instance)
if end_msg:
LOG.info(end_msg.format(name=c, result=result))
except (excp.NoTraceException) as e:
if self.force:
LOG.debug("Skipping exception [%s]" % (e))
else:
raise
def _handle_prereq(self, persona, instances, root_dir):
if not self.PREREQ:
return
components_needing_prereq = [
c
for (c, instance) in instances.items()
if self._instance_needs_prereq(instance)
]
if components_needing_prereq:
LOG.info("Processing prerequisite action [%s] requested by (%s) components.",
self.PREREQ.NAME, ", ".join(components_needing_prereq))
prereq = self.PREREQ(self.distro,
self.cfg,
self.pw_gen,
self.pkg_manager,
keep_old=self.keep_old,
force=self.force,
)
prereq.run(persona, root_dir)
def run(self, persona, root_dir):
instances = self._construct_instances(persona, root_dir)
self._handle_prereq(persona, instances, root_dir)
component_order = self._order_components(persona.wanted_components)
LOG.info("Processing components [%s] (in that order) for action [%s]",
"->".join(component_order), self.NAME)
self._verify_components(component_order, instances)
self._warm_components(component_order, instances)
self._run(persona, root_dir, component_order, instances)
return
class InstallRunner(ActionRunner):
NAME = 'install'
def _instance_needs_prereq(self, instance):
return False
def _write_rc_file(self, root_dir):
writer = env_rc.RcWriter(self.cfg, self.pw_gen, root_dir)
if not sh.isfile(settings.OSRC_FN):
LOG.info("Generating a file at [%s] that will contain your environment settings." % (settings.OSRC_FN))
LOG.info("Generating a file at [%s] that will contain your environment settings.",
settings.OSRC_FN)
writer.write(settings.OSRC_FN)
else:
LOG.info("Updating a file at [%s] that contains your environment settings." % (settings.OSRC_FN))
LOG.info("Updating a file at [%s] that contains your environment settings.",
settings.OSRC_FN)
am_upd = writer.update(settings.OSRC_FN)
LOG.info("Updated [%s] settings in rc file [%s]" % (am_upd, settings.OSRC_FN))
LOG.info("Updated [%s] settings in rc file [%s]",
am_upd, settings.OSRC_FN)
def _run_instances(self, action, component_order, instances):
for (start_msg, functor, end_msg) in ACTION_MP[action]:
for c in component_order:
instance = instances[c]
if start_msg:
LOG.info(start_msg.format(name=c))
try:
result = functor(instance)
if end_msg:
LOG.info(end_msg.format(name=c, result=result))
except (excp.NoTraceException) as e:
if self.force:
LOG.debug("Skipping exception [%s]" % (e))
else:
raise
def _run(self, persona, root_dir, component_order, instances):
self._write_rc_file(root_dir)
self._run_phase(
'Downloading {name}',
lambda i: i.download(),
"Performed {result} downloads.",
component_order,
instances,
)
self._run_phase(
'Configuring {name}',
lambda i: i.configure(),
"Configured {result} items.",
component_order,
instances,
)
self._run_phase(
'Pre-installing {name}',
lambda i: i.pre_install(),
None,
component_order,
instances,
)
self._run_phase(
'Installing {name}',
lambda i: i.install(),
"Finished install of {name} - check {result} for traces of what happened.",
component_order,
instances,
)
self._run_phase(
'Post-installing {name}',
lambda i: i.post_install(),
None,
component_order,
instances,
)
def _run_action(self, persona, action, root_dir):
instances = self._construct_instances(persona, action, root_dir)
if action in PREQ_ACTIONS:
(check_functor, preq_action) = PREQ_ACTIONS[action]
checks_passed_components = list()
for (c, instance) in instances.items():
if check_functor(instance):
checks_passed_components.append(c)
if checks_passed_components:
LOG.info("Activating prerequisite action [%s] requested by (%s) components."
% (preq_action, ", ".join(checks_passed_components)))
self._run_action(persona, preq_action, root_dir)
component_order = self._apply_reverse(action, persona.wanted_components)
LOG.info("Activating components [%s] (in that order) for action [%s]" %
("->".join(component_order), action))
self._verify_components(component_order, instances)
self._warm_components(component_order, instances)
if action in RC_FILE_MAKE_ACTIONS:
self._write_rc_file(root_dir)
self._run_instances(action, component_order, instances)
def run(self, persona, root_dir):
self._run_action(persona, self.action, root_dir)
class StartRunner(ActionRunner):
NAME = 'start'
PREREQ = InstallRunner
def _instance_needs_prereq(self, instance):
return not instance.is_installed()
def _run(self, persona, root_dir, component_order, instances):
self._run_phase(
'Configuring runner for {name}',
lambda i: i.configure(),
None,
component_order,
instances,
)
self._run_phase(
'Pre-starting {name}',
lambda i: i.pre_start(),
None,
component_order,
instances,
)
self._run_phase(
'Starting {name}',
lambda i: i.start(),
"Started {result} applications.",
component_order,
instances,
)
self._run_phase(
'Post-starting {name}',
lambda i: i.post_start(),
None,
component_order,
instances,
)
class StopRunner(ActionRunner):
NAME = 'stop'
def _instance_needs_prereq(self, instance):
return False
def _order_components(self, components):
components = super(StopRunner, self)._order_components(components)
components.reverse()
return components
def _run(self, persona, root_dir, component_order, instances):
self._run_phase(
'Stopping {name}',
lambda i: i.stop(),
'Stopped {result} items',
component_order,
instances,
)
class UninstallRunner(ActionRunner):
NAME = 'uninstall'
PREREQ = StopRunner
def _instance_needs_prereq(self, instance):
return instance.is_started()
def _order_components(self, components):
components = super(UninstallRunner, self)._order_components(components)
components.reverse()
return components
def _run(self, persona, root_dir, component_order, instances):
self._run_phase(
'Unconfiguring {name}',
lambda i: i.unconfigure(),
None,
component_order,
instances,
)
self._run_phase(
'Pre-uninstalling {name}',
lambda i: i.pre_uninstall(),
None,
component_order,
instances,
)
self._run_phase(
'Uninstalling {name}',
lambda i: i.uninstall(),
None,
component_order,
instances,
)
self._run_phase(
'Post-uninstalling {name}',
lambda i: i.post_uninstall(),
None,
component_order,
instances,
)
_NAMES_TO_RUNNER = {
'install': InstallRunner,
'uninstall': UninstallRunner,
'start': StartRunner,
'stop': StopRunner,
}
def get_action_names():
"""Returns a list of the available action names.
"""
return sorted(_NAMES_TO_RUNNER.keys())
def get_runner_factory(action):
"""Given an action name, look up the factory for that action runner.
"""
try:
return _NAMES_TO_RUNNER[action]
except KeyError:
raise ValueError('Unrecognized action %s' % action)

View File

@@ -47,14 +47,6 @@ RC_FN_TEMPL = "os-%s.rc"
LOCALRC_FN = RC_FN_TEMPL % ('local')
OSRC_FN = RC_FN_TEMPL % ('core')
# Program
# actions
INSTALL = "install"
UNINSTALL = "uninstall"
START = "start"
STOP = "stop"
ACTIONS = [INSTALL, UNINSTALL, START, STOP]
# Where the configs and templates should be at.
STACK_BIN_DIR = os.path.abspath(os.path.dirname(sys.argv[0]))
STACK_CONFIG_DIR = os.path.join(STACK_BIN_DIR, "conf")

23
stack
View File

@@ -39,15 +39,6 @@ from devstack.progs import actions
LOG = logging.getLogger("devstack.stack")
# This is used to map an action to a useful string for
# the welcome display
_WELCOME_MAP = {
settings.INSTALL: "INSTALLER",
settings.UNINSTALL: "UNINSTALLER",
settings.START: "STARTER",
settings.STOP: "STOPPER",
}
_CFG_GROUPS = {
cfg_helpers.make_id('passwords', None): 'Passwords',
cfg_helpers.make_id('db', None): 'Database info',
@@ -116,7 +107,7 @@ def setup_root(root_dir):
def run(args):
action = args.pop("action", '').strip().lower()
if not (action in settings.ACTIONS):
if action not in actions.get_action_names():
print(utils.color_text("No valid action specified!", "red"))
return False
@@ -139,7 +130,7 @@ def run(args):
persona_fn = sh.abspth(persona_fn)
# Welcome!
(repeat_string, line_max_len) = utils.welcome(_WELCOME_MAP.get(action))
(repeat_string, line_max_len) = utils.welcome(action.upper())
print(utils.center_text("Action Runner", repeat_string, line_max_len))
# !!
@@ -162,10 +153,12 @@ def run(args):
pkg_cls = dist.get_packager_factory()
pkg_manager = pkg_cls(dist, args.get('keep_old', False))
runner = actions.ActionRunner(dist, action,
config, pw_gen,
pkg_manager,
**args)
runner_factory = actions.get_runner_factory(action)
runner = runner_factory(dist,
config,
pw_gen,
pkg_manager,
**args)
LOG.info("Starting action %r on %s for distro: %r" % (action, date.rcf8222date(), dist.name))
LOG.info("Using persona: %r" % (persona_fn))