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
parent
9ed2cca8e2
commit
5fa572cb72
|
@ -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()
|
Loading…
Reference in New Issue