Project

General

Profile

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