Project

General

Profile

Download (50.2 KB) Statistics
| Branch: | Revision:

stabile / cgi / nodes.cgi @ master

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&nbsp;&nbsp;";
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') {
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
                'internalip' => $sys->{'internalip'}
1004
            };
1005
            $servers += ($sys->{'issystem'}?scalar @{$sys->{'children'}}:1);
1006
            my $monitors;
1007
            my $backups;
1008

    
1009
            if (%mons || $action eq "fullstatsb") {
1010
                if ($sys->{'issystem'}) {
1011
                    foreach my $dom (@{$sys->{'children'}}) {
1012
                        foreach my $service (@monservices) {
1013
                            my $id = $dom->{'uuid'} . ":$service";
1014
                            if ($mons{$id}) {
1015
                                my $last_status = $mons{$id}->{'last_success'} || $mons{$id}->{'last_failure'};
1016
                                $monitors .= "$dom->{'name'}/$service/$mons{$id}->{'status'}/$last_status, " ;
1017
                            }
1018
                        }
1019
                        if ($action eq "fullstatsb") {
1020
                            my $bups = Stabile::Images::Getserverbackups($dom->{'uuid'});
1021
                            $backups  .= "$bups, " if ($bups);
1022
                        }
1023
                    }
1024
                    $monitors = substr($monitors, 0,-2) if ($monitors);
1025
                    $backups = substr($backups, 0,-2) if ($backups);
1026
                } else {
1027
                    foreach my $service (@monservices) {
1028
                        my $id = $sys->{'uuid'} . ":$service";
1029
                        if ($mons{$id}) {
1030
                            my $last_status = $mons{$id}->{'last_success'} || $mons{$id}->{'last_failure'};
1031
                            $monitors .= "$sys->{'name'}/$service/$mons{$id}->{'status'}/$last_status, ";
1032
                        }
1033
                    }
1034
                    $monitors = substr($monitors, 0,-2) if ($monitors);
1035
                    $backups = Stabile::Images::Getserverbackups($sys->{'uuid'}) if ($action eq "fullstatsb");
1036
                }
1037
                $returnsys->{'monitors'} = $monitors if ($monitors);
1038
                $returnsys->{'backups'} = $backups if ($backups);
1039
            }
1040

    
1041
            push @returnsystems, $returnsys;
1042
        }
1043
        $uval{'systems'} = \@returnsystems;
1044

    
1045
        $uval{'nodestorage'} = int($billingreg{"$uval{username}--1-$year-$month"}->{'virtualsize'}/1024/1024) if ($billingreg{"$uval{username}--1-$year-$month"});
1046
        my $stor = 0;
1047
        for (my $i=0; $i <= scalar @tenderpathslist; $i++) {
1048
            $stor += $billingreg{"$uval{username}-$i-$year-$month"}->{'virtualsize'} if ($billingreg{"$uval{username}-$i-$year-$month"});
1049
        }
1050
        $uval{'storage'} = int($stor/1024/1024);
1051
        $uval{'vcpu'} = $vcpus;
1052
        $uval{'memory'} = $mem;
1053
        $uval{'servers'} = $servers;
1054

    
1055
        push @lusers, \%uval;
1056
    }
1057
    untie %billingreg;
1058
    my $ver = `cat /etc/stabile/version`; chomp $ver;
1059

    
1060
    $stortext .= "Nodes: " . int($nodestorused/1024/1024) . " (" . int($nodestortotal/1024/1024) . ") GB";
1061
    $stats{'status'} = ($readynodes>0?'ready':'nonodes');
1062
    $stats{'storavgs'} = \%storavgs;
1063
    $stats{'avgs'} = \%avgs;
1064
    $stats{'users'} = \@lusers;
1065
    $stats{'stortext'} = $stortext;
1066
    # $stats{'version'} = $version;
1067
    $stats{'version'} = $ver;
1068

    
1069
    my $json_text = to_json(\%stats, {pretty=>1});
1070
    $json_text =~ s/\x/ /g;
1071
    $json_text =~ s/null/""/g;
1072
    #$postreply = header("application/json") unless ($console);
1073
    $postreply .= $json_text;
1074
    return $postreply;
1075
}
1076

    
1077
sub do_list {
1078
    my ($uuid, $action, $obj) = @_;
1079
    if ($help) {
1080
        return <<END
1081
GET:uuid:
1082
List the nodes running this engine.
1083
END
1084
    }
1085
    if ($isadmin || index($privileges,"n")!=-1) {
1086
        my @regvalues = values %register;
1087
        my @curregvalues;
1088
        # Only include pistons we have heard from in the last 20 secs
1089
        foreach $valref (@regvalues) {
1090
            my $curstatus =  $valref->{'status'};
1091
            if (
1092
                ($current_time - ($valref->{'timestamp'}) > 20)
1093
                    && ($curstatus ne "joining") && ($curstatus ne "shutdown") && ($curstatus ne "reboot")
1094
                    && ($curstatus ne "asleep") && ($curstatus ne "waking") && ($curstatus ne "sleeping")
1095
            ) {$valref->{'status'} = "inactive"};
1096

    
1097
            $valref->{'name'} = $valref->{'mac'} unless ($valref->{'name'} && $valref->{'name'} ne '--');
1098
            my %val = %{$valref}; # Deference and assign to new ass array, effectively cloning object
1099
            # %{$valref}->{'cpucores'}  is the same as $valref->{'cpucores'};
1100
            # These values should be sent as numbers
1101
            $val{'cpucores'} += 0;
1102
            $val{'cpucount'} += 0;
1103
            $val{'memfree'} += 0;
1104
            $val{'memtotal'} += 0;
1105
            $val{'storfree'} += 0;
1106
            $val{'stortotal'} += 0;
1107
            $val{'vms'} += 0;
1108
            $val{'cpuload'} += 0;
1109

    
1110
            push @curregvalues,\%val ;
1111
        }
1112

    
1113
        # Sort @curregvalues
1114
        my $sort = 'name';
1115
        $sort = $2 if ($uripath =~ /sort\((\+|\-)(\S+)\)/);
1116
        my $reverse;
1117
        $reverse = 1 if ($1 eq '-');
1118
        if ($reverse) { # sort reverse
1119
            if ($sort =~ /cpucores|cpucount|memfree|memtotal|vms|cpuload/) {
1120
                @curregvalues = (sort {$b->{$sort} <=> $a->{$sort}} @curregvalues); # Sort as number
1121
            } else {
1122
                @curregvalues = (sort {$b->{$sort} cmp $a->{$sort}} @curregvalues); # Sort as string
1123
            }
1124
        } else {
1125
            if ($sort =~ /cpucores|cpucount|memfree|memtotal|vms|cpuload/) {
1126
                @curregvalues = (sort {$a->{$sort} <=> $b->{$sort}} @curregvalues); # Sort as number
1127
            } else {
1128
                @curregvalues = (sort {$a->{$sort} cmp $b->{$sort}} @curregvalues); # Sort as string
1129
            }
1130
        }
1131

    
1132
        if ($action eq 'tablelist') {
1133
            my $t2 = Text::SimpleTable->new(14,20,14,10,5,5,12,7);
1134
            $t2->row('mac', 'name', 'ip', 'identity', 'cores', 'vms', 'memfree', 'status');
1135
            $t2->hr;
1136
            my $pattern = $options{m};
1137
            foreach $rowref (@curregvalues){
1138
                if ($pattern) {
1139
                    my $rowtext = "$rowref->{'mac'} $rowref->{'name'} $rowref->{'ip'} $rowref->{'identity'} "
1140
                        . "$rowref->{'vms'} $rowref->{'memfree'} $rowref->{'status'}";
1141
                    $rowtext .= " " . $rowref->{'mac'} if ($isadmin);
1142
                    next unless ($rowtext =~ /$pattern/i);
1143
                }
1144
                $t2->row($rowref->{'mac'}, $rowref->{'name'}, $rowref->{'ip'}, $rowref->{'identity'}, $rowref->{'cpucores'},
1145
                    $rowref->{'vms'}, $rowref->{'memfree'}, $rowref->{'status'});
1146
            }
1147
            $postreply .= header("text/plain") unless ($console);
1148
            $postreply .= $t2->draw;
1149
        } elsif ($console) {
1150
            $postreply = Dumper(\@curregvalues);
1151
        } else {
1152
            my $json_text = to_json(\@curregvalues, {pretty=>1});
1153
            $json_text =~ s/""/"--"/g;
1154
            $json_text =~ s/null/"--"/g;
1155
            $json_text =~ s/\x/ /g;
1156
            $postreply .= qq|{"identifier": "mac", "label": "name", "items":| if ($action && $action ne 'list');
1157
            $postreply .= $json_text;
1158
            $postreply .= "}" if ($action && $action ne 'list');
1159
        }
1160
    } else {
1161
        $postreply .= q|{"identifier": "mac", "label": "name", "items":| if ($action && $action ne 'list');
1162
        $postreply .= "[]";
1163
        $postreply .= "}" if ($action && $action ne 'list');
1164
    }
1165
    return $postreply;
1166
}
1167

    
1168
sub do_uuidlookup {
1169
    if ($help) {
1170
        return <<END
1171
GET:uuid:
1172
Simple action for looking up a uuid or part of a uuid and returning the complete uuid.
1173
END
1174
    }
1175

    
1176
    my $u = $options{u};
1177
    $u = $params{'uuid'} unless ($u || $u eq '0');
1178
    my $ruuid;
1179
    if ($u || $u eq '0') {
1180
        foreach my $uuid (keys %register) {
1181
            if ($uuid =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/) {
1182
                return "$uuid\n";
1183
            }
1184
        }
1185
    }
1186
}
1187

    
1188
sub do_uuidshow {
1189
    if ($help) {
1190
        return <<END
1191
GET:uuid:
1192
Simple action for showing a single network.
1193
END
1194
    }
1195
    my $u = $options{u};
1196
    $u = $params{'uuid'} unless ($u || $u eq '0');
1197
    if ($u || $u eq '0') {
1198
        foreach my $uuid (keys %register) {
1199
            if ($uuid =~ /^$u/) {
1200
                my %hash = %{$register{$uuid}};
1201
                delete $hash{'action'};
1202
                my $dump = Dumper(\%hash);
1203
                $dump =~ s/undef/"--"/g;
1204
                return $dump;
1205
            }
1206
        }
1207
    }
1208
}
1209

    
1210
# Print list of available actions on objects
1211
sub do_plainhelp {
1212
    my $res;
1213
    $res .= header('text/plain') unless $console;
1214
    $res .= <<END
1215
* reboot: Reboots a node
1216
* shutdown: Shuts down a node
1217
* unjoin: Disassciates a node from the engine and reboots it. After rebooting, it will join the engine with the default
1218
node identity
1219
* delete: Deletes a node. Use if a node has been physically removed from engine
1220
* sleep: Puts an idle node to sleep. S3 sleep must be supported and enabled
1221
* wake: Tries to wake or start a node by sending a wake-on-LAN magic packet to the node.
1222
* evacuate: Tries to live-migrate all running servers away from node
1223
* maintenance: Puts the node in maintenance mode. A node in maintenance mode is not available for starting new servers.
1224
* carryon: Puts a node out of maintenance mode.
1225
* reload: Reloads the movepiston daemon on the node.
1226

    
1227
END
1228
;
1229
}
1230

    
1231

    
1232
sub updateRegister {
1233
    my @regvalues = values %register;
1234
# Mark pistons we haven't heard from in the last 20 secs as inactive
1235
    foreach $valref (@regvalues) {
1236
        my $curstatus =  $valref->{'status'};
1237
        if (
1238
            ($current_time - ($valref->{'timestamp'}) > 20)
1239
            && ($curstatus ne "joining") && ($curstatus ne "shutdown") && ($curstatus ne "reboot")
1240
            && ($curstatus ne "asleep") && ($curstatus ne "waking") && ($curstatus ne "sleeping")
1241
        ) {
1242
            $valref->{'status'} = 'inactive';
1243
            print "Marking node as inactive\n";
1244
            if ($curstatus ne 'inactive') {
1245
                $main::updateUI->({tab=>'nodes', user=>$user, uuid=>$valref->{'mac'}, status=>'inactive'});
1246
            }
1247
        }
1248
    }
1249
}
1250

    
1251
sub trim {
1252
   my $string = shift;
1253
   $string =~ s/^\s+|\s+$//g;
1254
   return $string;
1255
}
1256

    
1257
sub updateAmtInfo {
1258
    my @vals = values(%register);
1259
    if (scalar @vals == 1 && $vals[0]->{identity} eq 'local_kvm') {
1260
        return "Status=OK Only local node registered - not scanning for AMT\n"
1261
    }
1262
    my $amtinfo = `/usr/bin/nmap -n -v --send-ip -Pn -p 16992 10.0.0.*`;
1263
    my $match;
1264
    my %macs;
1265
    my $amtip;
1266
    my $res;
1267
    foreach my $line (split /\n/, $amtinfo) {
1268
        if ($line =~ /16992\/tcp open/) {
1269
            $match = 1;
1270
        } elsif ($line =~ /Nmap scan report for (\S+)/) {
1271
            $amtip = $1;
1272
        } elsif ($line =~ /Host (\S+) is up/) {
1273
            $amtip = $1;
1274
        }
1275
        if ($match && $line =~ /MAC Address: (\S+)/) {
1276
            my $amtmac = $1;
1277
            $amtmac =~ tr/://d;
1278
            $macs{$amtmac} = 1;
1279
            $match = 0;
1280
            $res .= "Status=OK Found $amtmac with $amtip\n";
1281
            $register{$amtmac}->{'amtip'} = $amtip if ($register{$amtmac});
1282
        }
1283
    };
1284
    if (%macs) {
1285
        my $n = scalar values %macs;
1286
        $res .= "Status=OK Found $n nodes with AMT enabled\n";
1287
    } else {
1288
        $res .= "Status=OK Could not find any nodes with AMT enabled\n";
1289
    }
1290
    return $res;
1291
}
1292

    
1293
sub Configurecgroups {
1294
    my ($uuid, $action, $obj) = @_;
1295
    if ($help) {
1296
        return <<END
1297
GET::
1298
Parse Stabile config nodeconfig.cfg and configure /etc/stabile/cgconfig.conf for all known node roots.
1299
END
1300
    }
1301

    
1302
    unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities',key=>'identity',CLOBBER=>3}, $Stabile::dbopts)) ) {return "Unable to access id register"};
1303
    my @noderoots;
1304
    # Build hash of known node roots
1305
    foreach my $valref (values %idreg) {
1306
        my $noderoot = $valref->{'path'} . "/casper/filesystem.dir";
1307
        next if ($noderoots{$noderoot}); # Node identities may share basedir and node config file
1308
        if (-e $noderoot && -e "$noderoot/etc/cgconfig.conf" && -e "$noderoot/etc/stabile/nodeconfig.cfg") {
1309
            push @noderoots, $noderoot;
1310
        }
1311
    }
1312
    untie %idreg;
1313
    push @noderoots, "/";
1314
    foreach my $noderoot (@noderoots) {
1315
        $noderoot = '' if ($noderoot eq '/');
1316
        next unless (-e "$noderoot/etc/stabile/nodeconfig.cfg");
1317
        my $nodecfg = new Config::Simple("$noderoot/etc/stabile/nodeconfig.cfg");
1318
        my $vm_readlimit = $nodecfg->param('VM_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
1319
        my $vm_writelimit = $nodecfg->param('VM_WRITE_LIMIT');
1320
        my $vm_iopsreadlimit = $nodecfg->param('VM_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
1321
        my $vm_iopswritelimit = $nodecfg->param('VM_IOPS_WRITE_LIMIT');
1322

    
1323
        my $piston_readlimit = $nodecfg->param('PISTON_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
1324
        my $piston_writelimit = $nodecfg->param('PISTON_WRITE_LIMIT');
1325
        my $piston_iopsreadlimit = $nodecfg->param('PISTON_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
1326
        my $piston_iopswritelimit = $nodecfg->param('PISTON_IOPS_WRITE_LIMIT');
1327

    
1328
        my $file = "$noderoot/etc/stabile/cgconfig.conf";
1329
        unless (open(FILE, "< $file")) {
1330
            $postreply .= "Status=Error problem opening $file\n";
1331
            return $postreply;
1332
        }
1333
        my @lines = <FILE>;
1334
        close FILE;
1335
        chomp @lines;
1336
        my $group;
1337
        my @newlines;
1338
        for my $line (@lines) {
1339
            $group = $1 if ($line =~ /group (\w+) /);
1340
            if ($group eq 'stabile' && $noderoot) {
1341
                # These are already set to valve values by pressurecontrol
1342
                $line =~ s/(blkio.throttle.read_bps_device = "\d+:\d+).*/$1 $piston_readlimit";/;
1343
                $line =~ s/(blkio.throttle.write_bps_device = "\d+:\d+).*/$1 $piston_writelimit";/;
1344
                $line =~ s/(blkio.throttle.read_iops_device = "\d+:\d+).*/$1 $piston_iopsreadlimit";/;
1345
                $line =~ s/(blkio.throttle.write_iops_device = "\d+:\d+).*/$1 $piston_iopswritelimit";/;
1346
            }
1347
            elsif ($group eq 'stabilevm') {
1348
                $line =~ s/(blkio.throttle.read_bps_device = "\d+:\d+).*/$1 $vm_readlimit";/;
1349
                $line =~ s/(blkio.throttle.write_bps_device = "\d+:\d+).*/$1 $vm_writelimit";/;
1350
                $line =~ s/(blkio.throttle.read_iops_device = "\d+:\d+).*/$1 $vm_iopsreadlimit";/;
1351
                $line =~ s/(blkio.throttle.write_iops_device = "\d+:\d+).*/$1 $vm_iopswritelimit";/;
1352
            }
1353
            push @newlines, $line;
1354
        }
1355
        unless (open(FILE, "> $file")) {
1356
            $postreply .= "Status=Error Problem opening $file\n";
1357
            return $postreply;
1358
        }
1359
        print FILE join("\n", @newlines);
1360
        close(FILE);
1361
        $postreply .= "Status=OK Setting VM and auxilliary cgroups limits in $file: $vm_readlimit, $vm_writelimit, $vm_iopsreadlimit, $vm_iopswritelimit\n";
1362
    }
1363
    return $postreply;
1364
}
(4-4/9)