cloudstack/setup/bindir/cloud-setup-databases.in
Manuel Amador (Rudd-O) 05c020e1f6 Source code committed
2010-08-11 09:13:29 -07:00

355 lines
13 KiB
Python
Executable File

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import subprocess
import glob
from random import choice
import string
from optparse import OptionParser
import commands
# ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ----
# ---- We do this so cloud_utils can be looked up in the following order:
# ---- 1) Sources directory
# ---- 2) waf configured PYTHONDIR
# ---- 3) System Python path
for pythonpath in (
"@PYTHONDIR@",
os.path.join(os.path.dirname(__file__),os.path.pardir,os.path.pardir,"python","lib"),
):
if os.path.isdir(pythonpath): sys.path.insert(0,pythonpath)
# ---- End snippet of code ----
from cloud_utils import check_selinux, CheckFailed, resolves_to_ipv6
try:
from subprocess import check_call as check_call
from subprocess import CalledProcessError
except ImportError:
def check_call(*popenargs, **kwargs):
import subprocess
retcode = subprocess.call(*popenargs, **kwargs)
cmd = kwargs.get("args")
if cmd is None: cmd = popenargs[0]
if retcode: raise CalledProcessError(retcode, cmd)
return retcode
class CalledProcessError(Exception):
def __init__(self, returncode, cmd):
self.returncode = returncode ; self.cmd = cmd
def __str__(self): return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
#---------------------- option parsing and command line checks ------------------------
usage = """%prog user:password@mysqlhost:port <kvm|xenserver> [--deploy-as=rootuser:rootpassword] [--auto=/path/to/server-setup.xml]
This command sets up the CloudStack Management Server and CloudStack Usage Server database configuration (connection credentials and host information) based on the first argument.
If the the --deploy-as option is present, this command will also connect to the database using the administrative credentials specified as the value for the --deploy-as argument, construct the database environment needed to run the CloudStack Management Server, and alter the password specified for the user in the first argument. In this case, the user name specified in --deploy-as= cannot be the same as the user name specified for the connection credentials that the CloudStack Management Server will be set up with.
If a server-setup.xml cloud setup information file is specified with the --auto option, this command will also construct a customized database environment according to the cloud setup information in the file.
The port and the password are optional and can be left out.. If host is omitted altogether, it will default to localhost.
Examples:
%prog cloud:secret xenserver
sets user cloud and password 'secret' up in
@MSCONF@/db.properties, using localhost as the
database server and XenServer as the VT technology
%prog sheng:rules@192.168.1.1 kvm
sets these credentials up in @MSCONF@/db.properties
prepares the system for KVM hosts
%prog alex:founder@1.2.3.4 --deploy-as=root:nonsense kvm
sets alex up as the MySQL user, then connects as the root user
with password 'nonsense', and recreates the databases, creating
the user alex with password 'founder' as necessary
prepares the system for KVM hosts
%prog alena:tests@5.6.7.8 --deploy-as=root:nonsense --auto=/root/server-setup.xml kvm
sets alena up as the MySQL user, then connects as the root user
with password 'nonsense' to server 5.6.7.8, then recreates the
databases and sets up the alena user, then performs an automated
database setup using the information in server-setup.xml
prepares the system for KVM hosts
"""
parser = OptionParser(usage=usage)
parser.add_option("-v", "--debug", action="store_true", dest="debug", default=False,
help="If enabled, print the commands it will run as they run")
parser.add_option("-d", "--deploy-as", action="store", type="string", dest="rootcreds", default="",
help="Colon-separated user name and password of a MySQL user with administrative privileges")
parser.add_option("-a", "--auto", action="store", type="string", dest="serversetup", default="",
help="Path to an XML file describing an automated unattended cloud setup")
#------------------ functions --------------------
def e(msg): parser.error(msg)
def parse_userpw(crds):
stuff = crds.split(":")
if len(stuff) == 1:
user = stuff[0]
password = ''
else:
user,password = stuff[0],":".join(stuff[1:])
if len(user) < 1: e("User cannot be empty")
forbidden = "' \\`"
for f in forbidden:
if f in password: e("Password cannot have the %r character"%f)
if f in user: e("User cannot have the %r character"%f)
return user,password
def parse_hostport(crds):
stuff = crds.split(":")
if len(stuff) == 1:
host = stuff[0]
port = 3306
elif len(stuff) == 2:
host = stuff[0]
try: port = int(stuff[1])
except ValueError: e("Database port must be an integer")
if port < 1: e("Database port must be a positive integer")
else:
e("Invalid host and port specification")
if len(host) < 1: e("Host cannot be empty")
return host,port
def get_creds(parser,options,args):
arg = args[0]
stuff = arg.split("@")
if len(stuff) == 1: stuff.append("localhost")
creds,hostinfo = "@".join(stuff[:-1]),stuff[-1]
user,password = parse_userpw(creds)
host,port = parse_hostport(hostinfo)
return (user,password,host,port)
def run_mysql(text,user,password,host,port,extraargs=None):
cmd = ["mysql",
"--user=%s"%user,
"--host=%s"%host,
]
if password:
cmd.append("--password=%s"%password)
if password:
cmd.append("--port=%s"%port)
if extraargs:
cmd.extend(extraargs)
p = subprocess.Popen(cmd,stdin=subprocess.PIPE)
p.communicate(text)
ret = p.wait()
if ret != 0: raise CalledProcessError(ret,cmd)
def ifaces():
status,lines = commands.getstatusoutput('LANG=C /sbin/ip address show')
assert status == 0
lines = [ l.split()[1][:-1] for l in lines.splitlines() if not l.startswith(' ') ]
if 'lo' in lines: lines.remove('lo')
return lines
def ip(iface):
status,lines = commands.getstatusoutput('LANG=C /sbin/ip address show %s'%iface)
assert status == 0
lines = [ l for l in lines.splitlines() if l.startswith(' inet ') ]
if not lines: return None
toks = lines[0].split()
ip = toks[1]
if '/' in ip: ip = ip.split("/")[0]
return ip
def firstip(ifs):
ips = [ ip(iface) for iface in ifs if ip(iface) ]
if not ips: return None
return ips[0]
def checkutc():
import time
if time.tzname != ('UTC','UTC') and time.tzname != ('GMT','GMT'):
sys.stderr.write("The current time zone (standard,DST) of your machine is set to: %s\n"%(time.tzname,))
sys.stderr.write("You need to set your time zone to UTC and reboot your machine.\n")
sys.stderr.write("Use your operating system's time setup tools (timeconfig or\n")
sys.stderr.write("system-config-time) to set one.\n")
sys.exit(3)
def checkhostname():
output = subprocess.Popen(["hostname","--fqdn"],stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
output.communicate()
output.wait()
if output.returncode != 0:
sys.stderr.write("The host name of this computer does not resolve to an IP address.\n")
sys.stderr.write("Please use your operating system's network setup tools to fix this.\n")
sys.exit(3)
def checkdbserverhostname(host):
if resolves_to_ipv6(host,3306):
sys.stderr.write("%s resolves to an IPv6 address. The Cloud.com CloudStack does not support IPv6 yet.\n"%host)
sys.stderr.write("Please fix this issue in either /etc/hosts or your DNS configuration.\n")
sys.exit(3)
def checkselinux():
try:
check_selinux()
except CheckFailed,e:
sys.stderr.write("SELINUX is set to enforcing, please set it to permissive in /etc/selinux/config\n")
sys.stderr.write("then reboot the machine, after which you can run this program again.\n")
sys.exit(3)
def checknetwork():
ipaddr = firstip(ifaces())
if not ipaddr:
sys.stderr.write("We could not find any network configuration in this system.\n")
sys.stderr.write("Please set up networking properly, after which you can run this program again.\n")
sys.exit(3)
def setupconfigfile(fn,myipaddr,username,password,host,port):
#setup config file
dbproperties = file(fn).read().splitlines()
newdbp = []
for line in dbproperties:
if line.startswith("cluster.node.IP="): line="cluster.node.IP=%s"%myipaddr
for x in "username password host port".split():
if line.startswith("db.cloud.%s="%x): line="db.cloud.%s=%s"%(x,locals()[x])
if line.startswith("db.usage.%s="%x): line="db.usage.%s=%s"%(x,locals()[x])
newdbp.append(line)
file(fn,"w").write("\n".join(newdbp))
#------------- actual code --------------------
(options, args) = parser.parse_args()
if len(args) != 2: e("Wrong number of arguments")
if args[1] not in ["xenserver","kvm"]: e("You need to specify either xenserver or kvm after the database credentials")
virttech = args[1]
user,password,host,port= get_creds(parser,options,args)
rootuser,rootpassword = [None,None]
if options.rootcreds:
rootuser,rootpassword = parse_userpw(options.rootcreds)
if rootuser == user:
e("--deploy-as= user name cannot be the user name supplied for the connection credentials")
if options.serversetup and not options.rootcreds:
e("--auto= requires valid --deploy-as= credentials")
if options.serversetup and not os.path.isfile(options.serversetup):
e("%s is not a valid file"%options.serversetup)
dbfilepath = "@SETUPDATADIR@"
dbppaths = [ os.path.join("@MSCONF@","db.properties") ] # , os.path.join("@USAGESYSCONFDIR@","db.properties") ]
dbppaths = [ x for x in dbppaths if os.path.exists(x) ]
if not dbppaths:
print "No services to set up installed on this system. Refusing to continue."
sys.exit(21)
#run sanity checks
# checkutc()
checkdbserverhostname(host)
checkhostname()
try: checkselinux()
except OSError,e:
if e.errno == 2: pass
else: raise
checknetwork()
#initialize variables
ipaddr = firstip(ifaces())
if not ipaddr: ipaddr='127.0.0.1'
if rootuser:
print "Testing specified deployment credentials on server %s:%s"%(host,port)
try: run_mysql("SELECT * from mysql.user limit 0",rootuser,rootpassword,host,port)
except CalledProcessError:
print "The deployment credentials you specified are not valid. Refusing to continue."
sys.exit(19)
else:
print "Testing specified connection credentials on server %s:%s"%(host,port)
try: run_mysql("SELECT * from cloud.user limit 0",user,password,host,port)
except CalledProcessError:
print "The connection credentials you specified are not valid. Refusing to continue."
sys.exit(19)
print "Setting up user credentials in:"
for x in dbppaths:
print " -> %s ..."%x,
setupconfigfile(x,ipaddr,user,password,host,port)
print "done."
if rootuser:
#create database schema!
replacements = (
("CREATE USER cloud identified by 'cloud';",
""),
("cloud identified by 'cloud';",
"%s identified by '%s';"%(user,password)),
("cloud@`localhost` identified by 'cloud'",
"%s@`localhost` identified by '%s'"%(user,password)),
("cloud@`%` identified by 'cloud'",
"%s@`%%` identified by '%s'"%(user,password)),
("to cloud@`localhost`",
"to %s@`localhost`"%user),
("to cloud@`%`",
"to %s@`%%`"%user),
("TO cloud@`localhost`",
"to %s@`localhost`"%user),
("TO cloud@`%`",
"to %s@`%%`"%user),
("WHERE `User` = 'cloud' and host =",
"WHERE `User` = '%s' and host ="%user),
("DROP USER 'cloud'",
"DROP USER '%s'"%user),
("CALL `test`.`drop_user_if_exists`() ;",
""),
)
for f in ["create-database","create-schema","create-database-premium","create-schema-premium"]:
p = os.path.join(dbfilepath,"%s.sql"%f)
if not os.path.exists(p): continue
text = file(p).read()
for t,r in replacements: text = text.replace(t,r)
print "Applying file %s to the database on server %s:%s"%(p,host,port)
try: run_mysql(text,rootuser,rootpassword,host,port)
except CalledProcessError: sys.exit(20)
if options.serversetup:
systemjars = "@SYSTEMJARS@".split()
pipe = subprocess.Popen(["build-classpath"]+systemjars,stdout=subprocess.PIPE)
systemcp,throwaway = pipe.communicate()
systemcp = systemcp.strip()
if pipe.wait(): # this means that build-classpath failed miserably
systemcp = "@SYSTEMCLASSPATH@"
pcp = os.path.pathsep.join( glob.glob( os.path.join ( "@PREMIUMJAVADIR@" , "*" ) ) )
mscp = "@MSCLASSPATH@"
depscp = "@DEPSCLASSPATH@"
conf = os.path.dirname(dbppaths[0])
classpath = os.path.pathsep.join([pcp,systemcp,depscp,mscp,conf])
print "Performing unattended automated setup using file %s"%options.serversetup
cmd = ["java","-cp",classpath,"com.cloud.test.DatabaseConfig",options.serversetup]
if options.debug: print "Running command: %s"%" ".join(cmd)
try: check_call(cmd)
except CalledProcessError:
print "Automated setup could not be completed."
sys.exit(21)
else:
for f in ["server-setup"]:
p = os.path.join(dbfilepath,"%s.sql"%f)
text = file(p).read()
print "Applying file %s to the database on server %s:%s"%(p,host,port)
try: run_mysql(text,rootuser,rootpassword,host,port)
except CalledProcessError: sys.exit(22)
for f in ["templates.%s"%virttech,"create-index-fk"]:
p = os.path.join(dbfilepath,"%s.sql"%f)
text = file(p).read()
print "Applying file %s to the database on server %s:%s"%(p,host,port)
try: run_mysql(text,rootuser,rootpassword,host,port)
except CalledProcessError: sys.exit(22)