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