startvms: Script to start VMs after a network service comes up

This script waits for a network service on a remote host (default LDAP on
Arcturus) to become available and then starts one or more virtual machines. It
is used on Atria to start Bellatrix and Rigel, since they need Active
Directory to be up in order to boot correctly.
master
Dustin C. Hatch 2014-12-03 19:05:53 -06:00
parent 9ed2cca8e2
commit 5fa572cb72
1 changed files with 185 additions and 0 deletions

185
startvms.py Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env python
'''Start virtual machines when a network service becomes available
Example OpenRC init script::
#!/sbin/runscript
depend() {
need libvirtd
}
start() {
ebegin "Starting additional virtual machines"
startvms -l ${STARTVMS_LOG:-/var/log/startvms.log} ${STARTVMS_ARGS} \
${VIRTUAL_MACHINES}
eend $?
}
'''
import argparse
import datetime
import libvirt
import logging
import os
import signal
import socket
import sys
import time
LIBVIRT_DEFAULT_URI = os.environ.get('LIBVIRT_DEFAULT_URI', 'qemu:///session')
WAIT_FOR_HOST = "arcturus.pyrocufflink.jazz"
WAIT_FOR_PORT = 389
WAIT_TIMEOUT = 300 # seconds
log = logging.getLogger('startvms')
class Timeout(Exception):
pass
class MaxLevelFilter(logging.Filter):
def __init__(self, max_level=logging.WARNING, *args, **kwargs):
logging.Filter.__init__(self, *args, **kwargs)
self.max_level = max_level
def filter(self, record):
return record.levelno <= self.max_level
def _parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--debug', action='store_true', default=False)
parser.add_argument('--foreground', '-F', action='store_true',
default=False,
help='Do not fork into the background before waiting')
parser.add_argument('--log-file', '-l', metavar='FILENAME',
help='Send log messages to FILENAME')
parser.add_argument('--connect', '-c', metavar='URI', dest='uri',
default=LIBVIRT_DEFAULT_URI,
help='libvirt connection URI')
parser.add_argument('--timeout', '-t', default=WAIT_TIMEOUT, type=int,
metavar='SECONDS',
help='Wait a maximum of SECONDS')
parser.add_argument('--port', '-p', default=WAIT_FOR_PORT, type=int,
help='Wait for PORT on remote host')
parser.add_argument('--host', '-H', default=WAIT_FOR_HOST,
help='Wait for HOST to come up before starting VMs')
parser.add_argument('vms', nargs='+', metavar='VMNAME',
help='Virtual machine(s) to start')
return parser.parse_args()
def _setup_logging(debug=False, log_file=None):
root_log = logging.getLogger()
root_log.setLevel(logging.NOTSET)
stderr_handler = logging.StreamHandler(sys.stderr)
stderr_handler.setLevel(logging.ERROR)
root_log.addHandler(stderr_handler)
if log_file:
file_handler = logging.FileHandler(log_file)
if debug:
file_handler.setLevel(logging.DEBUG)
else:
file_handler.setLevel(logging.INFO)
file_formatter = logging.Formatter(
'%(asctime)s [%(name)s] %(levelname)s %(message)s'
)
file_handler.setFormatter(file_formatter)
root_log.addHandler(file_handler)
else:
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.addFilter(MaxLevelFilter())
if debug:
stdout_handler.setLevel(logging.DEBUG)
else:
stdout_handler.setLevel(logging.WARNING)
root_log.addHandler(stdout_handler)
def _daemonize():
if os.fork():
raise SystemExit(0)
signal.signal(signal.SIGHUP, signal.SIG_IGN)
os.setsid()
os.chdir('/')
if os.fork():
raise SystemExit(0)
def wait_for(host, port, timeout=None):
log.info('Waiting {time} for port {port} on {host}'.format(
time='{0} seconds'.format(timeout) if timeout else 'indefinitely',
port=port,
host=host,
))
if timeout is not None:
end = datetime.datetime.now() + datetime.timedelta(seconds=timeout)
keep_going = lambda: datetime.datetime.now() < end
else:
keep_going = lambda: True
while keep_going():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
try:
log.debug('Attempting connection to {0}:{1}'.format(host, port))
s.connect((host, port))
except:
log.debug('Connection failed')
time.sleep(1)
else:
log.debug('Connection succeeded')
s.shutdown(socket.SHUT_RDWR)
break
finally:
s.close()
else:
raise Timeout('Timed out waiting for port {port} on {host}'.format(
port=port,
host=host,
))
def start_vm(conn, name):
domain = conn.lookupByName(name)
if domain.isActive():
log.debug('Domain {0} is already active, skipping'.format(name))
else:
log.info('Starting domain {0}'.format(name))
domain.create()
def main():
args = _parse_args()
if not args.foreground:
_daemonize()
_setup_logging(args.debug, args.log_file)
try:
wait_for(args.host, args.port, args.timeout)
except Timeout as e:
log.error('{0}'.format(e))
raise SystemExit(os.EX_NOHOST)
try:
conn = libvirt.open(args.uri)
except libvirt.libvirtError as e:
log.error('{0}'.format(e))
raise SystemExit(os.EX_UNAVAILABLE)
error = False
for vm in args.vms:
try:
start_vm(conn, vm)
except libvirt.libvirtError as e:
error = True
log.error('{0}'.format(e))
continue
raise SystemExit(int(not error))
if __name__ == '__main__':
main()