Files
anvil/devstack/progs/actions.py
2012-01-27 17:42:56 -08:00

418 lines
16 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2012 Yahoo! Inc. 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..
#requires http://pypi.python.org/pypi/termcolor
#but the colors make it worth it :-)
from termcolor import colored, cprint
from devstack import cfg
from devstack import date
from devstack import exceptions as excp
from devstack import log as logging
from devstack import settings
from devstack import shell as sh
from devstack import utils
from devstack.components import db
from devstack.components import glance
from devstack.components import horizon
from devstack.components import keystone
from devstack.components import keystone_client
from devstack.components import nova
from devstack.components import nova_client
from devstack.components import novnc
from devstack.components import openstack_x
from devstack.components import quantum
from devstack.components import quantum_client
from devstack.components import rabbit
from devstack.components import swift
from devstack.packaging import apt
from devstack.packaging import yum
LOG = logging.getLogger("devstack.progs.actions")
#this map controls which distro has
#which package management class
_PKGR_MAP = {
settings.UBUNTU11: apt.AptPackager,
settings.RHEL6: yum.YumPackager,
}
# 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",
}
# This determines what classes to use to install/uninstall/...
_ACTION_CLASSES = {
settings.INSTALL: {
settings.NOVA: nova.NovaInstaller,
settings.GLANCE: glance.GlanceInstaller,
settings.QUANTUM: quantum.QuantumInstaller,
settings.SWIFT: swift.SwiftInstaller,
settings.HORIZON: horizon.HorizonInstaller,
settings.KEYSTONE: keystone.KeystoneInstaller,
settings.DB: db.DBInstaller,
settings.RABBIT: rabbit.RabbitInstaller,
settings.KEYSTONE_CLIENT: keystone_client.KeyStoneClientInstaller,
settings.NOVA_CLIENT: nova_client.NovaClientInstaller,
settings.OPENSTACK_X: openstack_x.OpenstackXInstaller,
settings.NOVNC: novnc.NoVNCInstaller,
settings.QUANTUM_CLIENT: quantum_client.QuantumClientInstaller,
},
settings.UNINSTALL: {
settings.NOVA: nova.NovaUninstaller,
settings.GLANCE: glance.GlanceUninstaller,
settings.QUANTUM: quantum.QuantumUninstaller,
settings.SWIFT: swift.SwiftUninstaller,
settings.HORIZON: horizon.HorizonUninstaller,
settings.KEYSTONE: keystone.KeystoneUninstaller,
settings.DB: db.DBUninstaller,
settings.RABBIT: rabbit.RabbitUninstaller,
settings.KEYSTONE_CLIENT: keystone_client.KeyStoneClientUninstaller,
settings.NOVA_CLIENT: nova_client.NovaClientUninstaller,
settings.OPENSTACK_X: openstack_x.OpenstackXUninstaller,
settings.NOVNC: novnc.NoVNCUninstaller,
settings.QUANTUM_CLIENT: quantum_client.QuantumClientUninstaller,
},
settings.START: {
settings.NOVA: nova.NovaRuntime,
settings.GLANCE: glance.GlanceRuntime,
settings.QUANTUM: quantum.QuantumRuntime,
settings.SWIFT: swift.SwiftRuntime,
settings.HORIZON: horizon.HorizonRuntime,
settings.KEYSTONE: keystone.KeystoneRuntime,
settings.DB: db.DBRuntime,
settings.RABBIT: rabbit.RabbitRuntime,
settings.KEYSTONE_CLIENT: keystone_client.KeyStoneClientRuntime,
settings.NOVA_CLIENT: nova_client.NovaClientRuntime,
settings.OPENSTACK_X: openstack_x.OpenstackXRuntime,
settings.NOVNC: novnc.NoVNCRuntime,
settings.QUANTUM_CLIENT: quantum_client.QuantumClientRuntime,
},
settings.STOP: {
settings.NOVA: nova.NovaRuntime,
settings.GLANCE: glance.GlanceRuntime,
settings.QUANTUM: quantum.QuantumRuntime,
settings.SWIFT: swift.SwiftRuntime,
settings.HORIZON: horizon.HorizonRuntime,
settings.KEYSTONE: keystone.KeystoneRuntime,
settings.DB: db.DBRuntime,
settings.RABBIT: rabbit.RabbitRuntime,
settings.KEYSTONE_CLIENT: keystone_client.KeyStoneClientRuntime,
settings.NOVA_CLIENT: nova_client.NovaClientRuntime,
settings.OPENSTACK_X: openstack_x.OpenstackXRuntime,
settings.NOVNC: novnc.NoVNCRuntime,
settings.QUANTUM_CLIENT: quantum_client.QuantumClientRuntime,
},
}
def _clean_action(action):
if action is None:
return None
action = action.strip().lower()
if not (action in settings.ACTIONS):
return None
return action
def _get_pkg_manager(distro, keep_packages):
cls = _PKGR_MAP.get(distro)
return cls(distro, keep_packages)
def _get_action_cls(action_name, component_name):
action_cls_map = _ACTION_CLASSES.get(action_name)
if not action_cls_map:
return None
return action_cls_map.get(component_name)
def _check_roots(action, rootdir, components):
to_skip = list()
if action == settings.INSTALL:
if sh.isdir(rootdir):
to_skip = list()
for c in components:
check_pth = sh.joinpths(rootdir, c)
if sh.isdir(check_pth) and len(sh.listdir(check_pth)) != 0:
LOG.warn("Component directory [%s] already exists and its not empty (skipping installing that component)!" % check_pth)
LOG.warn("If this is undesired please remove it or uninstall %s!" % (c))
to_skip.append(c)
return to_skip
def _pre_run(action_name, **kargs):
if action_name == settings.INSTALL:
root_dir = kargs.get("root_dir")
if root_dir:
sh.mkdir(root_dir)
def _post_run(action_name, **kargs):
if action_name == settings.UNINSTALL:
root_dir = kargs.get("root_dir")
if root_dir:
sh.rmdir(root_dir)
def _print_cfgs(config_obj, action):
#this will make the items nice and pretty
def item_format(key, value):
return "\t%s=%s" % (str(key), str(value))
def map_print(mp):
for key in sorted(mp.keys()):
value = mp.get(key)
LOG.info(item_format(key, value))
#now make it pretty
passwords_gotten = config_obj.pws
full_cfgs = config_obj.configs_fetched
db_dsns = config_obj.db_dsns
if passwords_gotten or full_cfgs or db_dsns:
LOG.info("After action (%s) your settings are:" % (action))
if passwords_gotten:
LOG.info("Passwords:")
map_print(passwords_gotten)
if full_cfgs:
#TODO
#better way to do this?? (ie a list difference?)
filtered_mp = dict()
for key in full_cfgs.keys():
if key in passwords_gotten:
continue
filtered_mp[key] = full_cfgs.get(key)
if filtered_mp:
LOG.info("Configs:")
map_print(filtered_mp)
if db_dsns:
LOG.info("Data source names:")
map_print(db_dsns)
def _install(component_name, instance):
LOG.info("Downloading %s." % (component_name))
am_downloaded = instance.download()
LOG.info("Performed %s downloads." % (am_downloaded))
LOG.info("Configuring %s." % (component_name))
am_configured = instance.configure()
LOG.info("Configured %s items." % (am_configured))
LOG.info("Pre-installing %s." % (component_name))
instance.pre_install()
LOG.info("Installing %s." % (component_name))
instance.install()
LOG.info("Post-installing %s." % (component_name))
trace = instance.post_install()
if trace:
LOG.info("Finished install of %s - check %s for traces of what happened." % (component_name, trace))
else:
LOG.info("Finished install of %s" % (component_name))
return trace
def _stop(component_name, instance, skip_notrace):
try:
LOG.info("Stopping %s." % (component_name))
stop_amount = instance.stop()
LOG.info("Stopped %s items." % (stop_amount))
LOG.info("Finished stop of %s" % (component_name))
except excp.NoTraceException, e:
if skip_notrace:
LOG.info("Passing on stopping %s since no trace file was found." % (component_name))
else:
raise
def _start(component_name, instance):
LOG.info("Pre-starting %s." % (component_name))
instance.pre_start()
LOG.info("Starting %s." % (component_name))
start_info = instance.start()
LOG.info("Post-starting %s." % (component_name))
instance.post_start()
if type(start_info) == list:
LOG.info("Check [%s] for traces of what happened." % (", ".join(start_info)))
elif type(start_info) == int:
LOG.info("Started %s applications." % (start_info))
start_info = None
LOG.info("Finished start of %s." % (component_name))
return start_info
def _uninstall(component_name, instance, skip_notrace):
try:
LOG.info("Unconfiguring %s." % (component_name))
instance.unconfigure()
LOG.info("Uninstalling %s." % (component_name))
instance.uninstall()
except excp.NoTraceException, e:
if skip_notrace:
LOG.info("Passing on uninstalling %s since no trace file was found." % (component_name))
else:
raise
def _get_config():
cfg_fn = sh.canon_path(settings.STACK_CONFIG_LOCATION)
LOG.info("Loading config from [%s]" % (cfg_fn))
config_instance = cfg.EnvConfigParser()
config_instance.read(cfg_fn)
return config_instance
def _run_components(action_name, component_order, components, distro, root_dir, program_args):
LOG.info("Will %s [%s] (in that order) using root directory \"%s\"" % (action_name, ", ".join(component_order), root_dir))
non_components = set(components.keys()).difference(set(component_order))
if non_components:
LOG.info("Using reference components [%s]" % (", ".join(sorted(non_components))))
pkg_manager = _get_pkg_manager(distro, program_args.pop('keep_packages', True))
config = _get_config()
#form the active instances (this includes ones we won't use)
all_instances = dict()
for component in components.keys():
action_cls = _get_action_cls(action_name, component)
instance = action_cls(instances=all_instances,
distro=distro,
packager=pkg_manager,
config=config,
root=root_dir,
opts=components.get(component, list()))
all_instances[component] = instance
#run anything before it gets going...
_pre_run(action_name, root_dir=root_dir, pkg=pkg_manager, cfg=config)
results = list()
for component in component_order:
#this instance was just made
instance = all_instances.get(component)
#activate the correct function for the given action
if action_name == settings.INSTALL:
install_result = _install(component, instance)
if install_result:
if type(install_result) == list:
results += install_result
else:
results.append(str(install_result))
elif action_name == settings.STOP:
_stop(component, instance, program_args.get('force', False))
elif action_name == settings.START:
start_result = _start(component, instance)
if start_result:
if type(start_result) == list:
results += start_result
else:
results.append(str(start_result))
elif action_name == settings.UNINSTALL:
_uninstall(component, instance, program_args.get('force', False))
else:
#TODO throw?
pass
#display any configs touched...
_print_cfgs(config, action_name)
#any post run actions go now
_post_run(action_name, root_dir=root_dir, pkg=pkg_manager, cfg=config)
return results
def _get_def_components():
#this seems to be the default list of what to install
#ENABLED_SERVICES=${ENABLED_SERVICES:-g-api,g-reg,key,n-api,
#n-crt,n-obj,n-cpu,n-net,n-sch,n-novnc,n-xvnc,n-cauth,horizon,mysql,rabbit}
def_components = dict()
#TODO glance subcomponents should be api/reg
def_components[settings.GLANCE] = []
def_components[settings.KEYSTONE] = []
#we seem to be missing the nova object store (n-obj)
#and nova network (n-net) and nova cert (n-crt)
#and nova scheduler (n-sch) and n-cauth (console auth)
#TODO talk to ken about these...
def_components[settings.NOVA] = [nova.NCPU, nova.NVOL, nova.NAPI]
def_components[settings.NOVNC] = []
#TODO n-xvnc?
def_components[settings.HORIZON] = []
def_components[settings.DB] = []
def_components[settings.RABBIT] = []
return def_components
def _run_action(args):
defaulted_components = False
components = settings.parse_components(args.pop("components"))
if not components:
defaulted_components = True
components = _get_def_components()
action = _clean_action(args.pop("action"))
if not action:
cprint("No valid action specified!", "red")
return False
rootdir = args.pop("dir")
if rootdir is None:
cprint("No root directory specified!", "red")
return False
#ensure os/distro is known
(distro, platform) = utils.determine_distro()
if distro is None:
print("Unsupported platform " + colored(platform, "red") + "!")
return False
#start it
(rep, maxlen) = utils.welcome(_WELCOME_MAP.get(action))
header = utils.center_text("Action Runner", rep, maxlen)
print(header)
#need to figure out dependencies for components (if any)
ignore_deps = args.pop('ignore_deps', False)
if not defaulted_components:
LOG.info("Activating components [%s]" % (", ".join(sorted(components.keys()))))
else:
LOG.info("Activating default components [%s]" % (", ".join(sorted(components.keys()))))
if not ignore_deps:
new_components = settings.resolve_dependencies(components.keys())
component_diff = new_components.difference(components.keys())
if component_diff:
LOG.info("Having to activate dependent components: [%s]" % (", ".join(sorted(component_diff))))
for new_component in component_diff:
components[new_component] = list()
component_skips = _check_roots(action, rootdir, components.keys())
for c in component_skips:
components.pop(c)
if not components:
LOG.error("After checking the various components roots, no components ended up being specified!")
return False
#get the right component order (by priority)
component_order = settings.prioritize_components(components.keys())
#add in any that will just be referenced but which will not actually do anything (ie the action will not be applied to these)
ref_components = settings.parse_components(args.pop("ref_components"))
for c in ref_components.keys():
if c not in components:
components[c] = ref_components.get(c)
#now do it!
LOG.info("Starting action [%s] on %s for distro [%s]" % (action, date.rcf8222date(), distro))
results = _run_components(action, component_order, components, distro, rootdir, args)
LOG.info("Finished action [%s] on %s" % (action, date.rcf8222date()))
if results:
LOG.info('Check [%s] for traces of what happened.' % ", ".join(results))
return True
def run(args):
return _run_action(args)