Files
anvil/devstack/Util.py

429 lines
12 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
from Exceptions import (BadRegexException,
NoReplacementException,
FileException)
import Logger
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"
DB = "db"
RABBIT = "rabbit"
COMPONENT_NAMES = [NOVA, GLANCE, QUANTUM,
SWIFT, HORIZON, KEYSTONE,
DB, RABBIT]
#ordering of install (lower priority means earlier)
NAMES_PRIORITY = {
DB: 1,
RABBIT: 1,
KEYSTONE: 2,
GLANCE: 3,
QUANTUM: 4,
NOVA: 5,
SWIFT: 6,
HORIZON: 7,
}
#when a component is asked for it may
#need another component, that dependency
#map is listed here...
COMPONENT_DEPENDENCIES = {
DB: [],
RABBIT: [],
GLANCE: [KEYSTONE, DB],
KEYSTONE: [DB],
NOVA: [KEYSTONE, GLANCE, DB, RABBIT],
SWIFT: [],
HORIZON: [],
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:
[],
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'),
],
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]*)$")
LOG = Logger.getLogger("install.util")
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 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())