#!/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 <-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)