1
|
#!/usr/bin/perl
|
2
|
|
3
|
# All rights reserved and Copyright (c) 2020 Origo Systems ApS.
|
4
|
# This file is provided with no warranty, and is subject to the terms and conditions defined in the license file LICENSE.md.
|
5
|
# The license file is part of this source code package and its content is also available at:
|
6
|
# https://www.origo.io/info/stabiledocs/licensing/stabile-open-source-license
|
7
|
|
8
|
package Stabile::Nodes;
|
9
|
|
10
|
# use LWP::Simple;
|
11
|
use Error qw(:try);
|
12
|
use File::Basename;
|
13
|
use Config::Simple;
|
14
|
use lib dirname (__FILE__);
|
15
|
use Stabile;
|
16
|
|
17
|
|
18
|
my $backupdir = $Stabile::config->get('STORAGE_BACKUPDIR') || "/mnt/stabile/backups";
|
19
|
my $tenderpaths = $Stabile::config->get('STORAGE_POOLS_LOCAL_PATHS') || "/mnt/stabile/images";
|
20
|
my @tenderpathslist = split(/,\s*/, $tenderpaths);
|
21
|
my $tendernames = $Stabile::config->get('STORAGE_POOLS_NAMES') || "Standard storage";
|
22
|
my @tendernameslist = split(/,\s*/, $tendernames);
|
23
|
$amtpasswd = $Stabile::config->get('AMT_PASSWD') || "";
|
24
|
$brutalsleep = $Stabile::config->get('BRUTAL_SLEEP') || "";
|
25
|
|
26
|
$uiuuid;
|
27
|
$uistatus;
|
28
|
$help = 0; # If this is set, functions output help
|
29
|
|
30
|
our %ahash; # A hash of accounts and associated privileges current user has access to
|
31
|
#our %options=();
|
32
|
# -a action -h help -u uuid -m match pattern -f full list, i.e. all users
|
33
|
# -v verbose, include HTTP headers -s impersonate subaccount -t target [uuid or image]
|
34
|
# -g args to gearman task
|
35
|
#Getopt::Std::getopts("a:hfu:g:m:vs:t:", \%options);
|
36
|
|
37
|
try {
|
38
|
Init(); # Perform various initalization tasks
|
39
|
if (!$isadmin && $action ne "list" && $action ne "listnodeidentities" && $action ne "listlog" && $action ne "help") {return "Status=Error Insufficient privileges for $user ($tktuser)\n"};
|
40
|
process() if ($package);
|
41
|
|
42
|
} catch Error with {
|
43
|
my $ex = shift;
|
44
|
print header('text/html', '500 Internal Server Error') unless ($console);
|
45
|
if ($ex->{-text}) {
|
46
|
print "Got error: ", $ex->{-text}, " on line ", $ex->{-line}, " in file ", $ex->{-file}, "\n";
|
47
|
} else {
|
48
|
print "Status=ERROR\n";
|
49
|
}
|
50
|
} finally {
|
51
|
};
|
52
|
|
53
|
1;
|
54
|
|
55
|
sub getObj {
|
56
|
my %h = %{@_[0]};
|
57
|
$console = 1 if $h{"console"};
|
58
|
$api = 1 if $h{"api"};
|
59
|
$action = $action || $h{'action'};
|
60
|
my $mac = $h{"uuid"} || $h{"mac"};
|
61
|
my $dbobj = $register{$mac} || {};
|
62
|
my $obj;
|
63
|
my $status = $dbobj->{'status'} || $h{"status"}; # Trust db status if it exists
|
64
|
if ($action =~ /all$|configurecgroups/) {
|
65
|
$obj = \%h;
|
66
|
} else {
|
67
|
return 0 unless (($mac && length $mac == 12) );
|
68
|
my $name = $h{"name"} || $dbobj->{'name'};
|
69
|
$obj = $dbobj;
|
70
|
$obj->{"name"} = $name if ($name);
|
71
|
$obj->{"status"} = $status if ($status);
|
72
|
}
|
73
|
return $obj;
|
74
|
}
|
75
|
|
76
|
sub Init {
|
77
|
# Tie database tables to hashes
|
78
|
unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) ) {return "Unable to access nodes register"};
|
79
|
unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username'}, $Stabile::dbopts)) ) {return "Unable to access user register"};
|
80
|
|
81
|
# simplify globals initialized in Stabile.pm
|
82
|
$tktuser = $tktuser || $Stabile::tktuser;
|
83
|
$user = $user || $Stabile::user;
|
84
|
|
85
|
# Create aliases of functions
|
86
|
*header = \&CGI::header;
|
87
|
|
88
|
*Fullstats = \&Stats;
|
89
|
*Fullstatsb = \&Stats;
|
90
|
|
91
|
*do_help = \&action;
|
92
|
*do_remove = \&do_delete;
|
93
|
*do_tablelist = \&do_list;
|
94
|
*do_listnodes = \&do_list;
|
95
|
*do_stats = \&action;
|
96
|
*do_fullstats = \&privileged_action;
|
97
|
*do_fullstatsb = \&privileged_action;
|
98
|
*do_updateamtinfo = \&privileged_action;
|
99
|
*do_configurecgroups = \&privileged_action;
|
100
|
*do_gear_updateamtinfo = \&do_gear_action;
|
101
|
*do_gear_fullstats = \&do_gear_action;
|
102
|
*do_gear_fullstatsb = \&do_gear_action;
|
103
|
*do_gear_configurecgroups = \&do_gear_action;
|
104
|
|
105
|
}
|
106
|
|
107
|
sub do_listnodeidentities {
|
108
|
my ($uuid, $action, $obj) = @_;
|
109
|
if ($help) {
|
110
|
return <<END
|
111
|
GET::
|
112
|
List the identities supported by this engine.
|
113
|
END
|
114
|
}
|
115
|
unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access identity register"};
|
116
|
my @idvalues = values %idreg;
|
117
|
my @newidvalues;
|
118
|
my $i = 1;
|
119
|
foreach my $val (@idvalues) {
|
120
|
my %h = %$val;
|
121
|
if ($h{'identity'} eq "default") {$h{'id'} = "0";}
|
122
|
else {$h{'id'} = "$i"; $i++;};
|
123
|
push @newidvalues,\%h;
|
124
|
}
|
125
|
untie %idreg;
|
126
|
my $json_text = to_json(\@newidvalues, {pretty=>1});
|
127
|
$postreply = qq|{"identifier": "id", "label": "name", "items": $json_text }|;
|
128
|
return $postreply;
|
129
|
}
|
130
|
|
131
|
sub do_terminal {
|
132
|
my ($uuid, $action, $obj) = @_;
|
133
|
if ($help) {
|
134
|
return <<END
|
135
|
GET:mac:
|
136
|
Open direct ssh access to specified node through shellinabox.
|
137
|
END
|
138
|
}
|
139
|
my $mac = $uuid || $params{'mac'} || $obj->{'mac'};
|
140
|
if ($mac && $isadmin) {
|
141
|
my $macip = $register{$mac}->{'ip'};
|
142
|
my $macname = $register{$mac}->{'name'};
|
143
|
my $terminalcmd = qq[/usr/share/stabile/shellinabox/shellinaboxd --cgi -t --css=$Stabile::basedir/static/css/shellinabox.css --debug -s "/:www-data:www-data:HOME:/usr/bin/ssh -l irigo -i /var/www/.ssh/id_rsa_www -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $macip" 2>/tmp/sib.log];
|
144
|
my $cmdout = `$terminalcmd`;
|
145
|
$cmdout =~ s/<title>.+<\/title>/<title>Node: $macname<\/title>/;
|
146
|
$cmdout =~ s/:(\d+)\//\/shellinabox\/$1\//g;
|
147
|
$postreply = $cmdout;
|
148
|
} else {
|
149
|
$postreply = "Status=ERROR Unable to open terminal: $Stabile::basedir\n";
|
150
|
}
|
151
|
return $postreply;
|
152
|
}
|
153
|
|
154
|
sub do_save {
|
155
|
my ($uuid, $action, $obj) = @_;
|
156
|
if ($help) {
|
157
|
return <<END
|
158
|
PUT:name:
|
159
|
Set the name of node.
|
160
|
END
|
161
|
|
162
|
}
|
163
|
}
|
164
|
|
165
|
sub do_sol {
|
166
|
my ($uuid, $action, $obj) = @_;
|
167
|
if ($help) {
|
168
|
return <<END
|
169
|
GET:mac:
|
170
|
Open serial over lan access to specified node through shellinabox.
|
171
|
END
|
172
|
}
|
173
|
my $mac = $uuid || $params{'mac'} || $obj->{'mac'};
|
174
|
if ($mac && $isadmin) {
|
175
|
my $solcmd;
|
176
|
my $macname = $register{$mac}->{'name'};
|
177
|
my $amtip = $register{$mac}->{'amtip'};
|
178
|
my $ipmiip = $register{$mac}->{'ipmiip'};
|
179
|
if ($amtip && $amtip ne '--') {
|
180
|
`pkill -f 'amtterm $amtip'`;
|
181
|
$amtpasswd =~ s/\!/\\!/;
|
182
|
$solcmd = "AMT_PASSWORD='$amtpasswd' /usr/bin/amtterm $amtip";
|
183
|
} elsif ($ipmiip && $ipmiip ne '--') {
|
184
|
`ipmitool -I lanplus -H $ipmiip -U ADMIN -P ADMIN sol deactivate`;
|
185
|
$solcmd .= "ipmitool -I lanplus -H $ipmiip -U ADMIN -P ADMIN sol activate";
|
186
|
}
|
187
|
if ($solcmd ) {
|
188
|
my $terminalcmd = qq[/usr/share/stabile/shellinabox/shellinaboxd --cgi -t --css=$Stabile::basedir/static/css/shellinabox.css --debug -s "/:www-data:www-data:HOME:$solcmd" 2>/tmp/sib.log];
|
189
|
# print header(), "Got sol $terminalcmd\n"; exit;
|
190
|
my $cmdout = `$terminalcmd`;
|
191
|
$cmdout =~ s/<title>.+<\/title>/<title>SOL: $macname<\/title>/;
|
192
|
$cmdout =~ s/:(\d+)\//\/shellinabox\/$1\//g;
|
193
|
$postreply = $cmdout;
|
194
|
} else {
|
195
|
$postreply = "Status=ERROR This node does not support serial over lan\n";
|
196
|
}
|
197
|
} else {
|
198
|
$postreply = "Status=ERROR You must specify mac address and have admin rights.\n";
|
199
|
}
|
200
|
return $postreply;
|
201
|
}
|
202
|
|
203
|
sub do_maintenance {
|
204
|
my ($uuid, $action, $obj) = @_;
|
205
|
if ($help) {
|
206
|
return <<END
|
207
|
GET:mac:
|
208
|
Puts the specified node in maintenance mode. A node in maintenance mode is not available for starting new servers.
|
209
|
END
|
210
|
}
|
211
|
my $status = $obj->{'status'};
|
212
|
my $mac = $obj->{'mac'};
|
213
|
my $name = $obj->{'name'};
|
214
|
my $dbstatus = $register{$mac}->{'status'};
|
215
|
if ($dbstatus eq "running") {
|
216
|
$uistatus = "maintenance";
|
217
|
$uiuuid = $mac;
|
218
|
$register{$mac}->{'status'} = $uistatus;
|
219
|
$register{$mac}->{'maintenance'} = 1;
|
220
|
my $logmsg = "Node $mac marked for $action";
|
221
|
$main::syslogit->($user, "info", $logmsg);
|
222
|
$postreply .= "Status=$uistatus OK putting $name in maintenance mode\n";
|
223
|
$main::updateUI->({tab=>"nodes", user=>$user, uuid=>$uiuuid, status=>$uistatus});
|
224
|
} else {
|
225
|
$postreply .= "Status=ERROR Cannot $action a $status node\n";
|
226
|
}
|
227
|
return $postreply;
|
228
|
}
|
229
|
|
230
|
sub do_sleep {
|
231
|
my ($uuid, $action, $obj) = @_;
|
232
|
if ($help) {
|
233
|
return <<END
|
234
|
GET:mac:
|
235
|
Put an idle node to sleep. S3 sleep must be supported and enabled.
|
236
|
END
|
237
|
}
|
238
|
my $status = $obj->{'status'};
|
239
|
my $mac = $obj->{'mac'};
|
240
|
my $name = $obj->{'name'};
|
241
|
my $dbstatus = $register{$mac}->{'status'};
|
242
|
|
243
|
if ($status eq "running" && $register{$mac}->{'vms'}==0) {
|
244
|
my $logmsg = "Node $mac marked for $action ";
|
245
|
$uiuuid = $mac;
|
246
|
if ($brutalsleep && (
|
247
|
($register{$mac}->{'amtip'} && $register{$mac}->{'amtip'} ne '--')
|
248
|
|| ($register{$mac}->{'ipmiip'} && $register{$mac}->{'ipmiip'} ne '--')
|
249
|
)) {
|
250
|
my $sleepcmd;
|
251
|
$uistatus = "asleep";
|
252
|
if ($register{$mac}->{'amtip'} && $register{$mac}->{'amtip'} ne '--') {
|
253
|
$sleepcmd = "echo 'y' | AMT_PASSWORD='$amtpasswd' /usr/bin/amttool $register{$mac}->{'amtip'} powerdown";
|
254
|
} else {
|
255
|
$uistatus = "asleep";
|
256
|
$sleepcmd = "ipmitool -I lanplus -H $register{$mac}->{'ipmiip'} -U ADMIN -P ADMIN power off";
|
257
|
}
|
258
|
$uiuuid = $mac;
|
259
|
$register{$mac}->{'status'} = $uistatus;
|
260
|
$logmsg .= `$sleepcmd`;
|
261
|
} else {
|
262
|
$uistatus = "sleeping";
|
263
|
my $tasks = $register{$mac}->{'tasks'};
|
264
|
$register{$mac}->{'tasks'} = $tasks . $action . " $user \n";
|
265
|
$register{$mac}->{'action'} = "";
|
266
|
}
|
267
|
$register{$mac}->{'status'} = $uistatus;
|
268
|
$logmsg =~ s/\n/ /g;
|
269
|
$main::syslogit->($user, "info", $logmsg);
|
270
|
$postreply .= "Status=$uistatus OK putting $name to sleep\n";
|
271
|
} else {
|
272
|
$postreply .= "Status=ERROR Cannot $action a $dbstatus node or a node with running VMs\n";
|
273
|
}
|
274
|
return $postreply;
|
275
|
}
|
276
|
|
277
|
sub do_wake {
|
278
|
my ($uuid, $action, $obj) = @_;
|
279
|
if ($help) {
|
280
|
return <<END
|
281
|
GET:mac:
|
282
|
Tries to wake or start a node by sending a wake-on-LAN magic packet to the node.
|
283
|
END
|
284
|
}
|
285
|
my $status = $obj->{'status'};
|
286
|
my $mac = $obj->{'mac'} || $uuid;
|
287
|
my $name = $obj->{'name'};
|
288
|
my $wakecmd;
|
289
|
|
290
|
if (1 || $status eq "asleep" || $status eq "inactive" || $status eq "shutdown") {
|
291
|
$uistatus = "waking";
|
292
|
my $logmsg = "Node $mac marked for wake ";
|
293
|
if ($brutalsleep && (
|
294
|
($register{$mac}->{'amtip'} && $register{$mac}->{'amtip'} ne '--')
|
295
|
|| ($register{$mac}->{'ipmiip'} && $register{$mac}->{'ipmiip'} ne '--')
|
296
|
)) {
|
297
|
if ($register{$mac}->{'amtip'} && $register{$mac}->{'amtip'} ne '--') {
|
298
|
$wakecmd = "echo 'y' | AMT_PASSWORD='$amtpasswd' /usr/bin/amttool $register{$mac}->{'amtip'} powerup pxe";
|
299
|
} else {
|
300
|
$wakecmd = "ipmitool -I lanplus -H $register{$mac}->{'ipmiip'} -U ADMIN -P ADMIN power on";
|
301
|
}
|
302
|
$register{$mac}->{'status'} = $uistatus;
|
303
|
$logmsg .= `$wakecmd`;
|
304
|
} else {
|
305
|
$realmac = substr($mac,0,2).":".substr($mac,2,2).":".substr($mac,4,2).":".substr($mac,6,2).":".substr($mac,8,2).":".substr($mac,10,2);
|
306
|
my $broadcastip = $register{$mac}->{'ip'};
|
307
|
$broadcastip =~ s/\.\d{1,3}$/.255/;
|
308
|
$broadcastip = $broadcastip || '10.0.0.255';
|
309
|
$wakecmd = "/usr/bin/wakeonlan -i $broadcastip $realmac";
|
310
|
$logmsg .= `$wakecmd`;
|
311
|
}
|
312
|
$logmsg =~ s/\n/ /g;
|
313
|
$main::syslogit->($user, "info", $logmsg);
|
314
|
$register{$mac}->{'status'} = 'waking';
|
315
|
$postreply .= "Status=$uistatus OK $uistatus $name ($mac)\n";
|
316
|
} else {
|
317
|
$postreply .= "Status=ERROR Cannot $action up a $status node\n";
|
318
|
}
|
319
|
return $postreply;
|
320
|
}
|
321
|
|
322
|
sub do_carryon {
|
323
|
my ($uuid, $action, $obj) = @_;
|
324
|
if ($help) {
|
325
|
return <<END
|
326
|
GET:mac:
|
327
|
Puts the specified node out of maintenance mode. A node in maintenance mode is not available for starting new servers.
|
328
|
END
|
329
|
}
|
330
|
my $status = $obj->{'status'};
|
331
|
my $mac = $obj->{'mac'};
|
332
|
my $name = $obj->{'name'};
|
333
|
my $dbstatus = $register{$mac}->{'status'};
|
334
|
if ($dbstatus eq "maintenance") {
|
335
|
$uistatus = "running";
|
336
|
$uiuuid = $mac;
|
337
|
$register{$mac}->{'status'} = $uistatus;
|
338
|
$register{$mac}->{'maintenance'} = 0;
|
339
|
my $logmsg = "Node $mac marked for $action";
|
340
|
$main::syslogit->($user, "info", $logmsg);
|
341
|
$postreply .= "Status=$uistatus OK putting $name out of maintenance mode\n";
|
342
|
$main::updateUI->({tab=>"nodes", user=>$user, uuid=>$uiuuid, status=>$uistatus});
|
343
|
} else {
|
344
|
$postreply .= "Status=ERROR Cannot $action a $status node\n";
|
345
|
}
|
346
|
return $postreply;
|
347
|
}
|
348
|
|
349
|
sub do_reboot {
|
350
|
my ($uuid, $action, $obj) = @_;
|
351
|
if ($help) {
|
352
|
return <<END
|
353
|
GET:mac:
|
354
|
Reboots the specified node.
|
355
|
END
|
356
|
}
|
357
|
my $status = $obj->{'status'};
|
358
|
my $mac = $obj->{'mac'};
|
359
|
my $name = $obj->{'name'};
|
360
|
if (($status eq "running" || $status eq "maintenance" ) && $register{$mac}->{'vms'}==0) {
|
361
|
$uistatus = "rebooting";
|
362
|
$uiuuid = $mac;
|
363
|
my $tasks = $register{$mac}->{'tasks'};
|
364
|
$register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
|
365
|
$register{$mac}->{'action'} = "";
|
366
|
$register{$mac}->{'status'} = $uistatus;
|
367
|
my $logmsg = "Node $mac marked for $action";
|
368
|
$main::syslogit->($user, "info", $logmsg);
|
369
|
$postreply = "Status=$uistatus OK rebooting $name\n";
|
370
|
} else {
|
371
|
$postreply = "Status=ERROR Cannot $action a $status node or a node with running VMs\n";
|
372
|
}
|
373
|
return $postreply;
|
374
|
}
|
375
|
|
376
|
sub do_halt {
|
377
|
my ($uuid, $action, $obj) = @_;
|
378
|
if ($help) {
|
379
|
return <<END
|
380
|
GET:mac:
|
381
|
Halts the specified node.
|
382
|
END
|
383
|
}
|
384
|
my $mac = $obj->{'mac'};
|
385
|
my $name = $obj->{'name'};
|
386
|
$uistatus = "halting";
|
387
|
$uiuuid = $mac;
|
388
|
my $tasks = $register{$mac}->{'tasks'};
|
389
|
$register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
|
390
|
$register{$mac}->{'action'} = "";
|
391
|
$register{$mac}->{'status'} = $uistatus;
|
392
|
my $logmsg = "Node $mac marked for $action";
|
393
|
$main::syslogit->($user, "info", $logmsg);
|
394
|
$postreply .= "Status=$uistatus OK $uistatus $name\n";
|
395
|
return $postreply;
|
396
|
}
|
397
|
|
398
|
sub do_delete {
|
399
|
my ($uuid, $action, $obj) = @_;
|
400
|
if ($help) {
|
401
|
return <<END
|
402
|
GET:mac:
|
403
|
Deletes a node. Use if a node has been physically removed from engine.
|
404
|
END
|
405
|
}
|
406
|
my $mac = $obj->{'mac'};
|
407
|
my $name = $obj->{'name'};
|
408
|
if ($status ne "running" && $status ne "maintenance" && $status ne "sleeping"
|
409
|
&& $status ne "reload" && $status ne "reloading") {
|
410
|
if ($register{$mac}) {
|
411
|
$uistatus = "deleting";
|
412
|
$uiuuid = $mac;
|
413
|
my $logmsg = "Node $mac marked for deletion";
|
414
|
$main::syslogit->($user, "info", $logmsg);
|
415
|
$postreply .= "Status=$uistatus OK deleting $name ($mac)\n";
|
416
|
$mac =~ /(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)/;
|
417
|
my $file = "/mnt/stabile/tftp/pxelinux.cfg/01-$1-$2-$3-$4-$5-$6";
|
418
|
unlink $file if (-e $file);
|
419
|
delete $register{$mac};
|
420
|
$main::updateUI->({tab=>"nodes", user=>$user});
|
421
|
} else {
|
422
|
$postreply .= "Status=ERROR Node $mac not found\n" . Dumper($obj);
|
423
|
}
|
424
|
} else {
|
425
|
$postreply .= "Status=ERROR Cannot $action a $status node\n";
|
426
|
}
|
427
|
return $postreply;
|
428
|
}
|
429
|
|
430
|
sub do_shutdown {
|
431
|
my ($uuid, $action, $obj) = @_;
|
432
|
if ($help) {
|
433
|
return <<END
|
434
|
GET:mac:
|
435
|
Shuts down the specified node.
|
436
|
END
|
437
|
}
|
438
|
my $status = $obj->{'status'};
|
439
|
my $mac = $obj->{'mac'};
|
440
|
my $name = $obj->{'name'};
|
441
|
if ($status eq "running" && $register{$mac}->{'vms'}==0) {
|
442
|
$uistatus = "shuttingdown";
|
443
|
$uiuuid = $mac;
|
444
|
my $tasks = $register{$mac}->{'tasks'};
|
445
|
$register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
|
446
|
$register{$mac}->{'action'} = "";
|
447
|
$register{$mac}->{'status'} = $uistatus;
|
448
|
my $logmsg = "Node $mac marked for $action";
|
449
|
$main::syslogit->($user, "info", $logmsg);
|
450
|
$postreply .= "Status=$uistatus OK shutting down $name\n";
|
451
|
} else {
|
452
|
$postreply .= "Status=ERROR Cannot $action a $status node or a node with running VMs\n";
|
453
|
}
|
454
|
}
|
455
|
|
456
|
sub do_evacuate {
|
457
|
my ($uuid, $action, $obj) = @_;
|
458
|
if ($help) {
|
459
|
return <<END
|
460
|
GET:mac:
|
461
|
Evacuates the specified node, i.e. tries to migrate all servers away from the node. Node must be in maintenance mode.
|
462
|
END
|
463
|
}
|
464
|
my $status = $obj->{'status'};
|
465
|
my $mac = $obj->{'mac'};
|
466
|
my $name = $obj->{'name'};
|
467
|
my $dbstatus = $register{$mac}->{'status'};
|
468
|
if ($dbstatus eq "maintenance" || $dbstatus eq "running") {
|
469
|
$register{$mac}->{'status'} = 'maintenance' if ($dbstatus eq "running");
|
470
|
$uistatus = "evacuating";
|
471
|
$uiuuid = $mac;
|
472
|
unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
|
473
|
|
474
|
my $actionstr;
|
475
|
my $i = 0;
|
476
|
foreach my $dom (keys %domreg) {
|
477
|
if ($domreg{$dom}->{'mac'} eq $mac &&
|
478
|
($domreg{$dom}->{'status'} eq 'running' || $domreg{$dom}->{'status'} eq 'paused')) {
|
479
|
$actionstr .= qq[{"uuid": "$dom", "action": "move", "console": 1}, ];
|
480
|
$i++;
|
481
|
}
|
482
|
}
|
483
|
untie %domreg;
|
484
|
if ($actionstr) {
|
485
|
$actionstr = substr($actionstr,0,-2);
|
486
|
my $postdata = URI::Escape::uri_escape(
|
487
|
qq/{"identifier": "uuid", "label": "uuid", "items":[$actionstr]}/
|
488
|
);
|
489
|
my $res;
|
490
|
if ($console) {
|
491
|
$res = `REMOTE_USER=$user $Stabile::basedir/cgi/servers.cgi $postdata`;
|
492
|
$postreply .= "Stroke=OK Move: $res\n";
|
493
|
} else {
|
494
|
$res = `/usr/bin/ssh -l irigo -i /var/www/.ssh/id_rsa_www -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no localhost REMOTE_USER=$user $Stabile::basedir/cgi/servers.cgi $postdata`;
|
495
|
# $postreply .= "Stroke=OK Now moving: $res\n";
|
496
|
}
|
497
|
$res =~ s/\n/ - /g;
|
498
|
my $logmsg = "Node $mac marked for $action: $res";
|
499
|
$main::syslogit->($user, "info", $logmsg);
|
500
|
$postreply .= "Status=OK Node $name marked for evacuation ($i servers)\n";
|
501
|
} else {
|
502
|
$postreply .= "Status=OK No servers found to evacaute\n";
|
503
|
}
|
504
|
} else {
|
505
|
$postreply .= "Status=ERROR Cannot $action a $status node (not in maintenance, not running)\n";
|
506
|
}
|
507
|
return $postreply;
|
508
|
}
|
509
|
|
510
|
|
511
|
sub do_reset {
|
512
|
my ($uuid, $action, $obj) = @_;
|
513
|
if ($help) {
|
514
|
return <<END
|
515
|
GET:mac:
|
516
|
Resets the specified node.
|
517
|
END
|
518
|
}
|
519
|
my $mac = $obj->{'mac'};
|
520
|
my $name = $obj->{'name'};
|
521
|
my $dbstatus = $register{$mac}->{'status'};
|
522
|
if (($dbstatus eq "maintenance" && $register{$mac}->{'vms'} == 0)
|
523
|
|| $dbstatus eq "inactive"
|
524
|
|| $dbstatus eq "waking"
|
525
|
|| $dbstatus eq "sleeping"
|
526
|
|| $dbstatus eq "shuttingdown"
|
527
|
|| $dbstatus eq "shutdown"
|
528
|
|| $dbstatus eq "joining"
|
529
|
) {
|
530
|
my $resetcmd;
|
531
|
if ($register{$mac}->{'amtip'} && $register{$mac}->{'amtip'} ne '--') {
|
532
|
$uistatus = "reset";
|
533
|
$resetcmd = "echo 'y' | AMT_PASSWORD='$amtpasswd' /usr/bin/amttool $register{$mac}->{'amtip'} reset bios";
|
534
|
} elsif ($register{$mac}->{'ipmiip'} && $register{$mac}->{'ipmiip'} ne '--') {
|
535
|
$uistatus = "reset";
|
536
|
$resetcmd = "ipmitool -I lanplus -H $register{$mac}->{'ipmiip'} -U ADMIN -P ADMIN power reset";
|
537
|
} else {
|
538
|
$postreply .= "Status=ERROR This node does not support hardware reset\n";
|
539
|
}
|
540
|
if ($uistatus eq 'reset') {
|
541
|
$uiuuid = $mac;
|
542
|
$register{$mac}->{'status'} = $uistatus;
|
543
|
my $logmsg = "Node $mac marked for $action";
|
544
|
$logmsg .= `$resetcmd`;
|
545
|
$logmsg =~ s/\n/ /g;
|
546
|
$main::syslogit->($user, "info", $logmsg);
|
547
|
$postreply .= "Stroke=$uistatus OK resetting $name ";
|
548
|
}
|
549
|
} else {
|
550
|
$postreply .= "Status=ERROR Cannot $action a $dbstatus node\n";
|
551
|
}
|
552
|
return $postreply;
|
553
|
}
|
554
|
|
555
|
sub do_unjoin {
|
556
|
my ($uuid, $action, $obj) = @_;
|
557
|
if ($help) {
|
558
|
return <<END
|
559
|
GET:mac:
|
560
|
Disassciates a node from the engine and reboots it. After rebooting, it will join the engine with the default
|
561
|
node identity
|
562
|
END
|
563
|
}
|
564
|
my $mac = $obj->{'mac'};
|
565
|
my $name = $obj->{'name'};
|
566
|
my $dbstatus = $register{$mac}->{'status'};
|
567
|
if ($dbstatus eq "running" && $register{$mac}->{'vms'}==0) {
|
568
|
$uistatus = "unjoining";
|
569
|
$uiuuid = $mac;
|
570
|
my $tasks = $register{$mac}->{'tasks'};
|
571
|
$register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
|
572
|
$register{$mac}->{'action'} = "";
|
573
|
$register{$mac}->{'status'} = $uistatus;
|
574
|
my $logmsg = "Node $mac marked for $action";
|
575
|
$main::syslogit->($user, "info", $logmsg);
|
576
|
$postreply .= "Status=$uistatus OK unjoining $name\n";
|
577
|
} else {
|
578
|
$postreply .= "Status=ERROR Cannot $action a $dbstatus node or a node with running VMs\n";
|
579
|
}
|
580
|
return $postreply;
|
581
|
}
|
582
|
|
583
|
sub do_wipe {
|
584
|
my ($uuid, $action, $obj) = @_;
|
585
|
if ($help) {
|
586
|
return <<END
|
587
|
GET:mac:
|
588
|
Erases a node's harddrive and formats it with either ext4 or zfs, depending on settings.
|
589
|
Only allowed if /mnt/stabile/node is empty.
|
590
|
END
|
591
|
}
|
592
|
my $mac = $obj->{'mac'};
|
593
|
my $name = $obj->{'name'};
|
594
|
unless ($register{$mac}) {
|
595
|
$postreply .= "Status=ERROR Please specify a valid mac.\n";
|
596
|
return $postreply;
|
597
|
}
|
598
|
my $dbstatus = $register{$mac}->{'status'};
|
599
|
if ($dbstatus eq "running" && $register{$mac}->{'vms'}==0) {
|
600
|
$uistatus = "wiping";
|
601
|
$uiuuid = $mac;
|
602
|
my $tasks = $register{$mac}->{'tasks'};
|
603
|
$register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
|
604
|
$register{$mac}->{'action'} = "";
|
605
|
$register{$mac}->{'status'} = $uistatus;
|
606
|
my $logmsg = "Node $mac marked for $action";
|
607
|
$main::syslogit->($user, "info", $logmsg);
|
608
|
$postreply .= "Status=$uistatus OK wiping $name\n";
|
609
|
} else {
|
610
|
$postreply .= "Status=ERROR Cannot $action a $dbstatus node or a node with running VMs\n";
|
611
|
}
|
612
|
return $postreply;
|
613
|
}
|
614
|
|
615
|
sub do_setdefaultnodeidentity {
|
616
|
my ($uuid, $action, $obj) = @_;
|
617
|
if ($help) {
|
618
|
return <<END
|
619
|
GET:hid,sleepafter:
|
620
|
Sets the default identity a node should boot as. [sleepafter] is in seconds, [hid] is [name] of one the alternatives listed by [listnodeidentities].
|
621
|
END
|
622
|
}
|
623
|
my $hid = $params{'hid'};
|
624
|
my $sleepafter = $params{'sleepafter'};
|
625
|
unless ($hid) {return "Status=ERROR No identity selected\n"};
|
626
|
unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'name'}, $Stabile::dbopts)) ) {return "Unable to access id register"};
|
627
|
my @idvalues = values %idreg;
|
628
|
foreach my $val (@idvalues) {
|
629
|
my $identity = $val->{'name'};
|
630
|
if ($identity eq $hid) {$identity = "default"}
|
631
|
$idreg{$val->{'name'}} = {
|
632
|
identity=>$identity,
|
633
|
sleepafter=>int($sleepafter)
|
634
|
}
|
635
|
}
|
636
|
tied(%idreg)->commit;
|
637
|
untie %idreg;
|
638
|
$postreply = "Status=OK Set $hid as new default identity, sleeping after $sleepafter minutes\n";
|
639
|
}
|
640
|
|
641
|
sub do_listlog {
|
642
|
my ($uuid, $action, $obj) = @_;
|
643
|
if ($help) {
|
644
|
return <<END
|
645
|
GET::
|
646
|
Lists the last 200 lines from the local activity log file.
|
647
|
END
|
648
|
}
|
649
|
$postreply = header("text/plain");
|
650
|
if ($isadmin) {
|
651
|
$postreply .= `tail -n 200 $main::logfile`;
|
652
|
} else {
|
653
|
$postreply .= `tail -n 200 $main::logfile | grep ': $user :'`;
|
654
|
}
|
655
|
}
|
656
|
|
657
|
sub do_clearlog {
|
658
|
my ($uuid, $action, $obj) = @_;
|
659
|
if ($help) {
|
660
|
return <<END
|
661
|
GET::
|
662
|
Clear the local activity log file.
|
663
|
END
|
664
|
}
|
665
|
`> $main::logfile`;
|
666
|
# unlink $logfile;
|
667
|
$postreply = header("text/plain");
|
668
|
$postreply .= "Status=OK Log cleared\n";
|
669
|
return $postreply;
|
670
|
}
|
671
|
|
672
|
sub do_updateregister {
|
673
|
my ($uuid, $action, $obj) = @_;
|
674
|
if ($help) {
|
675
|
return <<END
|
676
|
GET::
|
677
|
Updates the node register.
|
678
|
END
|
679
|
}
|
680
|
updateRegister();
|
681
|
$postreply = "Stream=OK Updated node register for all users\n";
|
682
|
return $postreply;
|
683
|
}
|
684
|
|
685
|
sub do_reload {
|
686
|
my ($uuid, $action, $obj) = @_;
|
687
|
if ($help) {
|
688
|
return <<END
|
689
|
GET:mac,nodeaction:
|
690
|
Reload configuration on the specified node or perform specified action.
|
691
|
END
|
692
|
}
|
693
|
my $status = $obj->{'status'};
|
694
|
my $mac = $obj->{'mac'};
|
695
|
my $nodeaction = "reload" || $obj->{'nodeaction'};
|
696
|
if ($status eq "running") {
|
697
|
$uistatus = "reloading";
|
698
|
$uiuuid = $mac;
|
699
|
my $tasks = $register{$mac}->{'tasks'};
|
700
|
$register{$mac}->{'tasks'} = $tasks . $nodeaction . " $user\n";
|
701
|
$register{$mac}->{'action'} = "";
|
702
|
$register{$mac}->{'status'} = $uistatus;
|
703
|
my $logmsg = "Node $mac marked for $action";
|
704
|
$main::syslogit->($user, "info", $logmsg);
|
705
|
$postreply .= "Status=$uistatus OK reloading $name\n";
|
706
|
}
|
707
|
else {
|
708
|
$postreply .= "Status=ERROR Cannot $action a $status node\n";
|
709
|
}
|
710
|
return $postreply;
|
711
|
}
|
712
|
|
713
|
sub do_reloadall {
|
714
|
my ($uuid, $action, $obj) = @_;
|
715
|
if ($help) {
|
716
|
return <<END
|
717
|
GET:nodeaction:
|
718
|
Reload configuration on all nodes. Alternatively specify a "nodeaction" to have it executed on all nodes.
|
719
|
Currently supported nodeactions: CGLOAD [reload cgroup configuration]
|
720
|
END
|
721
|
}
|
722
|
my $nodeaction = $obj->{'nodeaction'} || "reload";
|
723
|
my @regvalues = values %register;
|
724
|
# Only include pistons we have heard from in the last 20 secs
|
725
|
foreach $val (@regvalues) {
|
726
|
my $curstatus = $val->{'status'};
|
727
|
my $mac = $val->{'mac'};
|
728
|
my $name = $val->{'name'};
|
729
|
if ($curstatus eq "running" || $curstatus eq "maintenance") {
|
730
|
$uistatus = "reloading";
|
731
|
$uiuuid = $mac;
|
732
|
my $tasks = $register{$mac}->{'tasks'};
|
733
|
$register{$mac}->{'tasks'} = $tasks . $nodeaction . " $user\n";
|
734
|
$register{$mac}->{'action'} = "";
|
735
|
$register{$mac}->{'status'} = $uistatus;
|
736
|
my $logmsg = "Node $mac marked for $nodeaction";
|
737
|
$main::syslogit->($user, "info", $logmsg);
|
738
|
$postreply .= "Status=OK $uistatus $name\n";
|
739
|
} else {
|
740
|
$postreply .= "Status=OK Node $mac ($register->{$mac}) is $register{$mac}->{'status'} not reloading\n";
|
741
|
}
|
742
|
}
|
743
|
return $postreply;
|
744
|
}
|
745
|
|
746
|
sub do_rebootall {
|
747
|
my ($uuid, $action, $obj) = @_;
|
748
|
if ($help) {
|
749
|
return <<END
|
750
|
GET::
|
751
|
Reboot all active nodes.
|
752
|
END
|
753
|
}
|
754
|
my @regvalues = values %register;
|
755
|
# Only include pistons we have heard from in the last 20 secs
|
756
|
foreach $val (@regvalues) {
|
757
|
my $curstatus = $val->{'status'};
|
758
|
my $mac = $val->{'mac'};
|
759
|
$action = "reboot";
|
760
|
my $name = $val->{'name'};
|
761
|
my $identity = $val->{'identity'};
|
762
|
if (($curstatus eq "running" || $curstatus eq "maintenance") && $identity ne 'local_kvm')
|
763
|
{
|
764
|
$uistatus = "rebooting";
|
765
|
$uiuuid = $mac;
|
766
|
my $tasks = $register{$mac}->{'tasks'};
|
767
|
$register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
|
768
|
$register{$mac}->{'action'} = "";
|
769
|
$register{$mac}->{'status'} = $uistatus;
|
770
|
my $logmsg = "Node $mac marked for $action";
|
771
|
$main::syslogit->($user, "info", $logmsg);
|
772
|
$postreply .= "Status=OK $uistatus $name\n";
|
773
|
}
|
774
|
}
|
775
|
$postreply = $postreply || "Status=ERROR No active nodes found\n";
|
776
|
return $postreply;
|
777
|
}
|
778
|
|
779
|
sub do_haltall {
|
780
|
my ($uuid, $action, $obj) = @_;
|
781
|
if ($help) {
|
782
|
return <<END
|
783
|
GET:nowait:
|
784
|
Unceremoniously halt all active nodes.
|
785
|
END
|
786
|
}
|
787
|
my @regvalues = values %register;
|
788
|
my $nowait = $obj->{'nowait'};
|
789
|
# Only include pistons we have heard from in the last 20 secs
|
790
|
foreach $val (@regvalues) {
|
791
|
my $curstatus = $val->{'status'};
|
792
|
my $identity = $val->{'identity'};
|
793
|
my $mac = $val->{'mac'};
|
794
|
$action = "halt";
|
795
|
my $name = $val->{'name'};
|
796
|
if (($curstatus eq "running" || $curstatus eq "maintenance") && $identity ne 'local_kvm')
|
797
|
{
|
798
|
$uistatus = "halting";
|
799
|
$uiuuid = $mac;
|
800
|
my $tasks = $register{$mac}->{'tasks'};
|
801
|
$register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
|
802
|
$register{$mac}->{'action'} = "";
|
803
|
$register{$mac}->{'status'} = $uistatus;
|
804
|
my $logmsg = "Node $mac marked for $action";
|
805
|
$main::syslogit->($user, "info", $logmsg);
|
806
|
$postreply .= "Status=OK $uistatus $name\n";
|
807
|
}
|
808
|
}
|
809
|
unless ($nowait) {
|
810
|
$postreply .= "Status=OK Waiting up to 100 seconds for running nodes to shut down\n";
|
811
|
my $livenodes = 0;
|
812
|
for (my $i; $i<10; $i++) {
|
813
|
$livenodes = 0;
|
814
|
do_list();
|
815
|
foreach $val (@regvalues) {
|
816
|
my $curstatus = $val->{'status'};
|
817
|
my $identity = $val->{'identity'};
|
818
|
my $mac = $val->{'mac'};
|
819
|
my $name = $val->{'name'};
|
820
|
if (($curstatus eq "running" || $curstatus eq "maintenance" || $curstatus eq "halting") && $identity ne 'local_kvm') {
|
821
|
$livenodes = 1;
|
822
|
}
|
823
|
}
|
824
|
last unless ($livenodes);
|
825
|
sleep 10;
|
826
|
}
|
827
|
|
828
|
}
|
829
|
$postreply = $postreply || "Status=ERROR No active nodes found\n";
|
830
|
return $postreply;
|
831
|
}
|
832
|
|
833
|
sub Updateamtinfo {
|
834
|
my ($uuid, $action, $obj) = @_;
|
835
|
if ($help) {
|
836
|
return <<END
|
837
|
GET::
|
838
|
Updates info about the nodes' AMT configuration by scanning the network.
|
839
|
END
|
840
|
}
|
841
|
$postreply = updateAmtInfo();
|
842
|
return $postreply;
|
843
|
}
|
844
|
|
845
|
sub Stats {
|
846
|
my ($uuid, $action, $obj) = @_;
|
847
|
if ($help) {
|
848
|
return <<END
|
849
|
GET::
|
850
|
Collect and show stats for this engine. May also be called as fullstats or fullstatsb (includes backup info).
|
851
|
END
|
852
|
}
|
853
|
return "Status=Error Not allowed\n" unless ($isadmin);
|
854
|
my @regvalues = values %register;
|
855
|
my %stats;
|
856
|
my $cpuloadsum = 0;
|
857
|
my $memtotalsum = 0;
|
858
|
my $memfreesum = 0;
|
859
|
my $memusedsum = 0;
|
860
|
my $corestotal = 0;
|
861
|
my $vmstotal = 0;
|
862
|
my $vmvcpustotal = 0;
|
863
|
my $nodestorfree = 0;
|
864
|
my $nodestorused = 0;
|
865
|
my $nodestortotal = 0;
|
866
|
my $i = 0;
|
867
|
|
868
|
$Stabile::Systems::user = $user;
|
869
|
require "$Stabile::basedir/cgi/systems.cgi";
|
870
|
$Stabile::Systems::console = 1;
|
871
|
#$console = 1;
|
872
|
|
873
|
# Only include pistons we have heard from in the last 20 secs
|
874
|
foreach $val (@regvalues) {
|
875
|
if ((($val->{'status'} eq "asleep") || ($current_time - ($val->{'timestamp'}) < 20)) && ($val->{'status'} ne "joining") && ($val->{'status'} ne "shutdown") && ($val->{'status'} ne "reboot") ) {
|
876
|
$cpuloadsum += $val->{'cpuload'} / ($val->{'cpucount'} * $val->{'cpucores'}) if ($val->{'cpucount'}>0);
|
877
|
$memtotalsum += $val->{'memtotal'};
|
878
|
$memfreesum += $val->{'memfree'};
|
879
|
$corestotal += $val->{'cpucount'} * $val->{'cpucores'};
|
880
|
$vmstotal += $val->{'vms'};
|
881
|
$vmvcpustotal += $val->{'vmvcpus'};
|
882
|
$nodestorfree += $val->{'storfree'};
|
883
|
$nodestortotal += $val->{'stortotal'};
|
884
|
$readynodes ++ if ($val->{'status'} eq 'running' || $val->{'status'} eq 'maintenance' || $val->{'status'} eq 'asleep');
|
885
|
$i++;
|
886
|
# } elsif (($val->{'identity'} ne "local_kvm") &&($val->{'status'} eq 'running' || $val->{'status'} eq 'maintenance')) {
|
887
|
# $readynodes++;
|
888
|
}
|
889
|
}
|
890
|
$memusedsum = $memtotalsum - $memfreesum;
|
891
|
$nodestorused = $nodestortotal - $nodestorfree;
|
892
|
|
893
|
$cpuloadsum = $cpuloadsum / $i if ($i > 0); # Avoid division by zero
|
894
|
my %avgs = ("cpuloadavg" => $cpuloadsum, "memtotalsum" => $memtotalsum, "memfreesum" => $memfreesum,
|
895
|
"nodestotal" => $i,"corestotal" => $corestotal, "readynodes" => $readynodes,
|
896
|
"vmstotal" => $vmstotal, "vmvcpustotal" => $vmvcpustotal,
|
897
|
"nodestortotal" => $nodestortotal, "nodestorfree" => $nodestorfree);
|
898
|
|
899
|
my %storavgs;
|
900
|
my $stortext;
|
901
|
my $j = 0;
|
902
|
push @tenderpathslist, $backupdir;
|
903
|
push @tendernameslist, "Backup";
|
904
|
foreach my $storpath (@tenderpathslist) {
|
905
|
my $storfree = `df $storpath`;
|
906
|
$storfree =~ m/(\d\d\d\d+)(\s+)(\d\d+)(\s+)(\d\d+)(\s+)(\S+)/i;
|
907
|
my $stortotal = $1;
|
908
|
my $storused = $3;
|
909
|
$storfree = $5;
|
910
|
$storavgs{$tendernameslist[$j].'-used'} = $storused;
|
911
|
$storavgs{$tendernameslist[$j].'-total'} = $stortotal;
|
912
|
$stortext .= $tendernameslist[$j] . ": " .int($storused/1024/1024) . " (" . int($stortotal/1024/1024) . ") GB ";
|
913
|
$j++;
|
914
|
}
|
915
|
|
916
|
my %mons;
|
917
|
my @monservices = ('ping', 'diskspace', 'http', 'https', 'smtp', 'smtps', 'ldap', 'imap', 'imaps', 'telnet');
|
918
|
if ($action eq "fullstats" || $action eq "fullstatsb") {
|
919
|
$Stabile::Systems::fulllist = 1;
|
920
|
%mons = Stabile::Systems::getOpstatus();
|
921
|
$Stabile::Systems::fulllist = 0;
|
922
|
}
|
923
|
if ($action eq "fullstatsb") {
|
924
|
require "images.cgi";
|
925
|
$Stabile::Images::isadmin = $isadmin;
|
926
|
$Stabile::Images::console = 1;
|
927
|
}
|
928
|
my @lusers;
|
929
|
# We use images billing to report storage usage
|
930
|
unless ( tie(%billingreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_images', key=>'userstoragepooltime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
|
931
|
foreach my $uref (values %userreg) {
|
932
|
my %uval = %{$uref};
|
933
|
|
934
|
delete $uval{'password'};
|
935
|
delete $uval{'lasttkt'};
|
936
|
delete $uval{'tasks'};
|
937
|
|
938
|
# Skip if not logged in in 5 days
|
939
|
# next unless ($uval{'lastlogin'} && ($current_time-$uval{'lastlogin'} < 5 * 86400));
|
940
|
my @systems = Stabile::Systems::getSystemsListing('arraylist', '', $uval{'username'});
|
941
|
# Skip if user has no systems
|
942
|
# next unless (@systems);
|
943
|
|
944
|
my @returnsystems;
|
945
|
my $vcpus = 0;
|
946
|
my $mem = 0;
|
947
|
my $servers = 0;
|
948
|
foreach my $sys (@systems) {
|
949
|
my $sysvcpus = 0;
|
950
|
my $sysmem = 0;
|
951
|
my $sysstor = 0;
|
952
|
my $sysnodestor = 0;
|
953
|
if ($sys->{'issystem'}) {
|
954
|
foreach my $dom (@{$sys->{'children'}}) {
|
955
|
my $status = $dom->{'status'};
|
956
|
# if ($status ne 'shutoff' && $status ne 'inactive') { # We now report usage also when not running
|
957
|
$sysvcpus += $dom->{'vcpu'};
|
958
|
$sysmem += $dom->{'memory'};
|
959
|
# }
|
960
|
$sysstor += $dom->{'storage'}/1024/1024;
|
961
|
$sysnodestor += $dom->{'nodestorage'}/1024/1024;
|
962
|
}
|
963
|
} else {
|
964
|
my $status = $sys->{'status'};
|
965
|
# if ($status ne 'shutoff' && $status ne 'inactive') {
|
966
|
$sysvcpus = $sys->{'vcpu'};
|
967
|
$sysmem = $sys->{'memory'};
|
968
|
# }
|
969
|
$sysstor = $sys->{'storage'}/1024/1024;
|
970
|
$sysnodestor = $sys->{'nodestorage'}/1024/1024;
|
971
|
}
|
972
|
$vcpus += $sysvcpus;
|
973
|
$mem += $sysmem;
|
974
|
my $serveruuids = $sys->{'uuid'};
|
975
|
if ($sys->{'issystem'}) {
|
976
|
my @suuids;
|
977
|
foreach my $child (@{$sys->{'children'}}) {
|
978
|
push @suuids, $child->{'uuid'};
|
979
|
};
|
980
|
$serveruuids = join(', ', @suuids);
|
981
|
}
|
982
|
|
983
|
$returnsys = {
|
984
|
'appid'=>$sys->{'appid'},
|
985
|
'version'=>$sys->{'version'},
|
986
|
'managementurl'=>$sys->{'managementurl'},
|
987
|
'upgradeurl'=>$sys->{'upgradeurl'},
|
988
|
'terminalurl'=>$sys->{'terminalurl'},
|
989
|
'master'=>$sys->{'master'},
|
990
|
'name'=>$sys->{'name'},
|
991
|
'image'=>$sys->{'image'},
|
992
|
'status'=>$sys->{'status'},
|
993
|
'user'=>$sys->{'user'},
|
994
|
'uuid'=>$sys->{'uuid'},
|
995
|
'servers'=>($sys->{'issystem'}?scalar @{$sys->{'children'}}:1),
|
996
|
'serveruuids' => $serveruuids,
|
997
|
'vcpus' => $sysvcpus,
|
998
|
'memory' => $sysmem,
|
999
|
'storage' => $sysstor+0,
|
1000
|
'nodestorage' => $sysnodestor+0,
|
1001
|
'externalips' => $sys->{'externalips'}+0,
|
1002
|
'externalip' => $sys->{'externalip'},
|
1003
|
'ports' => $sys->{'ports'},
|
1004
|
'internalip' => $sys->{'internalip'}
|
1005
|
};
|
1006
|
$servers += ($sys->{'issystem'}?scalar @{$sys->{'children'}}:1);
|
1007
|
my $monitors;
|
1008
|
my $backups;
|
1009
|
|
1010
|
if (%mons || $action eq "fullstatsb") {
|
1011
|
if ($sys->{'issystem'}) {
|
1012
|
foreach my $dom (@{$sys->{'children'}}) {
|
1013
|
foreach my $service (@monservices) {
|
1014
|
my $id = $dom->{'uuid'} . ":$service";
|
1015
|
if ($mons{$id}) {
|
1016
|
my $last_status = $mons{$id}->{'last_success'} || $mons{$id}->{'last_failure'};
|
1017
|
$monitors .= "$dom->{'name'}/$service/$mons{$id}->{'status'}/$last_status, " ;
|
1018
|
}
|
1019
|
}
|
1020
|
if ($action eq "fullstatsb") {
|
1021
|
my $bups = Stabile::Images::Getserverbackups($dom->{'uuid'});
|
1022
|
$backups .= "$bups, " if ($bups);
|
1023
|
}
|
1024
|
}
|
1025
|
$monitors = substr($monitors, 0,-2) if ($monitors);
|
1026
|
$backups = substr($backups, 0,-2) if ($backups);
|
1027
|
} else {
|
1028
|
foreach my $service (@monservices) {
|
1029
|
my $id = $sys->{'uuid'} . ":$service";
|
1030
|
if ($mons{$id}) {
|
1031
|
my $last_status = $mons{$id}->{'last_success'} || $mons{$id}->{'last_failure'};
|
1032
|
$monitors .= "$sys->{'name'}/$service/$mons{$id}->{'status'}/$last_status, ";
|
1033
|
}
|
1034
|
}
|
1035
|
$monitors = substr($monitors, 0,-2) if ($monitors);
|
1036
|
$backups = Stabile::Images::Getserverbackups($sys->{'uuid'}) if ($action eq "fullstatsb");
|
1037
|
}
|
1038
|
$returnsys->{'monitors'} = $monitors if ($monitors);
|
1039
|
$returnsys->{'backups'} = $backups if ($backups);
|
1040
|
}
|
1041
|
|
1042
|
push @returnsystems, $returnsys;
|
1043
|
}
|
1044
|
$uval{'systems'} = \@returnsystems;
|
1045
|
|
1046
|
$uval{'nodestorage'} = int($billingreg{"$uval{username}--1-$year-$month"}->{'virtualsize'}/1024/1024) if ($billingreg{"$uval{username}--1-$year-$month"});
|
1047
|
my $stor = 0;
|
1048
|
for (my $i=0; $i <= scalar @tenderpathslist; $i++) {
|
1049
|
$stor += $billingreg{"$uval{username}-$i-$year-$month"}->{'virtualsize'} if ($billingreg{"$uval{username}-$i-$year-$month"});
|
1050
|
}
|
1051
|
$uval{'storage'} = int($stor/1024/1024);
|
1052
|
$uval{'vcpu'} = $vcpus;
|
1053
|
$uval{'memory'} = $mem;
|
1054
|
$uval{'servers'} = $servers;
|
1055
|
|
1056
|
push @lusers, \%uval;
|
1057
|
}
|
1058
|
untie %billingreg;
|
1059
|
my $ver = `cat /etc/stabile/version`; chomp $ver;
|
1060
|
|
1061
|
$stortext .= "Nodes: " . int($nodestorused/1024/1024) . " (" . int($nodestortotal/1024/1024) . ") GB";
|
1062
|
$stats{'status'} = ($readynodes>0?'ready':'nonodes');
|
1063
|
$stats{'storavgs'} = \%storavgs;
|
1064
|
$stats{'avgs'} = \%avgs;
|
1065
|
$stats{'users'} = \@lusers;
|
1066
|
$stats{'stortext'} = $stortext;
|
1067
|
# $stats{'version'} = $version;
|
1068
|
$stats{'version'} = $ver;
|
1069
|
|
1070
|
my $json_text = to_json(\%stats, {pretty=>1});
|
1071
|
$json_text =~ s/\x/ /g;
|
1072
|
$json_text =~ s/null/""/g;
|
1073
|
#$postreply = header("application/json") unless ($console);
|
1074
|
$postreply .= $json_text;
|
1075
|
return $postreply;
|
1076
|
}
|
1077
|
|
1078
|
sub do_list {
|
1079
|
my ($uuid, $action, $obj) = @_;
|
1080
|
if ($help) {
|
1081
|
return <<END
|
1082
|
GET:uuid:
|
1083
|
List the nodes running this engine.
|
1084
|
END
|
1085
|
}
|
1086
|
if ($isadmin || index($privileges,"n")!=-1) {
|
1087
|
my @regvalues = values %register;
|
1088
|
my @curregvalues;
|
1089
|
# Only include pistons we have heard from in the last 20 secs
|
1090
|
foreach $valref (@regvalues) {
|
1091
|
my $curstatus = $valref->{'status'};
|
1092
|
if (
|
1093
|
($current_time - ($valref->{'timestamp'}) > 20)
|
1094
|
&& ($curstatus ne "joining") && ($curstatus ne "shutdown") && ($curstatus ne "reboot")
|
1095
|
&& ($curstatus ne "asleep") && ($curstatus ne "waking") && ($curstatus ne "sleeping")
|
1096
|
) {$valref->{'status'} = "inactive"};
|
1097
|
|
1098
|
$valref->{'name'} = $valref->{'mac'} unless ($valref->{'name'} && $valref->{'name'} ne '--');
|
1099
|
my %val = %{$valref}; # Deference and assign to new ass array, effectively cloning object
|
1100
|
# %{$valref}->{'cpucores'} is the same as $valref->{'cpucores'};
|
1101
|
# These values should be sent as numbers
|
1102
|
$val{'cpucores'} += 0;
|
1103
|
$val{'cpucount'} += 0;
|
1104
|
$val{'memfree'} += 0;
|
1105
|
$val{'memtotal'} += 0;
|
1106
|
$val{'storfree'} += 0;
|
1107
|
$val{'stortotal'} += 0;
|
1108
|
$val{'vms'} += 0;
|
1109
|
$val{'cpuload'} += 0;
|
1110
|
|
1111
|
push @curregvalues,\%val ;
|
1112
|
}
|
1113
|
|
1114
|
# Sort @curregvalues
|
1115
|
my $sort = 'name';
|
1116
|
$sort = $2 if ($uripath =~ /sort\((\+|\-)(\S+)\)/);
|
1117
|
my $reverse;
|
1118
|
$reverse = 1 if ($1 eq '-');
|
1119
|
if ($reverse) { # sort reverse
|
1120
|
if ($sort =~ /cpucores|cpucount|memfree|memtotal|vms|cpuload/) {
|
1121
|
@curregvalues = (sort {$b->{$sort} <=> $a->{$sort}} @curregvalues); # Sort as number
|
1122
|
} else {
|
1123
|
@curregvalues = (sort {$b->{$sort} cmp $a->{$sort}} @curregvalues); # Sort as string
|
1124
|
}
|
1125
|
} else {
|
1126
|
if ($sort =~ /cpucores|cpucount|memfree|memtotal|vms|cpuload/) {
|
1127
|
@curregvalues = (sort {$a->{$sort} <=> $b->{$sort}} @curregvalues); # Sort as number
|
1128
|
} else {
|
1129
|
@curregvalues = (sort {$a->{$sort} cmp $b->{$sort}} @curregvalues); # Sort as string
|
1130
|
}
|
1131
|
}
|
1132
|
|
1133
|
if ($action eq 'tablelist') {
|
1134
|
my $t2 = Text::SimpleTable->new(14,20,14,10,5,5,12,7);
|
1135
|
$t2->row('mac', 'name', 'ip', 'identity', 'cores', 'vms', 'memfree', 'status');
|
1136
|
$t2->hr;
|
1137
|
my $pattern = $options{m};
|
1138
|
foreach $rowref (@curregvalues){
|
1139
|
if ($pattern) {
|
1140
|
my $rowtext = "$rowref->{'mac'} $rowref->{'name'} $rowref->{'ip'} $rowref->{'identity'} "
|
1141
|
. "$rowref->{'vms'} $rowref->{'memfree'} $rowref->{'status'}";
|
1142
|
$rowtext .= " " . $rowref->{'mac'} if ($isadmin);
|
1143
|
next unless ($rowtext =~ /$pattern/i);
|
1144
|
}
|
1145
|
$t2->row($rowref->{'mac'}, $rowref->{'name'}, $rowref->{'ip'}, $rowref->{'identity'}, $rowref->{'cpucores'},
|
1146
|
$rowref->{'vms'}, $rowref->{'memfree'}, $rowref->{'status'});
|
1147
|
}
|
1148
|
$postreply .= header("text/plain") unless ($console);
|
1149
|
$postreply .= $t2->draw;
|
1150
|
} elsif ($console) {
|
1151
|
$postreply = Dumper(\@curregvalues);
|
1152
|
} else {
|
1153
|
my $json_text = to_json(\@curregvalues, {pretty=>1});
|
1154
|
$json_text =~ s/""/"--"/g;
|
1155
|
$json_text =~ s/null/"--"/g;
|
1156
|
$json_text =~ s/\x/ /g;
|
1157
|
$postreply .= qq|{"identifier": "mac", "label": "name", "items":| if ($action && $action ne 'list');
|
1158
|
$postreply .= $json_text;
|
1159
|
$postreply .= "}" if ($action && $action ne 'list');
|
1160
|
}
|
1161
|
} else {
|
1162
|
$postreply .= q|{"identifier": "mac", "label": "name", "items":| if ($action && $action ne 'list');
|
1163
|
$postreply .= "[]";
|
1164
|
$postreply .= "}" if ($action && $action ne 'list');
|
1165
|
}
|
1166
|
return $postreply;
|
1167
|
}
|
1168
|
|
1169
|
sub do_uuidlookup {
|
1170
|
if ($help) {
|
1171
|
return <<END
|
1172
|
GET:uuid:
|
1173
|
Simple action for looking up a uuid or part of a uuid and returning the complete uuid.
|
1174
|
END
|
1175
|
}
|
1176
|
|
1177
|
my $u = $options{u};
|
1178
|
$u = $params{'uuid'} unless ($u || $u eq '0');
|
1179
|
my $ruuid;
|
1180
|
if ($u || $u eq '0') {
|
1181
|
foreach my $uuid (keys %register) {
|
1182
|
if ($uuid =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/) {
|
1183
|
return "$uuid\n";
|
1184
|
}
|
1185
|
}
|
1186
|
}
|
1187
|
}
|
1188
|
|
1189
|
sub do_uuidshow {
|
1190
|
if ($help) {
|
1191
|
return <<END
|
1192
|
GET:uuid:
|
1193
|
Simple action for showing a single network.
|
1194
|
END
|
1195
|
}
|
1196
|
my $u = $options{u};
|
1197
|
$u = $params{'uuid'} unless ($u || $u eq '0');
|
1198
|
if ($u || $u eq '0') {
|
1199
|
foreach my $uuid (keys %register) {
|
1200
|
if ($uuid =~ /^$u/) {
|
1201
|
my %hash = %{$register{$uuid}};
|
1202
|
delete $hash{'action'};
|
1203
|
my $dump = Dumper(\%hash);
|
1204
|
$dump =~ s/undef/"--"/g;
|
1205
|
return $dump;
|
1206
|
}
|
1207
|
}
|
1208
|
}
|
1209
|
}
|
1210
|
|
1211
|
# Print list of available actions on objects
|
1212
|
sub do_plainhelp {
|
1213
|
my $res;
|
1214
|
$res .= header('text/plain') unless $console;
|
1215
|
$res .= <<END
|
1216
|
* reboot: Reboots a node
|
1217
|
* shutdown: Shuts down a node
|
1218
|
* unjoin: Disassciates a node from the engine and reboots it. After rebooting, it will join the engine with the default
|
1219
|
node identity
|
1220
|
* delete: Deletes a node. Use if a node has been physically removed from engine
|
1221
|
* sleep: Puts an idle node to sleep. S3 sleep must be supported and enabled
|
1222
|
* wake: Tries to wake or start a node by sending a wake-on-LAN magic packet to the node.
|
1223
|
* evacuate: Tries to live-migrate all running servers away from node
|
1224
|
* maintenance: Puts the node in maintenance mode. A node in maintenance mode is not available for starting new servers.
|
1225
|
* carryon: Puts a node out of maintenance mode.
|
1226
|
* reload: Reloads the movepiston daemon on the node.
|
1227
|
|
1228
|
END
|
1229
|
;
|
1230
|
}
|
1231
|
|
1232
|
|
1233
|
sub updateRegister {
|
1234
|
my @regvalues = values %register;
|
1235
|
# Mark pistons we haven't heard from in the last 20 secs as inactive
|
1236
|
foreach $valref (@regvalues) {
|
1237
|
my $curstatus = $valref->{'status'};
|
1238
|
if (
|
1239
|
($current_time - ($valref->{'timestamp'}) > 20)
|
1240
|
&& ($curstatus ne "joining") && ($curstatus ne "shutdown") && ($curstatus ne "reboot")
|
1241
|
&& ($curstatus ne "asleep") && ($curstatus ne "waking") && ($curstatus ne "sleeping")
|
1242
|
) {
|
1243
|
$valref->{'status'} = 'inactive';
|
1244
|
print "Marking node as inactive\n";
|
1245
|
if ($curstatus ne 'inactive') {
|
1246
|
$main::updateUI->({tab=>'nodes', user=>$user, uuid=>$valref->{'mac'}, status=>'inactive'});
|
1247
|
}
|
1248
|
}
|
1249
|
}
|
1250
|
}
|
1251
|
|
1252
|
sub trim {
|
1253
|
my $string = shift;
|
1254
|
$string =~ s/^\s+|\s+$//g;
|
1255
|
return $string;
|
1256
|
}
|
1257
|
|
1258
|
sub updateAmtInfo {
|
1259
|
my @vals = values(%register);
|
1260
|
if (scalar @vals == 1 && $vals[0]->{identity} eq 'local_kvm') {
|
1261
|
return "Status=OK Only local node registered - not scanning for AMT\n"
|
1262
|
}
|
1263
|
my $amtinfo = `/usr/bin/nmap -n -v --send-ip -Pn -p 16992 10.0.0.*`;
|
1264
|
my $match;
|
1265
|
my %macs;
|
1266
|
my $amtip;
|
1267
|
my $res;
|
1268
|
foreach my $line (split /\n/, $amtinfo) {
|
1269
|
if ($line =~ /16992\/tcp open/) {
|
1270
|
$match = 1;
|
1271
|
} elsif ($line =~ /Nmap scan report for (\S+)/) {
|
1272
|
$amtip = $1;
|
1273
|
} elsif ($line =~ /Host (\S+) is up/) {
|
1274
|
$amtip = $1;
|
1275
|
}
|
1276
|
if ($match && $line =~ /MAC Address: (\S+)/) {
|
1277
|
my $amtmac = $1;
|
1278
|
$amtmac =~ tr/://d;
|
1279
|
$macs{$amtmac} = 1;
|
1280
|
$match = 0;
|
1281
|
$res .= "Status=OK Found $amtmac with $amtip\n";
|
1282
|
$register{$amtmac}->{'amtip'} = $amtip if ($register{$amtmac});
|
1283
|
}
|
1284
|
};
|
1285
|
if (%macs) {
|
1286
|
my $n = scalar values %macs;
|
1287
|
$res .= "Status=OK Found $n nodes with AMT enabled\n";
|
1288
|
} else {
|
1289
|
$res .= "Status=OK Could not find any nodes with AMT enabled\n";
|
1290
|
}
|
1291
|
return $res;
|
1292
|
}
|
1293
|
|
1294
|
sub Configurecgroups {
|
1295
|
my ($uuid, $action, $obj) = @_;
|
1296
|
if ($help) {
|
1297
|
return <<END
|
1298
|
GET::
|
1299
|
Parse Stabile config nodeconfig.cfg and configure /etc/stabile/cgconfig.conf for all known node roots.
|
1300
|
END
|
1301
|
}
|
1302
|
|
1303
|
unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities',key=>'identity',CLOBBER=>3}, $Stabile::dbopts)) ) {return "Unable to access id register"};
|
1304
|
my @noderoots;
|
1305
|
# Build hash of known node roots
|
1306
|
foreach my $valref (values %idreg) {
|
1307
|
my $noderoot = $valref->{'path'} . "/casper/filesystem.dir";
|
1308
|
next if ($noderoots{$noderoot}); # Node identities may share basedir and node config file
|
1309
|
if (-e $noderoot && -e "$noderoot/etc/cgconfig.conf" && -e "$noderoot/etc/stabile/nodeconfig.cfg") {
|
1310
|
push @noderoots, $noderoot;
|
1311
|
}
|
1312
|
}
|
1313
|
untie %idreg;
|
1314
|
push @noderoots, "/";
|
1315
|
foreach my $noderoot (@noderoots) {
|
1316
|
$noderoot = '' if ($noderoot eq '/');
|
1317
|
next unless (-e "$noderoot/etc/stabile/nodeconfig.cfg");
|
1318
|
my $nodecfg = new Config::Simple("$noderoot/etc/stabile/nodeconfig.cfg");
|
1319
|
my $vm_readlimit = $nodecfg->param('VM_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
|
1320
|
my $vm_writelimit = $nodecfg->param('VM_WRITE_LIMIT');
|
1321
|
my $vm_iopsreadlimit = $nodecfg->param('VM_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
|
1322
|
my $vm_iopswritelimit = $nodecfg->param('VM_IOPS_WRITE_LIMIT');
|
1323
|
|
1324
|
my $piston_readlimit = $nodecfg->param('PISTON_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
|
1325
|
my $piston_writelimit = $nodecfg->param('PISTON_WRITE_LIMIT');
|
1326
|
my $piston_iopsreadlimit = $nodecfg->param('PISTON_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
|
1327
|
my $piston_iopswritelimit = $nodecfg->param('PISTON_IOPS_WRITE_LIMIT');
|
1328
|
|
1329
|
my $file = "$noderoot/etc/stabile/cgconfig.conf";
|
1330
|
unless (open(FILE, "< $file")) {
|
1331
|
$postreply .= "Status=Error problem opening $file\n";
|
1332
|
return $postreply;
|
1333
|
}
|
1334
|
my @lines = <FILE>;
|
1335
|
close FILE;
|
1336
|
chomp @lines;
|
1337
|
my $group;
|
1338
|
my @newlines;
|
1339
|
for my $line (@lines) {
|
1340
|
$group = $1 if ($line =~ /group (\w+) /);
|
1341
|
if ($group eq 'stabile' && $noderoot) {
|
1342
|
# These are already set to valve values by pressurecontrol
|
1343
|
$line =~ s/(blkio.throttle.read_bps_device = "\d+:\d+).*/$1 $piston_readlimit";/;
|
1344
|
$line =~ s/(blkio.throttle.write_bps_device = "\d+:\d+).*/$1 $piston_writelimit";/;
|
1345
|
$line =~ s/(blkio.throttle.read_iops_device = "\d+:\d+).*/$1 $piston_iopsreadlimit";/;
|
1346
|
$line =~ s/(blkio.throttle.write_iops_device = "\d+:\d+).*/$1 $piston_iopswritelimit";/;
|
1347
|
}
|
1348
|
elsif ($group eq 'stabilevm') {
|
1349
|
$line =~ s/(blkio.throttle.read_bps_device = "\d+:\d+).*/$1 $vm_readlimit";/;
|
1350
|
$line =~ s/(blkio.throttle.write_bps_device = "\d+:\d+).*/$1 $vm_writelimit";/;
|
1351
|
$line =~ s/(blkio.throttle.read_iops_device = "\d+:\d+).*/$1 $vm_iopsreadlimit";/;
|
1352
|
$line =~ s/(blkio.throttle.write_iops_device = "\d+:\d+).*/$1 $vm_iopswritelimit";/;
|
1353
|
}
|
1354
|
push @newlines, $line;
|
1355
|
}
|
1356
|
unless (open(FILE, "> $file")) {
|
1357
|
$postreply .= "Status=Error Problem opening $file\n";
|
1358
|
return $postreply;
|
1359
|
}
|
1360
|
print FILE join("\n", @newlines);
|
1361
|
close(FILE);
|
1362
|
$postreply .= "Status=OK Setting VM and auxilliary cgroups limits in $file: $vm_readlimit, $vm_writelimit, $vm_iopsreadlimit, $vm_iopswritelimit\n";
|
1363
|
}
|
1364
|
return $postreply;
|
1365
|
}
|