client/bindir/cloud-update-xenserver-licenses.in (150 lines of code) (raw):

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