1 #! /bin/bash |
|
2 # $Id$ |
|
3 # $URL$ |
|
4 # status: in development |
|
5 # 2008 Heiko Schlittermann |
|
6 |
|
7 # Should be written in Perl! |
|
8 |
|
9 # Structure of virtual machine directories |
|
10 # $BASE (see below, hard coded into this script) |
|
11 # | |
|
12 # +--<vm1> |
|
13 # | +--conf |
|
14 # | | +--options |
|
15 # | | +--id |
|
16 # | +--var |
|
17 # | | +--pid |
|
18 # | | +--running_options |
|
19 # | +--<image1> |
|
20 # | +-- .... |
|
21 # | +--<imageN> |
|
22 # | |
|
23 # +--<vm2> |
|
24 # | .... |
|
25 # +--<vmN> |
|
26 |
|
27 |
|
28 action="${1?}"; shift # mandatory |
|
29 vm="$1" ; shift # optional |
|
30 |
|
31 # no user service able parts below |
|
32 |
|
33 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin |
|
34 unset ${!LC_*} LANG CDPATH |
|
35 |
|
36 ME=$(basename $0) |
|
37 BASE=/kvm |
|
38 KVM=$(command -v kvm) |
|
39 NC=$(command -v nc) |
|
40 FUSER=$(command -v fuser) |
|
41 VNCVIEWER=$(command -v vncviewer) |
|
42 TIMEOUT=30 |
|
43 |
|
44 |
|
45 for f in "/etc/$ME.conf" ~/.$ME; do test -r "$f" && source "$f"; done |
|
46 |
|
47 |
|
48 die() { echo $ME: "$@" >&2; exit 1; } |
|
49 warn() { echo $ME: "$@" >&2; } |
|
50 silently() { "$@" &>/dev/null; } |
|
51 |
|
52 debug() { echo "DEBUG: $@" >&2; } |
|
53 |
|
54 unique_ids() { |
|
55 local dups=$(cat $BASE/*/conf/id 2>/dev/null | sort -n | uniq -c \ |
|
56 | egrep -v '^[[:space:]]+1 ' | tr -s ' ' | cut -f2 -d:) |
|
57 test "$dups" || return 0 |
|
58 ERR="$dups" && return 1 |
|
59 } |
|
60 |
|
61 convert_dirs() { |
|
62 for dir in $BASE/*; do |
|
63 test -d $dir/kvm || test -d $dir/conf || continue |
|
64 local old=$dir/kvm |
|
65 local new=$dir/conf |
|
66 test -d $old && mv $old $new |
|
67 test -f $new/cmdline && mv $new/cmdline $new/options |
|
68 done |
|
69 } |
|
70 |
|
71 monitor() { |
|
72 local port=$1; shift |
|
73 local command="$1"; shift |
|
74 |
|
75 # check, if the monitor is available, kill if somebody |
|
76 # else is using it |
|
77 local client=$(netstat -tn | egrep ":$port[[:space:]]+EST" | tr -s ' ' | cut -f4 -d' ') |
|
78 test "${client%:*}" = 127.0.0.1 && fuser -k ${client#*:}/tcp |
|
79 |
|
80 local pid0 pid1 |
|
81 ( |
|
82 local a=$tmp/a.fifo; mkfifo $a |
|
83 trap "rm -v $a" EXIT |
|
84 exec 10<>$a |
|
85 $NC localhost $port <&10 | { |
|
86 read || exit # banner |
|
87 while read -d' '; do test "$REPLY" = "(qemu)" && break; done |
|
88 echo "$command" >&10 |
|
89 while read -d' '; do test "$REPLY" = "(qemu)" && break; done |
|
90 } |
|
91 ) & pid0=$! |
|
92 ( sleep $TIMEOUT; kill $pid0 ) & pid1=$! |
|
93 wait $pid0 |
|
94 silently kill $pid0 $pid1 |
|
95 } |
|
96 |
|
97 ### MAIN |
|
98 |
|
99 # sanity |
|
100 test -x "$NC" || die "need nc (netcat)" |
|
101 test -x "$KVM" || die "need kvm/qemu/kqemu" |
|
102 test -x "$FUSER" || die "need fuser" |
|
103 test -d "$BASE" || die "base dir $BASE does not exist" |
|
104 test -d "$BASE/$vm" || die "vm $vm does not exist" |
|
105 |
|
106 unique_ids || die "duplicate ids (id: $ERR)" |
|
107 convert_dirs |
|
108 |
|
109 trap 'test "$TMP" && rm -fr $TMP' EXIT |
|
110 |
|
111 for vm in ${vm:-$(echo $BASE/*)}; do |
|
112 vm=$(basename $vm) |
|
113 conf=$BASE/$vm/conf |
|
114 var=$BASE/$vm/var |
|
115 |
|
116 tmp=$(mktemp -d) |
|
117 TMP="$TMP $tmp" |
|
118 |
|
119 test -d $conf || continue |
|
120 test -f $conf/id || { warn "need id file (vm: $vm)" && continue; } |
|
121 test -f $conf/options || { warn "need options file (vm: $vm)" && continue; } |
|
122 test -d $var || mkdir $var || exit 2 |
|
123 |
|
124 id=$(cat $conf/id) |
|
125 port=$((4000 + id)) |
|
126 pid=$(cat $var/pid 2>/dev/null) |
|
127 |
|
128 case "$action" in |
|
129 |
|
130 start) |
|
131 echo "starting $vm" |
|
132 options="$(grep -v '^#' $conf/options) \ |
|
133 -pidfile var/pid \ |
|
134 -name "$vm" \ |
|
135 -vnc :$id \ |
|
136 -monitor tcp:localhost:$port,server,nowait \ |
|
137 $@" |
|
138 options=$(echo "" $options) # shorten it |
|
139 echo "$options" > $var/running_options |
|
140 test -f $var/frozen && options="$options $(cat $var/frozen)" |
|
141 |
|
142 if silently fuser $var/pid; then |
|
143 echo "running, pid: $pid, display :$id, monitor tcp:127.0.0.1:$port" |
|
144 else |
|
145 ( cd $BASE/$vm && $KVM $options -daemonize ) |
|
146 pid=$(cat $var/pid 2>/dev/null) |
|
147 test "$pid" || { warn "serious problem ($vm), no pid" && continue; } |
|
148 echo "started, pid: $pid, display :$id, monitor tcp:127.0.0.1:$port" |
|
149 fi |
|
150 |
|
151 test "$DISPLAY" && test "$VNCVIEWER" && test -t \ |
|
152 && ( silently exec $VNCVIEWER :$id </dev/null & ) |
|
153 |
|
154 ;; |
|
155 stop) |
|
156 { |
|
157 echo "stopping (powerdown) $vm" |
|
158 monitor $port "system_powerdown" |
|
159 silently fuser $var/pid && $0 quit $vm |
|
160 } & |
|
161 ;; |
|
162 quit) |
|
163 { |
|
164 echo "quit $vm" |
|
165 monitor $port "quit" |
|
166 silently fuser $var/pid && $0 kill $vm |
|
167 } & |
|
168 ;; |
|
169 kill) |
|
170 echo "killing $vm" |
|
171 kill $pid |
|
172 ;; |
|
173 freeze) |
|
174 { |
|
175 echo "freezing $vm" |
|
176 monitor $port "savevm HIBERNATED" |
|
177 monitor $port "quit" |
|
178 echo "-loadvm HIBERNATED" >$var/frozen |
|
179 } & |
|
180 ;; |
|
181 qemu) |
|
182 echo "DO *NOT* USE \"quit\", use ^C for exiting!" |
|
183 $NC localhost $port |
|
184 ;; |
|
185 |
|
186 list) |
|
187 echo "$vm:" $(silently fuser $var/pid \ |
|
188 && echo "running (pid: $pid)" \ |
|
189 || echo "not running") |
|
190 ;; |
|
191 |
|
192 *) die "unknown command \"$action\"" |
|
193 ;; |
|
194 |
|
195 esac |
|
196 |
|
197 done |
|
198 |
|
199 wait |
|
200 |
|
201 # vim:ts=4 sw=4 et: |
|