mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-11-04 00:02:37 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			225 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			225 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/python3 -W ignore::DeprecationWarning
 | 
						|
# -*- coding: utf-8 -*-
 | 
						|
# Licensed to the Apache Software Foundation (ASF) under one
 | 
						|
# or more contributor license agreements.  See the NOTICE file
 | 
						|
# distributed with this work for additional information
 | 
						|
# regarding copyright ownership.  The ASF licenses this file
 | 
						|
# to you 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.
 | 
						|
 | 
						|
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import glob
 | 
						|
from random import choice
 | 
						|
import string
 | 
						|
from optparse import OptionParser
 | 
						|
import mysql.connector
 | 
						|
import paramiko
 | 
						|
from threading import Thread
 | 
						|
 | 
						|
# ---- 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_call, CalledProcessError, read_properties
 | 
						|
 | 
						|
cfg = "@MSCONF@/db.properties"
 | 
						|
 | 
						|
# ---------------------- option parsing and command line checks ------------------------
 | 
						|
 | 
						|
 | 
						|
usage = """%prog <license file> <-a | host names / IP addresses...>
 | 
						|
 | 
						|
This command deploys the license file specified in the command line into a specific XenServer host or all XenServer hosts known to the management server."""
 | 
						|
 | 
						|
parser = OptionParser(usage=usage)
 | 
						|
parser.add_option(
 | 
						|
    "-a",
 | 
						|
    "--all",
 | 
						|
    action="store_true",
 | 
						|
    dest="all",
 | 
						|
    default=False,
 | 
						|
    help="deploy to all known hosts rather that a single host",
 | 
						|
)
 | 
						|
 | 
						|
# ------------------ functions --------------------
 | 
						|
 | 
						|
 | 
						|
def e(msg):
 | 
						|
    parser.error(msg)
 | 
						|
 | 
						|
 | 
						|
def getknownhosts(host, username, password):
 | 
						|
    conn = mysql.connector.connect(host=host, user=username, password=password)
 | 
						|
    cur = conn.cursor()
 | 
						|
    cur.execute(
 | 
						|
        "SELECT h.private_ip_address,d.value FROM cloud.host h inner join cloud.host_details d on (h.id = d.host_id) where d.name = 'username' and setup = 1"
 | 
						|
    )
 | 
						|
    usernames = dict(cur.fetchall())
 | 
						|
    cur.execute(
 | 
						|
        "SELECT h.private_ip_address,d.value FROM cloud.host h inner join cloud.host_details d on (h.id = d.host_id) where d.name = 'password' and setup = 1"
 | 
						|
    )
 | 
						|
    passwords = dict(cur.fetchall())
 | 
						|
    creds = dict([[x, (usernames[x], passwords[x])] for x in list(usernames.keys())])
 | 
						|
    cur.close()
 | 
						|
    conn.close()
 | 
						|
    return creds
 | 
						|
 | 
						|
 | 
						|
def splitlast(string, splitter):
 | 
						|
    splitted = string.split(splitter)
 | 
						|
    first, last = splitter.join(splitted[:-1]), splitted[-1]
 | 
						|
    return first, last
 | 
						|
 | 
						|
 | 
						|
def parseuserpwfromhosts(hosts):
 | 
						|
    creds = {}
 | 
						|
    for host in hosts:
 | 
						|
        user = "root"
 | 
						|
        password = ""
 | 
						|
        if "@" in host:
 | 
						|
            user, host = splitlast(host, "@")
 | 
						|
            if ":" in user:
 | 
						|
                user, password = splitlast(user, ":")
 | 
						|
        creds[host] = (user, password)
 | 
						|
    return creds
 | 
						|
 | 
						|
 | 
						|
class XenServerConfigurator(Thread):
 | 
						|
 | 
						|
    def __init__(self, host, user, password, keyfiledata):
 | 
						|
        Thread.__init__(self)
 | 
						|
        self.host = host
 | 
						|
        self.user = user
 | 
						|
        self.password = password
 | 
						|
        self.keyfiledata = keyfiledata
 | 
						|
        self.retval = None  # means all's good
 | 
						|
        self.stdout = ""
 | 
						|
        self.stderr = ""
 | 
						|
        self.state = "initialized"
 | 
						|
 | 
						|
    def run(self):
 | 
						|
        try:
 | 
						|
            self.state = "running"
 | 
						|
            c = paramiko.SSHClient()
 | 
						|
            c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 | 
						|
            c.connect(self.host, username=self.user, password=self.password)
 | 
						|
            sftp = c.open_sftp()
 | 
						|
            sftp.chdir("/tmp")
 | 
						|
            f = sftp.open("xen-license", "w")
 | 
						|
            f.write(self.keyfiledata)
 | 
						|
            f.close()
 | 
						|
            sftp.close()
 | 
						|
            stdin, stdout, stderr = c.exec_command(
 | 
						|
                "xe host-license-add license-file=/tmp/xen-license"
 | 
						|
            )
 | 
						|
            c.exec_command("false")
 | 
						|
            self.stdout = stdout.read(-1)
 | 
						|
            self.stderr = stderr.read(-1)
 | 
						|
            self.retval = stdin.channel.recv_exit_status()
 | 
						|
            c.close()
 | 
						|
            if self.retval != 0:
 | 
						|
                self.state = "failed"
 | 
						|
            else:
 | 
						|
                self.state = "finished"
 | 
						|
 | 
						|
        except Exception as e:
 | 
						|
            self.state = "failed"
 | 
						|
            self.retval = e
 | 
						|
            # raise
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        if self.state == "failed":
 | 
						|
            return "<%s XenServerConfigurator on %s@%s: %s>" % (
 | 
						|
                self.state,
 | 
						|
                self.user,
 | 
						|
                self.host,
 | 
						|
                str(self.retval),
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            return "<%s XenServerConfigurator on %s@%s>" % (
 | 
						|
                self.state,
 | 
						|
                self.user,
 | 
						|
                self.host,
 | 
						|
            )
 | 
						|
 | 
						|
 | 
						|
# ------------- actual code --------------------
 | 
						|
 | 
						|
(options, args) = parser.parse_args()
 | 
						|
 | 
						|
try:
 | 
						|
    licensefile, args = args[0], args[1:]
 | 
						|
except IndexError:
 | 
						|
    e("The first argument must be the license file to use")
 | 
						|
 | 
						|
if options.all:
 | 
						|
    if len(args) != 0:
 | 
						|
        e("IP addresses cannot be specified if -a is specified")
 | 
						|
    config = read_properties(cfg)
 | 
						|
    creds = getknownhosts(
 | 
						|
        config["db.cloud.host"],
 | 
						|
        config["db.cloud.username"],
 | 
						|
        config["db.cloud.password"],
 | 
						|
    )
 | 
						|
    hosts = list(creds.keys())
 | 
						|
else:
 | 
						|
    if not args:
 | 
						|
        e("You must specify at least one IP address, or -a")
 | 
						|
    hosts = args
 | 
						|
    creds = parseuserpwfromhosts(hosts)
 | 
						|
 | 
						|
try:
 | 
						|
    keyfiledata = file(licensefile).read(-1)
 | 
						|
except OSError as e:
 | 
						|
    sys.stderr.write("The file %s cannot be opened" % licensefile)
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
configurators = []
 | 
						|
for host, (user, password) in list(creds.items()):
 | 
						|
    configurators.append(XenServerConfigurator(host, user, password, keyfiledata))
 | 
						|
 | 
						|
 | 
						|
for c in configurators:
 | 
						|
    c.start()
 | 
						|
 | 
						|
for c in configurators:
 | 
						|
    print(c.host + "...", end=" ")
 | 
						|
    c.join()
 | 
						|
    if c.state == "failed":
 | 
						|
        if c.retval:
 | 
						|
            msg = "failed with return code %s: %s%s" % (c.retval, c.stdout, c.stderr)
 | 
						|
            msg = msg.strip()
 | 
						|
            print(msg)
 | 
						|
        else:
 | 
						|
            print("failed: %s" % c.retval)
 | 
						|
    else:
 | 
						|
        print("done")
 | 
						|
 | 
						|
successes = len([a for a in configurators if not a.state == "failed"])
 | 
						|
failures = len([a for a in configurators if a.state == "failed"])
 | 
						|
 | 
						|
print("%3s successes" % successes)
 | 
						|
print("%3s failures" % failures)
 |