Project

General

Profile

Download (161 KB) Statistics
| Branch: | Revision:
1 95b003ff Origo
#!/usr/bin/perl
2
3
# All rights reserved and Copyright (c) 2020 Origo Systems ApS.
4
# This file is provided with no warranty, and is subject to the terms and conditions defined in the license file LICENSE.md.
5
# The license file is part of this source code package and its content is also available at:
6
# https://www.origo.io/info/stabiledocs/licensing/stabile-open-source-license
7
8
package Stabile::Systems;
9
10
use Webmin::API;
11
use File::Basename;
12
use lib dirname (__FILE__);
13
use Stabile;
14
use Error qw(:try);
15
use String::Escape qw( unbackslash backslash );
16
use Config::Simple;
17
use Time::Local;
18
use Mon::Client;
19
use File::Glob qw(bsd_glob);
20
use POSIX;
21
use Proc::Daemon;
22
use Data::UUID;
23
use LWP::Simple qw(!head);
24
use MIME::Lite;
25
use RRDTool::OO;
26
use Text::CSV_XS qw( csv );
27 4aef7ef6 hq
use Geo::IP;
28 95b003ff Origo
29
my $cfg = new Config::Simple("/etc/stabile/config.cfg");
30
31
my $engineid = $Stabile::config->get('ENGINEID') || "";
32
my $enginename = $Stabile::config->get('ENGINENAME') || "";
33
my $doxmpp = $Stabile::config->get('DO_XMPP') || "";
34
my $disablesnat = $Stabile::config->get('DISABLE_SNAT') || "";
35 2a63870a Christian Orellana
my ($datanic, $extnic) = $main::getNics->();
36 95b003ff Origo
my $extiprangestart = $Stabile::config->get('EXTERNAL_IP_RANGE_START');
37
my $extiprangeend = $Stabile::config->get('EXTERNAL_IP_RANGE_END');
38
39
if (!$Stabile::Servers::q && !$Stabile::Images::q  && !$Stabile::Networks::q && !$Stabile::Users::q && !$Stabile::Nodes::q) { # We are not being called from another script
40
    $q = new CGI;
41
    my %cgiparams = $q->Vars;
42
    %params = %cgiparams if (%cgiparams);
43
} else {
44
    $console = 1;
45
}
46
47
my %ahash; # A hash of accounts and associated privileges current user has access to
48
$uiuuid;
49
$uistatus;
50
$help = 0; # If this is set, functions output help
51
52
our %ahash; # A hash of accounts and associated privileges current user has access to
53
#our %options=();
54
# -a action -h help -u uuid -m match pattern -f full list, i.e. all users
55
# -v verbose, include HTTP headers -s impersonate subaccount -t target [uuid or image]
56
# -g args to gearman task
57
#Getopt::Std::getopts("a:hfu:g:m:vs:t:", \%options);
58
59
try {
60
    Init(); # Perform various initalization tasks
61
    process() if ($package);
62
63
} catch Error with {
64
	my $ex = shift;
65
    print header('text/html', '500 Internal Server Error') unless ($console);
66
	if ($ex->{-text}) {
67
        print "Got error $package: ", $ex->{-text}, " on line ", $ex->{-line}, "\n";
68
	} else {
69
	    print "Status=ERROR\n";
70
	}
71
} finally {
72
};
73
74
1;
75
76
sub getObj {
77
    my %h = %{@_[0]};
78
    $console = 1 if $obj->{"console"};
79
    my $obj;
80
    $action =  $action || $h{'action'};
81 a93267ad hq
    if ($action =~ /updateaccountinfo|monitors|listuptime|buildsystem|removeusersystems|updateengineinfo|^register$|^packages$|downloadmaster|start/) {
82 95b003ff Origo
        $obj = \%h;
83
        $obj->{domuuid} = $curdomuuid if ($curdomuuid);
84
    } else {
85
        my $uuid =$h{"uuid"} || $curuuid;
86
        $uuid = $curuuid if ($uuid eq 'this');
87
        my $status = $h{"status"};
88
        if ((!$uuid && $uuid ne '0') && (!$status || $status eq 'new')) {
89
            my $ug = new Data::UUID;
90
            $uuid = $ug->create_str();
91
            $status = 'new';
92
        };
93
        return 0 unless ($uuid && length $uuid == 36);
94
95
        $obj = {uuid => $uuid};
96 a93267ad hq
        my @props = qw(uuid name memory vcpu vmemory vgpu user  notes  created  opemail  opfullname  opphone  email  fullname  phone  services
97 95b003ff Origo
            recovery  alertemail  image  networkuuid1  internalip autostart issystem system systemstatus from to
98 04c16f26 hq
            appid callback installsystem installaccount networkuuids ports);
99 95b003ff Origo
        if ($register{$uuid}) {
100
            foreach my $prop (@props) {
101
                my $val = $h{$prop} || $register{$uuid}->{$prop};
102
                $obj->{$prop} = $val if ($val);
103
            }
104
        } else {
105
            foreach my $prop (@props) {
106
                my $val = $h{$prop};
107
                $obj->{$prop} = $val if ($val);
108
            }
109
        }
110
    }
111
    return $obj;
112
}
113
114
sub Init {
115
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {$posterror = "Unable to access user register"; return;};
116
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {$posterror = "Unable to access domain register"; return;};
117
    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {$posterror = "Unable to access network register"; return;};
118
    unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$posterror = "Unable to access system register"; return;};
119
120
    $cursysuuid = $domreg{$curuuid}->{'system'}if ($domreg{$curuuid});
121
    $tktuser = $tktuser || $Stabile::tktuser;
122
    $user = $user || $Stabile::user;
123
124
    *Deletesystem = \&Removesystem;
125
    *Backup = \&systemAction;
126 a2e0bc7e hq
    *Snapshot = \&systemAction;
127
    *Unsnap  = \&systemAction;
128 95b003ff Origo
129
    *do_help = \&action;
130
    *do_tablelist = \&do_list;
131
    *do_arraylist = \&do_list;
132
    *do_flatlist = \&do_list;
133
    *do_monitors = \&privileged_action;
134
    *do_suspend = \&systemAction;
135
    *do_resume = \&systemAction;
136
    *do_shutdown = \&systemAction;
137
    *do_destroy = \&systemAction;
138
    *do_start = \&systemAction;
139
    *do_backup = \&privileged_action;
140 a2e0bc7e hq
    *do_snapshot = \&privileged_action;
141
    *do_unsnap = \&privileged_action;
142 95b003ff Origo
    *do_packages_load = \&privileged_action;
143
    *do_monitors_save = \&privileged_action;
144
    *do_monitors_remove = \&privileged_action;
145
    *do_monitors_enable = \&privileged_action;
146
    *do_monitors_disable = \&privileged_action;
147
    *do_monitors_acknowledge = \&privileged_action;
148
    *do_save = \&privileged_action;
149
    *do_changemonitoremail = \&privileged_action;
150
    *do_buildsystem = \&privileged_action;
151
    *do_removesystem = \&privileged_action;
152
    *do_deletesystem = \&privileged_action;
153
    *do_removeusersystems = \&privileged_action;
154
    *do_updateengineinfo = \&privileged_action;
155 f222b89c hq
    *do_downloadmaster = \&privileged_action;
156 95b003ff Origo
157
    *do_gear_backup = \&do_gear_action;
158 a2e0bc7e hq
    *do_gear_snapshot = \&do_gear_action;
159
    *do_gear_unsnap = \&do_gear_action;
160 95b003ff Origo
    *do_gear_packages_load = \&do_gear_action;
161
    *do_gear_monitors = \&do_gear_action;
162
    *do_gear_monitors_enable = \&do_gear_action;
163
    *do_gear_monitors_save = \&do_gear_action;
164
    *do_gear_monitors_remove = \&do_gear_action;
165
    *do_gear_monitors_disable = \&do_gear_action;
166
    *do_gear_monitors_acknowledge = \&do_gear_action;
167
    *do_gear_save = \&do_gear_action;
168
    *do_gear_changemonitoremail = \&do_gear_action;
169
    *do_gear_buildsystem = \&do_gear_action;
170
    *do_gear_removesystem = \&do_gear_action;
171
    *do_gear_deletesystem = \&do_gear_action;
172
    *do_gear_removeusersystems = \&do_gear_action;
173
    *do_gear_updateengineinfo = \&do_gear_action;
174 f222b89c hq
    *do_gear_downloadmaster = \&do_gear_action;
175 95b003ff Origo
    *Monitors_remove = \&Monitors_save;
176
    *Monitors_enable = \&Monitors_action;
177
    *Monitors_disable = \&Monitors_action;
178
    *Monitors_acknowledge = \&Monitors_action;
179
}
180
181
sub do_uuidlookup {
182
    if ($help) {
183
        return <<END
184
GET:uuid:
185
Simple action for looking up a uuid or part of a uuid and returning the complete uuid.
186
END
187
    }
188
    my $res;
189
    $res .= header('text/plain') unless $console;
190
    my $u = $options{u};
191
    $u = $curuuid unless ($u || $u eq '0');
192
    my $ruuid;
193
    if ($u || $u eq '0') {
194
        my $match;
195
        foreach my $uuid (keys %register) {
196
            if ($uuid =~ /^$u/) {
197
                $ruuid = $uuid if ($register{$uuid}->{'user'} eq $user || index($privileges,"a")!=-1);
198
                $match = 1;
199
                last;
200
            }
201
        }
202
        unless ($match) {
203
            foreach my $uuid (keys %domreg) {
204
                if ($uuid =~ /^$u/) {
205
                    $ruuid = $uuid if ((!$domreg{$uuid}->{'system'} || $domreg{$uuid}->{'system'} eq '--' )&&  ($domreg{$uuid}->{'user'} eq $user || index($privileges,"a")!=-1));
206
                    last;
207
                }
208
            }
209
        }
210
    }
211
    $res .= "$ruuid\n" if ($ruuid);
212
    return $res;
213
}
214
215
sub do_uuidshow {
216
    if ($help) {
217
        return <<END
218
GET:uuid:
219
Simple action for showing a single system.
220
END
221
    }
222
    my $res;
223
    $res .= header('application/json') unless $console;
224
    my $u = $options{u};
225
    $u = $curuuid unless ($u || $u eq '0');
226
    if ($u) {
227
        foreach my $uuid (keys %register) {
228
            if (($register{$uuid}->{'user'} eq $user || $register{$uuid}->{'user'} eq 'common' || index($privileges,"a")!=-1)
229
                && $uuid =~ /^$u/) {
230
                my %hash = %{$register{$uuid}};
231
                delete $hash{'action'};
232
                delete $hash{'nextid'};
233
                my $dump = to_json(\%hash, {pretty=>1});
234
                $dump =~ s/undef/"--"/g;
235
                $res .= $dump;
236
                last;
237
            }
238
        }
239
    }
240
    return $res;
241
}
242
243
sub do_list {
244
    my ($uuid, $action, $obj) = @_;
245
    if ($help) {
246
        return <<END
247 8d7785ff Origo
GET:uuid:
248 95b003ff Origo
List systems current user has access to.
249
END
250
    }
251
    my $sysuuid;
252
    if ($uripath =~ /systems(\.cgi)?\/(\?|)(this)/) {
253
        $sysuuid = $cursysuuid || $curuuid;
254
    } elsif ($uripath =~ /systems(\.cgi)?\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})/) {
255
        $sysuuid = $2;
256
    } elsif ($params{'system'}) {
257
        $sysuuid = $obj->{'system'};
258
        $sysuuid = $cursysuuid || $curuuid if ($obj->{system} eq 'this');
259
    }
260
    $postreply = getSystemsListing($action, $uuid);
261
    return $postreply;
262
}
263
264
sub Monitors_action {
265
    my ($uuid, $action, $obj) = @_;
266
    if ($help) {
267
        return <<END
268
GET:id:
269
Enable, disable or acknowledge a monitor. Id is of the form serveruuid:service
270
END
271
    }
272
    my $monitor_action = "enable";
273
    $monitor_action = "disable" if ($action eq 'monitors_disable');
274
    $monitor_action = "acknowledge" if ($action eq 'monitors_acknowledge');
275
    my $log_action = uc $monitor_action;
276
    my $group;
277
    my $service;
278
    my $logline;
279
    if ($uuid =~ /(.+):(.+)/) {
280
        $group = $1;
281
        $service = $2;
282
    }
283
    if ($group && $service) {
284
        my $reguser = $domreg{$group}->{'user'};
285
        # Security check
286
        if ($user eq $reguser || index($privileges,"a")!=-1) {
287
            my $oplogfile = "/var/log/stabile/$year-$month:$group:$service";
288
            unless (-e $oplogfile) {
289
                `/usr/bin/touch "$oplogfile"`;
290
                `/bin/chown mon:mon "$oplogfile"`;
291
            }
292
            if ($monitor_action =~ /enable|disable/) {
293
                my $res = `/usr/bin/moncmd $monitor_action service $group $service`;
294
                chomp $res;
295
                $logline = "$current_time, $log_action, , $pretty_time";
296
            } elsif ($monitor_action eq "acknowledge") {
297
                my $ackcomment = $obj->{"ackcomment"};
298
                # my $ackcomment = backslash( $obj->{"ackcomment"} );
299
                #$ackcomment =~ s/ /\\\20/g;
300
                my $monc = new Mon::Client (
301
                    host => "127.0.0.1"
302
                );
303
                $ackcomment = ($ackcomment)?"$user, $ackcomment":$user;
304
                $monc->connect();
305
                $monc->ack($group, $service, $ackcomment);
306
                $monc->disconnect();
307
                $logline = "$current_time, ACKNOWLEDGE, $ackcomment, $pretty_time";
308
                my %emails;
309
                my @emaillist = split(/\n/, `/bin/cat /etc/mon/mon.cf`);
310
                my $emailuuid;
311
                foreach my $eline (@emaillist) {
312
                    my ($a, $b, $c, $d) = split(/ +/, $eline);
313
                    if ($a eq 'watch') {
314
                        if ($b =~ /\S+-\S+-\S+-\S+-\S+/) {$emailuuid = $b;}
315
                        else {$emailuuid = ''};
316
                    }
317
                    $emails{$emailuuid} = $d if ($emailuuid && $b eq 'alert' && $c eq 'stabile.alert');
318
                };
319
                my $email = $emails{$group};
320
                my $servername = $domreg{$group}->{'name'};
321
                my $serveruser = $domreg{$group}->{'user'};
322
                if ($email) {
323
                    my $mailtext = <<EOF;
324
Acknowledged by: $user
325
Server name: $servername
326
Server UUID: $group
327
System UUID: $sysuuid
328
Server user: $serveruser
329
Service: $service
330
EOF
331
                    ;
332
333
                    my $mailhtml = <<END;
334
<!DOCTYPE html
335
    PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
336
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
337
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
338
    <head>
339
        <title>Problems with $servername:$service are being handled</title>
340
        <meta http-equiv="Pragma" content="no-cache" />
341
		<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
342
        <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
343
    </head>
344
    <body class="tundra">
345
        <div>
346
            <div class="well" style="margin:20px;">
347
                <h3 style="color: #2980b9!important; margin-bottom:30px;">Relax, the problems with your service are being handled!</h3>
348
                <div>The problems with the service <strong>$service</strong> on the server <strong>$servername</strong> running on <strong>$enginename</strong> have been acknowledged at $pretty_time and are being handled by <strong>$tktuser ($user)</strong>.</div>
349
                <br>
350
                <div>Thanks,<br>your friendly monitoring daemon</div>
351
            </div>
352
        </div>
353
    </body>
354
</html>
355
END
356
                    ;
357
358
                    my $xmpptext = "ACK: $servername:$service is being handled ($pretty_time)\n";
359
                    $xmpptext .= "Acknowledged by: $tktuser ($user)\n";
360
361
                    my $msg = MIME::Lite->new(
362
                        From     => 'monitoring',
363
                        To       => $email,
364
                        Type     => 'multipart/alternative',
365
                        Subject  => "ACK: $servername:$service is being handled ($pretty_time)",
366
                    );
367
                    $msg->add("sysuuid" => $sysuuid);
368
369
                    my $att_text = MIME::Lite->new(
370
                        Type     => 'text',
371
                        Data     => $mailtext,
372
                        Encoding => 'quoted-printable',
373
                    );
374
                    $att_text->attr('content-type'
375
                        => 'text/plain; charset=UTF-8');
376
                    $msg->attach($att_text);
377
378
                    my $att_html = MIME::Lite->new(
379
                        Type     => 'text',
380
                        Data     => $mailhtml,
381
                        Encoding => 'quoted-printable',
382
                    );
383
                    $att_html->attr('content-type'
384
                        => 'text/html; charset=UTF-8');
385
                    $msg->attach($att_html);
386
387
                    $msg->send;
388
389
                    if ($doxmpp) {
390
                        foreach my $to (split /, */, $email) {
391
                            my $xres = $main::xmppSend->($to, $xmpptext, $engineid, $sysuuid);
392
                        }
393
                        # Send alerts to Origo operators on duty
394
                        my $oponduty = 'operator@sa.origo.io';
395
                        $msg->replace('to', $oponduty);
396
                        $msg->send;
397
                        my $xres = $main::xmppSend->($oponduty, $xmpptext, $engineid, $sysuuid);
398
                    }
399
                }
400
            }
401
            `/bin/echo >> $oplogfile "$logline"`;
402
            $postreply .= "Status=OK OK $monitor_action"." $service service\n";
403
        }
404
    } else {
405
        $postreply = "Status=Error problem $monitor_action monitor $uuid\n";
406
    }
407
    return $postreply;
408
}
409
410
sub do_register {
411
    my ($uuid, $action, $obj) = @_;
412
    if ($help) {
413
        return <<END
414
GET:uuid,format:
415
Print software register for server or system of servers with given uuid. Format is html, csv or json (default).
416
END
417
    }
418
419
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
420
    my @domregvalues = values %domreg;
421
    my %reghash;
422
    foreach my $valref (@domregvalues) {
423
        if ($valref->{'user'} eq $user || $fulllist) {
424
            if (!$uuid || $uuid eq '*' || $uuid eq $valref->{'uuid'} || $uuid eq $valref->{'system'}) {
425
                my $os = $valref->{'os'} || 'unknown';
426
                my $domname = $valref->{'name'};
427
                utf8::decode($domname);
428
                if ($reghash{$os}) {
429
                    $reghash{ $os . '-' . $reghash{$os}->{'oscount'} } = {
430
                        os=>'',
431
                        sortos=>$os."*",
432
                        user=>$valref->{'user'},
433
                        name=>$domname,
434
                        hostname=>$valref->{'hostname'}
435
                    };
436
                    $reghash{$os}->{'oscount'}++;
437
                } else {
438
                    $reghash{$os} = {
439
                        os=>$os,
440
                        sortos=>$os,
441
                        user=>$valref->{'user'},
442
                        name=>$domname,
443
                        hostname=>$valref->{'hostname'},
444
                        oscount=>1
445
                    }
446
                }
447
            }
448
        }
449
450
    }
451
    untie %domreg;
452
    my @sorted_oslist = sort {$a->{'sortos'} cmp $b->{'sortos'}} values %reghash;
453
    if ($obj->{'format'} eq 'html') {
454
        my $res;
455
        $res .= qq[<tr><th>OS</th><th>Name</th><th>Hostname</th><th>Count</th></tr>];
456
        foreach my $valref (@sorted_oslist) {
457
            $res .= qq[<tr><td>$valref->{'os'}</td><td>$valref->{'name'}</td><td>$valref->{'hostname'}</td><td>$valref->{'oscount'}</td></tr>];
458
        }
459
        $postreply = header();
460
        $postreply .= qq[<table cellspacing="0" frame="void" rules="rows" class="systemTables">$res</table>];
461
    } elsif ($obj->{'format'} eq 'csv') {
462
        $postreply = header("text/plain");
463
        csv(in => \@sorted_oslist, out => \my $csvdata);
464
        $postreply .= $csvdata;
465
    } else {
466
        $postreply .= to_json(\@sorted_oslist);
467
    }
468
    return $postreply;
469
470
}
471
472
sub Monitors {
473
    my ($uuid, $action, $obj) = @_;
474
    if ($help) {
475
        return <<END
476
GET:uuid:
477
Handling of monitors
478
END
479
    }
480
# We are dealing with a POST request, i.e. an action on a monitor
481
# or a PUT or DELETE request, i.e. creating/saving/deleting items
482
    if (($ENV{'REQUEST_METHOD'} eq 'DELETE' || $params{"PUTDATA"} || $ENV{'REQUEST_METHOD'} eq 'PUT' || $ENV{'REQUEST_METHOD'} eq 'POST') && !$isreadonly) {
483
        my @json_array;
484
        my %json_hash;
485
        my $delete;
486
        if ($ENV{'REQUEST_METHOD'} eq 'DELETE' && $uripath =~ /action=monitors\/(.+):(.+)/) {
487
            print header('text/json', '204 No Content') unless $console;
488
            %json_hash = ('serveruuid', $1, 'service', $2);
489
            @json_array = (\%json_hash);
490
            $delete = 1;
491
#            print Monitors_save(\%json_hash, $delete);
492
            print Monitors_save($uuid, "monitors_remove", $obj);
493
        } else {
494
            my $json_text = $params{"PUTDATA"} || $params{'keywords'};
495
            $json_text = encode('latin1', decode('utf8', $json_text));
496
            $json_text =~ s/\x/ /g;
497
            @json_array = from_json($json_text);
498
            $json_hash_ref = @json_array[0];
499
#            my $res = Monitors_save($json_hash_ref, $delete);
500
            my $res = Monitors_save($uuid, "monitors_save", $obj);
501
            if ($res =~ /^{/) {
502
                print header('text/json') unless $console;
503
                print $res;
504
            } else {
505
                print header('text/html', '400 Bad Request') unless $console;
506
                print qq|$res|;
507
            }
508
        }
509
510
# We are dealing with a regular GET request, i.e. a listing
511
    } else {
512
        my $selgroup;
513
        my $selservice;
514
        if ($uuid && $uuid ne '*') { # List all monitors for specific server
515
            $selgroup = $uuid;
516
            if ($uuid =~ /(.+):(.+)/){ # List specific monitor for specific server
517
                $selgroup = $1;
518
                $selservice = $2;
519
            }
520
        }
521
        my $usemoncmd = 0;
522
        my %opstatus = getOpstatus($selgroup, $selservice, $usemoncmd);
523
        my @monitors = values(%opstatus);
524
        my @sorted_monitors = sort {$a->{'opstatus'} cmp $b->{'opstatus'}} @monitors;
525
        my $json_text;
526
        if ($obj->{'listaction'} eq 'show' && scalar @monitors == 1) {
527
            $json_text = to_json($sorted_monitors[0], {pretty => 1});
528
        } else {
529
            $json_text = to_json(\@sorted_monitors, {pretty => 1});
530
        }
531
        utf8::decode($json_text);
532
        $postreply = $json_text;
533
        return $postreply;
534
    }
535
536
}
537
538
sub do_remove {
539
    my ($uuid, $action, $obj) = @_;
540
    if ($help) {
541
        return <<END
542
DELETE:uuid:
543
Delete a system from database and make all member servers free agents.
544
END
545
    }
546
    if ($register{$uuid}) {
547
        unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
548
        my @domregvalues = values %domreg;
549
        my @curregvalues;
550
        foreach my $valref (@domregvalues) {
551
            # Only include VM's belonging to current user (or all users if specified and user is admin)
552
            if ($user eq $valref->{'user'} || $fulllist) {
553
                my $system = $valref->{'system'};
554
                if ($system eq $uuid) {
555
                    $valref->{'system'} = '';
556
                    push(@curregvalues, $valref);
557
                }
558
            }
559
        }
560
        delete $register{$uuid};
561
        tied(%domreg)->commit;
562
        tied(%register)->commit;
563
        untie %domreg;
564
        if ($match) {
565
            $postreply = to_json(@curregvalues);
566
        } else {
567
            $postreply = header('text/plain', '204 No Content') unless $console;
568
        }
569
    }
570
    return $postreply;
571
}
572
573
sub Save {
574
    my ($uuid, $action, $obj) = @_;
575
    if ($help) {
576
        return <<END
577 a93267ad hq
PUT:uuid, name, servers, memory, vcpu, vmemory, vgpu, fullname, email, phone, opfullname, opemail, opphone, alertemail, services, recovery, notes, networkuuids:
578 d3d1a2d4 Origo
Save properties for a system. If no uuid is provided, a new stack is created.[networkuuids] is a comma-separated list of networks reserved to this stack for use not associated with specific servers.
579
[networkuuids] is a list of UUIDs of linked network connections, i.e. connections reserved for this system to handle
580
581
        Specify '--' to clear a value.
582 95b003ff Origo
END
583
    }
584 9de5a3f1 hq
585
    my $name = $obj->{"name"};
586 04c16f26 hq
    my $memory = $obj->{"memory"};
587
    my $vcpu = $obj->{"vcpu"};
588 a93267ad hq
    my $vmemory = $obj->{"vmemory"};
589
    my $vgpu = $obj->{"vgpu"};
590 95b003ff Origo
    my $reguser;
591
    $reguser = $register{$uuid}->{'user'} if ($register{$uuid});
592
    $console = 1 if ($obj->{'console'});
593
    my $issystem = $obj->{'issystem'} || $register{$uuid};
594
    my $notes = $obj->{"notes"};
595
    my $email = $obj->{'email'};
596
    my $fullname = $obj->{'fullname'};
597
    my $phone = $obj->{'phone'};
598
    my $opemail = $obj->{'opemail'};
599
    my $opfullname = $obj->{'opfullname'};
600
    my $opphone = $obj->{'opphone'};
601
    my $alertemail = $obj->{'alertemail'};
602
    my $services = $obj->{'services'};
603
    my $recovery = $obj->{'recovery'};
604 d3d1a2d4 Origo
    my $networkuuids = $obj->{'networkuuids'};
605 04c16f26 hq
    my $ports = $obj->{'ports'};
606 c899e439 Origo
    my $autostart = $obj->{'autostart'};
607 9de5a3f1 hq
    if (!$name) {
608
        if ($issystem) {
609
            $name = $register{$uuid}->{'name'};
610
        } else {
611
            $name = $domreg{$uuid}->{'name'};
612
        }
613
    }
614 95b003ff Origo
    if ((!$uuid)) {
615
        my $ug = new Data::UUID;
616
        $uuid = $ug->create_str();
617
        $issystem = 1;
618
    };
619
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access domain register"};
620
    unless ($register{$uuid} || $domreg{$uuid}) {
621
        $obj->{'status'} = 'new';
622
        $issystem = 1;
623
    }
624
    $issystem = 1 if ($register{$uuid});
625
    unless (($uuid && length $uuid == 36)) {
626
        $postreply = "Status=Error Invalid UUID\n";
627
        return $postreply;
628
    }
629
630
    # Sanity checks
631
    if ($name && length $name > 255) {
632
        $postreply .= "Status=Error Bad data: $name " . (length $name) . "\n";
633
        return $postreply;
634
    };
635
636
    if ($issystem) { # We are dealing with a system
637
        # Security check
638
        if (($user eq $reguser || $isadmin) && $register{$uuid}) { # Existing system
639 04c16f26 hq
            my @props = ('name', 'fullname','email','phone','opfullname','opemail','opphone','alertemail'
640 c899e439 Origo
                ,'notes','services','recovery','autostart');
641 95b003ff Origo
            my %oldvals;
642
            foreach my $prop (@props) {
643
                my $val = $obj->{$prop};
644
                if ($val) {
645
                    $val = '' if ($val eq '--');
646
                    $oldvals{$prop} = $register{$uuid}->{$prop} || $userreg{$user}->{$prop};
647 c899e439 Origo
                    if ($val eq $userreg{$user}->{$prop}) {
648 95b003ff Origo
                        $register{$uuid}->{$prop} = ''; # Same val as parent (user val), reset
649
                    } else {
650 04c16f26 hq
                        if ($prop eq 'name' && $obj->{ports}) {
651
                            next; # TODO: ugly hack because we dont know why UTF8 is not handled correctly
652
                        }
653 95b003ff Origo
                        $register{$uuid}->{$prop} = $val;
654
                    }
655 c899e439 Origo
                    if ($prop eq 'autostart') {
656
                        $register{$uuid}->{$prop} = ($val)?'1':'';
657
                    }
658 95b003ff Origo
                    if ($prop eq 'name') {
659 04c16f26 hq
                        my $json_text = qq|{"uuid": "$uuid" , "name": "$name"}|;
660 95b003ff Origo
                        $main::postAsyncToOrigo->($engineid, 'updateapps', "[$json_text]");
661
                    }
662
                }
663
            }
664
            my %childrenhash;
665
            my $alertmatch;
666 a93267ad hq
            push @props, ('vcpu', 'memory', 'vgpu', 'vmemory', 'ports');
667 95b003ff Origo
            foreach my $prop (@props) {
668
                my $val = $obj->{$prop};
669
                if ($val) {
670
                    $val = '' if ($val eq '--');
671
                    # Update children
672
                    foreach my $domvalref (values %domreg) {
673
                        if ($domvalref->{'user'} eq $user && $domvalref->{'system'} eq $uuid) {
674
                            my %domval = %{$domvalref};
675 04c16f26 hq
                            my $serveruuid = $domvalref->{'uuid'};
676
                            $childrenhash{$serveruuid} =\%domval unless ($childrenhash{$serveruuid});
677
                            $childrenhash{$serveruuid}->{$prop} = $val;
678 c899e439 Origo
                            if ($prop eq 'autostart') {
679 04c16f26 hq
                                $domvalref->{$prop} = ($val) ? '1' : ''; # Always update child servers with autostart prop
680
                            } elsif ((
681
                                ($obj->{'vcpu'} && $prop eq 'vcpu')
682
                                || ($obj->{'memory'} && $prop eq 'memory')
683 a93267ad hq
                                || ($obj->{'vgpu'} && $prop eq 'vgpu')
684
                                || ($obj->{'vmemory'} && $prop eq 'vmemory')
685 04c16f26 hq
                            ) && $domvalref->{status} eq 'shutoff') {
686
                                $Stabile::Servers::console = 1;
687
                                require "$Stabile::basedir/cgi/servers.cgi";
688
                                $postreply .= Stabile::Servers::Save($serveruuid, 'save',
689
                                    { uuid => $serveruuid, $prop => $obj->{$prop} });
690
                            } elsif ($obj->{'ports'} && $prop eq 'ports') {
691
                                $Stabile::Networks::console = 1;
692
                                require "$Stabile::basedir/cgi/networks.cgi";
693
                                my $networkuuid1 = $domvalref->{'networkuuid1'};
694
                                my $saveobj = {uuid => $networkuuid1};
695
                                $saveobj->{ports} = $ports;
696
                                $postreply .= Stabile::Networks::Deactivate($networkuuid1);
697
                                $postreply .= Stabile::Networks::Save($networkuuid1, 'save', $saveobj);
698
                                $postreply .= Stabile::Networks::Activate($networkuuid1);
699
                            } elsif (!$domvalref->{$prop} || $domvalref->{$prop} eq $oldvals{$prop}) { # Inheritance is implied, so delete redundant entries
700 95b003ff Origo
                                $domvalref->{$prop} = '';
701
                                if ($prop eq 'alertemail') {
702 04c16f26 hq
                                    if (change_monitor_email($serveruuid, $val, $oldvals{$prop})) {
703 95b003ff Origo
                                        $alertmatch = 1;
704
                                    }
705
                                }
706
                            }
707
                        }
708
                    }
709
                }
710
            }
711
            my @children = values %childrenhash;
712
            $obj->{'children'} = \@children if (@children);
713
            $postreply = getSystemsListing();
714
        } elsif ($obj->{'status'} eq 'new')  { # New system
715
            $register{$uuid} = {
716
                uuid=>$uuid,
717
                name=>$name,
718
                user=>$user,
719
                created=>$current_time
720
            };
721
            my $valref = $register{$uuid};
722
            my %val = %{$valref};
723
            $val{'issystem'} = 1;
724
            $val{'status'} = '--';
725
            $dojson = 1;
726
            $postreply = to_json(\%val, {pretty=>1});
727
        } else {
728
            $postreply .= "Status=Error Not enough privileges: $user\n";
729
        }
730
    } else { # We are dealing with a server
731
        my $valref = $domreg{$uuid};
732
        if (!$valref && $obj->{'uuid'}[0]) {$valref = $domreg{ $obj->{'uuid'}[0] }}; # We are dealing with a newly created server
733
        if ($valref && ($valref->{'user'} eq $user || $isadmin)) {
734
            my $system = $obj->{'system'};
735
            my $servername = $obj->{'name'};
736
            if ($servername && $servername ne $valref->{'name'}) {
737
                $valref->{'name'} = $servername;
738
                # Update status of images
739
                my @imgs = ($domreg{$uuid}->{image}, $domreg{$uuid}->{image2}, $domreg{$uuid}->{image3}, $domreg{$uuid}->{image4});
740
                my @imgkeys = ('image', 'image2', 'image3', 'image4');
741
                unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access image register"};
742
                for (my $i=0; $i<4; $i++) {
743
                    my $img = $imgs[$i];
744
                    my $k = $imgkeys[$i];
745
                    if ($img && $img ne '--') {
746
                        $imagereg{$img}->{'domains'} = $uuid;
747
                        $imagereg{$img}->{'domainnames'} = $servername;
748
                    }
749
                }
750
                untie %imagereg;
751
                my $json_text = qq|{"uuid": "$uuid" , "name": "$servername"}|;
752
                $main::postAsyncToOrigo->($engineid, 'updateapps', "[$json_text]");
753
            }
754
            $valref->{'system'} = ($system eq '--'?'':$system) if ($system);
755
            $valref->{'notes'} = (($notes eq '--')?'':$notes) if ($notes);
756
            $valref->{'email'} = ($email eq '--'?'':$email) if ($email);
757
            $valref->{'fullname'} = ($fullname eq '--'?'':$fullname) if ($fullname);
758
            $valref->{'phone'} = ($phone eq '--'?'':$phone) if ($phone);
759
            $valref->{'opemail'} = ($opemail eq '--'?'':$opemail) if ($opemail);
760
            $valref->{'opfullname'} = ($opfullname eq '--'?'':$opfullname) if ($opfullname);
761
            $valref->{'opphone'} = ($opphone eq '--'?'':$opphone) if ($opphone);
762
            $valref->{'services'} = ($services eq '--'?'':$services) if ($services);
763
            $valref->{'recovery'} = ($recovery eq '--'?'':$recovery) if ($recovery);
764 c899e439 Origo
            $valref->{'autostart'} = ($autostart && $autostart ne '--'?'1':'');
765 95b003ff Origo
            if ($alertemail) {
766
                $alertemail = '' if ($alertemail eq '--');
767
                if ($valref->{'alertemail'} ne $alertemail) {
768
                    # If alert email is changed, update monitor if it is configured with this email
769
                    if (change_monitor_email($valref->{'uuid'}, $alertemail, $valref->{'alertemail'})){
770
                        $alertmatch = 1;
771
                        #`/usr/bin/moncmd reset keepstate`;
772
                    }
773
                    $valref->{'alertemail'} = $alertemail;
774
                }
775
            }
776 a93267ad hq
            if (($vcpu || $memory || $vgpu || $vmemory) && $valref->{status} eq 'shutoff') {
777 04c16f26 hq
                $Stabile::Servers::console = 1;
778
                require "$Stabile::basedir/cgi/servers.cgi";
779
                my $saveobj = {uuid => $valref->{'uuid'}};
780
                $saveobj->{vcpu} = $vcpu if ($vcpu);
781
                $saveobj->{memory} = $memory if ($memory);
782 a93267ad hq
                $saveobj->{vgpu} = $vcpu if ($vgpu);
783
                $saveobj->{vmemory} = $memory if ($vmemory);
784 04c16f26 hq
                $postreply .= Stabile::Servers::Save($valref->{'uuid'}, 'save', $saveobj);
785
            }
786
            if ($ports) {
787
                $Stabile::Networks::console = 1;
788
                require "$Stabile::basedir/cgi/networks.cgi";
789
                my $networkuuid1 = $valref->{'networkuuid1'};
790
                my $saveobj = {uuid => $networkuuid1};
791
                $saveobj->{ports} = $ports;
792
                $postreply .= Stabile::Networks::Deactivate($networkuuid1);
793
                $postreply .= Stabile::Networks::Save($networkuuid1, 'save', $saveobj);
794
                $postreply .= Stabile::Networks::Activate($networkuuid1);
795 95b003ff Origo
796 04c16f26 hq
            }
797 95b003ff Origo
            tied(%domreg)->commit;
798
            $postreply = getSystemsListing(); # Hard to see what else to do, than to send entire table
799
        }
800
    }
801 c899e439 Origo
    if ($networkuuids && $networkuuids ne '--') { # link networks to this system
802 d3d1a2d4 Origo
        my @networks = split(/, ?/, $networkuuids);
803
        my @newnetworks = ();
804
        my @newnetworknames = ();
805
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
806
        foreach my $networkuuid (@networks) {
807 c899e439 Origo
            next unless ($networkreg{$networkuuid});
808 d3d1a2d4 Origo
            if (
809
                !$networkreg{$networkuuid}->{'domains'} # a network cannot both be linked and in active use
810
                    && (!$networkreg{$networkuuid}->{'systems'} ||  $networkreg{$networkuuid}->{'systems'} eq $uuid) # check if network is already linked to another system
811
            ) {
812
                $networkreg{$networkuuid}->{'systems'} = $uuid;
813
                $networkreg{$networkuuid}->{'systemnames'} = $name;
814
                push @newnetworks, $networkuuid;
815
                push @newnetworknames, $networkreg{$networkuuid}->{'name'};
816
            }
817
        }
818 c899e439 Origo
        if ($issystem && $register{$uuid}) {
819 d3d1a2d4 Origo
            $register{$uuid}->{'networkuuids'} = join(", ", @newnetworks);
820
            $register{$uuid}->{'networknames'} = join(", ", @newnetworknames);
821 c899e439 Origo
        } elsif ($domreg{$uuid}) {
822 d3d1a2d4 Origo
            $domreg{$uuid}->{'networkuuids'} = join(", ", @newnetworks);
823
            $domreg{$uuid}->{'networknames'} = join(", ", @newnetworknames);
824
        }
825
    }
826 95b003ff Origo
    untie %domreg;
827
    return $postreply;
828
}
829
830
sub do_resettoaccountinfo {
831
    my ($uuid, $action, $obj) = @_;
832
    if ($help) {
833
        return <<END
834
GET::
835
Recursively reset contact data for all systems and servers
836
END
837
    }
838
    my @props = ('fullname','email','phone','opfullname','opemail','opphone','alertemail');
839
    my $alertmatch;
840
    foreach my $sysvalref (values %register) {
841
        if ($user eq $sysvalref->{'user'}) {
842
            my $sysuuid = $sysvalref->{'uuid'};
843
            foreach my $prop (@props) {
844
                # Does this system have a value?
845
                if ($sysvalref->{$prop}) {
846
                    $sysvalref->{$prop} = ''; # An empty val refers to parent (user) val
847
                }
848
            }
849
        }
850
    }
851
    # Update domains
852
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {$posterror = "Unable to access domain register"; return;};
853
    foreach my $domvalref (values %domreg) {
854
        if ($domvalref->{'user'} eq $user) {
855
            foreach my $prop (@props) {
856
                if ($domvalref->{$prop}) {
857
                    $domvalref->{$prop} = '';
858
                }
859
                if ($prop eq 'alertemail') {
860
                    if (change_monitor_email($domvalref->{'uuid'}, $userreg{$user}->{$prop})) {
861
                        $alertmatch = 1;
862
                    }
863
                }
864
            }
865
        }
866
    }
867
    tied(%domreg)->commit;
868
    untie %domreg;
869
    #`/usr/bin/moncmd reset keepstate` if ($alertmatch);
870
    $postreply .= "Status=OK OK - reset systems and servers contacts to account values\n";
871
    return $postreply;
872
}
873
874
sub do_start_server {
875
    my ($uuid, $action, $obj) = @_;
876
    if ($help) {
877
        return <<END
878
GET:uuid:
879
Start specific server.
880
END
881
    }
882
    $Stabile::Servers::console = 1;
883
    require "$Stabile::basedir/cgi/servers.cgi";
884
    $postreply .= Stabile::Servers::Start($uuid, 'start', { buildsystem => 0 });
885
}
886
887
sub systemAction {
888
    my ($uuid, $action, $obj) = @_;
889
    if ($help) {
890
        return <<END
891
GET:uuid:
892
Suspend, resume, start, shutdown, destroy og backup individual servers or servers belonging to a system.
893
END
894
    }
895
    my $issystem = $obj->{'issystem'} || $register{$uuid};
896 91a21c75 hq
    my $reguser;
897
    $reguser = $register{$uuid}->{'user'} if ($register{$uuid});
898 95b003ff Origo
899
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
900
    unless (tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access images register"}|; return $res;};
901
902
    if ($issystem) { # Existing system
903
        if (($user eq $reguser || $isadmin) && $register{$uuid}){ # Existing system
904
            my $domactions;
905
            my $imageactions;
906
907
            foreach my $domvalref (values %domreg) {
908
                if (($domvalref->{'system'} eq $uuid || $domvalref->{'uuid'} eq $uuid)
909
                    && ($domvalref->{'user'} eq $user || $isadmin)) {
910
                    my $domaction;
911
                    my $imageaction;
912
                    if ($domvalref->{'status'} eq 'paused' && ($action eq 'start' || $action eq 'resume')) {
913
                        $domaction = 'resume';
914
                    } elsif ($domvalref->{'status'} eq 'running' && $action eq 'suspend') {
915
                        $domaction = $action;
916
                    } elsif ($domvalref->{'status'} eq 'shutoff' && $action eq 'start') {
917
                        $domaction = $action;
918
                    } elsif ($domvalref->{'status'} eq 'inactive' && $action eq 'start') {
919
                        $domaction = $action;
920
                    } elsif ($domvalref->{'status'} eq 'running' && $action eq 'shutdown') {
921
                        $domaction = $action;
922
                    } elsif ($domvalref->{'status'} eq 'running' && $action eq 'destroy') {
923
                        $domaction = $action;
924
                    } elsif ($domvalref->{'status'} eq 'shuttingdown' && $action eq 'destroy') {
925
                        $domaction = $action;
926
                    } elsif ($domvalref->{'status'} eq 'destroying' && $action eq 'destroy') {
927
                        $domaction = $action;
928
                    } elsif ($domvalref->{'status'} eq 'starting' && $action eq 'destroy') {
929
                        $domaction = $action;
930
                    } elsif ($domvalref->{'status'} eq 'inactive' && $action eq 'destroy') {
931
                        $domaction = $action;
932
                    } elsif ($domvalref->{'status'} eq 'paused' && $action eq 'destroy') {
933
                        $domaction = $action;
934 a2e0bc7e hq
                    } elsif ($action eq 'backup' || $action eq 'snapshot' || $action eq 'unsnap') {
935 95b003ff Origo
                        $imageaction = $action;
936
                    }
937
                    if ($domaction) {
938
                        $domactions .= qq/{"uuid":"$domvalref->{'uuid'}","action":"$domaction"},/;
939
                    }
940
                    if ($imageaction) {
941
                        my $image = $domvalref->{'image'};
942
                        if ($imagereg{$image}->{'status'} =~ /used|active/) {
943 a2e0bc7e hq
                            $imageactions .= qq/{"uuid":"$imagereg{$image}->{'uuid'}","action":"gear_$imageaction"},/;
944 95b003ff Origo
                        }
945
                        my $image2 = $domvalref->{'image2'};
946
                        if ($image2 && $image2 ne '--' && $imagereg{$image2}->{'status'} =~ /used|active/) {
947 a2e0bc7e hq
                            $imageactions .= qq/{"uuid":"$imagereg{$image2}->{'uuid'}","action":"gear_$imageaction"},/;
948 95b003ff Origo
                        }
949
                        my $image3 = $domvalref->{'image3'};
950
                        if ($image3 && $image3 ne '--' && $imagereg{$image3}->{'status'} =~ /used|active/) {
951 a2e0bc7e hq
                            $imageactions .= qq/{"uuid":"$imagereg{$image3}->{'uuid'}","action":"gear_$imageaction"},/;
952 95b003ff Origo
                        }
953
                        my $image4 = $domvalref->{'image4'};
954
                        if ($image4 && $image4 ne '--' && $imagereg{$image4}->{'status'} =~ /used|active/) {
955 a2e0bc7e hq
                            $imageactions .= qq/{"uuid":"$imagereg{$image4}->{'uuid'}","action":"gear_$imageaction"},/;
956 95b003ff Origo
                        }
957
                    }
958
                }
959
            }
960
961
            if ($domactions) {
962
                $domactions = substr($domactions,0,-1);
963
                my $uri_action = qq/{"items":[$domactions]}/;
964
                $uri_action = URI::Escape::uri_escape($uri_action);
965
                $uri_action =~ /(.+)/; $uri_action = $1; #untaint
966
                $postreply .= `REMOTE_USER=$user $Stabile::basedir/cgi/servers.cgi -k $uri_action`;
967
            }
968
            if ($imageactions) {
969
                $imageactions = substr($imageactions,0,-1);
970
                my $uri_action = qq/{"items":[$imageactions]}/;
971
                $uri_action = URI::Escape::uri_escape($uri_action);
972
                $uri_action =~ /(.+)/; $uri_action = $1; #untaint
973 a2e0bc7e hq
                my $cmd = qq|REQUEST_METHOD=POST REMOTE_USER=$user $Stabile::basedir/cgi/images.cgi -k $uri_action|;
974
                $postreply .= `$cmd`;
975 95b003ff Origo
            }
976
            if (!$domactions && !$imageactions) {
977
                $postreply .= "Stream=ERROR $action";
978
            }
979
        }
980
    } else {
981 a2e0bc7e hq
        if ($action eq 'backup' || $action eq 'snapshot' || $action eq 'unsnap') {
982 95b003ff Origo
            my $image = $domreg{$uuid}->{'image'};
983
            my $imageactions;
984
            if ($imagereg{$image}->{'status'} =~ /used|active/) {
985 a2e0bc7e hq
                $imageactions .= qq/{"uuid":"$imagereg{$image}->{'uuid'}","action":"gear_$action"},/;
986 95b003ff Origo
            }
987
            my $image2 = $domreg{$uuid}->{'image2'};
988
            if ($image2 && $image2 ne '--' && $imagereg{$image2}->{'status'} =~ /used|active/) {
989 a2e0bc7e hq
                $imageactions .= qq/{"uuid":"$imagereg{$image2}->{'uuid'}","action":"gear_$action"},/;
990 95b003ff Origo
            }
991
            my $image3 = $domreg{$uuid}->{'image3'};
992
            if ($image3 && $image3 ne '--' && $imagereg{$image3}->{'status'} =~ /used|active/) {
993 a2e0bc7e hq
                $imageactions .= qq/{"uuid":"$imagereg{$image3}->{'uuid'}","action":"gear_$action"},/;
994 95b003ff Origo
            }
995
            my $image4 = $domreg{$uuid}->{'image4'};
996
            if ($image4 && $image4 ne '--' && $imagereg{$image4}->{'status'} =~ /used|active/) {
997 a2e0bc7e hq
                $imageactions .= qq/{"uuid":"$imagereg{$image4}->{'uuid'}","action":"gear_$action"},/;
998 95b003ff Origo
            }
999
            if ($imageactions) {
1000
                $imageactions = substr($imageactions,0,-1);
1001
                my $uri_action = qq/{"items":[$imageactions]}/;
1002
                $uri_action = URI::Escape::uri_escape($uri_action);
1003
                $uri_action = $1 if $uri_action =~ /(.+)/; #untaint
1004 a2e0bc7e hq
                my $cmd = qq|REQUEST_METHOD=POST REMOTE_USER=$user $Stabile::basedir/cgi/images.cgi -k "$uri_action"|;
1005
                $postreply .= `$cmd`;
1006 95b003ff Origo
            }
1007
        } else {
1008
            my $cmd = qq|REQUEST_METHOD=GET REMOTE_USER=$user $Stabile::basedir/cgi/servers.cgi -a $action -u $uuid|;
1009
            $postreply = `$cmd`;
1010
            #$postreply = $cmd;
1011
            my $uistatus = $action."ing";
1012
            $uistatus = "resuming" if ($action eq 'resume');
1013
            $uistatus = "shuttingdown" if ($action eq 'shutdown');
1014
            $main::updateUI->({ tab => 'servers',
1015
                user                => $user,
1016
                uuid                => $uuid,
1017
                status              => $uistatus })
1018
1019
        }
1020
    }
1021
    untie %domreg;
1022
    untie %imagereg;
1023
1024
    return $postreply;
1025
}
1026
1027
sub Updateengineinfo {
1028
    my ($uuid, $action, $obj) = @_;
1029
    if ($help) {
1030
        return <<END
1031 f222b89c hq
PUT:downloadmasters, downloadallmasters, externaliprangestart, externaliprangeend, proxyiprangestart, proxyiprangeend, proxygw, vmreadlimit, vmwritelimit, vmiopsreadlimit, vmiopswritelimit:
1032 95b003ff Origo
Save engine information.
1033
END
1034
    }
1035
    unless ($isadmin) {
1036
        $postreply = "Status=Error Not allowed\n";
1037
        return $postreply;
1038
    }
1039
    my $msg = "Engine updated";
1040 2a63870a Christian Orellana
    my $dl = $obj->{'downloadmasters'};
1041
    if ($dl eq '--' || $dl eq '0') {
1042 95b003ff Origo
        if ($downloadmasters) {
1043
            $downloadmasters = '';
1044
            `perl -pi -e 's/DOWNLOAD_MASTERS=.*/DOWNLOAD_MASTERS=0/;' /etc/stabile/config.cfg`;
1045
        }
1046
        $postreply .= "Status=OK Engine updated\n";
1047
        my @ps = split("\n",  `pgrep pressurecontrol` ); `kill -HUP $ps[0]`;
1048
    }
1049 2a63870a Christian Orellana
    elsif ($dl eq '1' || $dl eq '2') {
1050
        if (!$downloadmasters || $dl eq '2') { # We use a value of 2 to force check for downloads
1051 95b003ff Origo
            $downloadmasters = 1;
1052 2a63870a Christian Orellana
            `perl -pi -e 's/DOWNLOAD_MASTERS=.*/DOWNLOAD_MASTERS=$dl/;' /etc/stabile/config.cfg`;
1053
        }
1054
        if ($dl eq '2') {
1055
            $msg = "Checking for new or updated masters...";
1056 95b003ff Origo
        }
1057
        $postreply .= "Status=OK Engine updated\n";
1058 f222b89c hq
        my @ps = split("\n",  `pgrep pressurecontrol` ); `kill -HUP $ps[0]`;
1059
    }
1060
    elsif ($obj->{'downloadallmasters'} eq '--' || $obj->{'downloadallmasters'} eq '0') {
1061
        if ($disablesnat) {
1062
            $disablesnat = '';
1063
            `perl -pi -e 's/DOWNLOAD_ALL_MASTERS=.*/DOWNLOAD_ALL_MASTERS=0/;' /etc/stabile/config.cfg`;
1064
        }
1065
        $postreply .= "Status=OK Engine updated\n";
1066
    }
1067
    elsif ($obj->{'downloadallmasters'} eq '1') {
1068
        if ($disablesnat) {
1069
            $disablesnat = '';
1070
            `perl -pi -e 's/DOWNLOAD_ALL_MASTERS=.*/DOWNLOAD_ALL_MASTERS=1/;' /etc/stabile/config.cfg`;
1071
        }
1072
        $postreply .= "Status=OK Engine updated\n";
1073 95b003ff Origo
    }
1074
    elsif ($obj->{'disablesnat'} eq '--' || $obj->{'disablesnat'} eq '0') {
1075
        if ($disablesnat) {
1076
            $disablesnat = '';
1077
            `perl -pi -e 's/DISABLE_SNAT=.*/DISABLE_SNAT=0/;' /etc/stabile/config.cfg`;
1078
        }
1079
        $postreply .= "Status=OK Engine updated\n";
1080
    }
1081
    elsif ($obj->{'disablesnat'} eq '1') {
1082
        unless ($disablesnat) {
1083
            $disablesnat = 1;
1084
            `perl -pi -e 's/DISABLE_SNAT=.*/DISABLE_SNAT=1/;' /etc/stabile/config.cfg`;
1085
        }
1086
        $postreply .= "Status=OK Engine updated\n";
1087
    }
1088 d3805c61 hq
    elsif ($obj->{'enforceiolimits'} eq '--' || $obj->{'enforceiolimits'} eq '0') {
1089
        if ($enforceiolimits) {
1090
            $enforceiolimits = '';
1091
            `perl -pi -e 's/ENFORCE_IO_LIMITS=.*/ENFORCE_IO_LIMITS=0/;' /etc/stabile/config.cfg`;
1092
        }
1093
        $postreply .= "Status=OK Engine updated\n";
1094
    }
1095
    elsif ($obj->{'enforceiolimits'} eq '1') {
1096
        unless ($enforceiolimits) {
1097
            $enforceiolimits = 1;
1098
            `perl -pi -e 's/ENFORCE_IO_LIMITS=.*/ENFORCE_IO_LIMITS=1/;' /etc/stabile/config.cfg`;
1099
        }
1100
        $postreply .= "Status=OK Engine updated\n";
1101
    }
1102 95b003ff Origo
    elsif ($obj->{'externaliprangestart'}) {
1103
        if ($obj->{'externaliprangestart'} =~ /\d+\.\d+\.\d+\.\d+/) {
1104
            $extiprangestart = $obj->{'externaliprangestart'};
1105
            $msg = "Setting external IP range start to $extiprangestart";
1106
            `perl -pi -e 's/EXTERNAL_IP_RANGE_START=.*/EXTERNAL_IP_RANGE_START=$extiprangestart/;' /etc/stabile/config.cfg`;
1107
            $postreply .= "Status=OK Engine updated\n";
1108
        } else {
1109
            $msg = "Not changing IP range - $obj->{'externaliprangestart'} is not valid";
1110
        }
1111
    }
1112
    elsif ($obj->{'externaliprangeend'}) {
1113
        if ($obj->{'externaliprangeend'} =~ /\d+\.\d+\.\d+\.\d+/) {
1114
            $extiprangeend = $obj->{'externaliprangeend'};
1115
            $msg = "Setting external IP range end to $extiprangeend";
1116
            `perl -pi -e 's/EXTERNAL_IP_RANGE_END=.*/EXTERNAL_IP_RANGE_END=$extiprangeend/;' /etc/stabile/config.cfg`;
1117
            $postreply .= "Status=OK Engine updated\n";
1118
        } else {
1119
            $msg = "Not changing IP range - $obj->{'externaliprangeend'} is not valid";
1120
        }
1121
    }
1122
    elsif ($obj->{'proxyiprangestart'}) {
1123
        if ($obj->{'proxyiprangestart'} =~ /\d+\.\d+\.\d+\.\d+/) {
1124
            $extiprangestart = $obj->{'proxyiprangestart'};
1125
            $msg = "Setting proxy IP range start to $extiprangestart";
1126
            `perl -pi -e 's/PROXY_IP_RANGE_START=.*/PROXY_IP_RANGE_START=$extiprangestart/;' /etc/stabile/config.cfg`;
1127
            $postreply .= "Status=OK Engine updated\n";
1128
        } else {
1129
            $msg = "Not changing IP range - $obj->{'proxyiprangestart'} is not valid";
1130
        }
1131
    }
1132
    elsif ($obj->{'proxyiprangeend'}) {
1133
        if ($obj->{'proxyiprangeend'} =~ /\d+\.\d+\.\d+\.\d+/) {
1134
            $extiprangeend = $obj->{'proxyiprangeend'};
1135
            $msg = "Setting proxy IP range end to $extiprangeend";
1136
            `perl -pi -e 's/PROXY_IP_RANGE_END=.*/PROXY_IP_RANGE_END=$extiprangeend/;' /etc/stabile/config.cfg`;
1137
            $postreply .= "Status=OK Engine updated\n";
1138
        } else {
1139
            $msg = "Not changing IP range - $obj->{'proxyiprangeend'} is not valid";
1140
        }
1141
    }
1142
    elsif ($obj->{'proxygw'}) {
1143
        if ($obj->{'proxygw'} =~ /\d+\.\d+\.\d+\.\d+/) {
1144
            $proxygw = $obj->{'proxygw'};
1145
            $msg = "Setting proxy gw to $proxygw";
1146
            `perl -pi -e 's/PROXY_GW=.*/PROXY_GW=$proxygw/;' /etc/stabile/config.cfg`;
1147
            $postreply .= "Status=OK Engine updated\n";
1148
        } else {
1149
            $msg = "Not changing IP range - $obj->{'proxygw'} is not valid";
1150
        }
1151
    }
1152
    elsif ($obj->{'vmreadlimit'} || $obj->{'vmwritelimit'} || $obj->{'vmiopsreadlimit'} || $obj->{'vmiopswritelimit'}) {
1153
        my $lim = 'vmreadlimit';
1154
        my $uclim = 'VM_READ_LIMIT';
1155
        if ($obj->{'vmwritelimit'}) {
1156
            $lim = 'vmwritelimit';
1157
            $uclim = 'VM_WRITE_LIMIT';
1158
        } elsif ($obj->{'vmiopsreadlimit'}) {
1159
            $lim = 'vmiopsreadlimit';
1160
            $uclim = 'VM_IOPS_READ_LIMIT';
1161
        } elsif ($obj->{'vmiopswritelimit'}) {
1162
            $lim = 'vmiopswritelimit';
1163
            $uclim = 'VM_IOPS_WRITE_LIMIT';
1164
        }
1165
        if ($obj->{$lim} >= 0 &&  $obj->{$lim} < 10000 *1024*1024) { #sanity checks
1166
            unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities',key=>'identity',CLOBBER=>3}, $Stabile::dbopts)) ) {return "Unable to access id register"};
1167
            my @nodeconfigs;
1168
            # Build hash of known node config files
1169
            foreach my $valref (values %idreg) {
1170
                my $nodeconfigfile = $valref->{'path'} . "/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
1171
                next if ($nodeconfigs{$nodeconfigfile}); # Node identities may share basedir and node config file
1172
                if (-e $nodeconfigfile) {
1173
                    push @nodeconfigs, $nodeconfigfile;
1174
                }
1175
            }
1176
            untie %idreg;
1177
            push @nodeconfigs, "/etc/stabile/nodeconfig.cfg";
1178
            my $limit = int $obj->{$lim};
1179 a2e0bc7e hq
            $msg = "Setting $uclim limit to $limit";
1180 95b003ff Origo
            foreach my $nodeconfig (@nodeconfigs) {
1181
                my $cfg = new Config::Simple($nodeconfig);
1182
                $cfg->param($uclim, $limit);
1183
                $cfg->save();
1184
            }
1185
            $Stabile::Nodes::console = 1;
1186
            require "$Stabile::basedir/cgi/nodes.cgi";
1187
            $postreply .= Stabile::Nodes::Configurecgroups();
1188
            $postreply .= Stabile::Nodes::do_reloadall('','reloadall', {'nodeaction'=>'CGLOAD'});
1189
            $postreply .= "Status=OK Engine and nodes updated: $lim set to $limit\n";
1190
        } else {
1191
            $msg = "Not changing limit - $obj->{$lim} is not valid";
1192
        }
1193
    }
1194
    if (!$postreply) {
1195
        $msg = "Engine not updated";
1196
        $postreply = "Status=Error Engine not updated\n" ;
1197
    }
1198
    $main::updateUI->({tab=>'home', user=>$user, type=>'update', message=>$msg});
1199
    return $postreply;
1200
}
1201
1202
sub do_updateaccountinfo {
1203
    my ($uuid, $action, $obj) = @_;
1204
    if ($help) {
1205
        return <<END
1206
PUT:fullname, email, phone, opfullname, opemail, opphone, alertemail, allowfrom, allowinternalapi:
1207
Save user information.
1208
END
1209
    }
1210
    my @props = ('fullname','email','phone','opfullname','opemail','opphone','alertemail', 'allowfrom', 'allowinternalapi');
1211
    my %oldvals;
1212
    if ($obj->{'allowfrom'} && $obj->{'allowfrom'} ne '--') {
1213
        my @allows = split(/,\s*/, $obj->{'allowfrom'});
1214
        $obj->{'allowfrom'} = '';
1215 4aef7ef6 hq
        my %allowshash;
1216 95b003ff Origo
        foreach my $ip (@allows) {
1217 4aef7ef6 hq
            $allowshash{"$1$2"} = 1 if ($ip =~ /(\d+\.\d+\.\d+\.\d+)(\/\d+)?/);
1218
            if ($ip =~ /\w\w/) { # Check if we are dealing with a country code
1219
                $ip = uc $ip;
1220
                my $geoip = Geo::IP->new(GEOIP_MEMORY_CACHE);
1221
                my $tz = $geoip->time_zone($ip, '');
1222
                $allowshash{$ip} = 1 if ($tz); # We have a valid country code
1223
            }
1224 95b003ff Origo
        }
1225 4aef7ef6 hq
        $obj->{'allowfrom'} = join(", ", sort(keys %allowshash));
1226 95b003ff Origo
        unless ($obj->{'allowfrom'}) {
1227
            $postreply .= "Status=Error Account not updated\n";
1228
            return $postreply;
1229
        }
1230
    }
1231
1232
    foreach my $prop (@props) {
1233
        if ($obj->{$prop}) {
1234
            $obj->{$prop} = '' if ($obj->{$prop} eq '--');
1235
            $oldvals{$prop} = $userreg{$user}->{$prop};
1236
            $userreg{$user}->{$prop} = decode('utf8', $obj->{$prop});
1237
        }
1238
    }
1239
1240
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1241
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access user register"};
1242
    my $alertmatch;
1243
    foreach my $sysvalref (values %register) {
1244
        if ($user eq $sysvalref->{'user'}) {
1245
            my $sysuuid = $sysvalref->{'uuid'};
1246
            foreach my $prop (@props) {
1247
                my $val = $obj->{$prop};
1248
                if ($val) {
1249
                    $val = '' if ($val eq '--');
1250
                    # Does this system have the same value as the old user value or, equivalently, is it empty?
1251
                    if (!$sysvalref->{$prop} || $sysvalref->{$prop} eq $oldvals{$prop}) {
1252
                    #    $postreply .= "Resetting system prop $prop to $val\n";
1253
                        $sysvalref->{$prop} = ''; # An empty val refers to parent (user) val
1254
                    # Update children
1255
                        foreach my $domvalref (values %domreg) {
1256
                            if ($domvalref->{'user'} eq $user && ($domvalref->{'system'} eq $sysuuid || $domvalref->{'system'} eq '--' || !$domvalref->{'system'})) {
1257
                                if (!$domvalref->{$prop} || $domvalref->{$prop} eq $oldvals{$prop}) {
1258
                                    $domvalref->{$prop} = '';
1259
                                    if ($prop eq 'alertemail') {
1260
                                        if (change_monitor_email($domvalref->{'uuid'}, $val, $oldvals{$prop})) {
1261
                                            $alertmatch = 1;
1262
                                        }
1263
                                    }
1264
                                }
1265
                            }
1266
                        }
1267
                    }
1268
                }
1269
            }
1270
        }
1271
    }
1272
    #`/usr/bin/moncmd reset keepstate` if ($alertmatch);
1273
    tied(%domreg)->commit;
1274
    tied(%userreg)->commit;
1275
    untie %domreg;
1276
    untie %userreg;
1277
    $postreply .= "Status=OK Account updated\n";
1278
    # Send changes to origo.io
1279
    $Stabile::Users::console = 1;
1280
    require "$Stabile::basedir/cgi/users.cgi";
1281
    $postreply .= Stabile::Users::sendEngineUser($user) if ($enginelinked);
1282
    $main::updateUI->({tab=>'home', user=>$user, type=>'update', message=>"Account updated"});
1283
    return $postreply;
1284
}
1285
1286
sub do_listuptime {
1287
    my ($uuid, $action, $obj) = @_;
1288
    if ($help) {
1289
        return <<END
1290
GET:yearmonth,uuid,format:
1291
List uptime for defined monitors. If uuid is supplied, only uptime for matching server or servers belonging to matching
1292
system is shown. Format is either html or json.
1293
END
1294
    }
1295
    my $format = $obj->{'format'};
1296
    my $yearmonth = $obj->{'yearmonth'} || "$year-$month";
1297
    my $pathid = $yearmonth . ':';
1298
    my $name;
1299
1300
    my %sysdoms;
1301
    if ($uuid && $register{$uuid}) {
1302
        $name = $register{$uuid}->{'name'};
1303
        foreach my $valref (values %domreg) {
1304
            $sysdoms{$valref->{'uuid'}} = $uuid if ($valref->{system} eq $uuid);
1305
        }
1306
    } else {
1307
        $pathid .= $uuid;
1308
        $name = $domreg{$uuid}->{'name'} if ($domreg{$uuid});
1309
    }
1310
    my %uptimes;
1311
    my $jtext = {};
1312
    my @csvrows;
1313
1314
    unless ($pathid =~ /\// || $pathid =~ /\./) { # Security check
1315
        my $path = "/var/log/stabile/$pathid*"; # trailing / is required. No $pathid lists all files in log dir.
1316
        my $utext = '';
1317
        my %numfiles;
1318
        my %sumupp;
1319
        ## loop through the files contained in the directory
1320
        for my $eachFile (bsd_glob($path.'*')) {
1321
            if (!(-d $eachFile) && $eachFile =~ /\/var\/log\/stabile\/(.+):(.+):(.+)/) {
1322
                my $ymonth = $1;
1323
                my $domuuid = $2;
1324
                my $service = $3;
1325
                next unless ($domreg{$domuuid});
1326
                my $servername = $domreg{$domuuid}->{'name'};
1327
                if ($domreg{$domuuid}->{'user'} eq $user) {
1328
                    next if (%sysdoms && !$sysdoms{$domuuid}); # If we are listing a system, match system uuid
1329
                    open(FILE, $eachFile) or {print("Unable to access $eachFile")};
1330
                    @lines = <FILE>;
1331
                    close(FILE);
1332
                    my $starttime;
1333
                    my $lastup;
1334
                    my $firststamp; # First timestamp of measuring period
1335
                    my $laststamp; # Last timestamp of measuring period
1336
                    my $curstate = 'UNKNOWN';
1337
                    my $dstate = 'UNKNOWN';
1338
                    my ($y, $m) = split('-', $ymonth);
1339
                    my $timespan = 0;
1340
                    my $dtime = 0; # Time disabled
1341
                    my $lastdtime = 0;
1342
                    my $uptime = 0;
1343
                    foreach my $line (@lines) {
1344
                        my ($timestamp, $event, $summary, $ptime) = split(/, */,$line);
1345
                        if (!$starttime) { # First line
1346
                            $starttime = $timestamp;
1347
                            # Find 00:00 of first day of month - http://www.perlmonks.org/?node_id=97120
1348
                            $firststamp = POSIX::mktime(0,0,0,1,$m-1,$year-1900,0,0,-1);
1349
                            # Round to month start if within 15 min
1350
                            $starttime = $firststamp if ($starttime-$firststamp<15*60);
1351
                            $lastup = $starttime if ($event eq 'STARTUP' || $event eq 'UP');
1352
                            $curstate = 'UP'; # Assume up - down alerts are always triggered
1353
                        }
1354
                        if ($event eq 'UP') {
1355
                            if ($curstate eq 'UP') {
1356
                                $uptime += ($timestamp - $lastup) if ($lastup);
1357
                            }
1358
                            $lastup = $timestamp;
1359
                            $curstate = 'UP';
1360
                        } elsif ($event eq 'DOWN') {
1361
                            if ($curstate eq 'UP' && $lastup!=$starttime) { # If down is immediately after startup - dont count uptime
1362
                                $uptime += ($timestamp - $lastup) if ($lastup);
1363
                                $lastup = $timestamp;
1364
                            }
1365
                            $curstate = 'DOWN';
1366
                        } elsif ($event eq 'STARTUP') {
1367
                        } elsif ($event eq 'DISABLE' && $curstate ne 'UNKNOWN') {
1368
                            if ($curstate eq 'UP') {
1369
                                $uptime += ($timestamp - $lastup) if ($lastup);
1370
                                $lastup = $timestamp;
1371
                            }
1372
                            $lastdtime = $timestamp;
1373
                            $dstate = $curstate;
1374
                            $curstate = 'UNKNOWN';
1375
                        } elsif ($event eq 'ENABLE') {
1376
                            if ($dstate eq 'UP' && $curstate eq 'UNKNOWN') {
1377
                                $lastup = $timestamp;
1378
                            }
1379
                            $curstate = 'UP';
1380
                        }
1381
                        # All non-disable events must mean monitor is enabled again
1382
                        if ($event ne 'DISABLE') {
1383
                            if ($lastdtime) {
1384
                                $dtime += ($timestamp - $lastdtime);
1385
                                $lastdtime = 0;
1386
                            }
1387
                        }
1388
1389
                    }
1390
                    if ($ymonth ne "$year-$month") { # If not current month, assume monitoring to end of month
1391
                        # Find 00:00 of first day of next month - http://www.perlmonks.org/?node_id=97120
1392
                        $laststamp = POSIX::mktime(0,0,0,1,$m,$year-1900,0,0,-1);
1393
                    } else {
1394
                        $laststamp = $current_time;
1395
                    }
1396
                    if ($curstate eq 'UP' && !$lastdtime && $lastup) {
1397
                        $uptime += ($laststamp - $lastup);
1398
                    }
1399
                    if ($lastdtime) {
1400
                        $dtime += ($laststamp - $lastdtime);
1401
                    }
1402
                    $timespan = $laststamp - $starttime;
1403
                    $uptimes{"$domuuid:$service"}->{'timespan'} = $timespan;
1404
                    $uptimes{"$domuuid:$service"}->{'uptime'} = $uptime;
1405
                    my $timespanh = int(0.5 + 100*$timespan/3600)/100;
1406
                    my $dtimeh = int(0.5 + 100*$dtime/3600)/100;
1407
                    my $uptimeh = int(0.5 + 100*$uptime/3600)/100;
1408
                    my $upp = int(0.5+ 10000*$uptime/($timespan-$dtime) ) / 100;
1409
                    $sumupp{$service} += $upp;
1410
                    $numfiles{$service} += 1;
1411
1412
                    utf8::decode($servername);
1413
1414
                    $utext .= qq[<div class="uptime_header">$service on $servername:</div>\n];
1415
                    my $color = ($upp<98)?'red':'green';
1416
                    $utext .= qq[<span style="color: $color;">Uptime: $uptimeh hours ($upp%)</span>\n];
1417
                    $utext .= qq{[timespan: $timespanh hours, \n};
1418
                    $utext .= qq{disabled: $dtimeh hours]\n};
1419
1420
                    $jtext->{$domuuid}->{'servername'} = $servername;
1421
                    $jtext->{$domuuid}->{$service}->{'uptime'} = $upp;
1422
                    $jtext->{$domuuid}->{$service}->{'uptimeh'} = $uptimeh;
1423
                    $jtext->{$domuuid}->{$service}->{'color'} = ($upp<98)?'red':'green';
1424
                    $jtext->{$domuuid}->{$service}->{'disabledtimeh'} = $dtimeh;
1425
                    $jtext->{$domuuid}->{$service}->{'timespanh'} = $timespanh;
1426
1427
                    push @csvrows, {serveruuid=>$domuuid, service=>$service, servername=>$servername, uptime=>$upp, uptimeh=>$uptimeh, color=>($upp<98)?'red':'green',disabledtimeh=>$dtimeh, timespanh=>$timespanh, yearmonth=>$yearmonth};
1428
                }
1429
            }
1430
        }
1431
        my @avgtxt;
1432
        my $alertclass = "info";
1433
        my $compcolor;
1434
        $jtext->{'averages'} = {};
1435
        $jtext->{'year-month'} = $yearmonth;
1436
        foreach $svc (keys %sumupp) {
1437
            my $avgupp = int(0.5 + 100*$sumupp{$svc}/$numfiles{$svc})/100;
1438
            my $color = ($avgupp<98)?'red':'green';
1439
            push @avgtxt, qq[<span style="color: $color;" class="uptime_header">$svc: $avgupp%</span>\n];
1440
            $jtext->{'averages'}->{$svc}->{'uptime'} = $avgupp;
1441
            $jtext->{'averages'}->{$svc}->{'color'} = $color;
1442
            $compcolor = ($compcolor)? ( ($compcolor eq $color)? $color : 'info' ) : $color;
1443
        }
1444
        $alertclass = "warning" if ($compcolor eq 'red');
1445
        $alertclass = "success" if ($compcolor eq 'green');
1446
        $postreply = header();
1447
        if ($name) {
1448
            $postreply .= qq[<div class="alert alert-$alertclass uptime_alert"><h4 class="uptime_header">Average uptime for $name:</h4>\n<div style="margin-top:10px;">\n];
1449
        } else {
1450
            $postreply .= qq[<div class="alert alert-$alertclass uptime_alert"><h4 class="uptime_header">Average uptime report</h4>\n<div style="margin-top:10px;">\n];
1451
        }
1452
        $postreply .= join(", ", @avgtxt);
1453
        my $uuidlink = "&uuid=$uuid" if ($uuid);
1454
        $postreply .= qq[</div></div><hr class="uptime_line"><h5 class="uptime_header">Uptime details: (<span><a href="/stabile/systems?action=listuptime&format=csv$uuidlink&yearmonth=$yearmonth" target="blank" title="Download as CSV">csv</a></span>)</h5>\n];
1455
        $postreply .= "<span class=\"uptime_text\">$utext</span>";
1456
    }
1457
    if ($params{'format'} eq 'csv') {
1458
        $postreply = header("text/plain");
1459
        csv(in => \@csvrows, out => \my $csvdata, key => "servername");
1460
        $postreply .= $csvdata;
1461
    } elsif ($format ne 'html') {
1462
        $postreply = to_json($jtext, {pretty=>1});
1463
    }
1464
    return $postreply;
1465
}
1466
1467 f222b89c hq
sub Downloadmaster {
1468
    my ($uuid, $action, $obj) = @_;
1469
    if ($help) {
1470
        return <<END
1471 c05aff24 hq
GET:filename,stackmaster,user:
1472 f222b89c hq
Downloads a master image (and if relevant the associated data image) with [filename] belonging to [user] (default "common") to the engine from Origo Registry.
1473
END
1474
    }
1475
    if ($isadmin) {
1476
        $Stabile::Images::console = 1;
1477
        $Stabile::Images::user = $user;
1478
        require "$Stabile::basedir/cgi/images.cgi";
1479
        my @spools = @Stabile::Images::spools;
1480
        my $downloadpath = "$spools[0]->{path}/$obj->{user}/$obj->{filename}";
1481
        `echo "downloading" > "$downloadpath.meta"`;
1482 c05aff24 hq
1483
        # Check if we need to download the stack's master image as well
1484
        if ($obj->{stackmaster}) {
1485
            my $match = 0;
1486
            unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$postreply = "Unable to access image register"; return;};
1487
            foreach my $pool (@spools) {
1488
                if ($imagereg{"$pool->{path}/$obj->{user}/$obj->{stackmaster}"}) {
1489
                    $match = 1;
1490
                }
1491
            }
1492
            untie %imagereg;
1493
            unless ($match) {
1494
                my $stackmasterpath = "$spools[0]->{path}/$obj->{user}/$obj->{stackmaster}";
1495
                `echo "downloading" > "$stackmasterpath.meta"`;
1496
            }
1497
        }
1498
1499 f222b89c hq
        `perl -pi -e 's/DOWNLOAD_MASTERS=.*/DOWNLOAD_MASTERS=2/;' /etc/stabile/config.cfg`;
1500
        my @ps = split("\n",  `pgrep pressurecontrol` ); `kill -HUP $ps[0]`;
1501
        $postreply = "Status=OK Download of $downloadpath initiated...\n";
1502
    } else {
1503
        $postreply = "Status=Error Download of master images can only be initiated by administrators\n";
1504
    }
1505
    return $postreply;
1506
}
1507
1508 95b003ff Origo
sub do_appstore {
1509
    my ($uuid, $action, $obj) = @_;
1510
    if ($help) {
1511
        return <<END
1512
GET:appid,callback:
1513
Look up app info for app with given appid in appstore on origo.io. Data is returned as padded JSON (JSONP).
1514
Optionally provide name of your JSONP callback function, which should parse the returned script data.
1515 f222b89c hq
If no appid is provided, all available masters at Origo Registry are returned.
1516 95b003ff Origo
END
1517
    }
1518
    my $appid = $params{'appid'};
1519
    my $callback = $params{'callback'};
1520
    if ($appid) {
1521
        $postreply = header("application/javascript");
1522
        $postreply .= $main::postToOrigo->($engineid, 'engineappstore', $appid, 'appid', $callback);
1523
    } else {
1524 f222b89c hq
        $postreply = header("application/json");
1525
        # Build a hash of master images we already have downloaded
1526
        $Stabile::Images::console = 1;
1527
        require "$Stabile::basedir/cgi/images.cgi";
1528
        my $masters = Stabile::Images::do_listmasterimages('', 'listmasterimages', {raw=>1});
1529
        my %master_hash;
1530
        my %appid_hash;
1531
        foreach my $master (@$masters) {
1532
            my $path = $master->{path};
1533
            my $muser = $master->{user};
1534
            my $appid = $master->{appid};
1535
            my $filename = $1 if ($path =~ /.*\/(.*)$/);
1536
            $master_hash{"$muser:$filename"} = 1; # the id format we use here
1537
            $appid_hash{$appid} = 1;
1538
        }
1539
        # Get complete list of master images from Origo and filter out those we already have
1540
        my $json_text = $main::postToOrigo->($engineid, 'liststackmasters', 1, 'flat');
1541
        my $json_obj = from_json($json_text);
1542
        my @missing_stacks = ({name=>'--', id=>'--'});
1543
        foreach my $stack (@$json_obj) {
1544 51e32e00 hq
            if ($master_hash{ $stack->{id} } || !$stack->{current}) {
1545
                # already downloaded or not current
1546 f222b89c hq
            } else {
1547
                $stack->{summary} = URI::Escape::uri_unescape($stack->{summary});
1548
                $stack->{description} = URI::Escape::uri_unescape($stack->{description});
1549
                # new version of stack is available for download
1550
                $stack->{name} = "$stack->{name} (new version)" if ($appid_hash{$stack->{appid}});
1551
                push @missing_stacks, $stack ;
1552
            }
1553
        #    $postreply .=  "$stack->{id}\n";
1554
        }
1555
        $json_text = to_json(\@missing_stacks);
1556
1557
        $postreply = qq/{"identifier": "id", "label": "name", "items": $json_text }/;
1558 95b003ff Origo
    }
1559
    return $postreply;
1560
}
1561
1562
sub do_resetmonitoring {
1563
    my ($uuid, $action, $obj) = @_;
1564
    if ($help) {
1565
        return <<END
1566
GET::
1567
Reset mon daemon while keeping states.
1568
END
1569
    }
1570
    saveOpstatus();
1571
    $postreply = "Status=OK " . `/usr/bin/moncmd reset keepstate`;
1572
    return $postreply;
1573
}
1574
1575
sub do_installsystem {
1576
    my ($uuid, $action, $obj) = @_;
1577
    if ($help) {
1578
        return <<END
1579
GET:installsystem,installaccount:
1580
Helper function to initiate the installation of a new stack with system ID [installsystem] to account [installaccount] by redirecting with appropriate cookies set.
1581
END
1582
    }
1583
    my $installsystem = $obj->{'installsystem'};
1584
    my $installaccount = $obj->{'installaccount'};
1585
    my $systemcookie;
1586
    my $ia_cookie;
1587
    my $sa_cookie;
1588
1589
    push(@INC, "$Stabile::basedir/auth");
1590
    require Apache::AuthTkt;# 0.03;
1591
    require AuthTktConfig;
1592
    my $at = Apache::AuthTkt->new(conf => $ENV{MOD_AUTH_TKT_CONF});
1593
    my ($server_name, $server_port) = split /:/, $ENV{HTTP_HOST} if $ENV{HTTP_HOST};
1594
    $server_name ||= $ENV{SERVER_NAME} if $ENV{SERVER_NAME};
1595
    $server_port ||= $ENV{SERVER_PORT} if $ENV{SERVER_PORT};
1596
    my $AUTH_DOMAIN = $at->domain || $server_name;
1597
    my @auth_domain = $AUTH_DOMAIN ? ( -domain => $AUTH_DOMAIN ) : ();
1598
1599
    if ($installsystem) {
1600
        $systemcookie = CGI::Cookie->new(
1601
            -name => 'installsystem',
1602
            -value => "$installsystem",
1603
            -path => '/',
1604
            @auth_domain
1605
        );
1606
    };
1607
    if ($installaccount) {
1608
        $ia_cookie = CGI::Cookie->new(
1609
            -name => 'installaccount',
1610
            -value => "$installaccount",
1611
            -path => '/',
1612
            @auth_domain
1613
        );
1614
        $sa_cookie = CGI::Cookie->new(
1615
            -name => 'steamaccount',
1616
            -value => "$installaccount",
1617
            -path => '/',
1618
            @auth_domain
1619
        );
1620
    };
1621
1622
    $tktcookie = CGI::Cookie->new(
1623
        -name => 'tktuser',
1624
        -value => "$tktuser",
1625
        -path => '/',
1626
        @auth_domain
1627
    );
1628
1629
    $postreply = redirect(
1630
        -uri => '/stabile/mainvalve/',
1631
        -cookie => [$tktcookie, $systemcookie, $ia_cookie, $sa_cookie]
1632
    );
1633
    return $postreply;
1634
}
1635
1636
sub Changemonitoremail {
1637
    my ($uuid, $action, $obj) = @_;
1638
    if ($help) {
1639
        return <<END
1640
GET:uuid,email:
1641
Change the email for all monitors belonging to server with given uuid. May be called with command line switches -u server uuid, -m old email, -k new email.
1642
END
1643
    }
1644
    if ($isreadonly) {
1645
        $postreply = "Status=Error Not permitted\n";
1646
    } else {
1647
        my $serveruuid = $options{u} || $uuid;
1648
        my $email = $options{k} || $obj->{'email'};
1649
        if (change_monitor_email($serveruuid, $email)) {
1650
            $postreply = "Status=OK " . `/usr/bin/moncmd reset keepstate`;
1651
        } else {
1652
            $postreply = "Status=Error There was a problem changing monitor email for $serveruuid\n";
1653
        }
1654
    }
1655
    return $postreply;
1656
}
1657
1658
sub do_getmetrics {
1659
    my ($suuid, $action, $obj) = @_;
1660
    if ($help) {
1661
        return <<END
1662
GET:uuid,metric,from,until,last,format:
1663
Get performance and load metrics in JSON format from Graphite backend. [metric] is one of: cpuload, diskreads, diskwrites, networkactivityrx, networkactivitytx
1664
From and until are Unix timestamps. Alternatively specify "last" number of seconds you want metrics for. Format is "json" (default) or "csv".
1665
END
1666
    }
1667
    my $metric = $params{metric} || "cpuLoad";
1668
    my $now = time();
1669
    my $from = $params{"from"} || ($now-$params{"last"}) || ($now-300);
1670
    my $until = $params{"until"} || $now;
1671
1672
    my @uuids;
1673
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1674
1675
    if ($domreg{$suuid}) { # We are dealing with a server
1676
        push @uuids, $suuid;
1677
    } else { # We are dealing with a system
1678
        foreach my $valref (values %domreg) {
1679
            my $sysuuid = $valref->{'system'};
1680
            push @uuids, $valref->{'uuid'} if ($sysuuid eq $suuid)
1681
        }
1682
    }
1683
    untie %domreg;
1684
1685
    my @datapoints;
1686
    my @targets;
1687
    my $all;
1688
    my $jobj = [];
1689
    foreach my $uuid (@uuids) {
1690
        next unless (-e "/var/lib/graphite/whisper/domains/$uuid");
1691
        my $url = "https://127.0.0.1/graphite/graphite.wsgi/render?format=json&from=$from&until=$until&target=domains.$uuid.$metric";
1692
        my $jstats = `curl -k "$url"`;
1693
        $jobj = from_json($jstats);
1694
        push @targets, $jobj->[0]->{target};
1695
        if ($jobj->[0]->{target}) {
1696
            if (@datapoints) {
1697
                my $j=0;
1698
                foreach my $p ( @{$jobj->[0]->{datapoints}} ) {
1699
#                    print "adding: ", $datapoints[$j]->[0], " + ", $p->[0];
1700
                    $datapoints[$j]->[0] += $p->[0];
1701
#                    print " = ", $datapoints[$j]->[0], " to ",$datapoints[$j]->[1],  "\n";
1702
                    $j++;
1703
                }
1704
            } else {
1705
                @datapoints = @{$jobj->[0]->{datapoints}};
1706
            }
1707
        }
1708
    }
1709
    pop @datapoints; # We discard the last datapoint because of possible clock drift
1710
    $all = [{targets=>\@targets, datapoints=>\@datapoints, period=>{from=>$from, until=>$until, span=>$until-$from}}];
1711
    if ($params{'format'} eq 'csv') {
1712
        $postreply = header("text/plain");
1713
        csv(in => \@datapoints, out => \my $csvdata);
1714
        $postreply .= $csvdata;
1715
    } else {
1716
        $postreply = to_json($all);
1717
    }
1718
    return $postreply;
1719
}
1720
1721
sub do_metrics {
1722
    my ($suuid, $action, $obj) = @_;
1723
    if ($help) {
1724
        return <<END
1725
GET:uuid,metric,from,to:
1726
Get performance and load metrics in JSON format from RRD backend. [metric] is one of: cpuload, diskreads, diskwrites, networkactivityrx, networkactivitytx
1727
From and to are Unix timestamps.
1728
END
1729
    }
1730
1731
    my $from = $params{"from"};
1732
    my $to = $params{"to"};
1733
    my $dif = $to - $from;
1734
    my $now = time();
1735
1736
    my @items;
1737
    my %cpuLoad = ();
1738
    my %networkActivityRX = ();
1739
    my %networkActivityTX = ();
1740
    my %diskReads = ();
1741
    my %diskWrites = ();
1742
1743
    my $i = 0;
1744
    my @uuids;
1745
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1746
1747
    if ($domreg{$suuid}) { # We are dealing with a server
1748
        push @uuids, $suuid;
1749
    } else { # We are dealing with a system
1750
        foreach my $valref (values %domreg) {
1751
            my $sysuuid = $valref->{'system'};
1752
            push @uuids, $valref->{'uuid'} if ($sysuuid eq $suuid)
1753
        }
1754
    }
1755
    untie %domreg;
1756
1757
    foreach my $uuid (@uuids) {
1758
        next unless hasRRD($uuid);
1759
        $i++;
1760
        # Fetch data from RRD buckets...
1761
        my $rrd = RRDTool::OO->new(file =>"/var/cache/rrdtool/".$uuid."_highres.rrd");
1762
        my $last = $rrd->last();
1763
        $rrd->fetch_start(start => $now-$dif, end=> $now);
1764
        while(my($timestamp, @value) = $rrd->fetch_next()) {
1765
            last if ($timestamp >= $last && $now-$last<20);
1766
            my $domain_cpuTime = shift(@value);
1767
            my $blk_hda_rdBytes = shift(@value);
1768
            my $blk_hda_wrBytes = shift(@value);
1769
            my $if_vnet0_rxBytes = shift(@value);
1770
            my $if_vnet0_txBytes = shift(@value);
1771
1772
            # domain_cpuTime is avg. nanosecs spent pr. 1s
1773
            # convert to value [0;1]
1774
            $domain_cpuTime = $domain_cpuTime / 10**9 if ($domain_cpuTime);
1775
            $cpuLoad{$timestamp} +=  $domain_cpuTime;
1776
1777
            $blk_hda_rdBytes = $blk_hda_rdBytes if ($blk_hda_rdBytes);
1778
            $diskReads{$timestamp} += $blk_hda_rdBytes;
1779
1780
            $blk_hda_wrBytes = $blk_hda_wrBytes if ($blk_hda_wrBytes);
1781
            $diskWrites{$timestamp} += $blk_hda_wrBytes;
1782
1783
            $networkActivityRX{$timestamp} += $if_vnet0_rxBytes;
1784
            $networkActivityTX{$timestamp} += $if_vnet0_txBytes;
1785
        }
1786
    }
1787
    my @t = ( $now-$dif, $now);
1788
    my @a = (undef, undef);
1789
    $i = $i || 1;
1790
1791
    my $item = ();
1792
    $item->{"uuid"} = $suuid if ($suuid);
1793
    my @tstamps = sort keys %cpuLoad;
1794
    $item->{"timestamps"} = \@tstamps || \@t;
1795
1796
    if ($params{"metric"} eq "cpuload" || $params{'cpuload'}) {
1797
        my @vals;
1798
        my $load = int(100*$cpuLoad{$_})/100;
1799
        $load = $i if  ($cpuLoad{$_} > $i);
1800
        foreach(@tstamps) {push @vals, $load};
1801
        $item->{"cpuload"} = \@vals || \@a;
1802
    }
1803
    elsif ($params{"metric"} eq "diskreads" || $params{'diskReads'}) {
1804
        my @vals;
1805
        foreach(@tstamps) {push @vals, int(100*$diskReads{$_})/100;};
1806
        $item->{"diskReads"} = \@vals || \@a;
1807
      }
1808
    elsif ($params{"metric"} eq "diskwrites" || $params{'diskWrites'}) {
1809
        my @vals;
1810
        foreach(@tstamps) {push @vals, int(100*$diskWrites{$_})/100;};
1811
        $item->{"diskWrites"} = \@vals || \@a;
1812
    }
1813
    elsif ($params{"metric"} eq "networkactivityrx" || $params{'networkactivityrx'}) {
1814
        my @vals;
1815
        foreach(@tstamps) {push @vals, int(100*$networkActivityRX{$_})/100;};
1816
        $item->{"networkactivityrx"} = \@vals || \@a;
1817
    }
1818
    elsif ($params{"metric"} eq "networkactivitytx" || $params{'networkactivitytx'}) {
1819
        my @vals;
1820
        foreach(@tstamps) {push @vals, int(100*$networkActivityTX{$_})/100;};
1821
        $item->{"networkactivitytx"} = \@vals || \@a;
1822
    }
1823
    push @items, $item;
1824
    $postreply .= to_json(\@items, {pretty=>1});
1825
    return $postreply;
1826
}
1827
1828
sub hasRRD {
1829
	my($uuid) = @_;
1830
	my $rrd_file = "/var/cache/rrdtool/".$uuid."_highres.rrd";
1831
1832
	if ((not -e $rrd_file) and ($uuid)) {
1833
		return(0);
1834
	} else {
1835
		return(1);
1836
	}
1837
}
1838
1839
sub do_packages_remove {
1840
    my ($uuid, $action, $obj) = @_;
1841
    if ($help) {
1842
        return <<END
1843
DELETE:uuid:
1844
Remove packages belonging to server or system with given uuid.
1845
END
1846
    }
1847
    my $issystem = $obj->{"issystem"} || $register{$uuid};
1848
    unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access package register"};
1849
    my @domains;
1850
    if ($issystem) {
1851
        foreach my $valref (values %domreg) {
1852
            if (($valref->{'system'} eq $uuid || $uuid eq '*')
1853
                    && ($valref->{'user'} eq $user || $fulllist)) {
1854
                push(@domains, $valref->{'uuid'});
1855
            }
1856
        }
1857
    } else { # Allow if domain no longer exists or belongs to user
1858
        push(@domains, $uuid) if (!$domreg{$uuid} || $domreg{$uuid}->{'user'} eq $user || $fulllist);
1859
    }
1860
    foreach my $domuuid (@domains) {
1861
        foreach my $packref (values %packreg) {
1862
            my $id = $packref->{'id'};
1863
            if (substr($id, 0,36) eq $domuuid || ($uuid eq '*' && $packref->{'user'} eq $user)) {
1864
                delete $packreg{$id};
1865
            }
1866
        }
1867
    }
1868
    tied(%packreg)->commit;# if (%packreg);
1869
    if ($issystem && $register{$uuid}) {
1870
        $postreply = "Status=OK Cleared packages for $register{$uuid}->{'name'}\n";
1871
    } elsif ($domreg{$uuid}) {
1872
        $postreply = "Status=OK Cleared packages for $domreg{$uuid}->{'name'}\n";
1873
    } else {
1874
        $postreply = "Status=OK Cleared packages. System not registered\n";
1875
    }
1876
    return $postreply;
1877
}
1878
1879
sub Packages_load {
1880
    my ($uuid, $action, $obj) = @_;
1881
    if ($help) {
1882
        return <<END
1883
POST:uuid:
1884
Load list of installed software packages that are installed on the image. Image must contain a valid OS.
1885
END
1886
    }
1887
    if (!$isreadonly) {
1888
        unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access package register"};
1889
        unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1890
        my $curimg;
1891
        my $apps;
1892
        my @domains;
1893
        my $issystem = $obj->{'issystem'};
1894
        if ($issystem) {
1895
            foreach my $valref (values %domreg) {
1896
                if (($valref->{'system'} eq $uuid || $uuid eq '*')
1897
                        && ($valref->{'user'} eq $user || $fulllist)) {
1898
                    push(@domains, $valref->{'uuid'});
1899
                }
1900
            }
1901
        } else {
1902
            push(@domains, $uuid) if ($domreg{$uuid}->{'user'} eq $user || $fulllist);
1903
        }
1904
1905
        foreach my $domuuid (@domains) {
1906
            if ($domreg{$domuuid}) {
1907
                $curimg = $domreg{$domuuid}->{'image'};
1908
                $apps = getPackages($curimg);
1909
                if ($apps) {
1910
                    my @packages;
1911
                    my @packages2;
1912
                    open my $fh, '<', \$apps or die $!;
1913
                    my $distro;
1914
                    my $hostname;
1915
                    my $i;
1916
                    while (<$fh>) {
1917
                        if (!$distro) {
1918
                            $distro = $_;
1919
                            chomp $distro;
1920
                        } elsif (!$hostname) {
1921
                            $hostname = $_;
1922
                            chomp $hostname;
1923
                        } elsif ($_ =~ /\[(\d+)\]/) {
1924
                            push @packages2, $packages[$i];
1925
                            $i = $1;
1926
                        } elsif ($_ =~ /(\S+): (.+)/ && $2) {
1927
                            $packages[$i]->{$1} = $2;
1928
                        }
1929
                    }
1930
                    close $fh or die $!;
1931
                    $domreg{$domuuid}->{'os'} = $distro;
1932
                    $domreg{$domuuid}->{'hostname'} = $hostname;
1933
                    foreach $package (@packages) {
1934
                        my $id = "$domuuid-$package->{'app_name'}";
1935
                        $packreg{$id} = $package;
1936
                        $packreg{$id}->{'app_display_name'} = $packreg{$id}->{'app_name'} unless ($packreg{$id}->{'app_display_name'});
1937
                        $packreg{$id}->{'domuuid'} = $domuuid;
1938
                        $packreg{$id}->{'user'} = $user;
1939
                    }
1940
                    $postreply .= "Status=OK Updated packages for $domreg{$domuuid}->{'name'}\n";
1941
                } else {
1942
                    $domreg{$domuuid}->{'os'} = 'unknown';
1943
                    $domreg{$domuuid}->{'hostname'} = 'unknown';
1944
                    $postreply .= "Status=Error Could not update packages for $domreg{$domuuid}->{'name'}";
1945
                }
1946
            }
1947
        }
1948
        tied(%packreg)->commit;
1949
        tied(%domreg)->commit;
1950
        untie %domreg;
1951
        untie %packreg;
1952
1953
    } else {
1954
        $postreply .= "Status=Error Not allowed\n";
1955
    }
1956
    return $postreply;
1957
}
1958
1959
sub do_packages {
1960
    my ($uuid, $action, $obj) = @_;
1961
    if ($help) {
1962
        return <<END
1963
GET:uuid:
1964
Handling of packages
1965
END
1966
    }
1967
1968
    unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access package register"};
1969
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1970
1971
    # List packages
1972
    my @packregvalues = values %packreg;
1973
    my @curregvalues;
1974
    my %packhash;
1975
    my %sysdoms; # Build list of members of system
1976
    foreach $sysdom (values %domreg) {
1977
        if ($sysdom->{'system'} eq $curuuid) {
1978
            $sysdoms{$sysdom->{'uuid'}} = $curuuid;
1979
        }
1980
    }
1981
    foreach my $valref (@packregvalues) {
1982
        if ($valref->{'user'} eq $user || $fulllist) {
1983
            if ((!$curuuid || $curuuid eq '*') # List packages from all servers
1984
                || ($domreg{$curuuid} && $curuuid eq $valref->{'domuuid'}) # List packages from a single server
1985
                || ($register{$curuuid} && $sysdoms{ $valref->{'domuuid'} }) # List packages from multiple servers - a system
1986
            ) {
1987
            #    push(@curregvalues, $valref);
1988
                my $packid = "$valref->{'app_display_name'}:$valref->{'app_version'}";
1989
                if ($packhash{$packid}) {
1990
                    ($packhash{$packid}->{'app_count'})++;
1991
                } else {
1992
                    $packhash{$packid} = {
1993
                        app_display_name=>$valref->{'app_display_name'},
1994
                        app_name=>$valref->{'app_name'},
1995
                        app_release=>$valref->{'app_release'},
1996
                    #    app_publisher=>$valref->{'app_publisher'},
1997
                        app_version=>$valref->{'app_version'},
1998
                        app_count=>1
1999
                    }
2000
                }
2001
            }
2002
        }
2003
    }
2004
    my @sorted_packs = sort {$a->{'app_display_name'} cmp $b->{'app_display_name'}} values %packhash;
2005
    if ($obj->{format} eq 'html') {
2006
        my $res;
2007
        $res .= qq[<tr><th>Name</th><th>Version</th><th>Count</th></tr>\n];
2008
        foreach my $valref (@sorted_packs) {
2009
            $res .= qq[<tr><td>$valref->{'app_display_name'}</td><td>$valref->{'app_version'}</td><td>$valref->{'app_count'}</td></tr>\n];
2010
        }
2011
        $postreply .= qq[<table cellspacing="0" frame="void" rules="rows" class="systemTables">\n$res</table>\n];
2012
    } elsif ($obj->{'format'} eq 'csv') {
2013
        $postreply = header("text/plain");
2014
        csv(in => \@sorted_packs, out => \my $csvdata);
2015
        $postreply .= $csvdata;
2016
    } else {
2017
        $postreply .= to_json(\@sorted_packs);
2018
    }
2019
    untie %domreg;
2020
    untie %packreg;
2021
    return $postreply;
2022
}
2023
2024
sub Buildsystem {
2025
    my ($uuid, $action, $obj) = @_;
2026
    if ($help) {
2027
        return <<END
2028 a93267ad hq
GET:name, master, storagepool, system, instances, networkuuid, bschedule, networktype1, ports, memory, vcpu, vmemory, vgpu, diskbus, cdrom, boot, loader, nicmodel1, nicmac1, networkuuid2, nicmac2, storagepool2, monitors, managementlink, start:
2029 95b003ff Origo
Build a complete system from cloned master image.
2030 c899e439 Origo
master is the only required parameter. Set [storagepool2] to -1 if you want data images to be put on node storage.
2031 95b003ff Origo
END
2032
    }
2033
    $curuuid = $uuid unless ($curuuid);
2034
    $postreply = buildSystem(
2035
        $obj->{name},
2036
        $obj->{master},
2037
        $obj->{storagepool},
2038
        $obj->{system},
2039
        $obj->{instances},
2040
        $obj->{networkuuid1},
2041
        $obj->{bschedule},
2042
        $obj->{networktype1},
2043
        $obj->{ports},
2044
        $obj->{memory},
2045
        $obj->{vcpu},
2046 a93267ad hq
        $obj->{vmemory},
2047
        $obj->{vgpu},
2048 95b003ff Origo
        $obj->{diskbus},
2049
        $obj->{cdrom},
2050
        $obj->{boot},
2051
        $obj->{nicmodel1},
2052
        $obj->{nicmac1},
2053
        $obj->{networkuuid2},
2054
        $obj->{nicmac2},
2055
        $obj->{monitors},
2056
        $obj->{managementlink},
2057
        $obj->{start},
2058 c899e439 Origo
        $obj->{domuuid},
2059 04c16f26 hq
        $obj->{storagepool2},
2060
        $obj->{loader}
2061 95b003ff Origo
    );
2062
    
2063
    return $postreply;
2064
}
2065
2066
sub Upgradesystem {
2067
    my ($uuid, $action, $obj) = @_;
2068
    if ($help) {
2069
        return <<END
2070
GET:uuid,internalip:
2071
Upgrades a system
2072
END
2073
    }
2074
    my $internalip = $params{'internalip'};
2075
    $postreply = upgradeSystem($internalip);
2076
    return $postreply;
2077
}
2078
2079
sub Removeusersystems {
2080
    my ($uuid, $action, $obj) = @_;
2081
    if ($help) {
2082
        return <<END
2083 6372a66e hq
GET:username:
2084 95b003ff Origo
Removes all systems belonging to a user, i.e. completely deletes all servers, images and networks belonging to an account.
2085
Use with extreme care.
2086
END
2087
    }
2088 6372a66e hq
    my $username = $obj->{username};
2089
    $username = $username || $user;
2090
    $postreply = removeusersystems($username); # method performs security check
2091 95b003ff Origo
    return $postreply;
2092
}
2093
2094
sub Removesystem {
2095
    my ($uuid, $action, $obj) = @_;
2096
    if ($help) {
2097
        return <<END
2098
GET:uuid:
2099
Removes specified system, i.e. completely deletes all servers, images, networks and backups belonging to a system.
2100
Use with care.
2101
END
2102
    }
2103 9de5a3f1 hq
    my $duuid = $obj->{uuid} || $uuid;
2104
    $postreply = remove($duuid, 0, 1);
2105 95b003ff Origo
    return $postreply;
2106
}
2107
2108
1;
2109
2110
# Print list of available actions on objects
2111
sub do_plainhelp {
2112
    my $res;
2113
    $res .= header('text/plain') unless $console;
2114
    $res .= <<END
2115
new [name="name"]
2116
start
2117
suspend
2118
resume
2119
shutdown
2120
destroy
2121
buildsystem [master, storagepool, system (uuid), instances, networkuuid1,bschedule,
2122 a93267ad hq
networktype1, ports, memory, vcpu, vmemory, vgpu, diskbus, cdrom, boot, nicmodel1, nicmac1, networkuuid2,
2123 95b003ff Origo
nicmac2, monitors, start]
2124
removesystem
2125
updateaccountinfo
2126
resettoaccountinfo
2127
2128
END
2129
;
2130
}
2131
2132
# Save current mon status to /etc/stabile/opstatus, in order to preserve state when reloading mon
2133
sub saveOpstatus {
2134
    my $deleteid = shift;
2135
    my %opstatus = getSavedOpstatus();
2136
    my @monarray = split("\n", `/usr/bin/moncmd list opstatus`);
2137
    my $opfile = "/etc/stabile/opstatus";
2138
    open(FILE, ">$opfile") or {throw Error::Simple("Unable to write $opfile")};
2139
    foreach my $line (@monarray) {
2140
        my @pairs = split(/ /,$line);
2141
        my %h;
2142
        my $ALERT;
2143
        foreach my $pair (@pairs) {
2144
            my ($key, $val) = split(/=/,$pair);
2145
            $obj->{$key} = $val;
2146
        }
2147
        my $ops = $opstatus{"$group:$service"};
2148
        my $group = $obj->{'group'};
2149
        my $service = $obj->{'service'};
2150
        my $curstatus = $ops->{'opstatus'};
2151
        my $curack = $ops->{'ack'};
2152
        my $curackcomment = $ops->{'ackcomment'};
2153
        my $curline = $ops->{'line'};
2154
        if ($deleteid && $deleteid eq "$group:$service") {
2155
            ; # Don't write line for service we are deleting
2156
        } elsif (($obj->{'opstatus'} eq '0' || $obj->{'opstatus'} eq '7') && $curack && $curstatus eq '0') {
2157
            # A failure has been acknowledged and service is still down
2158
            print FILE "$curline\n";
2159
            $ALERT = ($obj->{'opstatus'}?'UP':'DOWN');
2160
        } elsif (($obj->{'opstatus'} || $obj->{'opstatus'} eq '0') && $obj->{'opstatus'} ne '7') {
2161
            print FILE "$line\n";
2162
            $ALERT = ($obj->{'opstatus'}?'UP':'DOWN');
2163
        } elsif (($curstatus || $curstatus eq '0') && $curstatus ne '7') {
2164
            print FILE "$curline\n";
2165
            $ALERT = ($obj->{'opstatus'}?'UP':'DOWN');
2166
        } else {
2167
            # Don't write anything if neither is different from 7
2168
        }
2169
    # Create empty log file if it does not exist
2170
        my $oplogfile = "/var/log/stabile/$year-$month:$group:$service";
2171
        unless (-s $oplogfile) {
2172
            if ($group && $service && $ALERT) {
2173
                `/usr/bin/touch "$oplogfile"`;
2174
                `/bin/chown mon:mon "$oplogfile"`;
2175
                my $logline = "$current_time, $ALERT, MARK, $pretty_time";
2176
                `/bin/echo >> $oplogfile "$logline"`;
2177
            }
2178
        }
2179
    }
2180
    close (FILE);
2181
    #if ((!-e $opfile) || ($current_time - (stat($opfile))[9] > 120) ) {
2182
    #    `/usr/bin/moncmd list opstatus > $opfile`;
2183
    #}
2184
}
2185
2186
sub getSavedOpstatus {
2187
    my $dounbackslash = shift;
2188
    my $opfile = "/etc/stabile/opstatus";
2189
    my @oparray;
2190
    my %opstatus;
2191
    # Build hash (%opstatus) with opstatus'es etc. to use for services that are in state unknown because of mon reload
2192
    if (-e $opfile) {
2193
        open(FILE, $opfile) or {throw Error::Simple("Unable to read $opfile")};
2194
        @oparray = <FILE>;
2195
        close(FILE);
2196
        foreach my $line (@oparray) {
2197
            my @pairs = split(/ /,$line);
2198
            my %h;
2199
            foreach my $pair (@pairs) {
2200
                my ($key, $val) = split(/=/,$pair);
2201
                if ($key eq 'last_result' || !$dounbackslash) {
2202
                    $obj->{$key} = $val;
2203
                } else {
2204
                    $val =~ s/\\/\\x/g;
2205
                    $obj->{$key} = unbackslash($val);
2206
                }
2207
            }
2208
            $obj->{'line'} = $line;
2209
            $opstatus{"$obj->{'group'}:$obj->{'service'}"} = \%h;
2210
        }
2211
    }
2212
    return %opstatus;
2213
}
2214
2215
sub getOpstatus {
2216
    my ($selgroup, $selservice, $usemoncmd) = @_;
2217
    my %opcodes = ("", "checking", "0", "down", "1", "ok", "3", "3", "4", "4", "5", "5", "6", "6", "7", "checking", "9", "disabled");
2218
    my %s;
2219
    my %opstatus;
2220
    my %savedopstatus = getSavedOpstatus(1);
2221
    my %sysdoms;
2222
2223
    my %disabled;
2224
    my %desc;
2225
    my @dislist = split(/\n/, `/usr/bin/moncmd list disabled`);
2226
    foreach my $disline (@dislist) {
2227
        my ($a, $b, $c, $d) = split(' ', $disline);
2228
        $disabled{"$b" . ($d?":$d":'')} = 1;
2229
    };
2230
    my %emails;
2231
    my @emaillist = split(/\n/, `/bin/cat /etc/mon/mon.cf`);
2232
    my $emailuuid;
2233
    foreach my $eline (@emaillist) {
2234
        my ($a, $b, $c, $d) = split(/ +/, $eline, 4);
2235
        if ($a eq 'watch') {
2236
            if ($b =~ /\S+-\S+-\S+-\S+-\S+/) {$emailuuid = $b;}
2237
            else {$emailuuid = ''};
2238
        }
2239
        $emails{$emailuuid} = $d if ($emailuuid && $b eq 'alert' && $c eq 'stabile.alert');
2240
    };
2241
2242
    # We are dealing with a system group rather than a domain, build hash of domains in system
2243
    if ($selgroup && !$domreg{$selgroup} && $register{$selgroup}) {
2244
        foreach my $valref (values %domreg) {
2245
            $sysdoms{$valref->{'uuid'}} = $selgroup if ($valref->{system} eq $selgroup);
2246
        }
2247
    }
2248
    if ($usemoncmd) {
2249
        my @oparray = split("\n", `/usr/bin/moncmd list opstatus`);
2250
        foreach my $line (@oparray) {
2251
            my @pairs = split(/ /,$line);
2252
            my %h;
2253
            foreach my $pair (@pairs) {
2254
                my ($key, $val) = split(/=/,$pair);
2255
                if ($key eq 'last_result') {
2256
                    $obj->{$key} = $val;
2257
                } else {
2258
                    $val =~ s/\\/\\x/g;
2259
                    $obj->{$key} = unbackslash($val);
2260
                }
2261
            }
2262
            if (!$selgroup || $sysdoms{$obj->{'group'}}
2263
                (!$selservice && $selgroup eq $obj->{'group'}) ||
2264
                ($selgroup eq $obj->{'group'} && $selservice eq $obj->{'service'})
2265
            )
2266
            {
2267
                #$obj->{'line'} = $line;
2268
                #$opstatus{"$obj->{'group'}:$obj->{'service'}"} = \%h;
2269
                $s{$obj->{'group'}}->{$obj->{'service'}} = \%h if($obj->{'group'});
2270
            }
2271
        }
2272
2273
    } else {
2274
        my $monc;
2275
        $monc = new Mon::Client (
2276
            host => "127.0.0.1"
2277
        );
2278
        $monc->connect();
2279
        %desc = $monc->list_descriptions; # Get descriptions
2280
        #%disabled = $monc->list_disabled;
2281
        $selgroup = '' if (%sysdoms);
2282
        my @selection = [$selgroup, $selservice];
2283
        if ($selgroup && $selservice) {%s = $monc->list_opstatus( @selection );}
2284
        elsif ($selgroup) {%s = $monc->list_opstatus( (@selection) );}# List selection
2285
        else {%s = $monc->list_opstatus;} # List all
2286
        $monc->disconnect();
2287
    }
2288
2289
    foreach my $group (keys %s) {
2290
        if ($domreg{$group} && ($domreg{$group}->{'user'} eq $user || $fulllist)) {
2291
            foreach my $service (values %{$s{$group}}) {
2292
2293
                next if (%sysdoms && !$sysdoms{$group});
2294
                next unless ($service->{'monitor'});
2295
                my $ostatus = $service->{'opstatus'};
2296
                my $id = "$group:$service->{'service'}";
2297
                if (%sysdoms) {
2298
                    $service->{'system'} = $sysdoms{$group};
2299
                }
2300
                if ($ostatus == 7 && $savedopstatus{$id}) { # Get status etc. from %savedopstatus because mon has recently been reloaded
2301
                    $service->{'opstatus'} = $savedopstatus{$id}->{'opstatus'};
2302
                    $service->{'last_success'} = $savedopstatus{$id}->{'last_success'};
2303
                    $service->{'last_check'} = $savedopstatus{$id}->{'last_check'};
2304
                    $service->{'last_detail'} = $savedopstatus{$id}->{'last_detail'};
2305
                    $service->{'checking'} = "1";
2306
                }
2307
#                if (($ostatus == 7 || $ostatus == 0) &&  $savedopstatus{$id}->{'ack'}) { # Get ack because mon has recently been reloaded
2308
                if ($ostatus == 7 &&  $savedopstatus{$id}->{'ack'}) { # Get ack because mon has recently been reloaded
2309
                    $service->{'ack'} = $savedopstatus{$id}->{'ack'};
2310
                    $service->{'ackcomment'} = $savedopstatus{$id}->{'ackcomment'};
2311
                    $service->{'first_failure'} = $savedopstatus{$id}->{'first_failure'};
2312
                }
2313
                $service->{'ackcomment'} = $1 if ($service->{'ackcomment'} =~ /^: *(.*)/);
2314
                my $status = $opcodes{$service->{'opstatus'}};
2315
                if ($disabled{$id} || $disabled{$group}){
2316
                    $status = 'disabled';
2317
                    $service->{'opstatus'} = "9";
2318
                }
2319
                $service->{'status'} = $status;
2320
                $service->{'id'} = $id;
2321
                $service->{'name'} = "$domreg{$group}->{'name'} : $service->{'service'}";
2322
                $service->{'servername'} = $domreg{$group}->{'name'};
2323
                $service->{'serveruuid'} = $domreg{$group}->{'uuid'};
2324
                $service->{'serverstatus'} = $domreg{$group}->{'status'};
2325 6fdc8676 hq
                my $serverip = `cat /etc/mon/mon.cf |sed -n -e 's/^hostgroup $group //p'`;
2326
                chomp $serverip;
2327
                $service->{'serverip'} = $serverip;
2328 95b003ff Origo
2329
                my $desc = $desc{$group}->{$service->{'service'}};
2330
                $desc = '' if ($desc eq '--');
2331
                $service->{'desc'} = $desc;
2332
                $service->{'last_detail'} =~ s/-//g;
2333
                $service->{'last_detail'} =~ s/^\n//;
2334
                $service->{'last_detail'} =~ s/\n+/\n/g;
2335
2336
                my $monitor = $service->{'monitor'};
2337
2338
                $service->{'request'} = $service->{'okstring'} = $service->{'port'} = $service->{'email'} = '';
2339
                #$monitor = URI::Escape::uri_unescape($monitor);
2340
                #if ( $monitor =~ /stabile-diskspace\.monitor\s+(\S+)\s+(\S+)\s+(\S+)/ ) {
2341
                if ( $monitor =~ /stabile-diskspace\.monitor\s+(\S+)\s+(\S+)/ ) {
2342
                    $service->{'request'} = $2 if ( $monitor =~ /stabile-diskspace\.monitor\s+(\S+)\s+(\S+)/ );
2343
                    $service->{'okstring'} = $3 if ( $monitor =~ /stabile-diskspace\.monitor\s+(\S+)\s+(\S+)\s+(\S+)/ );
2344
                }
2345
2346
                $service->{'okstring'} = $1 if ( $monitor =~ /--okstring \"(.*)\"/ );
2347
                $service->{'okstring'} = $1 if ( $monitor =~ /-l \"(.*)\"/ );
2348
#                $service->{'request'} = $2 if ( $monitor =~ /http(s*):\/\/.+\/(.*)/ );
2349
                $service->{'request'} = $2 if ( $monitor =~ /http(s*):\/\/[^\/]+\/(.*)/ );
2350
                $service->{'port'} = $2 if ( $monitor =~ /http(s*):\/\/.+:(\d+)/ );
2351
                $service->{'request'} = $1 if ( $monitor =~ /--from \"(\S*)\"/ );
2352
                $service->{'okstring'} = $1 if ( $monitor =~ /--to \"(\S*)\"/ );
2353
                $service->{'port'} = $1 if ( $monitor =~ /--port (\d+)/ );
2354
2355
                $service->{'email'} = $emails{$group};
2356
2357
                $opstatus{$id} = $service;
2358
                #push @monitors, $service;
2359
            }
2360
        }
2361
    }
2362
    return %opstatus;
2363
}
2364
2365
sub change_monitor_email {
2366
    my $serveruuid = shift;
2367
    my $email = shift;
2368
    my $match;
2369
    if ($email && $serveruuid) {
2370
        unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
2371
        if ($domreg{$serveruuid}->{'user'} eq $user || $isadmin) {
2372
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf"); # $^I is the in-place edit switch
2373
            # undef $/; # This makes <> read in the entire file in one go
2374
            my $uuidmatch;
2375
            while (<>) {
2376
                if (/^watch (\S+)/) {
2377
                    if ($1 eq $serveruuid) {$uuidmatch = $serveruuid}
2378
                    else {$uuidmatch = ''};
2379
                };
2380
                if ($uuidmatch) {
2381
                    $match = 1 if (s/(stabile\.alert) (.*)/$1 $email/);
2382
                }
2383
                print;
2384
                close ARGV if eof;
2385
        #       $match = 1 if (s/(watch $serveruuid\n.+\n.+\n.+\n.+\n.+)$oldemail(\n.+)$oldemail(\n.+)$oldemail/$1$email$2$email$3$email/g);
2386
            }
2387
        #    $/ = "\n";
2388
        }
2389
    }
2390
    return $match;
2391
}
2392
2393
# Delete all monitors belonging to a server
2394
sub deleteMonitors {
2395
    my ($serveruuid) = @_;
2396
    my $match;
2397
    if ($serveruuid) {
2398
        if ($domreg{$serveruuid}->{'user'} eq $user || $isadmin) {
2399
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf");
2400
            # undef $/; # This makes <> read in the entire file in one go
2401
            my $uuidmatch;
2402
            while (<>) {
2403
                if (/^watch (\S+)/) {
2404
                    if ($1 eq $serveruuid) {$uuidmatch = $serveruuid}
2405
                    else {$uuidmatch = ''};
2406
                };
2407
                if ($uuidmatch) {
2408
                    $match = 1;
2409
                } else {
2410
                    #chomp;
2411
                    print unless (/^hostgroup $serveruuid/);
2412
                }
2413
                close ARGV if eof;
2414
            }
2415
            #$/ = "\n";
2416
        }
2417
        unlink glob "/var/log/stabile/*:$serveruuid:*";
2418
    }
2419
    `/usr/bin/moncmd reset keepstate` if ($match);
2420
    return $match;
2421
}
2422
2423
# Add a monitors to a server when building system
2424
sub addSimpleMonitors {
2425
    my ($serveruuid, $email, $monitors_ref) = @_;
2426
    my @mons = @{$monitors_ref};
2427
2428
    my $match;
2429
    my $hmatch1;
2430
    my $hmatch2;
2431
    my $hmatch3;
2432 3657de20 Origo
    if ($serveruuid && $domreg{$serveruuid}) {
2433 95b003ff Origo
        if ($domreg{$serveruuid}->{'user'} eq $user || $isadmin) {
2434
            my $monitors = {
2435
                ping=>"fping.monitor",
2436
                diskspace=>"stabile-diskspace.monitor $serveruuid",
2437
                http=>"http_tppnp.monitor",
2438
                https=>"http_tppnp.monitor",
2439
                smtp=>"smtp3.monitor",
2440
                smtps=>"smtp3.monitor",
2441
                imap=>"imap.monitor",
2442
                imaps=>"imap-ssl.monitor",
2443
                ldap=>"ldap.monitor",
2444
                telnet=>"telnet.monitor"
2445
            };
2446
2447
            if (!$email) {$email = $domreg{$serveruuid}->{'alertemail'}};
2448
            if (!$email && $register{$domreg{$serveruuid}->{'system'}}) {$email = $register{$domreg{$serveruuid}->{'system'}}->{'alertemail'}};
2449
            if (!$email) {$email = $userreg{$user}->{'alertemail'}};
2450
2451
            unless (tie %networkreg,'Tie::DBI', {
2452
                db=>'mysql:steamregister',
2453
                table=>'networks',
2454
                key=>'uuid',
2455
                autocommit=>0,
2456
                CLOBBER=>3,
2457
                user=>$dbiuser,
2458
                password=>$dbipasswd}) {throw Error::Simple("Stroke=Error Register could not be accessed")};
2459
2460
            my $networkuuid1 = $domreg{$serveruuid}->{'networkuuid1'};
2461
            my $networktype = $networkreg{$networkuuid1}->{'type'};
2462
            my $ip = $networkreg{$networkuuid1}->{'internalip'};
2463
            $ip = $networkreg{$networkuuid1}->{'externalip'} if ($networktype eq 'externalip');
2464
            $ip = '127.0.0.1' if ($networktype eq 'gateway'); #Dummy IP - we only support diskspace checks
2465
            untie %networkreg;
2466
2467
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf");
2468
            my $uuidmatch;
2469
            while (<>) {
2470
                $hmatch1=1 if (/^hostgroup/);
2471
                $hmatch2=1 if ($hmatch1 && !/^hostgroup/);
2472
                if ($hmatch1 && $hmatch2 && !$hmatch3) {
2473
                    print "hostgroup $serveruuid $ip\n";
2474
                    $hmatch3 = 1;
2475
                }
2476
                print;
2477
                if (eof) {
2478
                    print "watch $serveruuid\n";
2479
                    foreach $service (@mons) {
2480
                        print <<END;
2481
    service $service
2482
        interval 1m
2483
        monitor $monitors->{$service}
2484
        description --
2485
        period
2486
            alert stabile.alert $email
2487
            upalert stabile.alert $email
2488
            startupalert stabile.alert $email
2489
            numalerts 2
2490
            no_comp_alerts
2491
END
2492
;
2493
                        my $oplogfile = "/var/log/stabile/$year-$month:$serveruuid:$service";
2494
                        unless (-e $oplogfile) {
2495
                            `/usr/bin/touch "$oplogfile"`;
2496
                            `/bin/chown mon:mon "$oplogfile"`;
2497
                            my $logline = "$current_time, UP, STARTUP, $pretty_time";
2498
                            `/bin/echo >> $oplogfile "$logline"`;
2499
                        }
2500
                    }
2501
                    close ARGV;
2502
                }
2503
            }
2504
        } else {
2505 3657de20 Origo
            return "Server $serveruuid not available";
2506 95b003ff Origo
        }
2507
    } else {
2508 3657de20 Origo
        return "Invalid uuid $serveruuid";
2509 95b003ff Origo
    }
2510
    return "OK";
2511
}
2512
2513
sub Monitors_save {
2514
    my ($id, $action, $obj) = @_;
2515
    if ($help) {
2516
        return <<END
2517
PUT:id:
2518
Enable, disable or acknowledge a monitor. Id is of the form serveruuid:service
2519
END
2520
    }
2521
2522
    my $delete = ($action eq 'monitors_remove'); # Delete an existing monitor
2523
    $id = $obj->{'id'} || $id; # ID in params supersedes id in path
2524
    my $update; # Update an existing monitor?
2525
    my $postmsg;
2526
2527
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
2528
    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
2529
    foreign_require("mon", "mon-lib.pl");
2530
    $conf = mon::get_mon_config();
2531
#    my @ogroups = mon::find("hostgroup", $conf);
2532
#    my @owatches = mon::find("watch", $conf);
2533
    my $doreset;
2534
    my $email;
2535
    my $serveruuid;
2536
    my $servicename;
2537
    if ($id =~ /(.+):(.+)/){ # List specific monitor for specific server
2538
        $serveruuid = $1;
2539
        $servicename = $2;
2540
    }
2541
    $serveruuid = $serveruuid || $obj->{'serveruuid'};
2542
    my $desc = $obj->{'desc'};
2543
    my $okstring = $obj->{'okstring'};
2544
    my $request = $obj->{'request'};
2545
    my $port = $obj->{'port'};
2546
    $servicename = $servicename || $obj->{'service'};
2547
    my $interval = '1'; # Number of minutes between checks
2548
    $interval = '20' if ($servicename eq 'diskspace');
2549 f222b89c hq
    $email = $obj->{'alertemail'} || $obj->{'email'};
2550 95b003ff Origo
    my $serv = $domreg{$serveruuid};
2551
    if (!$email) {$email = $serv->{'alertemail'}};
2552
    if (!$email && $serv->{'system'}) {$email = $register{$serv->{'system'}}->{'alertemail'}};
2553
    if (!$email) {$email = $userreg{$user}->{'alertemail'}};
2554
    my $networkuuid1 = $serv->{'networkuuid1'};
2555
    my $networktype = $networkreg{$networkuuid1}->{'type'};
2556
    my $deleteid;
2557
    
2558
    if (!$serveruuid || !$servicename) {
2559
        $postmsg = qq|No monitor specified|;
2560
        $postreply = "Status=Error $postmsg\n";
2561
        return $postreply;
2562
    }
2563
2564
    if (!$delete && $networktype eq 'gateway' && $servicename ne 'diskspace'
2565
            && (!$obj->{'serverip'} || !($obj->{'serverip'} =~ /^\d+\.\d+\.\d+\.\d+$/) )) {
2566
        $postmsg = qq|Invalid IP address|;
2567
    } elsif (!$domreg{$serveruuid}) {
2568
        $postmsg = qq|Unknown server $serveruuid|;
2569
# Security check
2570
    } elsif ($domreg{$serveruuid}->{'user'} ne $user) {
2571
        $postmsg = qq|Bad server|;
2572
    } else {
2573
        my $monitors = {
2574
            ping=>"fping.monitor",
2575
            diskspace=>"stabile-diskspace.monitor",
2576
            http=>"http_tppnp.monitor",
2577
            https=>"http_tppnp.monitor",
2578
            smtp=>"smtp3.monitor",
2579
            smtps=>"smtp3.monitor",
2580
            imap=>"imap.monitor",
2581
            imaps=>"imap-ssl.monitor",
2582
            ldap=>"ldap.monitor",
2583
            telnet=>"telnet.monitor"
2584
        };
2585
        my $args = '';
2586
        my $ip = $networkreg{$networkuuid1}->{'internalip'};
2587
        $ip = $networkreg{$networkuuid1}->{'externalip'} if ($networktype eq 'externalip');
2588
        $ip = '127.0.0.1' if ($networktype eq 'gateway' && $servicename eq 'diskspace'); #Dummy IP - we only support diskspace checks
2589
        if ($networktype eq 'gateway' && $servicename eq 'ping') {
2590
            $ip = $obj->{'serverip'};
2591
        # We can only check 10.x.x.x addresses on vlan because of routing
2592
            if ($ip =~ /^10\./) {
2593
                $monitors->{'ping'} = "stabile-arping.monitor";
2594
                my $id = $networkreg{$networkuuid1}->{'id'};
2595
                if ($id > 1) {
2596
                    my $if = $datanic . "." . $id;
2597
                    $args = " $if";
2598
                } else {
2599
                    $args = " $extnic";
2600
                }
2601
                $args .= " $ip";
2602
            }
2603
        }
2604
2605
        if ($servicename eq 'ping') {
2606
            ;
2607
        } elsif ($servicename eq 'diskspace'){
2608
            #my $macip = $domreg{$serveruuid}->{'macip'};
2609
            #my $image = URI::Escape::uri_escape($domreg{$serveruuid}->{'image'});
2610
            #$args .= " $macip $image $serveruuid";
2611
            $args .= " $serveruuid";
2612
            $args .= ($request)?" $request":" 10"; #min free %
2613
            $args .= " $okstring" if ($okstring); #Comma-separated partion list, e.g. 0,1
2614
        } elsif ($servicename eq 'http'){
2615
            $args .= " --okcodes \"200,403\" --debuglog -";
2616
            $args .= " --okstring \"$okstring\"" if ($okstring);
2617
            $args .= " http://$ip";
2618
            $args .= ":$port" if ($port && $port>10 && $port<65535);
2619
            $request = substr($request,1) if ($request =~ /^\//);
2620
            $args .= "/$request" if ($request);
2621
        } elsif ($servicename eq 'https'){
2622
            $args .= " --okcodes \"200,403\" --debuglog -";
2623
            $args .= " --okstring \"$okstring\"" if ($okstring);
2624
            $args .= " https://$ip";
2625
            $args .= ":$port" if ($port && $port>10 && $port<65535);
2626
            $request = substr($request,1) if ($request =~ /^\//);
2627
            $args .= "/$request" if ($request);
2628
        } elsif ($servicename eq 'smtp'){
2629
            $args .= " --from \"$request\"" if ($request);
2630
            $args .= " --to \"$okstring\"" if ($okstring);
2631
            $args .= " --port $port" if ($port && $port>10 && $port<65535);
2632
        } elsif ($servicename eq 'smtps'){
2633
            $args .= " --requiretls";
2634
            $args .= " --from \"$request\"" if ($request);
2635
            $args .= " --to \"$okstring\"" if ($okstring);
2636
            $args .= " --port $port" if ($port && $port>10 && $port<65535);
2637
        } elsif ($servicename eq 'imap'){
2638
            $args .= " -p $port" if ($port && $port>10 && $port<65535);
2639
        } elsif ($servicename eq 'imaps'){
2640
            $args .= " -p $port" if ($port && $port>10 && $port<65535);
2641
        } elsif ($servicename eq 'ldap'){
2642
            $args .= " --port $port" if ($port && $port>10 && $port<65535);
2643 d24d9a01 hq
            $args .= " --basedn \"$request\"" if ($request);
2644
            $args .= " --attribute \"$okstring\"" if ($okstring);
2645 95b003ff Origo
        } elsif ($servicename eq 'telnet'){
2646
            $args .= " -l \"$okstring\"" if ($okstring);
2647
            $args .= " -p $port" if ($port && $port>10 && $port<65535);
2648
        }
2649
2650
        my @ogroups = mon::find("hostgroup", $conf);
2651
        my @owatches = mon::find("watch", $conf);
2652
2653
        $group = { 'name' => 'hostgroup', 'values' => [ $serveruuid, $ip ] };
2654
        my $ogroup = undef;
2655
        my $i;
2656
        for($i=0; $i<scalar @ogroups; $i++) {
2657
            if ($ogroups[$i]->{'values'}[0] eq  $serveruuid) {
2658
                $ogroup = $ogroups[$i];
2659
                last;
2660
            }
2661
        }
2662
        mon::save_directive($conf, $ogroup, $group); #Update host hostgroup
2663
2664
        $watch = { 'name' => 'watch','values' => [ $serveruuid ], 'members' => [ ] };
2665
        my $owatch = undef;
2666
        my $oservice = undef;
2667
        my $widx = undef;
2668
        for($i=0; $i<scalar @owatches; $i++) { # Run through all watches and locate match
2669
            if ($owatches[$i]->{'values'}[0] eq  $serveruuid) {
2670
                $owatch = $watch = $owatches[$i];
2671
                $widx = $owatch->{'index'};
2672
                my @oservices = mon::find("service", $watch->{'members'});
2673
                for($j=0; $j<@oservices; $j++) { # Run through all services for watch and locate match
2674
                    if ($oservices[$j]->{'values'}[0] eq $servicename) {
2675
                        $oservice = $oservices[$j];
2676
                        my $newmonargs = "$monitors->{$servicename}$args";
2677
                        $newmonargs =~ s/\s+$//; # Remove trailing spaces
2678
                        my $oldmonargs = "$oservices[$j]->{'members'}[2]->{'values'}[0] $oservices[$j]->{'members'}[2]->{'values'}[1]";
2679
                        $oldmonargs =~ s/\s+$//; # Remove trailing spaces
2680
                        if ($newmonargs ne $oldmonargs) {
2681
                            $update = 1; #We are changing an existing service definition
2682
                        };
2683
                        last;
2684
                    }
2685
                }
2686
                last;
2687
            }
2688
        }
2689
        my $in = {
2690
            args=>undef,
2691
            desc=>"$desc",
2692
            idx=>$widx,
2693
            interval=>$interval,
2694
            interval_u=>'m',
2695
            monitor=>$monitors->{$servicename} . $args,
2696
            monitor_def=>1,
2697
            name=>$servicename,
2698
            other=>undef,
2699
            sidx=>undef,
2700
            delete=>$delete,
2701
            email=>$email
2702
        };
2703
        if ($update || $delete) {
2704
            unlink glob "/var/log/stabile/*:$serveruuid:$servicename";
2705
        } else {
2706
            my $oplogfile = "/var/log/stabile/$year-$month:$serveruuid:$servicename";
2707
            unless (-e $oplogfile) {
2708
                `/usr/bin/touch "$oplogfile"`;
2709
                `/bin/chown mon:mon "$oplogfile"`;
2710
                my $logline = "$current_time, UP, STARTUP, $pretty_time";
2711
                `/bin/echo >> $oplogfile "$logline"`;
2712
            }
2713
        }
2714
        $deleteid = (($delete || $update)?"$serveruuid:$servicename":'');
2715
        save_service($in, $owatch, $oservice);
2716
        $doreset = 1;
2717
        $obj->{'last_check'} = '--';
2718
        $obj->{'opstatus'} = '7';
2719
        $obj->{'status'} = 'checking';
2720
        $obj->{'alertemail'} = $email;
2721
        mon::flush_file_lines();
2722
        $main::syslogit->($user, 'info', "updating monitor $serveruuid:$servicename" .  (($delete)?" delete":""));
2723
        saveOpstatus($deleteid);
2724
        `/usr/bin/moncmd reset keepstate`;
2725
    }
2726
2727
    untie %networkreg;
2728
    untie %domreg;
2729
2730
    $postreply = to_json(\%h, {pretty => 1});
2731
    $postmsg = "OK" unless ($postmsg);
2732
    return $postreply;
2733
}
2734
2735
## Copied from save_service.cgi (from webmin) and slightly modified - well heavily perhaps
2736
2737
sub save_service {
2738
    my $sin = shift;
2739
    my $owatch = shift;
2740
    my $oservice = shift;
2741
    my %in = %{$sin};
2742
    my $oldservice = undef;
2743
    my $service;
2744
    if ($oservice) {
2745
        # $oldservice = $service = $watch->{'members'}->[$in{'sidx'}];
2746
        $oldservice = $service = $oservice;
2747
    } else {
2748
        $service = { 'name' => 'service',
2749
                 'indent' => '    ',
2750
                 'members' => [ ] };
2751
    }
2752
    if ($in{'delete'}) {
2753
        # Delete this service from the watch
2754 51e32e00 hq
        mon::save_directive($watch->{'members'}, $service, '') if ($oservice);
2755 95b003ff Origo
        my @rservices = mon::find("service", $watch->{'members'});
2756
        # Delete watch and hostgroup if no services left
2757
        if (@rservices==0) {
2758 51e32e00 hq
            mon::save_directive($conf, $watch, '');
2759
            mon::save_directive($conf, $group, '');
2760 95b003ff Origo
        }
2761
    } else {
2762
        # Validate and store service inputs
2763
        $in{'name'} =~ /^\S+$/ || {$in{'name'} = 'ping'};
2764
        $service->{'values'} = [ $in{'name'} ];
2765
        $in{'interval'} =~ /^\d+$/ || {$in{'interval'} = 1};
2766
2767
        &set_directive($service->{'members'}, "interval", $in{'interval'}.$in{'interval_u'});
2768
2769
        if ($in{'monitor_def'}) {
2770
            &set_directive($service->{'members'}, "monitor", $in{'monitor'}.' '.$in{'args'});
2771
        }
2772
        else {
2773
            $in{'other'} =~ /^\S+$/ || return "No other monitor specified";
2774
            &set_directive($service->{'members'}, "monitor", $in{'other'}.' '.$in{'args'});
2775
        }
2776
2777
        # Save the description
2778
        if ($in{'desc'}) {
2779
            my $desc = $in{'desc'};
2780
            $desc =~ tr/\n/ /;
2781
            &set_directive($service->{'members'}, "description", $in{'desc'});
2782
        }
2783
        else {
2784
            &set_directive($service->{'members'}, "description", '--');
2785
        }
2786
2787
        my $period = { 'name' => 'period', 'members' => [ ] };
2788
        my @alert;
2789
        my @v = ( "stabile.alert", $in{'email'} );
2790
        my @num = (2); # The number of alerts to send
2791
        push(@alert, { 'name' => 'alert', 'values' => \@v });
2792
		&set_directive($period->{'members'}, "alert", @alert);
2793
        my @upalert;
2794
        push(@upalert, { 'name' => 'upalert', 'values' => \@v });
2795
		&set_directive($period->{'members'}, "upalert", @upalert);
2796
        my @startupalert;
2797
        push(@startupalert, { 'name' => 'startupalert', 'values' => \@v });
2798
		&set_directive($period->{'members'}, "startupalert", @startupalert);
2799
        my @numalerts;
2800
        push(@numalerts, { 'name' => 'numalerts', 'values' => \@num });
2801
		&set_directive($period->{'members'}, "numalerts", @numalerts);
2802
        my @no_comp_alerts;
2803
        push(@no_comp_alerts, { 'name' => 'no_comp_alerts', 'values' => 0 });
2804
		&set_directive($period->{'members'}, "no_comp_alerts", @no_comp_alerts);
2805
2806
        push(@period, $period);
2807
2808
    	&set_directive($service->{'members'}, "period", @period);
2809
2810
        if ($owatch) {
2811
            # Store the service in existing watch in the config file
2812
            mon::save_directive($watch->{'members'}, $oldservice, $service);
2813
        } else {
2814
            # Create new watch
2815
            push(@service, $service);
2816
            &set_directive($watch->{'members'}, "service", @service);
2817
            mon::save_directive($conf, undef, $watch);
2818
        }
2819
    }
2820
}
2821
2822
# set_directive(&config, name, value, value, ..)
2823
sub set_directive
2824
{
2825
local @o = mon::find($_[1], $_[0]);
2826
local @n = @_[2 .. @_-1];
2827
local $i;
2828
for($i=0; $i<@o || $i<@n; $i++) {
2829
	local $idx = &indexof($o[$i], @{$_[0]}) if ($o[$i]);
2830
	local $nv = ref($n[$i]) ? $n[$i] : { 'name' => $_[1],
2831
					     'values' => [ $n[$i] ] }
2832
						if (defined($n[$i]));
2833
	if ($o[$i] && defined($n[$i])) {
2834
		$_[0]->[$idx] = $nv;
2835
		}
2836
	elsif ($o[$i]) {
2837
		splice(@{$_[0]}, $idx, 1);
2838
		}
2839
	else {
2840
		push(@{$_[0]}, $nv);
2841
		}
2842
	}
2843
}
2844
2845
sub getSystemsListing {
2846
    my ($action, $curuuid, $username) = @_;
2847
    $username = $user unless ($username);
2848
    my @domregvalues = values %domreg;
2849
    my @curregvalues;
2850
    my %curreg;
2851
2852
    $userfullname = $userreg{$username}->{'fullname'};
2853
    $useremail = $userreg{$username}->{'email'};
2854
    $userphone = $userreg{$username}->{'phone'};
2855
    $useropfullname = $userreg{$username}->{'opfullname'};
2856
    $useropemail = $userreg{$username}->{'opemail'};
2857
    $useropphone = $userreg{$username}->{'opphone'};
2858
    $useralertemail = $userreg{$username}->{'alertemail'};
2859
2860
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$postreply = "Unable to access image register"; return;};
2861 d24d9a01 hq
    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
2862 95b003ff Origo
2863
    # Collect systems from domains and include domains as children
2864
    if ($action ne 'flatlist') { # Dont include children in select
2865
        my @imagenames = qw(image image2 image3 image4);
2866
        foreach my $valref (@domregvalues) {
2867
        # Only include VM's belonging to current user (or all users if specified and user is admin)
2868
            if ($username eq $valref->{'user'} || $fulllist) {
2869
                next unless (!$curuuid || ($valref->{'uuid'} eq $curuuid || $valref->{'system'} eq $curuuid));
2870
2871
                my %val = %{$valref}; # Deference and assign to new ass array, effectively cloning object
2872
                my $sysuuid = $val{'system'};
2873
                my $dbobj = $register{$sysuuid};
2874
                $val{'memory'} += 0;
2875
                $val{'vcpu'} += 0;
2876 a93267ad hq
                $val{'vmemory'} += 0;
2877
                $val{'vgpu'} += 0;
2878 95b003ff Origo
                $val{'nodetype'} = 'child';
2879
                $val{'fullname'} = $val{'fullname'} || $dbobj->{'fullname'} || $userfullname;
2880
                $val{'email'} = $val{'email'} || $dbobj->{'email'} || $useremail;
2881
                $val{'phone'} = $val{'phone'} || $dbobj->{'phone'} || $userphone;
2882
                $val{'opfullname'} = $val{'opfullname'} || $dbobj->{'opfullname'} || $useropfullname;
2883
                $val{'opemail'} = $val{'opemail'} || $dbobj->{'opemail'} || $useropemail;
2884
                $val{'opphone'} = $val{'opphone'} || $dbobj->{'opphone'} || $useropphone;
2885
                $val{'alertemail'} = $val{'alertemail'} || $dbobj->{'alertemail'} || $useralertemail;
2886 c899e439 Origo
                $val{'autostart'} = ($val{'autostart'})?'1':'';
2887 95b003ff Origo
2888
                foreach my $img (@imagenames) {
2889
                    if ($imagereg{$val{$img}} && $imagereg{$val{$img}}->{'storagepool'} == -1) {
2890
                        $val{'nodestorage'} += $imagereg{$val{$img}}->{'virtualsize'};
2891
                    } else {
2892
                        $val{'storage'} += $imagereg{$val{$img}}->{'virtualsize'} if ($imagereg{$val{$img}});
2893
                    }
2894
                }
2895
                $val{'externalips'} += 1 if ($networkreg{$val{'networkuuid1'}} && $networkreg{$val{'networkuuid1'}}->{'type'} =~ /externalip|ipmapping/);
2896
                $val{'externalips'} += 1 if ($networkreg{$val{'networkuuid2'}} && $networkreg{$val{'networkuuid2'}}->{'type'} =~ /externalip|ipmapping/);
2897
                $val{'externalips'} += 1 if ($networkreg{$val{'networkuuid3'}} && $networkreg{$val{'networkuuid3'}}->{'type'} =~ /externalip|ipmapping/);
2898
                $val{'networktype1'} = $networkreg{$val{'networkuuid1'}}->{'type'} if ($networkreg{$val{'networkuuid1'}});
2899
                $val{'imageuuid'} = $imagereg{$val{'image'}}->{'uuid'} if ($imagereg{$val{'image'}});
2900
                $val{'imageuuid2'} = $imagereg{$val{'image2'}}->{'uuid'} if ($imagereg{$val{'image2'}} && $val{'image2'} && $val{'image2'} ne '--');
2901 afc024ef hq
                $val{'internalip'} = $networkreg{$val{'networkuuid1'}}->{'internalip'} if ($networkreg{$val{'networkuuid1'}});
2902
                $val{'externalip'} = $networkreg{$val{'networkuuid1'}}->{'externalip'} if ($networkreg{$val{'networkuuid1'}});
2903 95b003ff Origo
2904
                my $networkuuid1; # needed for generating management url
2905 c899e439 Origo
                if ($sysuuid && $sysuuid ne '--') { # We are dealing with a server that's part of a system
2906 95b003ff Origo
                    if (!$register{$sysuuid}) { #System does not exist - create it
2907
                        $sysname = $val{'name'};
2908
                        $sysname = $1 if ($sysname =~ /(.+)\..*/);
2909
                        $sysname =~ s/server/System/i;
2910
                        $register{$sysuuid} = {
2911
                            uuid => $sysuuid,
2912
                            name => $sysname,
2913
                            user => $username,
2914
                            created => $current_time
2915
                        };
2916
                    }
2917
2918
                    my %pval = %{$register{$sysuuid}};
2919
                    $pval{'status'} = '--';
2920
                    $pval{'issystem'} = 1;
2921
                    $pval{'fullname'} = $pval{'fullname'} || $userfullname;
2922
                    $pval{'email'} = $pval{'email'} || $useremail;
2923
                    $pval{'phone'} = $pval{'phone'} || $userphone;
2924
                    $pval{'opfullname'} = $pval{'opfullname'} || $useropfullname;
2925
                    $pval{'opemail'} = $pval{'opemail'} || $useropemail;
2926
                    $pval{'opphone'} = $pval{'opphone'} || $useropphone;
2927
                    $pval{'alertemail'} = $pval{'alertemail'} || $useralertemail;
2928 c899e439 Origo
                    $pval{'autostart'} = ($pval{'autostart'})?'1':'';
2929 95b003ff Origo
2930
                    my @children;
2931
                    if ($curreg{$sysuuid}->{'children'}) {
2932
                        @children = @{$curreg{$sysuuid}->{'children'}};
2933
                    }
2934
                    # If system has an admin image, update networkuuid1 with the image's server's info
2935
                    if ($pval{'image'} && $pval{'image'} ne '--') {
2936
                        my $dbimg = $imagereg{$pval{'image'}};
2937
                        $networkuuid1 = $domreg{$dbimg->{'domains'}}->{'networkuuid1'} if ($domreg{$dbimg->{'domains'}});
2938 04c16f26 hq
                        my $externalip = '';
2939
                        my $ports = '';
2940
                        if ($networkreg{$networkuuid1}) {
2941
                            $externalip = $networkreg{$networkuuid1}->{'externalip'};
2942
                            $ports = $networkreg{$networkuuid1}->{'ports'}
2943
                        }
2944 95b003ff Origo
                        $register{$sysuuid}->{'networkuuid1'} = $networkuuid1;
2945
                        $register{$sysuuid}->{'internalip'} = $networkreg{$networkuuid1}->{'internalip'} if ($networkreg{$networkuuid1});
2946
                        $pval{'master'} = $dbimg->{'master'};
2947
                        $pval{'appid'} = $dbimg->{'appid'};
2948
                        $pval{'version'} = $dbimg->{'version'};
2949
                        my $managementurl;
2950
                        $managementurl = $dbimg->{'managementlink'};
2951
                        $managementurl =~ s/\{uuid\}/$networkuuid1/;
2952
                        $managementurl =~ s/\{externalip\}/$externalip/;
2953
                        $pval{'managementurl'} = $managementurl;
2954
                        my $upgradeurl;
2955
                        $upgradeurl = $dbimg->{'upgradelink'};
2956
                        $upgradeurl =~ s/\{uuid\}/$networkuuid1/;
2957
                        $pval{'upgradeurl'} = $upgradeurl;
2958
                        my $terminalurl;
2959
                        $terminalurl = $dbimg->{'terminallink'};
2960
                        $terminalurl =~ s/\{uuid\}/$networkuuid1/;
2961
                        $pval{'terminalurl'} = $terminalurl;
2962
                        $pval{'externalip'} = $externalip;
2963 04c16f26 hq
                        $pval{'ports'} = $ports;
2964 95b003ff Origo
                        $pval{'imageuuid'} = $dbimg->{'uuid'};
2965
                        $pval{'imageuuid2'} = $imagereg{$pval{'image2'}}->{'uuid'} if ($pval{'image2'} && $pval{'image2'} ne '--');
2966
                    }
2967
                    push @children,\%val;
2968
                    $pval{'children'} = \@children;
2969
                    $curreg{$sysuuid} = \%pval;
2970
                } else { # This server is not part of a system
2971
                    $sysuuid = $val{'uuid'};
2972
                    my $dbimg = $imagereg{$val{'image'}};
2973
                    $networkuuid1 = $domreg{$dbimg->{'domains'}}->{'networkuuid1'} if ($domreg{$dbimg->{'domains'}});
2974
                    my $externalip;
2975 04c16f26 hq
                    if ($networkreg{$networkuuid1}) {
2976
                        $externalip = $networkreg{$networkuuid1}->{'externalip'};
2977
                        $val{'internalip'} = $networkreg{$networkuuid1}->{'internalip'};
2978
                        $val{'ports'} = $networkreg{$networkuuid1}->{'ports'};
2979
                    }
2980 95b003ff Origo
                    $val{'networkuuid1'} = $networkuuid1;
2981
                    $val{'master'} = $dbimg->{'master'};
2982
                    $val{'appid'} = $dbimg->{'appid'};
2983
                    $val{'version'} = $dbimg->{'version'};
2984
                    $val{'imageuuid'} = $dbimg->{'uuid'};
2985
                    $val{'imageuuid2'} = $imagereg{$val{'image2'}}->{'uuid'} if ($val{'image2'} && $val{'image2'} ne '--' && $imagereg{$val{'image2'}});
2986
2987
                    my $managementurl = $dbimg->{'managementlink'};
2988
                    $managementurl =~ s/\{uuid\}/$networkuuid1/;
2989
                    $managementurl =~ s/\{externalip\}/$externalip/;
2990
                    $val{'managementurl'} = $managementurl;
2991
                    my $upgradeurl;
2992
                    $upgradeurl = $dbimg->{'upgradelink'};
2993
                    $upgradeurl =~ s/\{uuid\}/$networkuuid1/;
2994
                    $val{'upgradeurl'} = $upgradeurl;
2995
                    my $terminalurl;
2996
                    $terminalurl = $dbimg->{'terminallink'};
2997
                    $terminalurl =~ s/\{uuid\}/$networkuuid1/;
2998
                    $val{'terminalurl'} = $terminalurl;
2999
                    $val{'externalip'} = $externalip;
3000
                    $val{'system'} = '--';
3001
3002
                    $curreg{$sysuuid} = \%val;
3003
                }
3004
            }
3005
        }
3006
        tied(%register)->commit;
3007
    }
3008
    untie %imagereg;
3009
3010
    my @regvalues = values %register;
3011
    # Go through systems register, add empty systems and update statuses
3012
    foreach my $valref (@regvalues) {
3013
    # Only include items belonging to current user (or all users if specified and user is admin)
3014
        if ($username eq $valref->{'user'} || $fulllist) {
3015
            next unless (!$curuuid || $valref->{'uuid'} eq $curuuid);
3016
3017
            my %val = %{$valref};
3018
            # add empty system (must be empty since not included from going through servers
3019
            if (!($curreg{$val{'uuid'}})) {
3020
                $val{'issystem'} = 1;
3021
                $val{'status'} = 'inactive';
3022
                $curreg{$val{'uuid'}} = \%val;
3023
            } else {
3024
            # Update status
3025
                my $status = 'running';
3026 d24d9a01 hq
                my $externalips = 0;
3027 95b003ff Origo
                foreach my $child (@{$curreg{$val{'uuid'}}-> {'children'}}) {
3028
                    $status = $child->{'status'} unless ($child->{'status'} eq $status);
3029 d24d9a01 hq
                    $externalips += $child->{'externalips'} unless ($child->{'externalips'} eq '');
3030 95b003ff Origo
                }
3031
                $status = 'degraded' unless ($status eq 'running' || $status eq 'shutoff');
3032
                $curreg{$val{'uuid'}}->{'status'} = $status;
3033 d24d9a01 hq
                $curreg{$val{'uuid'}}->{'externalips'} = $externalips;
3034 322b9953 hq
                # $networkreg{$domreg{$curdomuuid}->{'networkuuid1'}}->{'internalip'};
3035
                if ($curuuid && !$curreg{$val{'uuid'}}->{'internalip'}) { # Add calling server's own internalip if it's part of an ad-hoc assembled system
3036
                    $curreg{$val{'uuid'}}->{'internalip'} = $networkreg{$domreg{$curdomuuid}->{'networkuuid1'}}->{'internalip'};
3037
                }
3038 95b003ff Origo
            }
3039
        }
3040
    }
3041 322b9953 hq
    untie %networkreg;
3042 95b003ff Origo
3043
    @curregvalues = values %curreg;
3044 2a63870a Christian Orellana
    my @sorted_systems = sort {$a->{'name'} cmp $b->{'name'}} @curregvalues;
3045
    @sorted_systems = sort {$a->{'status'} cmp $b->{'status'}} @sorted_systems;
3046 95b003ff Origo
3047
    if ($action eq 'tablelist') {
3048
        my $t2 = Text::SimpleTable->new(40,24,14);
3049
3050
        $t2->row('uuid', 'name', 'user');
3051
        $t2->hr;
3052
        my $pattern = $options{m};
3053
        foreach $rowref (@sorted_systems){
3054
            if ($pattern) {
3055
                my $rowtext = $rowref->{'uuid'} . " " . $rowref->{'name'} . " " . $rowref->{'user'};
3056
                next unless ($rowtext =~ /$pattern/i);
3057
            }
3058
            $t2->row($rowref->{'uuid'}, $rowref->{'name'}||'--', $rowref->{'user'}||'--');
3059
        }
3060
        return $t2->draw;
3061
    } elsif ($action eq 'removeusersystems') {
3062
        return @sorted_systems;
3063
    } elsif ($action eq 'arraylist') {
3064
        return @sorted_systems;
3065
    } elsif ($console) {
3066
        return Dumper(\@sorted_systems);
3067
    } else {
3068
        my %it = ('uuid','--','name','--', 'issystem', 1);
3069
        push(@sorted_systems, \%it) if ($action eq 'flatlist');
3070
        my $json_text = to_json(\@sorted_systems, {pretty => 1});
3071
        $json_text =~ s/"false"/false/g;
3072
        $json_text =~ s/"true"/true/g;
3073
#        $json_text =~ s/""/"--"/g;
3074
        $json_text =~ s/null/"--"/g;
3075
        $json_text =~ s/\x/ /g;
3076
        if ($action eq 'flatlist') {
3077
            return qq|{"identifier": "uuid", "label": "name", "items": $json_text}|;
3078
        } else {
3079
            return $json_text;
3080
        }
3081
    }
3082
}
3083
3084
# Build a complete system around cloned image
3085
sub buildSystem {
3086
    my ($name, $hmaster, $hstoragepool, $hsystem, $hinstances,
3087 a93267ad hq
        $hnetworkuuid1, $hbschedule, $hnetworktype1, $hports, $hmemory, $hvcpu, $hvmemory, $hvgpu, $hdiskbus,
3088 95b003ff Origo
        $hcdrom, $hboot, $hnicmodel1, $hnicmac1, $hnetworkuuid2, $hnicmac2, $hmonitors,
3089 04c16f26 hq
        $hmanagementlink, $hstart, $duuid, $hstoragepool2, $hloader ) = @_;
3090 95b003ff Origo
3091
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {$postreply = "Unable to access domain register"; return $postreply;};
3092
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$postreply = "Unable to access image register"; return $postreply;};
3093
3094
    my $master = $hmaster;
3095
3096
    if ($curuuid && !$domreg{$curuuid} && $duuid) { # curuuid is a system uuid
3097
        $curuuid = $duuid;
3098
    }
3099
3100
    if (!$master && $curuuid && $domreg{$curuuid} && $imagereg{$domreg{$curuuid}->{image}}) {
3101
        $master = $imagereg{$domreg{$curuuid}->{image}}->{master};
3102
    }
3103
    my $cdrom = $hcdrom;
3104
    my $storagepool = $hstoragepool;
3105 c899e439 Origo
    my $storagepool2 = $hstoragepool2 || '0';
3106 04c16f26 hq
    my $loader = $hloader || 'bios';
3107 95b003ff Origo
    my $image2;
3108
    $hinstances = 1 unless ($hinstances);
3109
    my $ioffset = 0;
3110
    if (!$name && $curuuid) {
3111
        $ioffset = 1; # Looks like we are called from an existing server - bump
3112
        $name = $domreg{$curuuid}->{'name'};
3113
        $name = $1 if ($name =~ /(.+)\.\d+$/);
3114
        foreach my $dom (values %domreg) { # Sequential naming of related systems
3115
            if ($dom->{'user'} eq $user && $dom->{'name'} =~ /$name\.(\d+)$/) {
3116
                $ioffset = $1+1 if ($1 >= $ioffset);
3117
            }
3118
        }
3119
    }
3120
    if ($master && !$imagereg{"$master"}) {
3121
    # Try to look up master based on file name
3122
        my @spoolpaths = $cfg->param('STORAGE_POOLS_LOCAL_PATHS');
3123
        my @users = ('common', $user);
3124
        foreach my $u (@accounts) {push @users,$u;};
3125
        # Include my sponsors master images
3126
        my $billto = $userreg{$user}->{'billto'};
3127
        push @users, $billto if ($billto);
3128
        # Also include my subusers' master images
3129
        my @userregkeys = (tied %userreg)->select_where("billto = '$user'");
3130
        push @users, @userregkeys if (@userregkeys);
3131
3132
        my $match;
3133
        foreach my $u (@users) {
3134
            foreach $sp (@spoolpaths) {
3135
                if ($imagereg{"$sp/$u/$master"}) {
3136
                    $master = "$sp/$u/$master";
3137
                    $match = 1;
3138
                    last;
3139
                }
3140
            }
3141
            last if ($match),
3142
        }
3143
    }
3144
3145
    if (!$imagereg{$master} && length $master == 36) {
3146
    # Try to look up master by uuid
3147
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {$postreply = "Unable to access image register"; return $postreply;};
3148
        $master = $imagereg2{$master}->{'path'} if ($imagereg2{$master});
3149
        untie %imagereg2;
3150
    }
3151
3152
    if (!$master && $curuuid) {
3153
        $master = $imagereg{$domreg{$curuuid}->{'image'}}->{'master'};
3154
    }
3155
3156
    unless ($imagereg{$master}) {$postreply = "Status=Error Invalid master $master"; return $postreply;};
3157 9de5a3f1 hq
    my $masterimage2 = $imagereg{$master}->{'image2'};
3158 95b003ff Origo
    my $sysuuid = $hsystem;
3159
3160
    if ($cdrom && $cdrom ne '--' && !$imagereg{"$cdrom"}) {
3161
    # Try to look up cdrom based on file name
3162
        my @spoolpaths = $cfg->param('STORAGE_POOLS_LOCAL_PATHS');
3163
        my @users = ('common', $user);
3164
        foreach my $u (@accounts) {push @users,$u;};
3165
        my $match;
3166
        foreach my $u (@users) {
3167
            foreach $sp (@spoolpaths) {
3168
                if ($imagereg{"$sp/$u/$cdrom"}) {
3169
                    $cdrom = "$sp/$u/$cdrom";
3170
                    $match = 1;
3171
                    last;
3172
                }
3173
            }
3174
            last if ($match),
3175
        }
3176
    }
3177
3178
    #open OUTPUT, '>', "/dev/null"; select OUTPUT;
3179
    $Stabile::Images::console = 1;
3180
    require "$Stabile::basedir/cgi/images.cgi";
3181
    $Stabile::Networks::console = 1;
3182
    require "$Stabile::basedir/cgi/networks.cgi";
3183
    $Stabile::Servers::console = 1;
3184
    require "$Stabile::basedir/cgi/servers.cgi";
3185
3186
    #close(OUTPUT); select STDOUT;
3187
    # reset stdout to be the default file handle
3188
    my $oipath; # This var stores admin servers image, if only one server initially
3189
    if ($sysuuid eq 'new') {
3190
        $sysuuid = '';
3191
    } elsif ($sysuuid eq 'auto' || (!$sysuuid && $curuuid)) { # $curuuid means request is coming from a running vm
3192
        my $domuuid = $curuuid || Stabile::Networks::ip2domain( $ENV{'REMOTE_ADDR'} );
3193
        if ($domuuid && $domreg{$domuuid}) {
3194
            if ($domreg{$domuuid}->{'system'}) {
3195
                $sysuuid = $domreg{$domuuid}->{'system'};
3196
            } else {
3197
                my $ug = new Data::UUID;
3198
                $sysuuid = $ug->create_str();
3199
                #$sysuuid = $domuuid; # Make sysuuid same as primary domains uuid
3200
                $domreg{$domuuid}->{'system'} = $sysuuid;
3201
                $oipath = $domreg{$domuuid}->{'image'};
3202
            }
3203
        } else {
3204
            $sysuuid = '';
3205
        }
3206
    }
3207
3208
    # Check if images should be moved to node storage
3209
    if ($storagepool eq "-1") {
3210
        if (index($privileges, 'n')==-1 && !$isadmin) {
3211
            $storagepool = '';
3212
        } else {
3213
            $storagepool = -1;
3214
            # %nodereg is needed in order to increment reservedvcpus for nodes
3215
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) ) {$postreply = "Unable to access node register"; return $postreply;};
3216
        }
3217
    }
3218
3219
    my @domains;
3220
    my $systemuuid;
3221
    for (my $i=$ioffset; $i<$hinstances+$ioffset; $i++) {
3222
        my $ipath;
3223
        my $mac;
3224
        my $res;
3225
        my $istr = ".$i";
3226
        $istr = '' if ($hinstances==1 && $i==0);
3227
3228
    # Clone image
3229
        my $imagename = $name;
3230
        $imagename =~ s/system/Image/i;
3231 c899e439 Origo
        $res = Stabile::Images::Clone($master, 'clone', '', $storagepool, '', "$imagename$istr", $hbschedule, 1, $hmanagementlink, $appid, 1, $hvcpu, $hmemory);
3232 95b003ff Origo
        $postreply .= $res;
3233
        if ($res =~ /path: (.+)/) {
3234
            $ipath = $1;
3235
        } else {
3236
            next;
3237
        }
3238
        $mac = $1 if ($res =~ /mac: (.+)/);
3239
        Stabile::Images::updateBilling();
3240
3241
        # Secondary image - clone it
3242
        if ($masterimage2 && $masterimage2 ne '--' && $masterimage2 =~ /\.master\.qcow2$/) {
3243 c899e439 Origo
            $res = Stabile::Images::Clone($masterimage2, 'clone', '', $storagepool2, $mac, "$imagename$istr-data", $hbschedule, 1, '', '', 1);
3244 95b003ff Origo
            $postreply .= $res;
3245
            $image2 = $1 if ($res =~ /path: (.+)/);
3246
        }
3247
3248
    # Create network
3249
        my $networkuuid1;
3250
        if ($hnetworkuuid1) { # An existing network was specified
3251
            $networkuuid1 = $hnetworkuuid1;
3252
        } else { # Create new network
3253
            my $networkname = $name;
3254
            $networkname =~ s/system/Connection/i;
3255 d3d1a2d4 Origo
            my $type = ($i==0)?$hnetworktype1 : '';
3256 95b003ff Origo
            if (!$type) {
3257 d3d1a2d4 Origo
                if ($curuuid && $i==0) { # This should never be true, leaving for now...
3258 95b003ff Origo
                    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {$postreply = "Unable to access networks register"; return $postreply;};
3259
                    $type = $networkreg{$domreg{$curuuid}->{'networkuuid1'}}->{'type'};
3260
                    untie %networkreg;
3261
                } else {
3262
                    $type = 'internalip';
3263
                }
3264
            }
3265
            $main::syslogit->($user, 'info', "saving network $networkname$istr");
3266
            $res = Stabile::Networks::save('', '', "$networkname$istr", 'new', $type, '','',$hports,1,$user);
3267
            $postreply .= $res;
3268
            if ($res =~ /uuid: (.+)/) {
3269
                $networkuuid1 = $1;
3270
            } else {
3271
                next;
3272
            }
3273 a2e0bc7e hq
            if ($hstart) {
3274
                Stabile::Networks::Activate($networkuuid1, 'activate'); # Ugly hack, seems to be needed
3275
            }
3276 95b003ff Origo
        }
3277
3278
    # Create server
3279
        my $servername = $name;
3280
        $servername =~ s/system/Server/i;
3281
        if ($curuuid) {
3282
            $hmemory = $hmemory || $domreg{$curuuid}->{'memory'};
3283
            $hvcpu = $hvcpu || $domreg{$curuuid}->{'vcpu'};
3284 a93267ad hq
            $hvmemory = $hmemory || $domreg{$curuuid}->{'vmemory'};
3285
            $hvgpu = $hvcpu || $domreg{$curuuid}->{'vgpu'};
3286 95b003ff Origo
            $hdiskbus = $hdiskbus || $domreg{$curuuid}->{'diskbus'};
3287
            $cdrom = $cdrom || $domreg{$curuuid}->{'cdrom'};
3288
            $hboot = $hboot || $domreg{$curuuid}->{'boot'};
3289
            $hnicmodel1 = $hnicmodel1 || $domreg{$curuuid}->{'nicmodel1'};
3290
        }
3291
3292
        $main::syslogit->($user, 'info', "saving server $servername$istr");
3293
        $res =  Stabile::Servers::Save('', '', {
3294
                 uuid => '',
3295
                 name => "$servername$istr",
3296
                 memory => $hmemory,
3297
                 vcpu => $hvcpu,
3298 a93267ad hq
                 vmemory => $hvmemory+0,
3299
                 vgpu => $hvgpu+0,
3300 95b003ff Origo
                 image => $ipath,
3301
                 imagename => '',
3302
                 image2 => $image2,
3303
                 image2name => '',
3304
                 diskbus => $hdiskbus,
3305
                 cdrom => $cdrom,
3306
                 boot => $hboot,
3307 04c16f26 hq
                 loader => $loader,
3308 95b003ff Origo
                 networkuuid1 => $networkuuid1,
3309
                 networkid1 => '',
3310
                 networkname1 => '',
3311
                 nicmodel1 => $hnicmodel1,
3312
                 nicmac1 => $hnicmac1,
3313
                 nicmac2 => $hnicmac2,
3314
                 status => 'new',
3315
                 notes => $notes,
3316
                 system => $sysuuid,
3317
                 newsystem => ($hinstances>1 && !$sysuuid),
3318
                 buildsystem => 1,
3319
                 console => 1
3320
             });
3321
3322 48fcda6b Origo
        $postreply .= "$res\n";
3323 3657de20 Origo
        $sysuuid = $1 if ($res =~ /sysuuid: (\S+)/);
3324 95b003ff Origo
        my $serveruuid;
3325 3657de20 Origo
        $serveruuid = $1 if ($res =~ /uuid: (\S+)/);
3326 95b003ff Origo
        my $sys = $register{$sysuuid};
3327
        if ($sysuuid && $i==$ioffset) {
3328
            $register{$sysuuid} = {
3329
                uuid => $sysuuid,
3330
                name => $sys->{'name'} || $servername, #Don't rename existing system
3331
                user => $user,
3332
                image => $sys->{'image'} || $oipath || $ipath, #Don't update admin image for existing system
3333
                created => $current_time
3334
            };
3335
        }
3336 a93267ad hq
    if (!$domreg{$serveruuid}) {
3337
        $postreply .= "Status=ERROR There was a problem creating server.\n";
3338
        return $postreply;
3339
    }
3340 95b003ff Origo
3341
    # Create monitors
3342
        my @monitors = split(",", $hmonitors);
3343
        if (@monitors) {
3344
            $res = addSimpleMonitors($serveruuid, $alertemail, \@monitors);
3345
            if ( $res eq 'OK' ) {
3346
                `/usr/bin/moncmd reset keepstate &`;
3347
                $postreply .= "Status=OK Saved monitors @monitors\n";
3348
            } else {
3349
                $postreply .= "Status=OK Not saving monitors: $res\n";
3350
            }
3351
3352
        }
3353
3354
        if ($serveruuid) {
3355
            unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {$postreply = "Unable to access networks register"; return $postreply;};
3356
            $networkreg{$networkuuid1}->{'domains'} = $serveruuid;
3357
            tied(%networkreg)->commit;
3358
            untie %networkreg;
3359
3360
            push @domains, $serveruuid;
3361
            $imagereg{$ipath}->{'domains'} = $serveruuid;
3362
            $imagereg{$ipath}->{'domainnames'} = "$servername$istr";
3363
            if ($storagepool == -1) {
3364
                # my $mac = $imagereg{$ipath}->{'mac'};
3365
                # Increment reserved vcpus in order for location of target node to spread out
3366
                $postreply .= "Status=OK Cloned image to node $mac: $nodereg{$mac}->{'reservedvcpus'}";
3367
                $nodereg{$mac}->{'reservedvcpus'} += $hvcpu;
3368
                $postreply .= ":$nodereg{$mac}->{'reservedvcpus'}\n";
3369
                tied(%nodereg)->commit;
3370
                if (!$hstart) { # If we are not starting servers, wake up node anyway to perform clone operation
3371
                    if ($nodereg{$mac}->{'status'} eq 'asleep') {
3372
                        require "$Stabile::basedir/cgi/nodes.cgi";
3373
                        $Stabile::Nodes::console = 1;
3374
                        Stabile::Nodes::wake($mac);
3375
                    }
3376
                }
3377
            }
3378
        }
3379
        $systemuuid = (($sysuuid)? $sysuuid : $serveruuid) unless ($systemuuid);
3380
    }
3381
    if ($storagepool == -1) {
3382
        untie %nodereg;
3383
    }
3384
3385
    $postreply .= "Status=OK sysuuid: $systemuuid\n" if ($systemuuid);
3386
    if ($hstart) {
3387
        foreach my $serveruuid (@domains) {
3388
            $postreply .= Stabile::Servers::Start($serveruuid, 'start',{buildsystem=>1});
3389
        }
3390
    } else {
3391
        $main::updateUI->({tab=>'servers', user=>$user, uuid=>$serveruuid, status=>'shutoff'});
3392
    }
3393
    untie %imagereg;
3394
    #if (@domains) {
3395
    #    return to_json(\@domains, {pretty=>1});
3396
    #} else {
3397
        return $postreply;
3398
    #}
3399
}
3400
3401
sub upgradeSystem {
3402
    my $internalip = shift;
3403
3404
    unless (tie %imagereg,'Tie::DBI', { # Needed for ValidateItem
3405
        db=>'mysql:steamregister',
3406
        table=>'images',
3407
        key=>'path',
3408
        autocommit=>0,
3409
        CLOBBER=>3,
3410
        user=>$dbiuser,
3411
        password=>$dbipasswd}) {throw Error::Simple("Stroke=ERROR Image register could not be accessed")};
3412
3413
    my $appid;
3414
    my $appversion;
3415
    my $appname;
3416
    my $master;
3417
    my $progress;
3418
    my $currentversion;
3419
3420
# Locate the system we should upgrade
3421
    if ($internalip) {
3422
        foreach my $network (values %networkreg) {
3423
            if ($internalip =~ /^10\.\d+\.\d+\.\d+/
3424
                && $network->{'internalip'} eq $internalip
3425
                && $network->{'user'} eq $user
3426
            ) {
3427
                $curuuid = $domreg{$network->{'domains'}}->{'uuid'};
3428
                $cursysuuid = $domreg{$curuuid}->{'system'};
3429
                $master = $imagereg{$domreg{$curuuid}->{'image'}}->{'master'};
3430
                $appid = $imagereg{$master}->{'appid'};
3431
                $appversion = $imagereg{$master}->{'version'};
3432
                $appname = $imagereg{$master}->{'name'};
3433
                last;
3434
            }
3435
        }
3436
    }
3437
# Locate the newest version of master image
3438
    my $currentmaster;
3439
    foreach my $imgref (values %imagereg) {
3440
        if ($imgref->{'path'} =~ /\.master\.qcow2$/
3441
            && $imgref->{'path'} !~ /-data\.master\.qcow2$/
3442
            && $imgref->{'appid'} eq $appid
3443
        ) {
3444
            if ($imgref->{'version'} > $currentversion) {
3445
                $currentmaster = $imgref;
3446
                $currentversion = $imgref->{'version'};
3447
            }
3448
        }
3449
    }
3450
# Build list of system members
3451
    my @doms;
3452
    if ($cursysuuid && $register{$cursysuuid}) {
3453
        $register{$cursysuuid}->{'status'} = 'upgrading';
3454
        foreach my $domref (values %domreg) {
3455
            push( @doms, $domref ) if ($domref->{'system'} eq $cursysuuid && $domref->{'user'} eq $user);
3456
        }
3457
    } else {
3458
        push( @doms, $domreg{$curuuid} ) if ($domreg{$curuuid}->{'user'} eq $user);
3459
    }
3460
    $membs = int @doms;
3461
3462
    my $problem = 0;
3463
    foreach my $dom (@doms) {
3464
        if ($dom->{'status'} ne 'running') {
3465
            $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user,
3466
            status=>qq|Server $dom->{name} is not running. All member servers must be running when upgrading an app.|});
3467
            $problem = 1;
3468
            last;
3469
        }
3470
    }
3471
# First dump each servers data to nfs
3472
    unless ($problem) {
3473
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"Already newest version, reinstalling version $currentversion!", title=>'Reinstalling, hold on...'});
3474
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>'Beginning data dump!'});
3475
3476
        my $browser = LWP::UserAgent->new;
3477
        $browser->agent('movepiston/1.0b');
3478
        $browser->protocols_allowed( [ 'http','https'] );
3479
3480
        foreach my $dom (@doms) {
3481
            my $upgradelink = $imagereg{$dom->{'image'}}->{'upgradelink'};
3482
            if ($upgradelink) {
3483
                my $res;
3484
                my $networkuuid1 = $dom->{'networkuuid1'};
3485
                my $ip = $networkreg{$networkuuid1}->{'internalip'};
3486
                $upgradelink = "http://internalip$upgradelink" unless ($upgradelink =~ s/\{internalip\}/$ip/);
3487
                $domreg{$dom->{'uuid'}}->{'status'} = 'upgrading';
3488
                $main::updateUI->({tab=>'servers', user=>$user, uuid=>$dom->{'uuid'}, status=>'upgrading'});
3489
                my $content = $browser->get($upgradelink)->content();
3490
                if ($content =~ /^\{/) { # Looks like json
3491
                    $jres = from_json($content);
3492
                    $res = $jres->{'message'};
3493
                    unless (lc $jres->{'status'} eq 'ok') {
3494
                        $problem = 2;
3495
                    }
3496
                } else { # no json returned, assume things went hayward
3497
                    $res = $content;
3498
                    $res =~ s/</&lt;/g;
3499
                    $res =~ s/>/&gt;/g;
3500
                    $problem = "Data dump failed ($upgradelink)";
3501
                }
3502
                $res =~ s/\n/ /;
3503
                $progress += 10;
3504
                $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"$ip: $res", progress=>$progress});
3505
            }
3506
        }
3507
    }
3508
    tied(%domreg)->commit;
3509
3510
# Shut down all servers
3511
    unless ($problem) {
3512
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>'Beginning shutdown of servers!'});
3513
        require "$Stabile::basedir/cgi/servers.cgi";
3514
        $Stabile::Servers::console = 1;
3515
        foreach my $dom (@doms) {
3516
            $progress += 10;
3517
            my $networkuuid1 = $dom->{'networkuuid1'};
3518
            my $ip = $networkreg{$networkuuid1}->{'internalip'};
3519
            $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"$ip: Shutting down...", progress=>$progress});
3520
            if ($dom->{'status'} eq 'shutoff' || $dom->{'status'} eq 'inactive') {
3521
                next;
3522
            } else {
3523
                my $res = Stabile::Servers::destroyUserServers($user, 1, $dom->{'uuid'});
3524
                if ($dom->{'status'} ne 'shutoff' && $dom->{'status'} ne 'inactive') {
3525
                    $problem = "ERROR $res"; # We could not shut down a server, fail...
3526
                    last;
3527
                }
3528
            }
3529
        }
3530
    }
3531
# Then replace each image with new version
3532
    unless ($problem) {
3533
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>'Attaching new images!'});
3534
        require "$Stabile::basedir/cgi/images.cgi";
3535
        $Stabile::Images::console = 1;
3536
        foreach my $dom (@doms) {
3537
            $progress += 10;
3538
            my $networkuuid1 = $dom->{'networkuuid1'};
3539
            my $ip = $networkreg{$networkuuid1}->{'internalip'};
3540
            $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"$ip: Attaching image...", progress=>$progress});
3541
            my $image = $imagereg{$dom->{'image'}};
3542
            my $ipath;
3543
            # Clone image
3544
            my $imagename = $image->{'name'};
3545
            my $res = Stabile::Images::Clone($currentmaster->{'path'}, 'clone', '', $image->{'storagepool'}, '', $imagename, $image->{'bschedule'}, 1, $currentmaster->{'managementlink'}, $appid, 1);
3546
            $postreply .= $res;
3547
            if ($res =~ /path: (.+)/) {
3548
                $ipath = $1;
3549
            } else {
3550
                $problem = 5;
3551
            }
3552
3553
            if ($ipath =~ /\.qcow2$/) {
3554
                Stabile::Images::updateBilling();
3555
                # Attach new image to server
3556
                $main::syslogit->($user, 'info', "attaching new image to server $dom->{'name'} ($dom->{'uuid'})");
3557
                $res =  Stabile::Servers::Save({
3558
                         uuid => $dom->{'uuid'},
3559
                         image => $ipath,
3560
                         imagename => $imagename,
3561
                     });
3562
                # Update systems admin image
3563
                $register{$cursysuuid}->{'image'} = $ipath if ($register{$cursysuuid} && $dom->{'uuid'} eq $curuuid);
3564
                # Update image properties
3565
                $imagereg{$ipath}->{'domains'} = $dom->{'uuid'};
3566
                $imagereg{$ipath}->{'domainnames'} = $dom->{'name'};
3567
            } else {
3568
                $problem = 6;
3569
            }
3570
        }
3571
    }
3572
3573
# Finally start all servers with new image
3574
    unless ($problem) {
3575
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>'Starting servers!'});
3576
        require "$Stabile::basedir/cgi/servers.cgi";
3577
        $Stabile::Servers::console = 1;
3578
        foreach my $dom (@doms) {
3579
            $progress += 10;
3580
            my $networkuuid1 = $dom->{'networkuuid1'};
3581
            my $ip = $networkreg{$networkuuid1}->{'internalip'};
3582
            $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"$ip: Starting...", progress=>$progress});
3583
            if ($dom->{'status'} eq 'shutoff' || $dom->{'status'} eq 'inactive') {
3584
                Stabile::Servers::Start($dom->{'uuid'}, 'start', {uistatus=>'upgrading'});
3585
                $main::updateUI->({ tab=>'servers',
3586
                                    user=>$user,
3587
                                    uuid=>$dom->{'uuid'},
3588
                                    status=>'upgrading'})
3589
            }
3590
        }
3591
    } else {
3592
        foreach my $dom (@doms) {
3593
            $dom->{'status'} = 'inactive'; # Prevent servers from being stuck in upgrading status
3594
        }
3595
    }
3596
3597
    my $nlink = $imagereg{$doms[0]->{'image'}}->{'managementlink'}; # There might be a new managementlink for image
3598
    my $nuuid = $doms[0]->{'networkuuid1'};
3599
    $nlink =~ s/\{uuid\}/$nuuid/;
3600
3601
    unless ($problem) {
3602
# All servers successfully upgraded
3603
        my $status = qq|Your $appname app has exported its data and new images have been attached to your servers. Now, your app will start again and import your data.|;
3604
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, progress=>100, status=>$status, managementlink=>$nlink, message=>"All done!"});
3605
    } else {
3606
        my $status = qq|Problem: $problem encountered. Your $appname could not be upgraded to the version $appversion. You can try again, or contact the app developer if this fails.|;
3607
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, progress=>100, status=>$status, managementlink=>$nlink, message=>"Something went wrong :("});
3608
    }
3609
    untie %imagereg;
3610
3611
    my $reply = qq|{"message": "Upgrading $domreg{$curuuid}->{name} with $membs members"}|;
3612
    return "$reply\n";
3613
}
3614
3615
sub removeusersystems {
3616
    my $username = shift;
3617 6372a66e hq
    return $postreply unless (($isadmin || $user eq $username) && !$isreadonly);
3618 95b003ff Origo
    $user = $username;
3619
    my @allsystems = getSystemsListing('removeusersystems');
3620
    foreach my $sys (@allsystems) {
3621
        next unless $sys->{'uuid'};
3622 6372a66e hq
#        $postreply .= "Status=OK Removing $username system $sys->{'name'} ($sys->{'uuid'})\n";
3623 95b003ff Origo
        remove($sys->{'uuid'}, $sys->{'issystem'}, 1);
3624
    }
3625
    return $postreply || "[]";
3626
}
3627
3628
3629
# Remove every trace of a system including servers, images, etc.
3630
sub remove {
3631
    my ($uuid, $issystem, $destroy) = @_;
3632
    my $sysuuid = $uuid;
3633
    my $reguser = $register{$uuid}->{'user'} if ($register{$uuid});
3634
    $reguser = $domreg{$uuid}->{'user'} if (!$reguser && $domreg{$uuid});
3635
3636
    $Stabile::Images::user = $user;
3637
    require "$Stabile::basedir/cgi/images.cgi";
3638
    $Stabile::Images::console = 1;
3639
3640
    $Stabile::Networks::user = $user;
3641
    require "$Stabile::basedir/cgi/networks.cgi";
3642
    $Stabile::Networks::console = 1;
3643
3644
    $Stabile::Servers::user = $user;
3645
    require "$Stabile::basedir/cgi/servers.cgi";
3646
    $Stabile::Servers::console = 1;
3647
3648
    $issystem = 1 if ($register{$uuid});
3649
    my @domains;
3650
    my $res;
3651
3652
    if ($issystem) {
3653
    # Delete child servers
3654
        if (($user eq $reguser || $isadmin) && $register{$uuid}){ # Existing system
3655 d3d1a2d4 Origo
        # First delete any linked networks
3656
            if ($register{$uuid}->{'networkuuids'} && $register{$uuid}->{'networkuuids'} ne '--') {
3657
                my @lnetworks = split /, ?/, $register{$uuid}->{'networkuuids'};
3658
                foreach my $networkuuid (@lnetworks) {
3659
                    if ($networkuuid) {
3660
                        Stabile::Networks::Deactivate($networkuuid);
3661
                        $res .= Stabile::Networks::Remove($networkuuid, 'remove', {force=>1});
3662
                    }
3663
                }
3664
            }
3665 95b003ff Origo
            foreach my $domvalref (values %domreg) {
3666
                if ($domvalref->{'system'} eq $uuid && ($domvalref->{'user'} eq $user || $isadmin)) {
3667
                    if ($domvalref->{'status'} eq 'shutoff' || $domvalref->{'status'} eq 'inactive') {
3668
                        push @domains, $domvalref->{'uuid'};
3669
                    } elsif ($destroy) {
3670
                        Stabile::Servers::destroyUserServers($reguser, 1, $domvalref->{'uuid'});
3671
                        push @domains, $domvalref->{'uuid'} if ($domvalref->{'status'} eq 'shutoff' || $domvalref->{'status'} eq 'inactive');
3672
                    }
3673
                }
3674
            }
3675
        }
3676
        $postreply .= "Status=removing OK Removing system $register{$uuid}->{'name'} ($uuid)\n";
3677
        delete $register{$uuid};
3678
        tied(%register)->commit;
3679
    } elsif ($domreg{$uuid} && $domreg{$uuid}->{uuid}) {
3680
    # Delete single server
3681
        if ($domreg{$uuid}->{'status'} eq 'shutoff' || $domreg{$uuid}->{'status'} eq 'inactive') {
3682
            push @domains, $uuid;
3683
        } elsif ($destroy) {
3684 54401133 hq
            Stabile::Servers::destroyUserServers($reguser, 1, $uuid);
3685 95b003ff Origo
            push @domains, $uuid if ($domreg{$uuid}->{'status'} eq 'shutoff' || $domreg{$uuid}->{'status'} eq 'inactive');
3686
        }
3687
     #   $postreply .= "Status=OK Removing server $domreg{$uuid}->{'name'} ($uuid)\n";
3688
    } else {
3689
        $postreply .= "Status=Error System $uuid not found\n";
3690
        return $postreply;
3691
    }
3692
    my $duuid;
3693
    foreach my $domuuid (@domains) {
3694
        if ($domreg{$domuuid}->{'status'} ne 'shutoff' && $domreg{$domuuid}->{'status'} ne 'inactive' ) {
3695
            $postreply .= "Status=ERROR Cannot delete server (active)\n";
3696
        } else {
3697
            my $imagepath = $domreg{$domuuid}->{'image'};
3698
            my $image2path = $domreg{$domuuid}->{'image2'};
3699
            my $networkuuid1 = $domreg{$domuuid}->{'networkuuid1'};
3700
            my $networkuuid2 = $domreg{$domuuid}->{'networkuuid2'};
3701
3702
            # Delete packages from software register
3703
        #    $postreply .= deletePackages($domuuid);
3704
            # Delete monitors
3705
        #    $postreply .= deleteMonitors($domuuid)?"Stream=OK Deleted monitors for $domreg{$domuuid}->{'name'}\n":"Stream=OK No monitors to delete for $domreg{$domuuid}->{'name'}\n";
3706
            # Delete server
3707
            $res .= Stabile::Servers::Remove($domuuid);
3708
3709
            # Delete images
3710
            $res .= Stabile::Images::Remove($imagepath);
3711
            if ($image2path && $image2path ne '--') {
3712
                $res .= Stabile::Images::Remove($image2path);
3713
            }
3714
            # Delete networks
3715
            if ($networkuuid1 && $networkuuid1 ne '--' && $networkuuid1 ne '0' && $networkuuid1 ne '1') {
3716
                Stabile::Networks::Deactivate($networkuuid1);
3717
                $res .= Stabile::Networks::Remove($networkuuid1);
3718
            }
3719
            if ($networkuuid2 && $networkuuid2 ne '--' && $networkuuid2 ne '0' && $networkuuid2 ne '1') {
3720
                Stabile::Networks::Deactivate($networkuuid2);
3721
                $res .= Stabile::Networks::Remove($networkuuid2);
3722
            }
3723
        }
3724
        $duuid = $domuuid;
3725
    }
3726 6fdc8676 hq
    if ($register{$uuid}) {
3727
        delete $register{$uuid};
3728
        tied(%register)->commit;
3729
    }
3730 95b003ff Origo
    if (@domains) {
3731
        $main::updateUI->(
3732
                        {tab=>'servers',
3733
                        user=>$user,
3734
                        type=>'update',
3735 2a63870a Christian Orellana
                        message=>((scalar @domains==1)?"Server has been removed":"Stack has been removed!")
3736 95b003ff Origo
                        },
3737
                        {tab=>'images',
3738
                        user=>$user
3739
                        },
3740
                        {tab=>'networks',
3741
                        user=>$user
3742
                        },
3743
                        {tab=>'home',
3744
                        user=>$user,
3745
                        type=>'removal',
3746
                        uuid=>$uuid,
3747
                        domuuid=>$duuid
3748
                        }
3749
                    );
3750
    } else {
3751
        $main::updateUI->(
3752
                        {tab=>'servers',
3753
                        user=>$user,
3754
                        type=>'update',
3755
                        message=>"Nothing to remove!"
3756
                        }
3757
                    );
3758
    }
3759 6fdc8676 hq
3760 95b003ff Origo
    if ($engineid && $enginelinked) {
3761
        # Remove domain from origo.io
3762
        my $json_text = qq|{"uuid": "$sysuuid" , "status": "delete"}|;
3763
        $main::postAsyncToOrigo->($engineid, 'updateapps', "[$json_text]");
3764
    }
3765 6fdc8676 hq
    return $postreply || qq|Content-type: application/json\n\n|;
3766 95b003ff Origo
}
3767
3768
sub getPackages {
3769
    my $curimg = shift;
3770
3771
    unless (tie %imagereg,'Tie::DBI', { # Needed for ValidateItem
3772
        db=>'mysql:steamregister',
3773
        table=>'images',
3774
        key=>'path',
3775
        autocommit=>0,
3776
        CLOBBER=>0,
3777
        user=>$dbiuser,
3778
        password=>$dbipasswd}) {throw Error::Simple("Stroke=ERROR Image register could not be accessed")};
3779
3780
    my $mac = $imagereg{$curimg}->{'mac'};
3781
    untie %imagereg;
3782
3783
    my $macip;
3784
    if ($mac && $mac ne '--') {
3785
        unless (tie %nodereg,'Tie::DBI', {
3786
            db=>'mysql:steamregister',
3787
            table=>'nodes',
3788
            key=>'mac',
3789
            autocommit=>0,
3790
            CLOBBER=>1,
3791
            user=>$dbiuser,
3792
            password=>$dbipasswd}) {return 0};
3793
        $macip = $nodereg{$mac}->{'ip'};
3794
        untie %nodereg;
3795
    }
3796
    $curimg =~ /(.+)/; $curimg = $1;
3797
    my $sshcmd;
3798
    if ($macip && $macip ne '--') {
3799
        $sshcmd = "/usr/bin/ssh -q -l irigo -i /var/www/.ssh/id_rsa_www -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $macip";
3800
    }
3801
    my $apps;
3802
3803
    if ($sshcmd) {
3804
        my $cmd = qq[eval \$(/usr/bin/guestfish --ro -a "$curimg" --i --listen); ]; # sets $GUESTFISH_PID shell var
3805
        $cmd .= qq[root="\$(/usr/bin/guestfish --remote inspect-get-roots)"; ];
3806
        $cmd .= qq[guestfish --remote inspect-get-product-name "\$root"; ];
3807
        $cmd .= qq[guestfish --remote inspect-get-hostname "\$root"; ];
3808
        $cmd .= qq[guestfish --remote inspect-list-applications "\$root"; ];
3809
        $cmd .= qq[guestfish --remote exit];
3810
        $cmd = "$sshcmd '$cmd'";
3811
        $apps = `$cmd`;
3812
    } else {
3813
        my $cmd;
3814
#        my $pid = open my $cmdpipe, "-|",qq[/usr/bin/guestfish --ro -a "$curimg" --i --listen];
3815
            $cmd .= qq[eval \$(/usr/bin/guestfish --ro -a "$curimg" --i --listen); ];
3816
        # Start listening guestfish
3817
        my $daemon = Proc::Daemon->new(
3818
                work_dir => '/usr/local/bin',
3819
                setuid => 'www-data',
3820
                exec_command => $cmd
3821
            ) or do {$posterror .= "Stream=ERROR $@\n";};
3822
        my $pid = $daemon->Init();
3823
        while ($daemon->Status($pid)) {
3824
            sleep 1;
3825
        }
3826
        # Find pid of the listening guestfish
3827
        my $pid2;
3828
        my $t = new Proc::ProcessTable;
3829
        foreach $p ( @{$t->table} ){
3830
            my $pcmd = $p->cmndline;
3831
            if ($pcmd =~ /guestfish.+$curimg/) {
3832
                $pid2 = $p->pid;
3833
                last;
3834
            }
3835
        }
3836
        my $cmd2;
3837
        if ($pid2) {
3838
            $cmd2 .= qq[root="\$(/usr/bin/guestfish --remote=$pid2 inspect-get-roots)"; ];
3839
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-get-product-name "\$root"; ];
3840
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-get-hostname "\$root"; ];
3841
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-list-applications "\$root"; ];
3842
            $cmd2 .= qq[guestfish --remote=$pid2 exit];
3843
        }
3844
        $apps = `$cmd2`;
3845
        $apps .= $cmd2;
3846
    }
3847
    return $apps;
3848
}