Mercurial > pub > Minions
comparison bin/minions.pl @ 0:cd5df456ee84
Initial working version of minions.pl ans sysyemd units
author | cin |
---|---|
date | Tue, 15 Nov 2016 01:33:09 +0300 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:cd5df456ee84 |
---|---|
1 #!/usr/bin/perl -T | |
2 use strict; | |
3 use warnings; | |
4 | |
5 package Hive; | |
6 | |
7 package main; | |
8 use Pod::Usage; | |
9 use Getopt::Long qw(:config auto_help); | |
10 use Sys::Virt; | |
11 use Sys::Virt::Domain; | |
12 | |
13 our $VERSION = 0.1; | |
14 our $CONFIG_FILE = '/etc/minions/hive.yaml'; | |
15 our $STATE_FILE = '/var/lib/minions/session.yaml'; | |
16 our $TIMEOUT = 300; | |
17 our $PARALLEL = 0; | |
18 our $RETRY_INTERVAL = 60; | |
19 | |
20 my $tasks = 2; | |
21 my @pending; | |
22 | |
23 my @uri = qw(qemu:///system lxc:///); | |
24 | |
25 my %commands = ( | |
26 stop => \&doStop, | |
27 start => \&doStart, | |
28 help => \&doHelp | |
29 ); | |
30 | |
31 my $cmd = shift @ARGV; | |
32 | |
33 &{ $commands{ lc( $cmd || '' ) } || \&doHelp }(@ARGV); | |
34 | |
35 exit 0; | |
36 | |
37 sub doHelp { | |
38 print <<END_HELP; | |
39 Minions v.$VERSION manages libvirt domains... | |
40 stop [domain[ stop-method]] | |
41 Stops the specified libvirt domain with the specified method. If the | |
42 domain isn't specified then stops all domains on all configured | |
43 connections and saves the list of stopped domains to | |
44 /var/lib/minions/session.yaml file. | |
45 | |
46 start [domain] | |
47 Starts the specified domain, if the domain is ommited restores the | |
48 previous session from /var/lib/minions/session.yaml and deletes this | |
49 file. | |
50 help | |
51 Prints this help | |
52 | |
53 domain must be written in format {connection}.{domain} where {connection} is | |
54 the one of configured connections from /etc/minions/hive.yaml and {domain} | |
55 is the name of the libvirt domain. | |
56 END_HELP | |
57 } | |
58 | |
59 sub doStart { | |
60 my $hive = Hive->new($CONFIG_FILE); | |
61 | |
62 if ( @_ > 0 ) { | |
63 if ( my ( $cn, $dn ) = ( $_[0] =~ m/(\w+)\.(\w+)/ ) ) { | |
64 $hive->startDomain( $cn, $dn ); | |
65 } | |
66 else { | |
67 die "Invalid parameter: {connection}.{domain} format is required"; | |
68 } | |
69 } | |
70 else { | |
71 $hive->start(); | |
72 } | |
73 $hive->waitPending(); | |
74 } | |
75 | |
76 sub doStop { | |
77 my $hive = Hive->new($CONFIG_FILE); | |
78 | |
79 if ( @_ > 0 ) { | |
80 if ( my ( $cn, $dn ) = ( $_[0] =~ m/(\w+)\.(\w+)/ ) ) { | |
81 $hive->stopDomain( $cn, $dn ); | |
82 } | |
83 else { | |
84 die "Invalid parameter: {connection}.{domain} format is required"; | |
85 } | |
86 } | |
87 else { | |
88 $hive->stop(); | |
89 } | |
90 | |
91 $hive->waitPending(); | |
92 } | |
93 | |
94 package Hive; | |
95 use fields qw(config vmms _pending); | |
96 use YAML::XS qw(DumpFile LoadFile Dump); | |
97 use Sys::Virt; | |
98 use Sys::Virt::Domain; | |
99 use File::Spec; | |
100 | |
101 BEGIN { | |
102 no strict 'refs'; | |
103 *{ __PACKAGE__ . "::$_" } = \*{"Sys::Virt::Domain::$_"} | |
104 for qw(STATE_SHUTOFF LIST_PERSISTENT LIST_ACTIVE); | |
105 } | |
106 | |
107 sub new { | |
108 my Hive $this = fields::new(shift); | |
109 $this->init(@_); | |
110 return $this; | |
111 } | |
112 | |
113 sub init { | |
114 my Hive $this = shift; | |
115 my $file = shift; | |
116 | |
117 my $config = $this->{config} = LoadFile($file); | |
118 | |
119 while ( my ( $name, $info ) = each %{ $config->{vmms} || {} } ) { | |
120 eval { | |
121 die "Invalid connection info $name" | |
122 unless ref($info) and $info->{uri}; | |
123 | |
124 $this->trace_info("Connection '$name': $info->{uri}"); | |
125 $this->{vmms}{$name}{stop} = lc( $info->{stop} || 'shutdown' ); | |
126 | |
127 die "Unsupported stop method: $info->{stop}" | |
128 unless grep $_ eq $this->{vmms}{$name}{stop}, | |
129 qw(suspend shutdown); | |
130 | |
131 $this->{vmms}{$name}{instance} = | |
132 Sys::Virt->new( uri => $info->{uri} ); | |
133 }; | |
134 $this->trace_error("Failed to connect '$name': $@") if $@; | |
135 } | |
136 } | |
137 | |
138 sub stop { | |
139 my Hive $this = shift; | |
140 | |
141 my @pending; | |
142 | |
143 my %stopped; | |
144 | |
145 while ( my ( $name, $vmm ) = each %{ $this->{vmms} || {} } ) { | |
146 next unless $vmm->{instance}; | |
147 | |
148 $this->trace_info("Do $vmm->{stop} for all domains on $name"); | |
149 | |
150 my @domains = | |
151 $vmm->{instance}->list_all_domains( LIST_PERSISTENT | LIST_ACTIVE ); | |
152 $stopped{$name} = [ map $_->get_name(), @domains ]; | |
153 | |
154 $this->trace_info( "\t-" . $_->get_name() ) | |
155 and $this->_safeStop( $_, $vmm->{stop} ) | |
156 for @domains; | |
157 } | |
158 | |
159 my $state = $this->{config}{sate_file} || $STATE_FILE; | |
160 | |
161 my $dir = ( File::Spec->splitpath($state) )[1]; | |
162 mkdir $dir unless -e $dir; | |
163 | |
164 DumpFile( $state, \%stopped ); | |
165 } | |
166 | |
167 sub start { | |
168 my Hive $this = shift; | |
169 | |
170 my $state = $this->{config}{sate_file} || $STATE_FILE; | |
171 my $session = -f $state ? LoadFile($state) : {}; | |
172 | |
173 while ( my ( $name, $machines ) = each %{$session} ) { | |
174 my $vmm = $this->{vmms}{$name} | |
175 or $this->trace_error( | |
176 "Failed to resotre session for '$name': no such connection") | |
177 and next; | |
178 | |
179 $this->trace_info("Restoring domains on '$name'"); | |
180 for my $m ( @{ $machines || [] } ) { | |
181 my $d = $vmm->{instance}->get_domain_by_name($m) | |
182 or $this->trace_error("\t-$m not found") | |
183 and next; | |
184 | |
185 eval { | |
186 $this->trace_info("\t-$m"); | |
187 $this->_safeStart($d); | |
188 }; | |
189 $this->trace_error("$@") if $@; | |
190 } | |
191 } | |
192 | |
193 unlink $state if -f $state; | |
194 } | |
195 | |
196 sub startDomain { | |
197 my Hive $this = shift; | |
198 my ( $cn, $dn ) = @_; | |
199 | |
200 $this->trace_info("Start $cn.$dn"); | |
201 my $con = $this->{vmms}{$cn} | |
202 or die "Connection '$cn' doesn't exists"; | |
203 | |
204 my $dom = $con->{instance}->get_domain_by_name($dn) | |
205 or die "Domain $dn isn't found in '$cn'"; | |
206 | |
207 return $this->_safeStart($dom); | |
208 } | |
209 | |
210 sub _resolveDomain { | |
211 my Hive $this = shift; | |
212 my ( $cn, $dn ) = @_; | |
213 | |
214 my $con = $this->{vmms}{$cn} | |
215 or die "Connection '$cn' doesn't exists"; | |
216 | |
217 my $dom = $con->{instance}->get_domain_by_name($dn) | |
218 or die "Domain $dn isn't found in '$cn'"; | |
219 return $dom; | |
220 } | |
221 | |
222 sub _safeStart { | |
223 my Hive $this = shift; | |
224 my $dom = shift; | |
225 | |
226 eval { | |
227 unless ( $dom->is_active() ) { | |
228 $dom->create(); | |
229 } | |
230 else { | |
231 $this->trace_info( | |
232 "The domain " . $dom->get_name() . " already active" ); | |
233 } | |
234 }; | |
235 if ($@) { | |
236 die "$@" unless $dom->is_active(); | |
237 $this->trace_info("$@"); | |
238 } | |
239 } | |
240 | |
241 sub stopDomain { | |
242 my Hive $this = shift; | |
243 my ( $cn, $dn, $method ) = @_; | |
244 | |
245 $this->trace_info("Stop $cn.$dn"); | |
246 my $dom = $this->_resolveDomain( $cn, $dn ); | |
247 | |
248 # if stop method is not specified use the default one | |
249 $method = $this->{vmms}{$cn}{stop} || 'shutdown' | |
250 unless $method; | |
251 | |
252 return $this->_safeStop( $dom, $method ); | |
253 } | |
254 | |
255 sub _safeStop { | |
256 my Hive $this = shift; | |
257 my ( $dom, $method ) = @_; | |
258 | |
259 eval { | |
260 if ( $method eq 'shutdown' ) { | |
261 push @{ $this->{_pending} }, Shutdown->new($dom); | |
262 } | |
263 elsif ( $method eq 'suspend' ) { | |
264 $dom->managed_save(); | |
265 } | |
266 }; | |
267 $this->trace_error( "failed to $method " . $dom->get_name() . ": $@" ) | |
268 if $@; | |
269 } | |
270 | |
271 sub waitPending { | |
272 my Hive $this = shift; | |
273 | |
274 my $timeout = $this->{config}{timeout} || $TIMEOUT; | |
275 my $parallel = $this->{config}{parallel} || $PARALLEL; | |
276 my $retry = $this->{config}{retryInterval} || $RETRY_INTERVAL; | |
277 | |
278 my @pending = @{ $this->{_pending} || [] }; | |
279 $this->{_pending} = []; | |
280 | |
281 my $spins = 0; | |
282 | |
283 $this->trace_info("Waiting for operations to complete") | |
284 if @pending; | |
285 | |
286 while (@pending) { | |
287 my @queue; | |
288 my $slots = $parallel; | |
289 $spins++; | |
290 foreach my $task (@pending) { | |
291 my $name = $task->getName(); | |
292 my $duration = $task->getDuration(); | |
293 | |
294 if ( $task->isComplete() ) { | |
295 $this->trace_info("\t- $name stopped in $duration s"); | |
296 } | |
297 elsif ( $duration > $timeout ) { | |
298 $this->trace_info( | |
299 "\t- $name destroyed due timeout after $duration s"); | |
300 $task->terminate(); | |
301 } | |
302 else { | |
303 $task->start() | |
304 if not $task->isStarted() | |
305 and ( $parallel == 0 || --$slots >= 0 ); | |
306 $this->trace_info("\tretry $name after $duration s") | |
307 and $task->signal() | |
308 if $retry and $spins % $retry == 0; | |
309 push @queue, $task; | |
310 } | |
311 } | |
312 sleep(1) if @pending = @queue; | |
313 } | |
314 } | |
315 | |
316 sub trace_info { | |
317 shift; | |
318 print @_, "\n"; | |
319 } | |
320 | |
321 sub trace_error { | |
322 shift; | |
323 print STDERR @_, "\n"; | |
324 } | |
325 | |
326 package Shutdown; | |
327 use fields qw(_domain _startTime _started); | |
328 | |
329 BEGIN { | |
330 no strict 'refs'; | |
331 *{ __PACKAGE__ . "::$_" } = \*{"Sys::Virt::Domain::$_"} | |
332 for qw(STATE_SHUTOFF LIST_PERSISTENT LIST_ACTIVE); | |
333 } | |
334 | |
335 sub new { | |
336 my Shutdown $self = fields::new(shift); | |
337 $self->{_domain} = shift; | |
338 return $self; | |
339 } | |
340 | |
341 sub isComplete { | |
342 my Shutdown $this = shift; | |
343 return $this->{_domain}->get_info()->{state} == STATE_SHUTOFF; | |
344 } | |
345 | |
346 sub getName { | |
347 my Shutdown $this = shift; | |
348 return $this->{_domain}->get_name(); | |
349 } | |
350 | |
351 sub getDuration { | |
352 my Shutdown $this = shift; | |
353 return ( $this->{_startTime} ? time - $this->{_startTime} : 0 ); | |
354 } | |
355 | |
356 sub terminate { | |
357 my Shutdown $this = shift; | |
358 return eval { $this->{_domain}->destroy() }; | |
359 } | |
360 | |
361 sub signal { | |
362 my Shutdown $this = shift; | |
363 | |
364 eval { $this->{_domain}->shutdown() }; | |
365 } | |
366 | |
367 sub start { | |
368 my Shutdown $this = shift; | |
369 | |
370 $this->{_started} = 1; | |
371 $this->{_startTime} = time; | |
372 | |
373 $this->signal(); | |
374 } | |
375 | |
376 sub isStarted { | |
377 my Shutdown $this = shift; | |
378 return $this->{_started}; | |
379 } |