Project

General

Profile

Download (50.4 KB) Statistics
| Branch: | Revision:
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": "stormove", "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
            my $cmd;
491
            if ($console) {
492
                $res = `REMOTE_USER=$user $Stabile::basedir/cgi/servers.cgi -g $postdata`;
493
                $postreply .= "Stroke=OK Move: $res\n";
494
            } else {
495
                $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|;
496
                $res = `$cmd`;
497
#                $postreply .= "Stroke=OK Now moving: $i servers $actionstr\n";
498
            }
499
#            $res =~ s/\n/ - /g;
500
            my $logmsg = "Node $mac marked for $action";
501
            $main::syslogit->($user, "info", $logmsg);
502
            $postreply .= "Status=OK Node $name marked for evacuation ($i servers)\n";
503
        } else {
504
            $postreply .= "Status=OK No servers found to evacaute\n";
505
        }
506
    } else {
507
        $postreply .= "Status=ERROR Cannot $action a $status node (not in maintenance, not running)\n";
508
    }
509
    return $postreply;
510
}
511

    
512

    
513
sub do_reset {
514
    my ($uuid, $action, $obj) = @_;
515
    if ($help) {
516
        return <<END
517
GET:mac:
518
Resets the specified node.
519
END
520
    }
521
    my $mac = $obj->{'mac'};
522
    my $name = $obj->{'name'};
523
    my $dbstatus = $register{$mac}->{'status'};
524
    if (($dbstatus eq "maintenance" && $register{$mac}->{'vms'} == 0)
525
        || $dbstatus eq "inactive"
526
        || $dbstatus eq "waking"
527
        || $dbstatus eq "sleeping"
528
        || $dbstatus eq "shuttingdown"
529
        || $dbstatus eq "shutdown"
530
        || $dbstatus eq "joining"
531
    ) {
532
        my $resetcmd;
533
        if ($register{$mac}->{'amtip'} && $register{$mac}->{'amtip'} ne '--') {
534
            $uistatus = "reset";
535
            $resetcmd = "echo 'y' | AMT_PASSWORD='$amtpasswd' /usr/bin/amttool $register{$mac}->{'amtip'} reset bios";
536
        } elsif ($register{$mac}->{'ipmiip'} && $register{$mac}->{'ipmiip'} ne '--') {
537
            $uistatus = "reset";
538
            $resetcmd = "ipmitool -I lanplus -H $register{$mac}->{'ipmiip'} -U ADMIN -P ADMIN power reset";
539
        } else {
540
            $postreply .= "Status=ERROR This node does not support hardware reset\n";
541
        }
542
        if ($uistatus eq 'reset') {
543
            $uiuuid = $mac;
544
            $register{$mac}->{'status'} = $uistatus;
545
            my $logmsg = "Node $mac marked for $action";
546
            $logmsg .= `$resetcmd`;
547
            $logmsg =~ s/\n/ /g;
548
            $main::syslogit->($user, "info", $logmsg);
549
            $postreply .= "Stroke=$uistatus OK resetting $name ";
550
        }
551
    } else {
552
        $postreply .= "Status=ERROR Cannot $action a $dbstatus node\n";
553
    }
554
    return $postreply;
555
}
556

    
557
sub do_unjoin {
558
    my ($uuid, $action, $obj) = @_;
559
    if ($help) {
560
        return <<END
561
GET:mac:
562
Disassciates a node from the engine and reboots it. After rebooting, it will join the engine with the default
563
node identity
564
END
565
    }
566
    my $mac = $obj->{'mac'};
567
    my $name = $obj->{'name'};
568
    my $dbstatus = $register{$mac}->{'status'};
569
    if ($dbstatus eq "running" && $register{$mac}->{'vms'}==0) {
570
        $uistatus = "unjoining";
571
        $uiuuid = $mac;
572
        my $tasks = $register{$mac}->{'tasks'};
573
        $register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
574
        $register{$mac}->{'action'} = "";
575
        $register{$mac}->{'status'} = $uistatus;
576
        my $logmsg = "Node $mac marked for $action";
577
        $main::syslogit->($user, "info", $logmsg);
578
        $postreply .= "Status=$uistatus OK unjoining $name\n";
579
    } else {
580
        $postreply .= "Status=ERROR Cannot $action a $dbstatus node or a node with running VMs\n";
581
    }
582
    return $postreply;
583
}
584

    
585
sub do_wipe {
586
    my ($uuid, $action, $obj) = @_;
587
    if ($help) {
588
        return <<END
589
GET:mac:
590
Erases a node's harddrive and formats it with either ext4 or zfs, depending on settings.
591
Only allowed if /mnt/stabile/node is empty.
592
END
593
    }
594
    my $mac = $obj->{'mac'};
595
    my $name = $obj->{'name'};
596
    unless ($register{$mac}) {
597
        $postreply .= "Status=ERROR Please specify a valid mac.\n";
598
        return $postreply;
599
    }
600
    my $dbstatus = $register{$mac}->{'status'};
601
    if ($dbstatus eq "running" && $register{$mac}->{'vms'}==0) {
602
        $uistatus = "wiping";
603
        $uiuuid = $mac;
604
        my $tasks = $register{$mac}->{'tasks'};
605
        $register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
606
        $register{$mac}->{'action'} = "";
607
        $register{$mac}->{'status'} = $uistatus;
608
        my $logmsg = "Node $mac marked for $action";
609
        $main::syslogit->($user, "info", $logmsg);
610
        $postreply .= "Status=$uistatus OK wiping $name\n";
611
    } else {
612
        $postreply .= "Status=ERROR Cannot $action a $dbstatus node or a node with running VMs\n";
613
    }
614
    return $postreply;
615
}
616

    
617
sub do_setdefaultnodeidentity {
618
    my ($uuid, $action, $obj) = @_;
619
    if ($help) {
620
        return <<END
621
GET:hid,sleepafter:
622
Sets the default identity a node should boot as. [sleepafter] is in seconds, [hid] is [name] of one the alternatives listed by [listnodeidentities].
623
END
624
    }
625
    my $hid = $params{'hid'};
626
    my $sleepafter = $params{'sleepafter'};
627
    unless ($hid) {return "Status=ERROR No identity selected\n"};
628
    unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'name'}, $Stabile::dbopts)) ) {return "Unable to access id register"};
629
    my @idvalues = values %idreg;
630
    foreach my $val (@idvalues) {
631
        my $identity = $val->{'name'};
632
        if ($identity eq $hid) {$identity = "default"}
633
        $idreg{$val->{'name'}} = {
634
            identity=>$identity,
635
            sleepafter=>int($sleepafter)
636
        }
637
    }
638
    tied(%idreg)->commit;
639
    untie %idreg;
640
    $postreply = "Status=OK Set $hid as new default identity, sleeping after $sleepafter minutes\n";
641
}
642

    
643
sub do_listlog {
644
    my ($uuid, $action, $obj) = @_;
645
    if ($help) {
646
        return <<END
647
GET::
648
Lists the last 200 lines from the local activity log file.
649
END
650
    }
651
    $postreply = header("text/plain");
652
    if ($isadmin) {
653
        $postreply .= `tail -n 200 $main::logfile`;
654
    } else {
655
        $postreply .= `tail -n 200 $main::logfile | grep ': $user :'`;
656
    }
657
}
658

    
659
sub do_clearlog {
660
    my ($uuid, $action, $obj) = @_;
661
    if ($help) {
662
        return <<END
663
GET::
664
Clear the local activity log file.
665
END
666
    }
667
    `> $main::logfile`;
668
    # unlink $logfile;
669
    $postreply = header("text/plain");
670
    $postreply .=  "Status=OK Log cleared\n";
671
    return $postreply;
672
}
673

    
674
sub do_updateregister {
675
    my ($uuid, $action, $obj) = @_;
676
    if ($help) {
677
        return <<END
678
GET::
679
Updates the node register.
680
END
681
    }
682
    updateRegister();
683
    $postreply = "Stream=OK Updated node register for all users\n";
684
    return $postreply;
685
}
686

    
687
sub do_reload {
688
    my ($uuid, $action, $obj) = @_;
689
    if ($help) {
690
        return <<END
691
GET:mac,nodeaction:
692
Reload configuration on the specified node or perform specified action.
693
END
694
    }
695
    my $status = $obj->{'status'};
696
    my $mac = $obj->{'mac'};
697
    my $nodeaction = "reload" || $obj->{'nodeaction'};
698
    if ($status eq "running") {
699
        $uistatus = "reloading";
700
        $uiuuid = $mac;
701
        my $tasks = $register{$mac}->{'tasks'};
702
        $register{$mac}->{'tasks'} = $tasks . $nodeaction . " $user\n";
703
        $register{$mac}->{'action'} = "";
704
        $register{$mac}->{'status'} = $uistatus;
705
        my $logmsg = "Node $mac marked for $action";
706
        $main::syslogit->($user, "info", $logmsg);
707
        $postreply .= "Status=$uistatus OK reloading $name\n";
708
    }
709
    else {
710
        $postreply .= "Status=ERROR Cannot $action a $status node\n";
711
    }
712
    return $postreply;
713
}
714

    
715
sub do_reloadall {
716
    my ($uuid, $action, $obj) = @_;
717
    if ($help) {
718
        return <<END
719
GET:nodeaction:
720
Reload configuration on all nodes. Alternatively specify a "nodeaction" to have it executed on all nodes.
721
Currently supported nodeactions: CGLOAD [reload cgroup configuration]
722
END
723
    }
724
    my $nodeaction = $obj->{'nodeaction'} || "reload";
725
    my @regvalues = values %register;
726
    # Only include pistons we have heard from in the last 20 secs
727
    foreach $val (@regvalues) {
728
        my $curstatus =  $val->{'status'};
729
        my $mac = $val->{'mac'};
730
        my $name = $val->{'name'};
731
        if ($curstatus eq "running" || $curstatus eq "maintenance") {
732
            $uistatus = "reloading";
733
            $uiuuid = $mac;
734
            my $tasks = $register{$mac}->{'tasks'};
735
            $register{$mac}->{'tasks'} = $tasks . $nodeaction . " $user\n";
736
            $register{$mac}->{'action'} = "";
737
            $register{$mac}->{'status'} = $uistatus;
738
            my $logmsg = "Node $mac marked for $nodeaction";
739
            $main::syslogit->($user, "info", $logmsg);
740
            $postreply .= "Status=OK $uistatus $name\n";
741
        } else {
742
            $postreply .= "Status=OK Node $mac ($register->{$mac}) is $register{$mac}->{'status'} not reloading\n";
743
        }
744
    }
745
    return $postreply;
746
}
747

    
748
sub do_rebootall {
749
    my ($uuid, $action, $obj) = @_;
750
    if ($help) {
751
        return <<END
752
GET::
753
Reboot all active nodes.
754
END
755
    }
756
    my @regvalues = values %register;
757
# Only include pistons we have heard from in the last 20 secs
758
    foreach $val (@regvalues) {
759
        my $curstatus =  $val->{'status'};
760
        my $mac = $val->{'mac'};
761
        $action = "reboot";
762
        my $name = $val->{'name'};
763
        my $identity = $val->{'identity'};
764
        if (($curstatus eq "running" || $curstatus eq "maintenance") && $identity ne 'local_kvm')
765
        {
766
              $uistatus = "rebooting";
767
              $uiuuid = $mac;
768
              my $tasks = $register{$mac}->{'tasks'};
769
              $register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
770
              $register{$mac}->{'action'} = "";
771
              $register{$mac}->{'status'} = $uistatus;
772
              my $logmsg = "Node $mac marked for $action";
773
              $main::syslogit->($user, "info", $logmsg);
774
              $postreply .= "Status=OK $uistatus $name\n";
775
        }
776
    }
777
    $postreply = $postreply || "Status=ERROR No active nodes found\n";
778
    return $postreply;
779
}
780

    
781
sub do_haltall {
782
    my ($uuid, $action, $obj) = @_;
783
    if ($help) {
784
        return <<END
785
GET:nowait:
786
Unceremoniously halt all active nodes.
787
END
788
    }
789
    my @regvalues = values %register;
790
    my $nowait = $obj->{'nowait'};
791
# Only include pistons we have heard from in the last 20 secs
792
    foreach $val (@regvalues) {
793
        my $curstatus =  $val->{'status'};
794
        my $identity = $val->{'identity'};
795
        my $mac = $val->{'mac'};
796
        $action = "halt";
797
        my $name = $val->{'name'};
798
        if (($curstatus eq "running" || $curstatus eq "maintenance") && $identity ne 'local_kvm')
799
        {
800
              $uistatus = "halting";
801
              $uiuuid = $mac;
802
              my $tasks = $register{$mac}->{'tasks'};
803
              $register{$mac}->{'tasks'} = $tasks . $action . " $user\n";
804
              $register{$mac}->{'action'} = "";
805
              $register{$mac}->{'status'} = $uistatus;
806
              my $logmsg = "Node $mac marked for $action";
807
              $main::syslogit->($user, "info", $logmsg);
808
              $postreply .= "Status=OK $uistatus $name\n";
809
        }
810
    }
811
    unless ($nowait) {
812
        $postreply .= "Status=OK Waiting up to 100 seconds for running nodes to shut down\n";
813
        my $livenodes = 0;
814
        for (my $i; $i<10; $i++) {
815
            $livenodes = 0;
816
            do_list();
817
            foreach $val (@regvalues) {
818
                my $curstatus =  $val->{'status'};
819
                my $identity = $val->{'identity'};
820
                my $mac = $val->{'mac'};
821
                my $name = $val->{'name'};
822
                if (($curstatus eq "running" || $curstatus eq "maintenance" || $curstatus eq "halting") && $identity ne 'local_kvm') {
823
                    $livenodes = 1;
824
                }
825
            }
826
            last unless ($livenodes);
827
            sleep 10;
828
        }
829

    
830
    }
831
    $postreply = $postreply || "Status=ERROR No active nodes found\n";
832
    return $postreply;
833
}
834

    
835
sub Updateamtinfo {
836
    my ($uuid, $action, $obj) = @_;
837
    if ($help) {
838
        return <<END
839
GET::
840
Updates info about the nodes' AMT configuration by scanning the network.
841
END
842
    }
843
    $postreply = updateAmtInfo();
844
    return $postreply;
845
}
846

    
847
sub Stats {
848
    my ($uuid, $action, $obj) = @_;
849
    if ($help) {
850
        return <<END
851
GET::
852
Collect and show stats for this engine. May also be called as fullstats or fullstatsb (includes backup info).
853
END
854
    }
855
    return "Status=Error Not allowed\n" unless ($isadmin);
856
    my @regvalues = values %register;
857
    my %stats;
858
    my $cpuloadsum = 0;
859
    my $memtotalsum = 0;
860
    my $memfreesum = 0;
861
    my $memusedsum = 0;
862
    my $corestotal = 0;
863
    my $vmstotal = 0;
864
    my $vmvcpustotal = 0;
865
    my $nodestorfree = 0;
866
    my $nodestorused = 0;
867
    my $nodestortotal = 0;
868
    my $i = 0;
869

    
870
    $Stabile::Systems::user = $user;
871
    require "$Stabile::basedir/cgi/systems.cgi";
872
    $Stabile::Systems::console = 1;
873
    #$console = 1;
874

    
875
    # Only include pistons we have heard from in the last 20 secs
876
    foreach $val (@regvalues) {
877
        if ((($val->{'status'} eq "asleep") || ($current_time - ($val->{'timestamp'}) < 20)) && ($val->{'status'} ne "joining") && ($val->{'status'} ne "shutdown") && ($val->{'status'} ne "reboot") ) {
878
            $cpuloadsum += $val->{'cpuload'} / ($val->{'cpucount'} * $val->{'cpucores'}) if ($val->{'cpucount'}>0);
879
            $memtotalsum += $val->{'memtotal'};
880
            $memfreesum += $val->{'memfree'};
881
            $corestotal += $val->{'cpucount'} * $val->{'cpucores'};
882
            $vmstotal += $val->{'vms'};
883
            $vmvcpustotal += $val->{'vmvcpus'};
884
            $nodestorfree += $val->{'storfree'};
885
            $nodestortotal += $val->{'stortotal'};
886
            $readynodes ++ if ($val->{'status'} eq 'running' || $val->{'status'} eq 'maintenance' || $val->{'status'} eq 'asleep');
887
            $i++;
888
#        } elsif (($val->{'identity'} ne "local_kvm") &&($val->{'status'} eq 'running' || $val->{'status'} eq 'maintenance')) {
889
#            $readynodes++;
890
        }
891
    }
892
    $memusedsum = $memtotalsum - $memfreesum;
893
    $nodestorused = $nodestortotal - $nodestorfree;
894

    
895
    $cpuloadsum = $cpuloadsum / $i if ($i > 0); # Avoid division by zero
896
    my %avgs = ("cpuloadavg" => $cpuloadsum, "memtotalsum" =>  $memtotalsum, "memfreesum" =>  $memfreesum,
897
        "nodestotal" => $i,"corestotal" => $corestotal, "readynodes" => $readynodes,
898
        "vmstotal" => $vmstotal, "vmvcpustotal" => $vmvcpustotal,
899
        "nodestortotal" => $nodestortotal, "nodestorfree" => $nodestorfree);
900

    
901
    my %storavgs;
902
    my $stortext;
903
    my $j = 0;
904
    push @tenderpathslist, $backupdir;
905
    push @tendernameslist, "Backup";
906
    foreach my $storpath (@tenderpathslist) {
907
        my $storfree = `df $storpath`;
908
        $storfree =~ m/(\d\d\d\d+)(\s+)(\d\d+)(\s+)(\d\d+)(\s+)(\S+)/i;
909
        my $stortotal = $1;
910
        my $storused = $3;
911
        $storfree = $5;
912
        $storavgs{$tendernameslist[$j].'-used'} = $storused;
913
        $storavgs{$tendernameslist[$j].'-total'} = $stortotal;
914
        $stortext .= $tendernameslist[$j] . ": " .int($storused/1024/1024) . " (" . int($stortotal/1024/1024) . ") GB&nbsp;&nbsp;";
915
        $j++;
916
    }
917

    
918
    my %mons;
919
    my @monservices = ('ping', 'diskspace', 'http', 'https', 'smtp', 'smtps', 'ldap', 'imap', 'imaps', 'telnet');
920
    if ($action eq "fullstats" || $action eq "fullstatsb") {
921
        $Stabile::Systems::fulllist = 1;
922
        %mons = Stabile::Systems::getOpstatus();
923
        $Stabile::Systems::fulllist = 0;
924
    }
925
    if ($action eq "fullstatsb") {
926
        require "images.cgi";
927
        $Stabile::Images::isadmin = $isadmin;
928
        $Stabile::Images::console = 1;
929
    }
930
    my @lusers;
931
    # We use images billing to report storage usage
932
    unless ( tie(%billingreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_images', key=>'userstoragepooltime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
933
    foreach my $uref (values %userreg) {
934
        my %uval = %{$uref};
935

    
936
        delete $uval{'password'};
937
        delete $uval{'lasttkt'};
938
        delete $uval{'tasks'};
939

    
940
        # Skip if not logged in in 5 days
941
        # next unless ($uval{'lastlogin'} && ($current_time-$uval{'lastlogin'} < 5 * 86400));
942
        my @systems = Stabile::Systems::getSystemsListing('arraylist', '', $uval{'username'});
943
        # Skip if user has no systems
944
        # next unless (@systems);
945

    
946
        my @returnsystems;
947
        my $vcpus = 0;
948
        my $mem = 0;
949
        my $servers = 0;
950
        foreach my $sys (@systems) {
951
            my $sysvcpus = 0;
952
            my $sysmem = 0;
953
            my $sysstor = 0;
954
            my $sysnodestor = 0;
955
            if ($sys->{'issystem'}) {
956
                foreach my $dom (@{$sys->{'children'}}) {
957
                    my $status = $dom->{'status'};
958
#                    if ($status ne 'shutoff' && $status ne 'inactive') { # We now report usage also when not running
959
                        $sysvcpus += $dom->{'vcpu'};
960
                        $sysmem += $dom->{'memory'};
961
#                    }
962
                    $sysstor += $dom->{'storage'}/1024/1024;
963
                    $sysnodestor += $dom->{'nodestorage'}/1024/1024;
964
                }
965
            } else {
966
                my $status = $sys->{'status'};
967
#                if ($status ne 'shutoff' && $status ne 'inactive') {
968
                    $sysvcpus = $sys->{'vcpu'};
969
                    $sysmem = $sys->{'memory'};
970
#                }
971
                $sysstor = $sys->{'storage'}/1024/1024;
972
                $sysnodestor = $sys->{'nodestorage'}/1024/1024;
973
            }
974
            $vcpus += $sysvcpus;
975
            $mem += $sysmem;
976
            my $serveruuids = $sys->{'uuid'};
977
            if ($sys->{'issystem'}) {
978
                my @suuids;
979
                foreach my $child (@{$sys->{'children'}}) {
980
                    push @suuids, $child->{'uuid'};
981
                };
982
                $serveruuids = join(', ', @suuids);
983
            }
984

    
985
            $returnsys = {
986
                'appid'=>$sys->{'appid'},
987
                'version'=>$sys->{'version'},
988
                'managementurl'=>$sys->{'managementurl'},
989
                'upgradeurl'=>$sys->{'upgradeurl'},
990
                'terminalurl'=>$sys->{'terminalurl'},
991
                'master'=>$sys->{'master'},
992
                'name'=>$sys->{'name'},
993
                'image'=>$sys->{'image'},
994
                'status'=>$sys->{'status'},
995
                'user'=>$sys->{'user'},
996
                'uuid'=>$sys->{'uuid'},
997
                'servers'=>($sys->{'issystem'}?scalar @{$sys->{'children'}}:1),
998
                'serveruuids' => $serveruuids,
999
                'vcpus' => $sysvcpus,
1000
                'memory' => $sysmem,
1001
                'storage' => $sysstor+0,
1002
                'nodestorage' => $sysnodestor+0,
1003
                'externalips' => $sys->{'externalips'}+0,
1004
                'externalip' => $sys->{'externalip'},
1005
                'ports' => $sys->{'ports'},
1006
                'internalip' => $sys->{'internalip'}
1007
            };
1008
            $servers += ($sys->{'issystem'}?scalar @{$sys->{'children'}}:1);
1009
            my $monitors;
1010
            my $backups;
1011

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

    
1044
            push @returnsystems, $returnsys;
1045
        }
1046
        $uval{'systems'} = \@returnsystems;
1047

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

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

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

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

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

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

    
1113
            push @curregvalues,\%val ;
1114
        }
1115

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

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

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

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

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

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

    
1230
END
1231
;
1232
}
1233

    
1234

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

    
1254
sub trim {
1255
   my $string = shift;
1256
   $string =~ s/^\s+|\s+$//g;
1257
   return $string;
1258
}
1259

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

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

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

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

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