183 lines
5.3 KiB
Python
183 lines
5.3 KiB
Python
#!/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():
|
|
try:
|
|
log.debug('Attempting connection to {0}:{1}'.format(host, port))
|
|
s = socket.create_connection((host, port), 1)
|
|
except:
|
|
log.debug('Connection failed')
|
|
time.sleep(1)
|
|
else:
|
|
log.debug('Connection succeeded')
|
|
s.shutdown(socket.SHUT_RDWR)
|
|
s.close()
|
|
break
|
|
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(error))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|