anvil/devstack/Util.py

473 lines
13 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 time import (localtime, strftime)
from termcolor import colored
import os
import platform
import re
import json
import subprocess
import netifaces
import operator
import Logger
#TODO fix these
from Exceptions import (BadRegexException,
NoReplacementException,
FileException)
from Shell import (joinpths, load_file, execute)
#constant goodies
VERSION = 0x2
VERSION_STR = "%0.2f" % (VERSION)
DEVSTACK = 'DEVSTACK'
#these also have meaning outside python
#ie in the pkg listings so update there also!
UBUNTU11 = "ubuntu-oneiric"
RHEL6 = "rhel-6"
#GIT master
MASTER_BRANCH = "master"
#other constants
PRE_INSTALL = 'pre-install'
POST_INSTALL = 'post-install'
IPV4 = 'IPv4'
IPV6 = 'IPv6'
DEFAULT_NET_INTERFACE = 'eth0'
DEFAULT_NET_INTERFACE_IP_VERSION = IPV4
PARAM_SUB_REGEX = "%([\\w\\d]+?)%"
#component name mappings
NOVA = "nova"
GLANCE = "glance"
QUANTUM = "quantum"
SWIFT = "swift"
HORIZON = "horizon"
KEYSTONE = "keystone"
KEYSTONE_CLIENT = 'keystone-client'
DB = "db"
RABBIT = "rabbit"
COMPONENT_NAMES = [NOVA, GLANCE, QUANTUM,
SWIFT, HORIZON, KEYSTONE,
DB, RABBIT, KEYSTONE_CLIENT]
#ordering of install (lower priority means earlier)
NAMES_PRIORITY = {
DB: 1,
RABBIT: 1,
KEYSTONE: 2,
GLANCE: 3,
QUANTUM: 4,
NOVA: 5,
SWIFT: 6,
KEYSTONE_CLIENT: 6,
HORIZON: 7,
}
#when a component is asked for it may
#need another component, that dependency
#map is listed here...
COMPONENT_DEPENDENCIES = {
DB: [],
KEYSTONE_CLIENT: [],
RABBIT: [],
GLANCE: [KEYSTONE, DB],
KEYSTONE: [DB],
NOVA: [KEYSTONE, GLANCE, DB, RABBIT],
SWIFT: [],
HORIZON: [KEYSTONE_CLIENT],
QUANTUM: [],
}
#program
#actions
INSTALL = "install"
UNINSTALL = "uninstall"
START = "start"
STOP = "stop"
ACTIONS = [INSTALL, UNINSTALL, START, STOP]
#this is used to map an action to a useful string for
#the welcome display...
WELCOME_MAP = {
INSTALL: "Installer",
UNINSTALL: "Uninstaller",
START: "Runner",
STOP: "Stopper",
}
#where we should get the config file...
STACK_CONFIG_DIR = "conf"
STACK_CFG_LOC = joinpths(STACK_CONFIG_DIR, "stack.ini")
#this regex is how we match python platform output to
#a known constant
KNOWN_OS = {
UBUNTU11: '/Ubuntu(.*)oneiric/i',
RHEL6: '/redhat-6\.(\d+)/i',
}
#the pip files that each component
#needs
PIP_MAP = {
NOVA:
[],
GLANCE:
[],
KEYSTONE:
[
joinpths(STACK_CONFIG_DIR, "pips", 'keystone.json'),
],
HORIZON:
[
joinpths(STACK_CONFIG_DIR, "pips", 'horizon.json'),
],
SWIFT:
[],
KEYSTONE_CLIENT:
[],
DB:
[],
RABBIT:
[],
}
#the pkg files that each component
#needs
PKG_MAP = {
NOVA:
[
joinpths(STACK_CONFIG_DIR, "pkgs", "nova.json"),
joinpths(STACK_CONFIG_DIR, "pkgs", "general.json"),
],
GLANCE:
[
joinpths(STACK_CONFIG_DIR, "pkgs", "general.json"),
joinpths(STACK_CONFIG_DIR, "pkgs", 'glance.json'),
],
KEYSTONE:
[
joinpths(STACK_CONFIG_DIR, "pkgs", "general.json"),
joinpths(STACK_CONFIG_DIR, "pkgs", 'keystone.json'),
],
HORIZON:
[
joinpths(STACK_CONFIG_DIR, "pkgs", "general.json"),
joinpths(STACK_CONFIG_DIR, "pkgs", 'horizon.json'),
],
SWIFT:
[
joinpths(STACK_CONFIG_DIR, "pkgs", "general.json"),
joinpths(STACK_CONFIG_DIR, "pkgs", 'swift.json'),
],
KEYSTONE_CLIENT:
[
joinpths(STACK_CONFIG_DIR, "pkgs", "keystone-client.json"),
],
DB:
[
joinpths(STACK_CONFIG_DIR, "pkgs", 'db.json'),
],
RABBIT:
[
joinpths(STACK_CONFIG_DIR, "pkgs", 'rabbitmq.json'),
],
}
#subdirs of a components dir
TRACE_DIR = "traces"
APP_DIR = "app"
CONFIG_DIR = "config"
#our ability to create regexes
#which is more like php, which is nicer
#for modifiers...
REGEX_MATCHER = re.compile("^/(.*?)/([a-z]*)$")
#these actions need to have there components depenencies
#to occur first (ie keystone starts before glance...)
DEP_ACTIONS_NEEDED = [START, STOP, INSTALL]
LOG = Logger.getLogger("install.util")
def resolve_dependencies(action, components):
if(action in DEP_ACTIONS_NEEDED):
new_components = list()
for c in components:
component_deps = list(set(fetch_deps(c)))
if(len(component_deps)):
new_components = new_components + component_deps
new_components.append(c)
return set(new_components)
else:
return set(components)
def execute_template(*cmds, **kargs):
if(not cmds or len(cmds) == 0):
return
params_replacements = kargs.pop('params')
ignore_missing = kargs.pop('ignore_missing', False)
outs = dict()
for cmdinfo in cmds:
cmd_to_run_templ = cmdinfo.get("cmd")
cmd_to_run = list()
for piece in cmd_to_run_templ:
if(params_replacements and len(params_replacements)):
cmd_to_run.append(param_replace(piece, params_replacements,
ignore_missing=ignore_missing))
else:
cmd_to_run.append(piece)
stdin_templ = cmdinfo.get('stdin')
stdin = None
if(stdin_templ and len(stdin_templ)):
stdin_full = list()
for piece in stdin_templ:
if(params_replacements and len(params_replacements)):
stdin_full.append(param_replace(piece, params_replacements,
ignore_missing=ignore_missing))
else:
stdin_full.append(piece)
stdin = joinlinesep(*stdin_full)
root_run = cmdinfo.get('run_as_root', False)
execute(*cmd_to_run, run_as_root=root_run, process_input=stdin, **kargs)
def fetch_deps(component, add=False):
if(add):
deps = list([component])
else:
deps = list()
cdeps = COMPONENT_DEPENDENCIES.get(component)
if(cdeps and len(cdeps)):
for d in cdeps:
deps = deps + fetch_deps(d, True)
return deps
def prioritize_components(components):
#get the right component order (by priority)
mporder = dict()
for c in components:
priority = NAMES_PRIORITY.get(c)
if(priority == None):
priority = sys.maxint
mporder[c] = priority
#sort by priority value
priority_order = sorted(mporder.iteritems(), key=operator.itemgetter(1))
#extract the right order
component_order = [x[0] for x in priority_order]
return component_order
def component_pths(root, compnent_type):
component_root = joinpths(root, compnent_type)
tracedir = joinpths(component_root, TRACE_DIR)
appdir = joinpths(component_root, APP_DIR)
cfgdir = joinpths(component_root, CONFIG_DIR)
out = dict()
out['root_dir'] = component_root
out['trace_dir'] = tracedir
out['app_dir'] = appdir
out['config_dir'] = cfgdir
return out
def load_json(fn):
data = load_file(fn)
lines = data.splitlines()
new_lines = list()
for line in lines:
if(line.lstrip().startswith('#')):
continue
new_lines.append(line)
data = joinlinesep(*new_lines)
return json.loads(data)
def get_host_ip(cfg=None):
ip = None
if(cfg):
cfg_ip = cfg.get('default', 'host_ip')
if(cfg_ip and len(cfg_ip)):
ip = cfg_ip
if(ip == None):
interfaces = get_interfaces()
def_info = interfaces.get(DEFAULT_NET_INTERFACE)
if(def_info):
ipinfo = def_info.get(DEFAULT_NET_INTERFACE_IP_VERSION)
if(ipinfo):
ip = ipinfo.get('addr')
LOG.debug("Got host ip %s" % (ip))
return ip
def get_interfaces():
interfaces = dict()
for intfc in netifaces.interfaces():
interface_info = dict()
interface_addresses = netifaces.ifaddresses(intfc)
ip6 = interface_addresses.get(netifaces.AF_INET6)
if(ip6 and len(ip6)):
#just take the first
interface_info[IPV6] = ip6[0]
ip4 = interface_addresses.get(netifaces.AF_INET)
if(ip4 and len(ip4)):
#just take the first
interface_info[IPV4] = ip4[0]
#there are others but this is good for now
interfaces[intfc] = interface_info
return interfaces
def create_regex(format):
mtch = REGEX_MATCHER.match(format)
if(not mtch):
raise BadRegexException("Badly formatted pre-regex: " + format)
else:
toberegex = mtch.group(1)
options = mtch.group(2).lower()
flags = 0
if(options.find("i") != -1):
flags = flags | re.IGNORECASE
if(options.find("m") != -1):
flags = flags | re.MULTILINE
if(options.find("u") != -1):
flags = flags | re.UNICODE
return re.compile(toberegex, flags)
def determine_os():
os = None
plt = platform.platform()
for aos, pat in KNOWN_OS.items():
reg = create_regex(pat)
if(reg.search(plt)):
os = aos
break
return (os, plt)
def get_pip_list(distro, component):
LOG.info("Getting pip packages for distro %s and component %s." % (distro, component))
all_pkgs = dict()
fns = PIP_MAP.get(component)
if(fns == None):
return all_pkgs
#load + merge them
for fn in fns:
js = load_json(fn)
distro_pkgs = js.get(distro)
if(distro_pkgs and len(distro_pkgs)):
combined = dict(all_pkgs)
for (pkgname, pkginfo) in distro_pkgs.items():
#we currently just overwrite
combined[pkgname] = pkginfo
all_pkgs = combined
return all_pkgs
def get_pkg_list(distro, component):
LOG.info("Getting packages for distro %s and component %s." % (distro, component))
all_pkgs = dict()
fns = PKG_MAP.get(component)
if(fns == None):
return all_pkgs
#load + merge them
for fn in fns:
js = load_json(fn)
distro_pkgs = js.get(distro)
if(distro_pkgs and len(distro_pkgs)):
combined = dict(all_pkgs)
for (pkgname, pkginfo) in distro_pkgs.items():
if(pkgname in all_pkgs.keys()):
oldpkginfo = all_pkgs.get(pkgname) or dict()
newpkginfo = dict(oldpkginfo)
for (infokey, infovalue) in pkginfo.items():
#this is expected to be a list of cmd actions
#so merge that accordingly
if(infokey == PRE_INSTALL or infokey == POST_INSTALL):
oldinstalllist = oldpkginfo.get(infokey) or []
infovalue = oldinstalllist + infovalue
newpkginfo[infokey] = infovalue
combined[pkgname] = newpkginfo
else:
combined[pkgname] = pkginfo
all_pkgs = combined
return all_pkgs
def joinlinesep(*pieces):
return os.linesep.join(pieces)
def param_replace(text, replacements, ignore_missing=False):
if(not replacements or len(replacements) == 0):
return text
if(len(text) == 0):
return text
if(ignore_missing):
LOG.debug("Performing parameter replacements (ignoring missing) on %s" % (text))
else:
LOG.debug("Performing parameter replacements (not ignoring missing) on %s" % (text))
def replacer(m):
org = m.group(0)
name = m.group(1)
v = replacements.get(name)
if(v == None and ignore_missing):
v = org
elif(v == None and not ignore_missing):
msg = "No replacement found for parameter %s" % (org)
raise NoReplacementException(msg)
else:
LOG.debug("Replacing [%s] with [%s]" % (org, str(v)))
return str(v)
return re.sub(PARAM_SUB_REGEX, replacer, text)
def welcome(program_action):
formatted_action = WELCOME_MAP.get(program_action)
lower = "!%s v%s!" % (formatted_action.upper(), VERSION)
welcome = r'''
___ ____ _____ _ _ ____ _____ _ ____ _ __
/ _ \| _ \| ____| \ | / ___|_ _|/ \ / ___| |/ /
| | | | |_) | _| | \| \___ \ | | / _ \| | | ' /
| |_| | __/| |___| |\ |___) || |/ ___ \ |___| . \
\___/|_| |_____|_| \_|____/ |_/_/ \_\____|_|\_\
'''
welcome = " " + welcome.strip()
lower_out = (" " * 17) + colored(DEVSTACK, 'green') + ": " + colored(lower, 'blue')
msg = welcome + os.linesep + lower_out
print(msg)
def rcf8222date():
return strftime("%a, %d %b %Y %H:%M:%S", localtime())
def fsSafeDate():
return strftime("%m_%d_%G-%H-%M-%S", localtime())