mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
355 lines
13 KiB
Python
Executable File
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)
|