Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Boot output works, vm console subcommand added

  • Loading branch information...
commit 459f12058600860c54e357a51f30081c8f59c5f5 1 parent 7135066
@thejimmyg thejimmyg authored
View
2  CHANGELOG.txt
@@ -5,7 +5,7 @@ Changes
-----
* Completely re-worked ``vm`` command with ``start``, ``stop``, ``status``,
- ``clone``, ``mount`` and ``umount`` sub-command
+ ``clone``, ``console``, ``mount`` and ``umount`` sub-commands
* Different ``vmtmp`` directory for each image
* Fixed a bug with ``repo remove``
View
42 buildkit/command/vm/console.py
@@ -0,0 +1,42 @@
+"""\
+Connect to the serial terminal of a running VM
+"""
+
+import os
+import stacks
+import sys
+
+#
+# Helpers
+#
+
+from .status import full_info
+
+arg_specs = [
+ dict(
+ metavar='NAME',
+ help_msg='name of the VM image directory within /var/lib/buildkit/vm/',
+ ),
+]
+
+opt_specs_by_name = dict(
+)
+
+def run(cmd):
+ if os.geteuid() != 0:
+ cmd.err("This script must be run as root")
+ return 1
+ result = full_info([cmd.args[0]])
+ if result.error:
+ cmd.err(result.error)
+ return 2
+ serial = result.instances[0].serial
+ cmd.out('Connecting to %r ...', serial)
+ result = stacks.process(
+ [
+ 'socat', '-', serial,
+ ],
+ in_data = '\n\n',
+ )
+
+
View
172 buildkit/command/vm/start.py
@@ -7,33 +7,6 @@
"""
-# To get dmesg output on boot:
-#
-# cat << EOF | sudo tee /boot/grub/menu.lst
-# title Ubuntu 10.04.3 LTS, kernel 2.6.32-33-server
-# uuid cebcbb77-a2bf-4bb8-b1b3-1323f27ac7a4
-# -1323f27ac7a4 ro console=ttyS0,115200n8 console=tty0
-# initrd /boot/initrd.img-2.6.32-33-server
-#
-# title Ubuntu 10.04.3 LTS, kernel 2.6.32-33-server (recovery mode)
-# uuid cebcbb77-a2bf-4bb8-b1b3-1323f27ac7a4
-# -1323f27ac7a4 ro single
-# initrd /boot/initrd.img-2.6.32-33-server
-# EOF
-#
-# Something like this might work too:
-#
-# cat << EOF | sudo tee /etc/default/grub
-# GRUB_CMDLINE_LINUX="console=ttyS0,38400n8 console=tty0"
-# EOF
-#
-# sudo update-grub
-#
-# For shutdown support:
-#
-# sudo aptitude install acpid acpi-support
-
-
import os
import socket
import stacks
@@ -72,7 +45,7 @@ def determine_ip():
shell=True
)
exception = Exception('Could not determine the IP address, is the host connected to the internet? If so, please --ip option to specify the IP address manually')
- if result.retcode:
+ if result.retcode or result.stderr:
raise exception
ip = result.stdout.strip().split('\n')[0]
if not valid_ip(ip):
@@ -84,7 +57,7 @@ def gen_mac():
r"dd if=/dev/urandom count=1 2>/dev/null | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\).*$/\1:\2:\3:\4/'",
shell=True,
)
- if result.retcode:
+ if result.retcode or result.stderr:
raise Exception('Could not generate a mac address automatically, please use the --mac-address option')
mac = '52:54:'+result.stdout.strip()
return mac
@@ -94,7 +67,7 @@ def determine_interface(ip):
"/sbin/ifconfig | grep -B 1 '%s' | grep -v '%s' | awk '{ print $1}'"%(ip, ip),
shell=True,
)
- if result.retcode:
+ if result.retcode or result.stderr:
raise Exception('Could not determine the interface automatically, please use the --interface option')
interface = result.stdout.strip()
return interface
@@ -179,7 +152,7 @@ def create_bridge(interface, bridge_name, add=False):
cmd += '\nbrctl addbr %s'%bridge_name
cmd += '\nifconfig %s 192.168.100.254 netmask 255.255.255.0 up'%bridge_name
cmd += '\necho "1" > /proc/sys/net/ipv4/ip_forward'
- result = stacks.process(cmd, shell=True, echo=True)
+ result = stacks.process(cmd, shell=True, echo=True, merge=True)
return result
def create_tunnel(tunnel):
@@ -188,7 +161,7 @@ def create_tunnel(tunnel):
tunctl -b -u root -t %(tunnel)s
brctl addif br0 %(tunnel)s
ifconfig %(tunnel)s up 0.0.0.0 promisc'''%dict(tunnel=tunnel)
- result = stacks.process(cmd, shell=True, echo=True)
+ result = stacks.process(cmd, shell=True, echo=True, merge=True)
return result
def buildkit_apt_cache_installed():
@@ -241,6 +214,7 @@ def update_image(image, host_ip, use_apt_proxy=True):
bash /etc/buildkit_on_start.sh
echo "done."
fi
+ echo "Buildkit exec_on_boot_completed"
;;
stop)
;;
@@ -276,6 +250,26 @@ def update_image(image, host_ip, use_apt_proxy=True):
exec /sbin/getty -L 38400 ttyS0 vt102
''')
fp.close()
+ fp = open(os.path.join(path, 'boot/grub/menu.lst'), 'r')
+ data = fp.read()
+ fp.close()
+ if "quiet splash" in data:
+ data = data.replace("quiet splash", "console=ttyS0,115200n8 console=tty0")
+ fp = open(os.path.join(path, 'boot/grub/menu.lst'), 'w')
+ fp.write(data)
+ fp.close()
+ # Now we need to enter a chroot and update the image
+ result = stacks.process(
+ [
+ 'chroot',
+ path,
+ 'update-grub',
+ ],
+ echo=True,
+ merge=True,
+ )
+ if result.retcode:
+ raise Exception('Could not mount the image %s. %s\n'%(image, result.stderr))
def mount_image(image, path):
if not os.path.exists(path):
@@ -287,6 +281,7 @@ def mount_image(image, path):
if files:
if len(files) > 1:
# Must be mounted
+ raise Exception('The VM already appears to be mounted at %r'%path)
umount_image(path)
elif len(files) > 0 and files[0] == 'vm.info':
os.remove(os.path.join(path, 'vm.info'))
@@ -305,7 +300,7 @@ def umount_image(path):
shell=True,
)
if result.retcode:
- raise Exception('Could not unmount the image %s. %s\n'%(image, result.stderr))
+ raise Exception('Could not unmount the path %s. %s\n'%(path, result.stderr))
#
@@ -421,13 +416,13 @@ def parse_file_specs(value):
metavar='NAME',
#converter=[gen_tunnel_if_missing],
),
- copy_dir = dict(
- flags=['-d', '--copy-dir'],
- help_msg='Copy a directory into the VM before it boots, removing the directory if it already exists. DIR_SPEC should be in the format \'SRC -> DST\' where SRC is the source path to the file and DST is the full destination path on the VM drive, begining with a / character.',
- metavar='DIR_SPEC',
- multiple=True,
- converter=[parse_dir_specs],
- ),
+ #copy_dir = dict(
+ # flags=['-d', '--copy-dir'],
+ # help_msg='Copy a directory into the VM before it boots, removing the directory if it already exists. DIR_SPEC should be in the format \'SRC -> DST\' where SRC is the source path to the file and DST is the full destination path on the VM drive, begining with a / character.',
+ # metavar='DIR_SPEC',
+ # multiple=True,
+ # converter=[parse_dir_specs],
+ #),
copy_file = dict(
flags=['-f', '--copy-file'],
help_msg='Copy a file into the VM before it boots. FILE_SPEC should be in the format \'SRC -> DST\' where SRC is the source path to the file and DST is the full destination path on the VM drive, begining with a / character. NOTE: /tmp directories are sometimes cleared during the boot process so you are best off copying files to other directories if you want them to be available at the end of the boot process.',
@@ -448,21 +443,27 @@ def parse_file_specs(value):
),
exec_on_boot = dict(
flags=['-e', '--exec-on-boot'],
- help_msg='A set of bash commands to be exectued at the end of the boot process, after files and directories have been copied',
+ help_msg='A set of bash commands to be exectued at the end of the boot process, after files and directories have been copied. If you are using bash you\'ll need to be careful about escaping, here\'s a correct example with newlines: --exec-on-boot=$\'echo 1\\nsleep 2\\necho 2\'',
metavar='COMMANDS',
),
- #wait_for_boot = dict(
- # flags=['--wait-for-boot'],
- # help_msg='Don\'t exit until the system is booted and the login prompt is displayed',
- #),
+ dont_wait = dict(
+ flags=['--dont-wait'],
+ help_msg='Return immediately, don\'t wait for the VM\'s wait message. See also --wait-message.',
+ ),
+ wait_message = dict(
+ metavar='MESSAGE',
+ flags=['--wait-message'],
+ help_msg='The message we wait for if --wait is specified. Defaults to the string output when the buildkit service finishes starting (this is usually the last service started)',
+ default='Buildkit exec_on_boot_completed',
+ ),
graphics = dict(
flags=['--graphics'],
help_msg='Also load a window for the VM. Note: If you do this, the start command won\'t return until the window is closed when the VM exits.',
),
- #console = dict(
- # flags=['--console'],
- # help_msg='Drop the user into a login prompt after boot',
- #),
+ no_console = dict(
+ flags=['--no-console'],
+ help_msg='Don\'t show boot output during start up',
+ ),
)
def run(cmd):
@@ -510,24 +511,48 @@ def run(cmd):
mount_image("/var/lib/buildkit/vm/%s/disk.raw"%cmd.args[0], path)
update_image(cmd.args[0], cmd.opts.ip, use_apt_proxy=cmd.opts.use_apt_proxy)
for spec in cmd.opts.copy_file:
+ cmd.out('Copying %r to %r ...', spec.src, os.path.join(path, spec.dst[1:]))
shutil.copy2(spec.src, os.path.join(path, spec.dst[1:]))
+ cmd.out('done.')
+ fp = open(os.path.join(path, 'etc/buildkit_on_start.sh'), 'w')
+ fp.write('#!/bin/sh\n\n')
if cmd.opts.exec_on_boot:
- fp = open(os.path.join(path, 'etc/buildkit_on_start.sh'), 'w')
- fp.write('#!/bin/sh\n\n')
fp.write(cmd.opts.exec_on_boot)
- fp.close()
- os.chmod(os.path.join(path, 'etc/buildkit_on_start.sh'), stat.S_IEXEC)
+ fp.close()
+ os.chmod(os.path.join(path, 'etc/buildkit_on_start.sh'), stat.S_IEXEC)
umount_image(path)
opts = cmd.opts.copy()
opts['tunnel'] = tunnel
cmd.out("Starting VM "+cmd.args[0]+" on %(tunnel)s to %(bridge_name)s with MAC %(mac_address)s ..."%opts)
- def out(fh, stdin, output, exit):
- pass
+
+ #import signal
+ #class Alarm(Exception):
+ # pass
+ #def alarm_handler(signum, frame):
+ # raise Alarm
+ #signal.signal(signal.SIGALRM, alarm_handler)
+ #signal.alarm(1)
+
monitor = []
serial = []
+
+ def out(fh, stdin, output, exit):
+ pass
+ #while not monitor or not serial:
+ # print "out(%s %s %s)\n" %(monitor, serial, exit)
+ # line = fh.readline()
+ # if not line:
+ # exit.append(11)
+ # break
+
def err(fh, stdin, output, exit):
while not monitor or not serial:
+ #print "err(%s %s %s)\n" %(monitor, serial, exit)
line = fh.readline()
+ if not line:
+ exit.append(11)
+ break
+ #import pdb; pdb.set_trace()
if line.startswith('char device redirected to '):
device = line[len('char device redirected to '):-1]
if monitor:
@@ -535,9 +560,12 @@ def err(fh, stdin, output, exit):
else:
monitor.append(device)
else:
+ if not cmd.opts.no_console:
+ cmd.out(line)
output.write(line)
if monitor and serial:
exit.append(10)
+
if not cmd.opts.graphics:
extra.append('-nographic')
result = stacks.process(
@@ -574,11 +602,31 @@ def err(fh, stdin, output, exit):
cmd.out('Started successfully with pid %r'%result.pid)
cmd.out('You can access the QEMU monitor for this VM like so:')
cmd.out(' $ sudo screen %s', monitor)
- cmd.out('Once connected you should type \'Ctrl+a\' followed by \'k\' to leave the monitor. Typing \'quit\' will exit the VM.')
- if 0:# cmd.opts.console:
- os.system('screen %s'%serial)
- else:
- cmd.out('You can connect to the VM\'s serial console like this:')
- cmd.out(' $ sudo screen %s', serial)
- cmd.out('Again you can exit the console with \'Ctrl+a\' followed by \'k\'.')
+ cmd.out('Once connected you should type \'Ctrl+a\' followed by \'k\' to leave the monitor. (Don\'t type \'quit\' as that will immediately exit the VM - akin to removing the power cable). If you are already running screen, you will need to type \'Ctrl+a a\' followed by k\' to quit the screen connected to %r.', serial)
+ cmd.out('You can connect to the VM\'s serial console like this:')
+ cmd.out(' $ sudo screen %s', serial)
+ cmd.out('Again you can exit the console with \'Ctrl+a\' followed by \'k\' (or \'Ctrl+a a\' followed by k\' if you are already in a screen session).')
+ if not cmd.opts.dont_wait:
+ def errfn(fh, stdin, output, exit):
+ pass
+ def outfn(fh, stdin, output, exit):
+ while not exit:
+ line = fh.readline()
+ if not cmd.opts.no_console:
+ cmd.out(line, end='')
+ output.write(line)
+ if cmd.opts.wait_message in line:
+ cmd.out("Found the wait message, exiting ...")
+ exit.append(0)
+ if not cmd.opts.no_console:
+ cmd.out('Connecting to the VM serial console ...')
+ result = stacks.process(
+ [
+ 'socat', '-', serial,
+ ],
+ out=outfn,
+ err=errfn,
+ wait_for_retcode=False,
+ )
+ cmd.out('done.')
View
69 buildkit/command/vm/status.py
@@ -13,6 +13,11 @@
from .start import get_vm_info
arg_specs = [
+ dict(
+ metavar='NAME',
+ help_msg='names of the VM image directory within /var/lib/buildkit/vm/',
+ min=0,
+ ),
]
opt_specs_by_name = dict(
@@ -34,29 +39,51 @@ def parse_vm_info(image):
pid = None,
)
-def run(cmd):
- if os.geteuid() != 0:
- cmd.err("This script must be run as root")
- return 1
+def full_info(images=None):
+ if images == []:
+ images = None
vm_info = get_vm_info()
+ error = None
+ instances = []
if not vm_info:
- cmd.out('No VMs running')
+ error = 'No VMs running'
else:
- spec = '%-6s %-20s %-8s %-12s %-12s'
- cmd.out(spec, 'PID', 'Image', 'Tunnel', 'Serial', 'Monitor')
+ not_running = images[:]
for instance in vm_info:
- info = parse_vm_info(instance.image)
- if instance.pid != info.pid:
- instance['serial'] = 'UNKNOWN'
- instance['monitor'] = 'UNKNOWN'
- else:
- instance.update(info)
- cmd.out(
- spec,
- instance.pid,
- instance.image,
- instance.tunnel,
- instance.serial,
- instance.monitor,
- )
+ if images is None or instance.image in images:
+ info = parse_vm_info(instance.image)
+ if instance.pid != info.pid:
+ instance['serial'] = 'UNKNOWN'
+ instance['monitor'] = 'UNKNOWN'
+ else:
+ instance.update(info)
+ instances.append(instance)
+ not_running.pop(not_running.index(instance.image))
+ if images is not None and not_running:
+ error = 'The following VMs are not running: %r'%(', '.join(not_running))
+ return stacks.obj(
+ error=error,
+ instances=instances,
+ )
+
+def run(cmd):
+ if os.geteuid() != 0:
+ cmd.err("This script must be run as root")
+ return 1
+ result = full_info(cmd.args)
+ if result.error:
+ cmd.err(result.error)
+ return 2
+ spec = '%-6s %-20s %-8s %-12s %-12s'
+ cmd.out(spec, 'PID', 'Image', 'Tunnel', 'Serial', 'Monitor')
+ for instance in result.instances:
+ cmd.out(
+ spec,
+ instance.pid,
+ instance.image,
+ instance.tunnel,
+ instance.serial,
+ instance.monitor,
+
+ )
View
43 buildkit/command/vm/stop.py
@@ -18,9 +18,13 @@
]
opt_specs_by_name = dict(
- wait = dict(
- flags=['--wait'],
- help_msg='Wait for shutdown to complete before returning',
+ dont_wait = dict(
+ flags=['--dont-wait'],
+ help_msg='Return immediately without waiting for shutdown to complete',
+ ),
+ no_console = dict(
+ flags=['--no-console'],
+ help_msg='Don\'t show boot output during start up',
),
)
@@ -43,16 +47,39 @@ def run(cmd):
cmd.err(' $ echo "system_powerdown" | sudo socat - /dev/pts/XX')
cmd.err('Failing that you could forcibly kill the VMs pid %r', vm.pid)
return 2
+ cmd.out('Sending system powerdown command ...')
stacks.process(
'echo "system_powerdown" | sudo socat - %s'%pts_info.monitor,
shell=True,
)
- cmd.out('System powerdown command sent.')
- if cmd.opts.wait:
- cmd.out('Waiting for shutdown to complete .', end='')
+ cmd.out('done.')
+ if not cmd.opts.dont_wait:
+ serial = pts_info.serial
+ if serial != 'UNKNOWN':
+ def err(fh, stdin, output, exit):
+ pass
+ def out(fh, stdin, output, exit):
+ while not exit:
+ line = fh.readline()
+ if not cmd.opts.no_console:
+ cmd.out(line, end='')
+ output.write(line)
+ if 'Power down.' in line:
+ cmd.out("Found the wait message, exiting ...")
+ exit.append(0)
+ if not cmd.opts.no_console:
+ cmd.out('Connecting to the VM serial console ...')
+ result = stacks.process(
+ [
+ 'socat', '-', serial,
+ ],
+ out=out,
+ err=err,
+ )
+ cmd.out('done.')
+ cmd.out('Waiting for shutdown to complete ...', end='')
wait = True
while wait:
- time.sleep(5)
vm_info = get_vm_info()
found = False
cmd.out('.', end='')
@@ -62,6 +89,8 @@ def run(cmd):
break
if not found:
wait=False
+ else:
+ time.sleep(2)
cmd.out('\nShutdown complete.')
View
5 buildkit/localstacks/stacks.py
@@ -4024,7 +4024,12 @@ def process(
raise Exception('The command should be a string if shell=True')
if not shell and not isinstance(cmd, (list, tuple)):
raise Exception('The command should be a list if shell=False')
+ if merge:
+ if print_err is not None:
+ raise Exception('You can\'t use \'merge=True\' to override \'print_err\' if it is already specified')
if echo:
+ if print_out is not None or print_err is not None:
+ raise Exception('You can\'t use \'echo=True\' to override \'print_out\' and \'print_err\' if they are already specified')
print_out = print_fn
print_err = print_err_fn
if merge:
Please sign in to comment.
Something went wrong with that request. Please try again.