# HG changeset patch # User Heiko # Date 1279179384 -7200 # Node ID 9504de20810699b20e047c781191ee1b67fe2fcb # Parent 3bed76a0fcd3c19453787cc4b3ee1af4224f76c2 Now we started over this "project" - using almost the script that we used on our rooh-* servers. diff -r 3bed76a0fcd3 -r 9504de208106 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Thu Jul 15 09:36:24 2010 +0200 @@ -0,0 +1,25 @@ +DESTDIR = / + +prefix = /usr/local +sbindir = ${prefix}/sbin +etcdir = /etc + +.PHONY: all clean install + +all: kvmtool + +kvmtool: kvmtool.sh + @sed -e 's|\[% VERSION %\]|'"`hg id -bit`"'|;' \ + -e 's|\[% BUILDINFO %\]|'"`hostname`:`pwd`"'|;' \ + <$^ >$@ + @chmod +x $@ + +clean: ; rm -f kvmtool + +install: all + install -m 0755 -d ${DESTDIR}${sbindir}/ + install -m 0555 kvmtool ${DESTDIR}${sbindir}/kvmtool + install -m 0755 -d ${DESTDIR}${etcdir}/ + test -f ${DESTDIR}${etcdir}/kvmtab \ + || install -m 0644 kvmtab.ex ${DESTDIR}${etcdir}/kvmtab + diff -r 3bed76a0fcd3 -r 9504de208106 kvmtab.ex --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kvmtab.ex Thu Jul 15 09:36:24 2010 +0200 @@ -0,0 +1,5 @@ +# this table is read by kvmtool (or vm-*) +# do not forget to sync this table to the other hosts (*-{a,b}) +# +# name host id mac config file (builtin if empty) + diff -r 3bed76a0fcd3 -r 9504de208106 kvmtool --- a/kvmtool Thu Jul 15 09:12:48 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,356 +0,0 @@ -#! /usr/bin/perl -# © 2009 Heiko Schlittermann - -# $opt_base/vm/{name}/ -# - -use strict; -use warnings; -use Getopt::Long qw(:config require_order); -use Pod::Usage; -use File::Basename; -use Perl6::Slurp; -use File::Path; -use IO::File; -use IO::Select; -use POSIX qw(setsid); -use feature qw(:5.10); -use if $ENV{DEBUG} => qw(Smart::Comments); - -my $ME = basename $0; -my $opt_dev = "/dev/kvm"; -my $opt_base = "$ENV{HOME}/.$ME"; -my $opt_cmd_monitor = undef; -my $opt_config = undef; -my $opt_import = undef; - -my $KVM = "kvm"; -my $KILL = 15; - -sub cmd_start(@); -sub cmd_kill(@); -sub cmd_list(@); -sub cmd_monitor(@); -sub _import(@); - -sub _base_dir($) { "$opt_base/vm/$_[0]" } -sub _running_config_file($) { "$opt_base/vm/$_[0]/running_config" } -sub _user_config_file($) { "$opt_base/vm/$_[0]/config" } -sub _monitor_link($) { "$opt_base/vm/$_[0]/monitor" } - -MAIN: { - - GetOptions( - "h|help" => sub { pod2usage(-verbose => 1, -exit => 0) }, - "man" => sub { pod2usage(-verbose => 2, -exit => 0) }, - "m|monitor" => \$opt_cmd_monitor, - "c|config=s" => \$opt_config, - "d|base-dir=s" => \$opt_base, - "i|import" => \$opt_import, - "dev=s" => \$opt_dev, - ) or pod2usage(-exit => 1); - - -w $opt_dev or die "$ME: write access to $opt_dev: $!\n"; - - given (shift) { - when ("start") { - cmd_start(@ARGV); - cmd_monitor(@ARGV) if $opt_cmd_monitor; - } - when ("monitor") { cmd_monitor(@ARGV) } - when ("kill") { cmd_kill(@ARGV) } - when ("list") { cmd_list(@ARGV) } - default { pod2usage() }; - } - -} - -sub cmd_start(@) { - my ($kvm, @kvm_opts) = @_; - pod2usage() if not defined $kvm; - - -d _base_dir $kvm or mkpath _base_dir $kvm; - - my $config = config->load_config_user($kvm, - defined $opt_config ? $opt_config : _user_config_file $kvm, @kvm_opts); - - if (defined(my $pidfile = $config->get("-pidfile"))) { - -d dirname $pidfile - or die "$ME: directory for pidfile ($pidfile): $!\n"; - } - - $config->save_user_config(_user_config_file $kvm) - if defined $opt_config and defined $opt_import; - - $config->save_running_config(_running_config_file $kvm, @kvm_opts); - - defined(my $pid = open(VM, "-|")) or die "Can't fork: $!\n"; - - if ($pid == 0) { - setsid(); - open(STDIN, "&STDOUT") - or die "Can't redirect STDERR to STDOUT: $!\n"; - exec $KVM "kvm-$kvm" => $config->get_all(); - die "Can't exec: $!"; - } - - if (defined($_ = )) { - print $_; - if (/char device redirected to (\S+)$/) { - unlink(_monitor_link $kvm); - symlink($1, _monitor_link $kvm); - exit; - } - - wait; - die "$ME: didn't start: (exit ", $? >> 8, ")\n"; - - } - -} - -sub cmd_monitor(@) { - my ($kvm, $cmd) = (shift, "@_"); - pod2usage() if not defined $kvm; - - my $monitor = _monitor_link $kvm; - my $pts = readlink $monitor - or die "$ME: $monitor: $!\n"; - - my $i = new IO::File("<$pts") or die "$pts: $!\n"; - my $o = new IO::File(">$pts") or die "$pts: $!\n"; - - $o->autoflush(1); - - if (length $cmd) { - die "passing a command to monitor is not yet implemented\n"; - $_ = ""; - while (not /^\(qemu\) /) { - sysread($i, $_, 1, length); - $_ = "" if /\n$/; - } - syswrite($o, "$cmd\n"); - $_ = ""; - while (not /^\(qemu\) /m) { - sysread($i, $_, 1, length); - } - s/^.*?\r?\n//; - s/(\r?\n).*?$/$1/; - die "<$_>"; - exit; - } - - say("** to leave the monitor, just use the key!"); - my $termio = `stty -g`; - my $rows = (split " ", `stty size`)[0]; - system("stty raw -echo"); - - eval { - my $row = 1; - INPUT: - while (my @ready = IO::Select->new($i, \*STDIN)->can_read(undef)) { - foreach my $fh (@ready) { - if ($fh == $i) { - sysread($i, $_, 1) == 0 - and last INPUT; - syswrite(STDOUT, $_); - if (/\n/ and $row++ >= $rows) { - syswrite(STDOUT, "more..."); - sysread(STDIN, $_, 1); - $row = 1; - } - } - elsif ($fh == \*STDIN) { - (sysread(STDIN, $_, 1) == 0 or /\e/) - and print("\r\n"), last INPUT; - syswrite($o, $_); - $row = 1; - } - } - } - }; - system("stty $termio"); -} - -sub cmd_list(@) { - foreach my $dir (glob(_base_dir "*")) { - my $kvm = basename $dir; - print "$kvm: "; - my ($config, $pid); - eval { - $config = config->load_running_config(_running_config_file $kvm); - $pid = slurp $config->get("-pidfile"), { chomp => 1 }; - }; - if ($@ or !kill 0 => $pid) { - print "not running"; - next; - } - - print "running ($pid)"; - - } continue { - print "\n"; - } -} - -sub cmd_kill(@) { - my ($kvm) = @_; - pod2usage() if not defined $kvm; - my $config = config->load_running_config(_running_config_file $kvm); - my $pidfile = $config->get("-pidfile"); - my $pid = slurp $pidfile, { chomp => 1 }; - - kill $KILL => $pid; - waitpid($pid, 0); - unlink $pidfile, _monitor_link $kvm, _running_config_file $kvm; - -} - -{ - - package config; - use strict; - use warnings; - use YAML::Syck; - use Perl6::Slurp; - use feature qw(:5.10); - - use Data::Dumper; - - my %data; - - sub load_config_user { - my ($class, $kvm, $file, @extra_opts) = @_; - my $self = bless do { \my $x } - => $class; - - my @config = slurp $file, { chomp => 1 }; - - $data{$self}{kvm} = $kvm; - push @config, "-name $kvm" unless /^-name\b/ ~~ @config; - push @config, "-monitor pty" unless /^-monitor\b/ ~~ @config; - push @config, "-pidfile $opt_base/vm/$kvm/pid" - unless /^-pidfile\b/ ~~ @config; - $data{$self}{config} = \@config; - $data{$self}{extra} = \@extra_opts; - - return $self; - } - - sub save_running_config { - my ($self, $file) = @_; - DumpFile($file, $data{$self}); - } - - sub save_user_config { - my ($self, $file) = @_; - my $fh = new IO::File ">$file" or die "$file: $!\n"; - $fh->print( - join "\n", - "# kvm configuration file", - "# imported kvm config, you *may* edit this file", - @{ $data{$self}{config} }, undef - ); - } - - sub load_running_config { - my ($class, $file) = @_; - my $self = bless do { \my $x } - => $class; - $data{$self} = LoadFile($file); - return $self; - } - - sub get { - my ($self, $key) = @_; - my @a = map { /^$key\s+(.*)/ ? $1 : () } @{ $data{$self}{config} }; - return wantarray ? @a : $a[0]; - } - - sub get_all { - my ($self) = @_; - return +(map { split " ", $_, 2 } grep !/^\s*#/, - @{ $data{$self}{config} }), @{ $data{$self}{extra} }; - } - - sub DESTROY { - my $self = shift; - delete $data{$self}; - } - -} - -__END__ - -=head1 NAME - -kvmtool - tool to start and stop kvm w/o any X11 - -=head1 SYNOPSIS - - kvmtool [options] start [kvm-options] - kvmtool [options] monitor - kvmtool [options] kill - kvmtool [options] list - -=head1 DESCRITPTION - -=head1 COMMANDS - -=head2 start - -The B command starts the virtual machine. It looks for the -startup config in the vm config space. For a new machine you may want to -use the B<--config> option to specify (and optionally import) the -new configuration. - -Options just used once (e.g. boot off a CDROM) may be passed too. - -=head2 monitor - -This opens a connection to the monitor of the specified VM. - -=head2 kill - -The B command kills the machine, using normal UNIX process -manipulation tools. - -=head1 OPTIONS - -Some of the following options are of general nature (as B<--base-dir>), some -other options are only useful in combination with some specific action -(as B<--monitor>). You may pass as many useless options as you want ;-) - -=over - -=item B<-c>|B<--config> I - -Use the specified configuration instead of the one once imported. -(default: I) - -=item B<--dev>=I - -Pathname of the KVM device. (default: F) - -=item B<-i>|B<--import> - -Import the specified configuration. (default: I) - -=item B<-h>|B<--help> - -Show a short help page. - -=item B<-m>|B<--man> - -Show this manual page. - -=back - -=head1 AUTHOR - - Heiko Schlittermann - -=cut - -# vim:sts=4 aw ai sm: diff -r 3bed76a0fcd3 -r 9504de208106 kvmtool.sh --- a/kvmtool.sh Thu Jul 15 09:12:48 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,201 +0,0 @@ -#! /bin/bash -# $Id$ -# $URL$ -# status: in development -# 2008 Heiko Schlittermann - -# Should be written in Perl! - -# Structure of virtual machine directories -# $BASE (see below, hard coded into this script) -# | -# +-- -# | +--conf -# | | +--options -# | | +--id -# | +--var -# | | +--pid -# | | +--running_options -# | +-- -# | +-- .... -# | +-- -# | -# +-- -# | .... -# +-- - - -action="${1?}"; shift # mandatory -vm="$1" ; shift # optional - -# no user service able parts below - -PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -unset ${!LC_*} LANG CDPATH - -ME=$(basename $0) -BASE=/kvm -KVM=$(command -v kvm) -NC=$(command -v nc) -FUSER=$(command -v fuser) -VNCVIEWER=$(command -v vncviewer) -TIMEOUT=30 - - -for f in "/etc/$ME.conf" ~/.$ME; do test -r "$f" && source "$f"; done - - -die() { echo $ME: "$@" >&2; exit 1; } -warn() { echo $ME: "$@" >&2; } -silently() { "$@" &>/dev/null; } - -debug() { echo "DEBUG: $@" >&2; } - -unique_ids() { - local dups=$(cat $BASE/*/conf/id 2>/dev/null | sort -n | uniq -c \ - | egrep -v '^[[:space:]]+1 ' | tr -s ' ' | cut -f2 -d:) - test "$dups" || return 0 - ERR="$dups" && return 1 -} - -convert_dirs() { - for dir in $BASE/*; do - test -d $dir/kvm || test -d $dir/conf || continue - local old=$dir/kvm - local new=$dir/conf - test -d $old && mv $old $new - test -f $new/cmdline && mv $new/cmdline $new/options - done -} - -monitor() { - local port=$1; shift - local command="$1"; shift - - # check, if the monitor is available, kill if somebody - # else is using it - local client=$(netstat -tn | egrep ":$port[[:space:]]+EST" | tr -s ' ' | cut -f4 -d' ') - test "${client%:*}" = 127.0.0.1 && fuser -k ${client#*:}/tcp - - local pid0 pid1 - ( - local a=$tmp/a.fifo; mkfifo $a - trap "rm -v $a" EXIT - exec 10<>$a - $NC localhost $port <&10 | { - read || exit # banner - while read -d' '; do test "$REPLY" = "(qemu)" && break; done - echo "$command" >&10 - while read -d' '; do test "$REPLY" = "(qemu)" && break; done - } - ) & pid0=$! - ( sleep $TIMEOUT; kill $pid0 ) & pid1=$! - wait $pid0 - silently kill $pid0 $pid1 -} - -### MAIN - -# sanity -test -x "$NC" || die "need nc (netcat)" -test -x "$KVM" || die "need kvm/qemu/kqemu" -test -x "$FUSER" || die "need fuser" -test -d "$BASE" || die "base dir $BASE does not exist" -test -d "$BASE/$vm" || die "vm $vm does not exist" - -unique_ids || die "duplicate ids (id: $ERR)" -convert_dirs - -trap 'test "$TMP" && rm -fr $TMP' EXIT - -for vm in ${vm:-$(echo $BASE/*)}; do - vm=$(basename $vm) - conf=$BASE/$vm/conf - var=$BASE/$vm/var - - tmp=$(mktemp -d) - TMP="$TMP $tmp" - - test -d $conf || continue - test -f $conf/id || { warn "need id file (vm: $vm)" && continue; } - test -f $conf/options || { warn "need options file (vm: $vm)" && continue; } - test -d $var || mkdir $var || exit 2 - - id=$(cat $conf/id) - port=$((4000 + id)) - pid=$(cat $var/pid 2>/dev/null) - - case "$action" in - - start) - echo "starting $vm" - options="$(grep -v '^#' $conf/options) \ - -pidfile var/pid \ - -name "$vm" \ - -vnc :$id \ - -monitor tcp:localhost:$port,server,nowait \ - $@" - options=$(echo "" $options) # shorten it - echo "$options" > $var/running_options - test -f $var/frozen && options="$options $(cat $var/frozen)" - - if silently fuser $var/pid; then - echo "running, pid: $pid, display :$id, monitor tcp:127.0.0.1:$port" - else - ( cd $BASE/$vm && $KVM $options -daemonize ) - pid=$(cat $var/pid 2>/dev/null) - test "$pid" || { warn "serious problem ($vm), no pid" && continue; } - echo "started, pid: $pid, display :$id, monitor tcp:127.0.0.1:$port" - fi - - test "$DISPLAY" && test "$VNCVIEWER" && test -t \ - && ( silently exec $VNCVIEWER :$id $var/frozen - } & - ;; - qemu) - echo "DO *NOT* USE \"quit\", use ^C for exiting!" - $NC localhost $port - ;; - - list) - echo "$vm:" $(silently fuser $var/pid \ - && echo "running (pid: $pid)" \ - || echo "not running") - ;; - - *) die "unknown command \"$action\"" - ;; - - esac - -done - -wait - -# vim:ts=4 sw=4 et: