Project

General

Profile

Download (160 KB) Statistics
| Branch: | Revision:
1
#!/usr/bin/perl
2

    
3
# All rights reserved and Copyright (c) 2020 Origo Systems ApS.
4
# This file is provided with no warranty, and is subject to the terms and conditions defined in the license file LICENSE.md.
5
# The license file is part of this source code package and its content is also available at:
6
# https://www.origo.io/info/stabiledocs/licensing/stabile-open-source-license
7

    
8
package Stabile::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
use Geo::IP;
28

    
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
my ($datanic, $extnic) = $main::getNics->();
36
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
    if ($action =~ /updateaccountinfo|monitors|listuptime|buildsystem|removeusersystems|updateengineinfo|^register$|^packages$|downloadmaster/) {
82
        $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
        my @props = qw(uuid name memory vcpu  user  notes  created  opemail  opfullname  opphone  email  fullname  phone  services
97
            recovery  alertemail  image  networkuuid1  internalip autostart issystem system systemstatus from to
98
            appid callback installsystem installaccount networkuuids ports);
99
        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
    *Snapshot = \&systemAction;
127
    *Unsnap  = \&systemAction;
128

    
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
    *do_snapshot = \&privileged_action;
141
    *do_unsnap = \&privileged_action;
142
    *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
    *do_downloadmaster = \&privileged_action;
156

    
157
    *do_gear_backup = \&do_gear_action;
158
    *do_gear_snapshot = \&do_gear_action;
159
    *do_gear_unsnap = \&do_gear_action;
160
    *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
    *do_gear_downloadmaster = \&do_gear_action;
175
    *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
GET:uuid:
248
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
PUT:uuid, name, servers, memory, vcpu, fullname, email, phone, opfullname, opemail, opphone, alertemail, services, recovery, notes, networkuuids:
578
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
END
583
    }
584

    
585
    my $name = $obj->{"name"};
586
    my $memory = $obj->{"memory"};
587
    my $vcpu = $obj->{"vcpu"};
588
    my $reguser;
589
    $reguser = $register{$uuid}->{'user'} if ($register{$uuid});
590
    $console = 1 if ($obj->{'console'});
591
    my $issystem = $obj->{'issystem'} || $register{$uuid};
592
    my $notes = $obj->{"notes"};
593
    my $email = $obj->{'email'};
594
    my $fullname = $obj->{'fullname'};
595
    my $phone = $obj->{'phone'};
596
    my $opemail = $obj->{'opemail'};
597
    my $opfullname = $obj->{'opfullname'};
598
    my $opphone = $obj->{'opphone'};
599
    my $alertemail = $obj->{'alertemail'};
600
    my $services = $obj->{'services'};
601
    my $recovery = $obj->{'recovery'};
602
    my $networkuuids = $obj->{'networkuuids'};
603
    my $ports = $obj->{'ports'};
604
    my $autostart = $obj->{'autostart'};
605
    if (!$name) {
606
        if ($issystem) {
607
            $name = $register{$uuid}->{'name'};
608
        } else {
609
            $name = $domreg{$uuid}->{'name'};
610
        }
611
    }
612
    if ((!$uuid)) {
613
        my $ug = new Data::UUID;
614
        $uuid = $ug->create_str();
615
        $issystem = 1;
616
    };
617
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access domain register"};
618
    unless ($register{$uuid} || $domreg{$uuid}) {
619
        $obj->{'status'} = 'new';
620
        $issystem = 1;
621
    }
622
    $issystem = 1 if ($register{$uuid});
623
    unless (($uuid && length $uuid == 36)) {
624
        $postreply = "Status=Error Invalid UUID\n";
625
        return $postreply;
626
    }
627

    
628
    # Sanity checks
629
    if ($name && length $name > 255) {
630
        $postreply .= "Status=Error Bad data: $name " . (length $name) . "\n";
631
        return $postreply;
632
    };
633

    
634
    if ($issystem) { # We are dealing with a system
635
        # Security check
636
        if (($user eq $reguser || $isadmin) && $register{$uuid}) { # Existing system
637
            my @props = ('name', 'fullname','email','phone','opfullname','opemail','opphone','alertemail'
638
                ,'notes','services','recovery','autostart');
639
            my %oldvals;
640
            foreach my $prop (@props) {
641
                my $val = $obj->{$prop};
642
                if ($val) {
643
                    $val = '' if ($val eq '--');
644
                    $oldvals{$prop} = $register{$uuid}->{$prop} || $userreg{$user}->{$prop};
645
                    if ($val eq $userreg{$user}->{$prop}) {
646
                        $register{$uuid}->{$prop} = ''; # Same val as parent (user val), reset
647
                    } else {
648
                        if ($prop eq 'name' && $obj->{ports}) {
649
                            next; # TODO: ugly hack because we dont know why UTF8 is not handled correctly
650
                        }
651
                        $register{$uuid}->{$prop} = $val;
652
                    }
653
                    if ($prop eq 'autostart') {
654
                        $register{$uuid}->{$prop} = ($val)?'1':'';
655
                    }
656
                    if ($prop eq 'name') {
657
                        my $json_text = qq|{"uuid": "$uuid" , "name": "$name"}|;
658
                        $main::postAsyncToOrigo->($engineid, 'updateapps', "[$json_text]");
659
                    }
660
                }
661
            }
662
            my %childrenhash;
663
            my $alertmatch;
664
            push @props, ('vcpu', 'memory', 'ports');
665
            foreach my $prop (@props) {
666
                my $val = $obj->{$prop};
667
                if ($val) {
668
                    $val = '' if ($val eq '--');
669
                    # Update children
670
                    foreach my $domvalref (values %domreg) {
671
                        if ($domvalref->{'user'} eq $user && $domvalref->{'system'} eq $uuid) {
672
                            my %domval = %{$domvalref};
673
                            my $serveruuid = $domvalref->{'uuid'};
674
                            $childrenhash{$serveruuid} =\%domval unless ($childrenhash{$serveruuid});
675
                            $childrenhash{$serveruuid}->{$prop} = $val;
676
                            if ($prop eq 'autostart') {
677
                                $domvalref->{$prop} = ($val) ? '1' : ''; # Always update child servers with autostart prop
678
                            } elsif ((
679
                                ($obj->{'vcpu'} && $prop eq 'vcpu')
680
                                || ($obj->{'memory'} && $prop eq 'memory')
681
                            ) && $domvalref->{status} eq 'shutoff') {
682
                                $Stabile::Servers::console = 1;
683
                                require "$Stabile::basedir/cgi/servers.cgi";
684
                                $postreply .= Stabile::Servers::Save($serveruuid, 'save',
685
                                    { uuid => $serveruuid, $prop => $obj->{$prop} });
686
                            } elsif ($obj->{'ports'} && $prop eq 'ports') {
687
                                $Stabile::Networks::console = 1;
688
                                require "$Stabile::basedir/cgi/networks.cgi";
689
                                my $networkuuid1 = $domvalref->{'networkuuid1'};
690
                                my $saveobj = {uuid => $networkuuid1};
691
                                $saveobj->{ports} = $ports;
692
                                $postreply .= Stabile::Networks::Deactivate($networkuuid1);
693
                                $postreply .= Stabile::Networks::Save($networkuuid1, 'save', $saveobj);
694
                                $postreply .= Stabile::Networks::Activate($networkuuid1);
695
                            } elsif (!$domvalref->{$prop} || $domvalref->{$prop} eq $oldvals{$prop}) { # Inheritance is implied, so delete redundant entries
696
                                $domvalref->{$prop} = '';
697
                                if ($prop eq 'alertemail') {
698
                                    if (change_monitor_email($serveruuid, $val, $oldvals{$prop})) {
699
                                        $alertmatch = 1;
700
                                    }
701
                                }
702
                            }
703
                        }
704
                    }
705
                }
706
            }
707
            my @children = values %childrenhash;
708
            $obj->{'children'} = \@children if (@children);
709
            $postreply = getSystemsListing();
710
        } elsif ($obj->{'status'} eq 'new')  { # New system
711
            $register{$uuid} = {
712
                uuid=>$uuid,
713
                name=>$name,
714
                user=>$user,
715
                created=>$current_time
716
            };
717
            my $valref = $register{$uuid};
718
            my %val = %{$valref};
719
            $val{'issystem'} = 1;
720
            $val{'status'} = '--';
721
            $dojson = 1;
722
            $postreply = to_json(\%val, {pretty=>1});
723
        } else {
724
            $postreply .= "Status=Error Not enough privileges: $user\n";
725
        }
726
    } else { # We are dealing with a server
727
        my $valref = $domreg{$uuid};
728
        if (!$valref && $obj->{'uuid'}[0]) {$valref = $domreg{ $obj->{'uuid'}[0] }}; # We are dealing with a newly created server
729
        if ($valref && ($valref->{'user'} eq $user || $isadmin)) {
730
            my $system = $obj->{'system'};
731
            my $servername = $obj->{'name'};
732
            if ($servername && $servername ne $valref->{'name'}) {
733
                $valref->{'name'} = $servername;
734
                # Update status of images
735
                my @imgs = ($domreg{$uuid}->{image}, $domreg{$uuid}->{image2}, $domreg{$uuid}->{image3}, $domreg{$uuid}->{image4});
736
                my @imgkeys = ('image', 'image2', 'image3', 'image4');
737
                unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access image register"};
738
                for (my $i=0; $i<4; $i++) {
739
                    my $img = $imgs[$i];
740
                    my $k = $imgkeys[$i];
741
                    if ($img && $img ne '--') {
742
                        $imagereg{$img}->{'domains'} = $uuid;
743
                        $imagereg{$img}->{'domainnames'} = $servername;
744
                    }
745
                }
746
                untie %imagereg;
747
                my $json_text = qq|{"uuid": "$uuid" , "name": "$servername"}|;
748
                $main::postAsyncToOrigo->($engineid, 'updateapps', "[$json_text]");
749
            }
750
            $valref->{'system'} = ($system eq '--'?'':$system) if ($system);
751
            $valref->{'notes'} = (($notes eq '--')?'':$notes) if ($notes);
752
            $valref->{'email'} = ($email eq '--'?'':$email) if ($email);
753
            $valref->{'fullname'} = ($fullname eq '--'?'':$fullname) if ($fullname);
754
            $valref->{'phone'} = ($phone eq '--'?'':$phone) if ($phone);
755
            $valref->{'opemail'} = ($opemail eq '--'?'':$opemail) if ($opemail);
756
            $valref->{'opfullname'} = ($opfullname eq '--'?'':$opfullname) if ($opfullname);
757
            $valref->{'opphone'} = ($opphone eq '--'?'':$opphone) if ($opphone);
758
            $valref->{'services'} = ($services eq '--'?'':$services) if ($services);
759
            $valref->{'recovery'} = ($recovery eq '--'?'':$recovery) if ($recovery);
760
            $valref->{'autostart'} = ($autostart && $autostart ne '--'?'1':'');
761
            if ($alertemail) {
762
                $alertemail = '' if ($alertemail eq '--');
763
                if ($valref->{'alertemail'} ne $alertemail) {
764
                    # If alert email is changed, update monitor if it is configured with this email
765
                    if (change_monitor_email($valref->{'uuid'}, $alertemail, $valref->{'alertemail'})){
766
                        $alertmatch = 1;
767
                        #`/usr/bin/moncmd reset keepstate`;
768
                    }
769
                    $valref->{'alertemail'} = $alertemail;
770
                }
771
            }
772
            if (($vcpu || $memory) && $valref->{status} eq 'shutoff') {
773
                $Stabile::Servers::console = 1;
774
                require "$Stabile::basedir/cgi/servers.cgi";
775
                my $saveobj = {uuid => $valref->{'uuid'}};
776
                $saveobj->{vcpu} = $vcpu if ($vcpu);
777
                $saveobj->{memory} = $memory if ($memory);
778
                $postreply .= Stabile::Servers::Save($valref->{'uuid'}, 'save', $saveobj);
779

    
780
            }
781
            if ($ports) {
782
                $Stabile::Networks::console = 1;
783
                require "$Stabile::basedir/cgi/networks.cgi";
784
                my $networkuuid1 = $valref->{'networkuuid1'};
785
                my $saveobj = {uuid => $networkuuid1};
786
                $saveobj->{ports} = $ports;
787
                $postreply .= Stabile::Networks::Deactivate($networkuuid1);
788
                $postreply .= Stabile::Networks::Save($networkuuid1, 'save', $saveobj);
789
                $postreply .= Stabile::Networks::Activate($networkuuid1);
790

    
791
            }
792
            tied(%domreg)->commit;
793
            $postreply = getSystemsListing(); # Hard to see what else to do, than to send entire table
794
        }
795
    }
796
    if ($networkuuids && $networkuuids ne '--') { # link networks to this system
797
        my @networks = split(/, ?/, $networkuuids);
798
        my @newnetworks = ();
799
        my @newnetworknames = ();
800
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
801
        foreach my $networkuuid (@networks) {
802
            next unless ($networkreg{$networkuuid});
803
            if (
804
                !$networkreg{$networkuuid}->{'domains'} # a network cannot both be linked and in active use
805
                    && (!$networkreg{$networkuuid}->{'systems'} ||  $networkreg{$networkuuid}->{'systems'} eq $uuid) # check if network is already linked to another system
806
            ) {
807
                $networkreg{$networkuuid}->{'systems'} = $uuid;
808
                $networkreg{$networkuuid}->{'systemnames'} = $name;
809
                push @newnetworks, $networkuuid;
810
                push @newnetworknames, $networkreg{$networkuuid}->{'name'};
811
            }
812
        }
813
        if ($issystem && $register{$uuid}) {
814
            $register{$uuid}->{'networkuuids'} = join(", ", @newnetworks);
815
            $register{$uuid}->{'networknames'} = join(", ", @newnetworknames);
816
        } elsif ($domreg{$uuid}) {
817
            $domreg{$uuid}->{'networkuuids'} = join(", ", @newnetworks);
818
            $domreg{$uuid}->{'networknames'} = join(", ", @newnetworknames);
819
        }
820
    }
821
    untie %domreg;
822
    return $postreply;
823
}
824

    
825
sub do_resettoaccountinfo {
826
    my ($uuid, $action, $obj) = @_;
827
    if ($help) {
828
        return <<END
829
GET::
830
Recursively reset contact data for all systems and servers
831
END
832
    }
833
    my @props = ('fullname','email','phone','opfullname','opemail','opphone','alertemail');
834
    my $alertmatch;
835
    foreach my $sysvalref (values %register) {
836
        if ($user eq $sysvalref->{'user'}) {
837
            my $sysuuid = $sysvalref->{'uuid'};
838
            foreach my $prop (@props) {
839
                # Does this system have a value?
840
                if ($sysvalref->{$prop}) {
841
                    $sysvalref->{$prop} = ''; # An empty val refers to parent (user) val
842
                }
843
            }
844
        }
845
    }
846
    # Update domains
847
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {$posterror = "Unable to access domain register"; return;};
848
    foreach my $domvalref (values %domreg) {
849
        if ($domvalref->{'user'} eq $user) {
850
            foreach my $prop (@props) {
851
                if ($domvalref->{$prop}) {
852
                    $domvalref->{$prop} = '';
853
                }
854
                if ($prop eq 'alertemail') {
855
                    if (change_monitor_email($domvalref->{'uuid'}, $userreg{$user}->{$prop})) {
856
                        $alertmatch = 1;
857
                    }
858
                }
859
            }
860
        }
861
    }
862
    tied(%domreg)->commit;
863
    untie %domreg;
864
    #`/usr/bin/moncmd reset keepstate` if ($alertmatch);
865
    $postreply .= "Status=OK OK - reset systems and servers contacts to account values\n";
866
    return $postreply;
867
}
868

    
869
sub do_start_server {
870
    my ($uuid, $action, $obj) = @_;
871
    if ($help) {
872
        return <<END
873
GET:uuid:
874
Start specific server.
875
END
876
    }
877
    $Stabile::Servers::console = 1;
878
    require "$Stabile::basedir/cgi/servers.cgi";
879
    $postreply .= Stabile::Servers::Start($uuid, 'start', { buildsystem => 0 });
880
}
881

    
882
sub systemAction {
883
    my ($uuid, $action, $obj) = @_;
884
    if ($help) {
885
        return <<END
886
GET:uuid:
887
Suspend, resume, start, shutdown, destroy og backup individual servers or servers belonging to a system.
888
END
889
    }
890
    my $issystem = $obj->{'issystem'} || $register{$uuid};
891
    my $reguser;
892
    $reguser = $register{$uuid}->{'user'} if ($register{$uuid});
893

    
894
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
895
    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;};
896

    
897
    if ($issystem) { # Existing system
898
        if (($user eq $reguser || $isadmin) && $register{$uuid}){ # Existing system
899
            my $domactions;
900
            my $imageactions;
901

    
902
            foreach my $domvalref (values %domreg) {
903
                if (($domvalref->{'system'} eq $uuid || $domvalref->{'uuid'} eq $uuid)
904
                    && ($domvalref->{'user'} eq $user || $isadmin)) {
905
                    my $domaction;
906
                    my $imageaction;
907
                    if ($domvalref->{'status'} eq 'paused' && ($action eq 'start' || $action eq 'resume')) {
908
                        $domaction = 'resume';
909
                    } elsif ($domvalref->{'status'} eq 'running' && $action eq 'suspend') {
910
                        $domaction = $action;
911
                    } elsif ($domvalref->{'status'} eq 'shutoff' && $action eq 'start') {
912
                        $domaction = $action;
913
                    } elsif ($domvalref->{'status'} eq 'inactive' && $action eq 'start') {
914
                        $domaction = $action;
915
                    } elsif ($domvalref->{'status'} eq 'running' && $action eq 'shutdown') {
916
                        $domaction = $action;
917
                    } elsif ($domvalref->{'status'} eq 'running' && $action eq 'destroy') {
918
                        $domaction = $action;
919
                    } elsif ($domvalref->{'status'} eq 'shuttingdown' && $action eq 'destroy') {
920
                        $domaction = $action;
921
                    } elsif ($domvalref->{'status'} eq 'destroying' && $action eq 'destroy') {
922
                        $domaction = $action;
923
                    } elsif ($domvalref->{'status'} eq 'starting' && $action eq 'destroy') {
924
                        $domaction = $action;
925
                    } elsif ($domvalref->{'status'} eq 'inactive' && $action eq 'destroy') {
926
                        $domaction = $action;
927
                    } elsif ($domvalref->{'status'} eq 'paused' && $action eq 'destroy') {
928
                        $domaction = $action;
929
                    } elsif ($action eq 'backup' || $action eq 'snapshot' || $action eq 'unsnap') {
930
                        $imageaction = $action;
931
                    }
932
                    if ($domaction) {
933
                        $domactions .= qq/{"uuid":"$domvalref->{'uuid'}","action":"$domaction"},/;
934
                    }
935
                    if ($imageaction) {
936
                        my $image = $domvalref->{'image'};
937
                        if ($imagereg{$image}->{'status'} =~ /used|active/) {
938
                            $imageactions .= qq/{"uuid":"$imagereg{$image}->{'uuid'}","action":"gear_$imageaction"},/;
939
                        }
940
                        my $image2 = $domvalref->{'image2'};
941
                        if ($image2 && $image2 ne '--' && $imagereg{$image2}->{'status'} =~ /used|active/) {
942
                            $imageactions .= qq/{"uuid":"$imagereg{$image2}->{'uuid'}","action":"gear_$imageaction"},/;
943
                        }
944
                        my $image3 = $domvalref->{'image3'};
945
                        if ($image3 && $image3 ne '--' && $imagereg{$image3}->{'status'} =~ /used|active/) {
946
                            $imageactions .= qq/{"uuid":"$imagereg{$image3}->{'uuid'}","action":"gear_$imageaction"},/;
947
                        }
948
                        my $image4 = $domvalref->{'image4'};
949
                        if ($image4 && $image4 ne '--' && $imagereg{$image4}->{'status'} =~ /used|active/) {
950
                            $imageactions .= qq/{"uuid":"$imagereg{$image4}->{'uuid'}","action":"gear_$imageaction"},/;
951
                        }
952
                    }
953
                }
954
            }
955

    
956
            if ($domactions) {
957
                $domactions = substr($domactions,0,-1);
958
                my $uri_action = qq/{"items":[$domactions]}/;
959
                $uri_action = URI::Escape::uri_escape($uri_action);
960
                $uri_action =~ /(.+)/; $uri_action = $1; #untaint
961
                $postreply .= `REMOTE_USER=$user $Stabile::basedir/cgi/servers.cgi -k $uri_action`;
962
            }
963
            if ($imageactions) {
964
                $imageactions = substr($imageactions,0,-1);
965
                my $uri_action = qq/{"items":[$imageactions]}/;
966
                $uri_action = URI::Escape::uri_escape($uri_action);
967
                $uri_action =~ /(.+)/; $uri_action = $1; #untaint
968
                my $cmd = qq|REQUEST_METHOD=POST REMOTE_USER=$user $Stabile::basedir/cgi/images.cgi -k $uri_action|;
969
                $postreply .= `$cmd`;
970
            }
971
            if (!$domactions && !$imageactions) {
972
                $postreply .= "Stream=ERROR $action";
973
            }
974
        }
975
    } else {
976
        if ($action eq 'backup' || $action eq 'snapshot' || $action eq 'unsnap') {
977
            my $image = $domreg{$uuid}->{'image'};
978
            my $imageactions;
979
            if ($imagereg{$image}->{'status'} =~ /used|active/) {
980
                $imageactions .= qq/{"uuid":"$imagereg{$image}->{'uuid'}","action":"gear_$action"},/;
981
            }
982
            my $image2 = $domreg{$uuid}->{'image2'};
983
            if ($image2 && $image2 ne '--' && $imagereg{$image2}->{'status'} =~ /used|active/) {
984
                $imageactions .= qq/{"uuid":"$imagereg{$image2}->{'uuid'}","action":"gear_$action"},/;
985
            }
986
            my $image3 = $domreg{$uuid}->{'image3'};
987
            if ($image3 && $image3 ne '--' && $imagereg{$image3}->{'status'} =~ /used|active/) {
988
                $imageactions .= qq/{"uuid":"$imagereg{$image3}->{'uuid'}","action":"gear_$action"},/;
989
            }
990
            my $image4 = $domreg{$uuid}->{'image4'};
991
            if ($image4 && $image4 ne '--' && $imagereg{$image4}->{'status'} =~ /used|active/) {
992
                $imageactions .= qq/{"uuid":"$imagereg{$image4}->{'uuid'}","action":"gear_$action"},/;
993
            }
994
            if ($imageactions) {
995
                $imageactions = substr($imageactions,0,-1);
996
                my $uri_action = qq/{"items":[$imageactions]}/;
997
                $uri_action = URI::Escape::uri_escape($uri_action);
998
                $uri_action = $1 if $uri_action =~ /(.+)/; #untaint
999
                my $cmd = qq|REQUEST_METHOD=POST REMOTE_USER=$user $Stabile::basedir/cgi/images.cgi -k "$uri_action"|;
1000
                $postreply .= `$cmd`;
1001
            }
1002
        } else {
1003
            my $cmd = qq|REQUEST_METHOD=GET REMOTE_USER=$user $Stabile::basedir/cgi/servers.cgi -a $action -u $uuid|;
1004
            $postreply = `$cmd`;
1005
            #$postreply = $cmd;
1006
            my $uistatus = $action."ing";
1007
            $uistatus = "resuming" if ($action eq 'resume');
1008
            $uistatus = "shuttingdown" if ($action eq 'shutdown');
1009
            $main::updateUI->({ tab => 'servers',
1010
                user                => $user,
1011
                uuid                => $uuid,
1012
                status              => $uistatus })
1013

    
1014
        }
1015
    }
1016
    untie %domreg;
1017
    untie %imagereg;
1018

    
1019
    return $postreply;
1020
}
1021

    
1022
sub Updateengineinfo {
1023
    my ($uuid, $action, $obj) = @_;
1024
    if ($help) {
1025
        return <<END
1026
PUT:downloadmasters, downloadallmasters, externaliprangestart, externaliprangeend, proxyiprangestart, proxyiprangeend, proxygw, vmreadlimit, vmwritelimit, vmiopsreadlimit, vmiopswritelimit:
1027
Save engine information.
1028
END
1029
    }
1030
    unless ($isadmin) {
1031
        $postreply = "Status=Error Not allowed\n";
1032
        return $postreply;
1033
    }
1034
    my $msg = "Engine updated";
1035
    my $dl = $obj->{'downloadmasters'};
1036
    if ($dl eq '--' || $dl eq '0') {
1037
        if ($downloadmasters) {
1038
            $downloadmasters = '';
1039
            `perl -pi -e 's/DOWNLOAD_MASTERS=.*/DOWNLOAD_MASTERS=0/;' /etc/stabile/config.cfg`;
1040
        }
1041
        $postreply .= "Status=OK Engine updated\n";
1042
        my @ps = split("\n",  `pgrep pressurecontrol` ); `kill -HUP $ps[0]`;
1043
    }
1044
    elsif ($dl eq '1' || $dl eq '2') {
1045
        if (!$downloadmasters || $dl eq '2') { # We use a value of 2 to force check for downloads
1046
            $downloadmasters = 1;
1047
            `perl -pi -e 's/DOWNLOAD_MASTERS=.*/DOWNLOAD_MASTERS=$dl/;' /etc/stabile/config.cfg`;
1048
        }
1049
        if ($dl eq '2') {
1050
            $msg = "Checking for new or updated masters...";
1051
        }
1052
        $postreply .= "Status=OK Engine updated\n";
1053
        my @ps = split("\n",  `pgrep pressurecontrol` ); `kill -HUP $ps[0]`;
1054
    }
1055
    elsif ($obj->{'downloadallmasters'} eq '--' || $obj->{'downloadallmasters'} eq '0') {
1056
        if ($disablesnat) {
1057
            $disablesnat = '';
1058
            `perl -pi -e 's/DOWNLOAD_ALL_MASTERS=.*/DOWNLOAD_ALL_MASTERS=0/;' /etc/stabile/config.cfg`;
1059
        }
1060
        $postreply .= "Status=OK Engine updated\n";
1061
    }
1062
    elsif ($obj->{'downloadallmasters'} eq '1') {
1063
        if ($disablesnat) {
1064
            $disablesnat = '';
1065
            `perl -pi -e 's/DOWNLOAD_ALL_MASTERS=.*/DOWNLOAD_ALL_MASTERS=1/;' /etc/stabile/config.cfg`;
1066
        }
1067
        $postreply .= "Status=OK Engine updated\n";
1068
    }
1069
    elsif ($obj->{'disablesnat'} eq '--' || $obj->{'disablesnat'} eq '0') {
1070
        if ($disablesnat) {
1071
            $disablesnat = '';
1072
            `perl -pi -e 's/DISABLE_SNAT=.*/DISABLE_SNAT=0/;' /etc/stabile/config.cfg`;
1073
        }
1074
        $postreply .= "Status=OK Engine updated\n";
1075
    }
1076
    elsif ($obj->{'disablesnat'} eq '1') {
1077
        unless ($disablesnat) {
1078
            $disablesnat = 1;
1079
            `perl -pi -e 's/DISABLE_SNAT=.*/DISABLE_SNAT=1/;' /etc/stabile/config.cfg`;
1080
        }
1081
        $postreply .= "Status=OK Engine updated\n";
1082
    }
1083
    elsif ($obj->{'enforceiolimits'} eq '--' || $obj->{'enforceiolimits'} eq '0') {
1084
        if ($enforceiolimits) {
1085
            $enforceiolimits = '';
1086
            `perl -pi -e 's/ENFORCE_IO_LIMITS=.*/ENFORCE_IO_LIMITS=0/;' /etc/stabile/config.cfg`;
1087
        }
1088
        $postreply .= "Status=OK Engine updated\n";
1089
    }
1090
    elsif ($obj->{'enforceiolimits'} eq '1') {
1091
        unless ($enforceiolimits) {
1092
            $enforceiolimits = 1;
1093
            `perl -pi -e 's/ENFORCE_IO_LIMITS=.*/ENFORCE_IO_LIMITS=1/;' /etc/stabile/config.cfg`;
1094
        }
1095
        $postreply .= "Status=OK Engine updated\n";
1096
    }
1097
    elsif ($obj->{'externaliprangestart'}) {
1098
        if ($obj->{'externaliprangestart'} =~ /\d+\.\d+\.\d+\.\d+/) {
1099
            $extiprangestart = $obj->{'externaliprangestart'};
1100
            $msg = "Setting external IP range start to $extiprangestart";
1101
            `perl -pi -e 's/EXTERNAL_IP_RANGE_START=.*/EXTERNAL_IP_RANGE_START=$extiprangestart/;' /etc/stabile/config.cfg`;
1102
            $postreply .= "Status=OK Engine updated\n";
1103
        } else {
1104
            $msg = "Not changing IP range - $obj->{'externaliprangestart'} is not valid";
1105
        }
1106
    }
1107
    elsif ($obj->{'externaliprangeend'}) {
1108
        if ($obj->{'externaliprangeend'} =~ /\d+\.\d+\.\d+\.\d+/) {
1109
            $extiprangeend = $obj->{'externaliprangeend'};
1110
            $msg = "Setting external IP range end to $extiprangeend";
1111
            `perl -pi -e 's/EXTERNAL_IP_RANGE_END=.*/EXTERNAL_IP_RANGE_END=$extiprangeend/;' /etc/stabile/config.cfg`;
1112
            $postreply .= "Status=OK Engine updated\n";
1113
        } else {
1114
            $msg = "Not changing IP range - $obj->{'externaliprangeend'} is not valid";
1115
        }
1116
    }
1117
    elsif ($obj->{'proxyiprangestart'}) {
1118
        if ($obj->{'proxyiprangestart'} =~ /\d+\.\d+\.\d+\.\d+/) {
1119
            $extiprangestart = $obj->{'proxyiprangestart'};
1120
            $msg = "Setting proxy IP range start to $extiprangestart";
1121
            `perl -pi -e 's/PROXY_IP_RANGE_START=.*/PROXY_IP_RANGE_START=$extiprangestart/;' /etc/stabile/config.cfg`;
1122
            $postreply .= "Status=OK Engine updated\n";
1123
        } else {
1124
            $msg = "Not changing IP range - $obj->{'proxyiprangestart'} is not valid";
1125
        }
1126
    }
1127
    elsif ($obj->{'proxyiprangeend'}) {
1128
        if ($obj->{'proxyiprangeend'} =~ /\d+\.\d+\.\d+\.\d+/) {
1129
            $extiprangeend = $obj->{'proxyiprangeend'};
1130
            $msg = "Setting proxy IP range end to $extiprangeend";
1131
            `perl -pi -e 's/PROXY_IP_RANGE_END=.*/PROXY_IP_RANGE_END=$extiprangeend/;' /etc/stabile/config.cfg`;
1132
            $postreply .= "Status=OK Engine updated\n";
1133
        } else {
1134
            $msg = "Not changing IP range - $obj->{'proxyiprangeend'} is not valid";
1135
        }
1136
    }
1137
    elsif ($obj->{'proxygw'}) {
1138
        if ($obj->{'proxygw'} =~ /\d+\.\d+\.\d+\.\d+/) {
1139
            $proxygw = $obj->{'proxygw'};
1140
            $msg = "Setting proxy gw to $proxygw";
1141
            `perl -pi -e 's/PROXY_GW=.*/PROXY_GW=$proxygw/;' /etc/stabile/config.cfg`;
1142
            $postreply .= "Status=OK Engine updated\n";
1143
        } else {
1144
            $msg = "Not changing IP range - $obj->{'proxygw'} is not valid";
1145
        }
1146
    }
1147
    elsif ($obj->{'vmreadlimit'} || $obj->{'vmwritelimit'} || $obj->{'vmiopsreadlimit'} || $obj->{'vmiopswritelimit'}) {
1148
        my $lim = 'vmreadlimit';
1149
        my $uclim = 'VM_READ_LIMIT';
1150
        if ($obj->{'vmwritelimit'}) {
1151
            $lim = 'vmwritelimit';
1152
            $uclim = 'VM_WRITE_LIMIT';
1153
        } elsif ($obj->{'vmiopsreadlimit'}) {
1154
            $lim = 'vmiopsreadlimit';
1155
            $uclim = 'VM_IOPS_READ_LIMIT';
1156
        } elsif ($obj->{'vmiopswritelimit'}) {
1157
            $lim = 'vmiopswritelimit';
1158
            $uclim = 'VM_IOPS_WRITE_LIMIT';
1159
        }
1160
        if ($obj->{$lim} >= 0 &&  $obj->{$lim} < 10000 *1024*1024) { #sanity checks
1161
            unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities',key=>'identity',CLOBBER=>3}, $Stabile::dbopts)) ) {return "Unable to access id register"};
1162
            my @nodeconfigs;
1163
            # Build hash of known node config files
1164
            foreach my $valref (values %idreg) {
1165
                my $nodeconfigfile = $valref->{'path'} . "/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
1166
                next if ($nodeconfigs{$nodeconfigfile}); # Node identities may share basedir and node config file
1167
                if (-e $nodeconfigfile) {
1168
                    push @nodeconfigs, $nodeconfigfile;
1169
                }
1170
            }
1171
            untie %idreg;
1172
            push @nodeconfigs, "/etc/stabile/nodeconfig.cfg";
1173
            my $limit = int $obj->{$lim};
1174
            $msg = "Setting $uclim limit to $limit";
1175
            foreach my $nodeconfig (@nodeconfigs) {
1176
                my $cfg = new Config::Simple($nodeconfig);
1177
                $cfg->param($uclim, $limit);
1178
                $cfg->save();
1179
            }
1180
            $Stabile::Nodes::console = 1;
1181
            require "$Stabile::basedir/cgi/nodes.cgi";
1182
            $postreply .= Stabile::Nodes::Configurecgroups();
1183
            $postreply .= Stabile::Nodes::do_reloadall('','reloadall', {'nodeaction'=>'CGLOAD'});
1184
            $postreply .= "Status=OK Engine and nodes updated: $lim set to $limit\n";
1185
        } else {
1186
            $msg = "Not changing limit - $obj->{$lim} is not valid";
1187
        }
1188
    }
1189
    if (!$postreply) {
1190
        $msg = "Engine not updated";
1191
        $postreply = "Status=Error Engine not updated\n" ;
1192
    }
1193
    $main::updateUI->({tab=>'home', user=>$user, type=>'update', message=>$msg});
1194
    return $postreply;
1195
}
1196

    
1197
sub do_updateaccountinfo {
1198
    my ($uuid, $action, $obj) = @_;
1199
    if ($help) {
1200
        return <<END
1201
PUT:fullname, email, phone, opfullname, opemail, opphone, alertemail, allowfrom, allowinternalapi:
1202
Save user information.
1203
END
1204
    }
1205
    my @props = ('fullname','email','phone','opfullname','opemail','opphone','alertemail', 'allowfrom', 'allowinternalapi');
1206
    my %oldvals;
1207
    if ($obj->{'allowfrom'} && $obj->{'allowfrom'} ne '--') {
1208
        my @allows = split(/,\s*/, $obj->{'allowfrom'});
1209
        $obj->{'allowfrom'} = '';
1210
        my %allowshash;
1211
        foreach my $ip (@allows) {
1212
            $allowshash{"$1$2"} = 1 if ($ip =~ /(\d+\.\d+\.\d+\.\d+)(\/\d+)?/);
1213
            if ($ip =~ /\w\w/) { # Check if we are dealing with a country code
1214
                $ip = uc $ip;
1215
                my $geoip = Geo::IP->new(GEOIP_MEMORY_CACHE);
1216
                my $tz = $geoip->time_zone($ip, '');
1217
                $allowshash{$ip} = 1 if ($tz); # We have a valid country code
1218
            }
1219
        }
1220
        $obj->{'allowfrom'} = join(", ", sort(keys %allowshash));
1221
        unless ($obj->{'allowfrom'}) {
1222
            $postreply .= "Status=Error Account not updated\n";
1223
            return $postreply;
1224
        }
1225
    }
1226

    
1227
    foreach my $prop (@props) {
1228
        if ($obj->{$prop}) {
1229
            $obj->{$prop} = '' if ($obj->{$prop} eq '--');
1230
            $oldvals{$prop} = $userreg{$user}->{$prop};
1231
            $userreg{$user}->{$prop} = decode('utf8', $obj->{$prop});
1232
        }
1233
    }
1234

    
1235
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1236
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access user register"};
1237
    my $alertmatch;
1238
    foreach my $sysvalref (values %register) {
1239
        if ($user eq $sysvalref->{'user'}) {
1240
            my $sysuuid = $sysvalref->{'uuid'};
1241
            foreach my $prop (@props) {
1242
                my $val = $obj->{$prop};
1243
                if ($val) {
1244
                    $val = '' if ($val eq '--');
1245
                    # Does this system have the same value as the old user value or, equivalently, is it empty?
1246
                    if (!$sysvalref->{$prop} || $sysvalref->{$prop} eq $oldvals{$prop}) {
1247
                    #    $postreply .= "Resetting system prop $prop to $val\n";
1248
                        $sysvalref->{$prop} = ''; # An empty val refers to parent (user) val
1249
                    # Update children
1250
                        foreach my $domvalref (values %domreg) {
1251
                            if ($domvalref->{'user'} eq $user && ($domvalref->{'system'} eq $sysuuid || $domvalref->{'system'} eq '--' || !$domvalref->{'system'})) {
1252
                                if (!$domvalref->{$prop} || $domvalref->{$prop} eq $oldvals{$prop}) {
1253
                                    $domvalref->{$prop} = '';
1254
                                    if ($prop eq 'alertemail') {
1255
                                        if (change_monitor_email($domvalref->{'uuid'}, $val, $oldvals{$prop})) {
1256
                                            $alertmatch = 1;
1257
                                        }
1258
                                    }
1259
                                }
1260
                            }
1261
                        }
1262
                    }
1263
                }
1264
            }
1265
        }
1266
    }
1267
    #`/usr/bin/moncmd reset keepstate` if ($alertmatch);
1268
    tied(%domreg)->commit;
1269
    tied(%userreg)->commit;
1270
    untie %domreg;
1271
    untie %userreg;
1272
    $postreply .= "Status=OK Account updated\n";
1273
    # Send changes to origo.io
1274
    $Stabile::Users::console = 1;
1275
    require "$Stabile::basedir/cgi/users.cgi";
1276
    $postreply .= Stabile::Users::sendEngineUser($user) if ($enginelinked);
1277
    $main::updateUI->({tab=>'home', user=>$user, type=>'update', message=>"Account updated"});
1278
    return $postreply;
1279
}
1280

    
1281
sub do_listuptime {
1282
    my ($uuid, $action, $obj) = @_;
1283
    if ($help) {
1284
        return <<END
1285
GET:yearmonth,uuid,format:
1286
List uptime for defined monitors. If uuid is supplied, only uptime for matching server or servers belonging to matching
1287
system is shown. Format is either html or json.
1288
END
1289
    }
1290
    my $format = $obj->{'format'};
1291
    my $yearmonth = $obj->{'yearmonth'} || "$year-$month";
1292
    my $pathid = $yearmonth . ':';
1293
    my $name;
1294

    
1295
    my %sysdoms;
1296
    if ($uuid && $register{$uuid}) {
1297
        $name = $register{$uuid}->{'name'};
1298
        foreach my $valref (values %domreg) {
1299
            $sysdoms{$valref->{'uuid'}} = $uuid if ($valref->{system} eq $uuid);
1300
        }
1301
    } else {
1302
        $pathid .= $uuid;
1303
        $name = $domreg{$uuid}->{'name'} if ($domreg{$uuid});
1304
    }
1305
    my %uptimes;
1306
    my $jtext = {};
1307
    my @csvrows;
1308

    
1309
    unless ($pathid =~ /\// || $pathid =~ /\./) { # Security check
1310
        my $path = "/var/log/stabile/$pathid*"; # trailing / is required. No $pathid lists all files in log dir.
1311
        my $utext = '';
1312
        my %numfiles;
1313
        my %sumupp;
1314
        ## loop through the files contained in the directory
1315
        for my $eachFile (bsd_glob($path.'*')) {
1316
            if (!(-d $eachFile) && $eachFile =~ /\/var\/log\/stabile\/(.+):(.+):(.+)/) {
1317
                my $ymonth = $1;
1318
                my $domuuid = $2;
1319
                my $service = $3;
1320
                next unless ($domreg{$domuuid});
1321
                my $servername = $domreg{$domuuid}->{'name'};
1322
                if ($domreg{$domuuid}->{'user'} eq $user) {
1323
                    next if (%sysdoms && !$sysdoms{$domuuid}); # If we are listing a system, match system uuid
1324
                    open(FILE, $eachFile) or {print("Unable to access $eachFile")};
1325
                    @lines = <FILE>;
1326
                    close(FILE);
1327
                    my $starttime;
1328
                    my $lastup;
1329
                    my $firststamp; # First timestamp of measuring period
1330
                    my $laststamp; # Last timestamp of measuring period
1331
                    my $curstate = 'UNKNOWN';
1332
                    my $dstate = 'UNKNOWN';
1333
                    my ($y, $m) = split('-', $ymonth);
1334
                    my $timespan = 0;
1335
                    my $dtime = 0; # Time disabled
1336
                    my $lastdtime = 0;
1337
                    my $uptime = 0;
1338
                    foreach my $line (@lines) {
1339
                        my ($timestamp, $event, $summary, $ptime) = split(/, */,$line);
1340
                        if (!$starttime) { # First line
1341
                            $starttime = $timestamp;
1342
                            # Find 00:00 of first day of month - http://www.perlmonks.org/?node_id=97120
1343
                            $firststamp = POSIX::mktime(0,0,0,1,$m-1,$year-1900,0,0,-1);
1344
                            # Round to month start if within 15 min
1345
                            $starttime = $firststamp if ($starttime-$firststamp<15*60);
1346
                            $lastup = $starttime if ($event eq 'STARTUP' || $event eq 'UP');
1347
                            $curstate = 'UP'; # Assume up - down alerts are always triggered
1348
                        }
1349
                        if ($event eq 'UP') {
1350
                            if ($curstate eq 'UP') {
1351
                                $uptime += ($timestamp - $lastup) if ($lastup);
1352
                            }
1353
                            $lastup = $timestamp;
1354
                            $curstate = 'UP';
1355
                        } elsif ($event eq 'DOWN') {
1356
                            if ($curstate eq 'UP' && $lastup!=$starttime) { # If down is immediately after startup - dont count uptime
1357
                                $uptime += ($timestamp - $lastup) if ($lastup);
1358
                                $lastup = $timestamp;
1359
                            }
1360
                            $curstate = 'DOWN';
1361
                        } elsif ($event eq 'STARTUP') {
1362
                        } elsif ($event eq 'DISABLE' && $curstate ne 'UNKNOWN') {
1363
                            if ($curstate eq 'UP') {
1364
                                $uptime += ($timestamp - $lastup) if ($lastup);
1365
                                $lastup = $timestamp;
1366
                            }
1367
                            $lastdtime = $timestamp;
1368
                            $dstate = $curstate;
1369
                            $curstate = 'UNKNOWN';
1370
                        } elsif ($event eq 'ENABLE') {
1371
                            if ($dstate eq 'UP' && $curstate eq 'UNKNOWN') {
1372
                                $lastup = $timestamp;
1373
                            }
1374
                            $curstate = 'UP';
1375
                        }
1376
                        # All non-disable events must mean monitor is enabled again
1377
                        if ($event ne 'DISABLE') {
1378
                            if ($lastdtime) {
1379
                                $dtime += ($timestamp - $lastdtime);
1380
                                $lastdtime = 0;
1381
                            }
1382
                        }
1383

    
1384
                    }
1385
                    if ($ymonth ne "$year-$month") { # If not current month, assume monitoring to end of month
1386
                        # Find 00:00 of first day of next month - http://www.perlmonks.org/?node_id=97120
1387
                        $laststamp = POSIX::mktime(0,0,0,1,$m,$year-1900,0,0,-1);
1388
                    } else {
1389
                        $laststamp = $current_time;
1390
                    }
1391
                    if ($curstate eq 'UP' && !$lastdtime && $lastup) {
1392
                        $uptime += ($laststamp - $lastup);
1393
                    }
1394
                    if ($lastdtime) {
1395
                        $dtime += ($laststamp - $lastdtime);
1396
                    }
1397
                    $timespan = $laststamp - $starttime;
1398
                    $uptimes{"$domuuid:$service"}->{'timespan'} = $timespan;
1399
                    $uptimes{"$domuuid:$service"}->{'uptime'} = $uptime;
1400
                    my $timespanh = int(0.5 + 100*$timespan/3600)/100;
1401
                    my $dtimeh = int(0.5 + 100*$dtime/3600)/100;
1402
                    my $uptimeh = int(0.5 + 100*$uptime/3600)/100;
1403
                    my $upp = int(0.5+ 10000*$uptime/($timespan-$dtime) ) / 100;
1404
                    $sumupp{$service} += $upp;
1405
                    $numfiles{$service} += 1;
1406

    
1407
                    utf8::decode($servername);
1408

    
1409
                    $utext .= qq[<div class="uptime_header">$service on $servername:</div>\n];
1410
                    my $color = ($upp<98)?'red':'green';
1411
                    $utext .= qq[<span style="color: $color;">Uptime: $uptimeh hours ($upp%)</span>\n];
1412
                    $utext .= qq{[timespan: $timespanh hours, \n};
1413
                    $utext .= qq{disabled: $dtimeh hours]\n};
1414

    
1415
                    $jtext->{$domuuid}->{'servername'} = $servername;
1416
                    $jtext->{$domuuid}->{$service}->{'uptime'} = $upp;
1417
                    $jtext->{$domuuid}->{$service}->{'uptimeh'} = $uptimeh;
1418
                    $jtext->{$domuuid}->{$service}->{'color'} = ($upp<98)?'red':'green';
1419
                    $jtext->{$domuuid}->{$service}->{'disabledtimeh'} = $dtimeh;
1420
                    $jtext->{$domuuid}->{$service}->{'timespanh'} = $timespanh;
1421

    
1422
                    push @csvrows, {serveruuid=>$domuuid, service=>$service, servername=>$servername, uptime=>$upp, uptimeh=>$uptimeh, color=>($upp<98)?'red':'green',disabledtimeh=>$dtimeh, timespanh=>$timespanh, yearmonth=>$yearmonth};
1423
                }
1424
            }
1425
        }
1426
        my @avgtxt;
1427
        my $alertclass = "info";
1428
        my $compcolor;
1429
        $jtext->{'averages'} = {};
1430
        $jtext->{'year-month'} = $yearmonth;
1431
        foreach $svc (keys %sumupp) {
1432
            my $avgupp = int(0.5 + 100*$sumupp{$svc}/$numfiles{$svc})/100;
1433
            my $color = ($avgupp<98)?'red':'green';
1434
            push @avgtxt, qq[<span style="color: $color;" class="uptime_header">$svc: $avgupp%</span>\n];
1435
            $jtext->{'averages'}->{$svc}->{'uptime'} = $avgupp;
1436
            $jtext->{'averages'}->{$svc}->{'color'} = $color;
1437
            $compcolor = ($compcolor)? ( ($compcolor eq $color)? $color : 'info' ) : $color;
1438
        }
1439
        $alertclass = "warning" if ($compcolor eq 'red');
1440
        $alertclass = "success" if ($compcolor eq 'green');
1441
        $postreply = header();
1442
        if ($name) {
1443
            $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];
1444
        } else {
1445
            $postreply .= qq[<div class="alert alert-$alertclass uptime_alert"><h4 class="uptime_header">Average uptime report</h4>\n<div style="margin-top:10px;">\n];
1446
        }
1447
        $postreply .= join(", ", @avgtxt);
1448
        my $uuidlink = "&uuid=$uuid" if ($uuid);
1449
        $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];
1450
        $postreply .= "<span class=\"uptime_text\">$utext</span>";
1451
    }
1452
    if ($params{'format'} eq 'csv') {
1453
        $postreply = header("text/plain");
1454
        csv(in => \@csvrows, out => \my $csvdata, key => "servername");
1455
        $postreply .= $csvdata;
1456
    } elsif ($format ne 'html') {
1457
        $postreply = to_json($jtext, {pretty=>1});
1458
    }
1459
    return $postreply;
1460
}
1461

    
1462
sub Downloadmaster {
1463
    my ($uuid, $action, $obj) = @_;
1464
    if ($help) {
1465
        return <<END
1466
GET:filename,user:
1467
Downloads a master image (and if relevant the associated data image) with [filename] belonging to [user] (default "common") to the engine from Origo Registry.
1468
END
1469
    }
1470
    if ($isadmin) {
1471
        $Stabile::Images::console = 1;
1472
        $Stabile::Images::user = $user;
1473
        require "$Stabile::basedir/cgi/images.cgi";
1474
        my @spools = @Stabile::Images::spools;
1475
        my $downloadpath = "$spools[0]->{path}/$obj->{user}/$obj->{filename}";
1476
        `echo "downloading" > "$downloadpath.meta"`;
1477
        `perl -pi -e 's/DOWNLOAD_MASTERS=.*/DOWNLOAD_MASTERS=2/;' /etc/stabile/config.cfg`;
1478
        my @ps = split("\n",  `pgrep pressurecontrol` ); `kill -HUP $ps[0]`;
1479
        $postreply = "Status=OK Download of $downloadpath initiated...\n";
1480
    } else {
1481
        $postreply = "Status=Error Download of master images can only be initiated by administrators\n";
1482
    }
1483
    return $postreply;
1484
}
1485

    
1486
sub do_appstore {
1487
    my ($uuid, $action, $obj) = @_;
1488
    if ($help) {
1489
        return <<END
1490
GET:appid,callback:
1491
Look up app info for app with given appid in appstore on origo.io. Data is returned as padded JSON (JSONP).
1492
Optionally provide name of your JSONP callback function, which should parse the returned script data.
1493
If no appid is provided, all available masters at Origo Registry are returned.
1494
END
1495
    }
1496
    my $appid = $params{'appid'};
1497
    my $callback = $params{'callback'};
1498
    if ($appid) {
1499
        $postreply = header("application/javascript");
1500
        $postreply .= $main::postToOrigo->($engineid, 'engineappstore', $appid, 'appid', $callback);
1501
    } else {
1502
        $postreply = header("application/json");
1503
        # Build a hash of master images we already have downloaded
1504
        $Stabile::Images::console = 1;
1505
        require "$Stabile::basedir/cgi/images.cgi";
1506
        my $masters = Stabile::Images::do_listmasterimages('', 'listmasterimages', {raw=>1});
1507
        my %master_hash;
1508
        my %appid_hash;
1509
        foreach my $master (@$masters) {
1510
            my $path = $master->{path};
1511
            my $muser = $master->{user};
1512
            my $appid = $master->{appid};
1513
            my $filename = $1 if ($path =~ /.*\/(.*)$/);
1514
            $master_hash{"$muser:$filename"} = 1; # the id format we use here
1515
            $appid_hash{$appid} = 1;
1516
        }
1517
        # Get complete list of master images from Origo and filter out those we already have
1518
        my $json_text = $main::postToOrigo->($engineid, 'liststackmasters', 1, 'flat');
1519
        my $json_obj = from_json($json_text);
1520
        my @missing_stacks = ({name=>'--', id=>'--'});
1521
        foreach my $stack (@$json_obj) {
1522
            if ($master_hash{ $stack->{id} }) {
1523
                # already downloaded
1524
            } else {
1525
                $stack->{summary} = URI::Escape::uri_unescape($stack->{summary});
1526
                $stack->{description} = URI::Escape::uri_unescape($stack->{description});
1527
                # new version of stack is available for download
1528
                $stack->{name} = "$stack->{name} (new version)" if ($appid_hash{$stack->{appid}});
1529
                push @missing_stacks, $stack ;
1530
            }
1531
        #    $postreply .=  "$stack->{id}\n";
1532
        }
1533
        $json_text = to_json(\@missing_stacks);
1534

    
1535
        $postreply = qq/{"identifier": "id", "label": "name", "items": $json_text }/;
1536
    }
1537
    return $postreply;
1538
}
1539

    
1540
sub do_resetmonitoring {
1541
    my ($uuid, $action, $obj) = @_;
1542
    if ($help) {
1543
        return <<END
1544
GET::
1545
Reset mon daemon while keeping states.
1546
END
1547
    }
1548
    saveOpstatus();
1549
    $postreply = "Status=OK " . `/usr/bin/moncmd reset keepstate`;
1550
    return $postreply;
1551
}
1552

    
1553
sub do_installsystem {
1554
    my ($uuid, $action, $obj) = @_;
1555
    if ($help) {
1556
        return <<END
1557
GET:installsystem,installaccount:
1558
Helper function to initiate the installation of a new stack with system ID [installsystem] to account [installaccount] by redirecting with appropriate cookies set.
1559
END
1560
    }
1561
    my $installsystem = $obj->{'installsystem'};
1562
    my $installaccount = $obj->{'installaccount'};
1563
    my $systemcookie;
1564
    my $ia_cookie;
1565
    my $sa_cookie;
1566

    
1567
    push(@INC, "$Stabile::basedir/auth");
1568
    require Apache::AuthTkt;# 0.03;
1569
    require AuthTktConfig;
1570
    my $at = Apache::AuthTkt->new(conf => $ENV{MOD_AUTH_TKT_CONF});
1571
    my ($server_name, $server_port) = split /:/, $ENV{HTTP_HOST} if $ENV{HTTP_HOST};
1572
    $server_name ||= $ENV{SERVER_NAME} if $ENV{SERVER_NAME};
1573
    $server_port ||= $ENV{SERVER_PORT} if $ENV{SERVER_PORT};
1574
    my $AUTH_DOMAIN = $at->domain || $server_name;
1575
    my @auth_domain = $AUTH_DOMAIN ? ( -domain => $AUTH_DOMAIN ) : ();
1576

    
1577
    if ($installsystem) {
1578
        $systemcookie = CGI::Cookie->new(
1579
            -name => 'installsystem',
1580
            -value => "$installsystem",
1581
            -path => '/',
1582
            @auth_domain
1583
        );
1584
    };
1585
    if ($installaccount) {
1586
        $ia_cookie = CGI::Cookie->new(
1587
            -name => 'installaccount',
1588
            -value => "$installaccount",
1589
            -path => '/',
1590
            @auth_domain
1591
        );
1592
        $sa_cookie = CGI::Cookie->new(
1593
            -name => 'steamaccount',
1594
            -value => "$installaccount",
1595
            -path => '/',
1596
            @auth_domain
1597
        );
1598
    };
1599

    
1600
    $tktcookie = CGI::Cookie->new(
1601
        -name => 'tktuser',
1602
        -value => "$tktuser",
1603
        -path => '/',
1604
        @auth_domain
1605
    );
1606

    
1607
    $postreply = redirect(
1608
        -uri => '/stabile/mainvalve/',
1609
        -cookie => [$tktcookie, $systemcookie, $ia_cookie, $sa_cookie]
1610
    );
1611
    return $postreply;
1612
}
1613

    
1614
sub Changemonitoremail {
1615
    my ($uuid, $action, $obj) = @_;
1616
    if ($help) {
1617
        return <<END
1618
GET:uuid,email:
1619
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.
1620
END
1621
    }
1622
    if ($isreadonly) {
1623
        $postreply = "Status=Error Not permitted\n";
1624
    } else {
1625
        my $serveruuid = $options{u} || $uuid;
1626
        my $email = $options{k} || $obj->{'email'};
1627
        if (change_monitor_email($serveruuid, $email)) {
1628
            $postreply = "Status=OK " . `/usr/bin/moncmd reset keepstate`;
1629
        } else {
1630
            $postreply = "Status=Error There was a problem changing monitor email for $serveruuid\n";
1631
        }
1632
    }
1633
    return $postreply;
1634
}
1635

    
1636
sub do_getmetrics {
1637
    my ($suuid, $action, $obj) = @_;
1638
    if ($help) {
1639
        return <<END
1640
GET:uuid,metric,from,until,last,format:
1641
Get performance and load metrics in JSON format from Graphite backend. [metric] is one of: cpuload, diskreads, diskwrites, networkactivityrx, networkactivitytx
1642
From and until are Unix timestamps. Alternatively specify "last" number of seconds you want metrics for. Format is "json" (default) or "csv".
1643
END
1644
    }
1645
    my $metric = $params{metric} || "cpuLoad";
1646
    my $now = time();
1647
    my $from = $params{"from"} || ($now-$params{"last"}) || ($now-300);
1648
    my $until = $params{"until"} || $now;
1649

    
1650
    my @uuids;
1651
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1652

    
1653
    if ($domreg{$suuid}) { # We are dealing with a server
1654
        push @uuids, $suuid;
1655
    } else { # We are dealing with a system
1656
        foreach my $valref (values %domreg) {
1657
            my $sysuuid = $valref->{'system'};
1658
            push @uuids, $valref->{'uuid'} if ($sysuuid eq $suuid)
1659
        }
1660
    }
1661
    untie %domreg;
1662

    
1663
    my @datapoints;
1664
    my @targets;
1665
    my $all;
1666
    my $jobj = [];
1667
    foreach my $uuid (@uuids) {
1668
        next unless (-e "/var/lib/graphite/whisper/domains/$uuid");
1669
        my $url = "https://127.0.0.1/graphite/graphite.wsgi/render?format=json&from=$from&until=$until&target=domains.$uuid.$metric";
1670
        my $jstats = `curl -k "$url"`;
1671
        $jobj = from_json($jstats);
1672
        push @targets, $jobj->[0]->{target};
1673
        if ($jobj->[0]->{target}) {
1674
            if (@datapoints) {
1675
                my $j=0;
1676
                foreach my $p ( @{$jobj->[0]->{datapoints}} ) {
1677
#                    print "adding: ", $datapoints[$j]->[0], " + ", $p->[0];
1678
                    $datapoints[$j]->[0] += $p->[0];
1679
#                    print " = ", $datapoints[$j]->[0], " to ",$datapoints[$j]->[1],  "\n";
1680
                    $j++;
1681
                }
1682
            } else {
1683
                @datapoints = @{$jobj->[0]->{datapoints}};
1684
            }
1685
        }
1686
    }
1687
    pop @datapoints; # We discard the last datapoint because of possible clock drift
1688
    $all = [{targets=>\@targets, datapoints=>\@datapoints, period=>{from=>$from, until=>$until, span=>$until-$from}}];
1689
    if ($params{'format'} eq 'csv') {
1690
        $postreply = header("text/plain");
1691
        csv(in => \@datapoints, out => \my $csvdata);
1692
        $postreply .= $csvdata;
1693
    } else {
1694
        $postreply = to_json($all);
1695
    }
1696
    return $postreply;
1697
}
1698

    
1699
sub do_metrics {
1700
    my ($suuid, $action, $obj) = @_;
1701
    if ($help) {
1702
        return <<END
1703
GET:uuid,metric,from,to:
1704
Get performance and load metrics in JSON format from RRD backend. [metric] is one of: cpuload, diskreads, diskwrites, networkactivityrx, networkactivitytx
1705
From and to are Unix timestamps.
1706
END
1707
    }
1708

    
1709
    my $from = $params{"from"};
1710
    my $to = $params{"to"};
1711
    my $dif = $to - $from;
1712
    my $now = time();
1713

    
1714
    my @items;
1715
    my %cpuLoad = ();
1716
    my %networkActivityRX = ();
1717
    my %networkActivityTX = ();
1718
    my %diskReads = ();
1719
    my %diskWrites = ();
1720

    
1721
    my $i = 0;
1722
    my @uuids;
1723
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1724

    
1725
    if ($domreg{$suuid}) { # We are dealing with a server
1726
        push @uuids, $suuid;
1727
    } else { # We are dealing with a system
1728
        foreach my $valref (values %domreg) {
1729
            my $sysuuid = $valref->{'system'};
1730
            push @uuids, $valref->{'uuid'} if ($sysuuid eq $suuid)
1731
        }
1732
    }
1733
    untie %domreg;
1734

    
1735
    foreach my $uuid (@uuids) {
1736
        next unless hasRRD($uuid);
1737
        $i++;
1738
        # Fetch data from RRD buckets...
1739
        my $rrd = RRDTool::OO->new(file =>"/var/cache/rrdtool/".$uuid."_highres.rrd");
1740
        my $last = $rrd->last();
1741
        $rrd->fetch_start(start => $now-$dif, end=> $now);
1742
        while(my($timestamp, @value) = $rrd->fetch_next()) {
1743
            last if ($timestamp >= $last && $now-$last<20);
1744
            my $domain_cpuTime = shift(@value);
1745
            my $blk_hda_rdBytes = shift(@value);
1746
            my $blk_hda_wrBytes = shift(@value);
1747
            my $if_vnet0_rxBytes = shift(@value);
1748
            my $if_vnet0_txBytes = shift(@value);
1749

    
1750
            # domain_cpuTime is avg. nanosecs spent pr. 1s
1751
            # convert to value [0;1]
1752
            $domain_cpuTime = $domain_cpuTime / 10**9 if ($domain_cpuTime);
1753
            $cpuLoad{$timestamp} +=  $domain_cpuTime;
1754

    
1755
            $blk_hda_rdBytes = $blk_hda_rdBytes if ($blk_hda_rdBytes);
1756
            $diskReads{$timestamp} += $blk_hda_rdBytes;
1757

    
1758
            $blk_hda_wrBytes = $blk_hda_wrBytes if ($blk_hda_wrBytes);
1759
            $diskWrites{$timestamp} += $blk_hda_wrBytes;
1760

    
1761
            $networkActivityRX{$timestamp} += $if_vnet0_rxBytes;
1762
            $networkActivityTX{$timestamp} += $if_vnet0_txBytes;
1763
        }
1764
    }
1765
    my @t = ( $now-$dif, $now);
1766
    my @a = (undef, undef);
1767
    $i = $i || 1;
1768

    
1769
    my $item = ();
1770
    $item->{"uuid"} = $suuid if ($suuid);
1771
    my @tstamps = sort keys %cpuLoad;
1772
    $item->{"timestamps"} = \@tstamps || \@t;
1773

    
1774
    if ($params{"metric"} eq "cpuload" || $params{'cpuload'}) {
1775
        my @vals;
1776
        my $load = int(100*$cpuLoad{$_})/100;
1777
        $load = $i if  ($cpuLoad{$_} > $i);
1778
        foreach(@tstamps) {push @vals, $load};
1779
        $item->{"cpuload"} = \@vals || \@a;
1780
    }
1781
    elsif ($params{"metric"} eq "diskreads" || $params{'diskReads'}) {
1782
        my @vals;
1783
        foreach(@tstamps) {push @vals, int(100*$diskReads{$_})/100;};
1784
        $item->{"diskReads"} = \@vals || \@a;
1785
      }
1786
    elsif ($params{"metric"} eq "diskwrites" || $params{'diskWrites'}) {
1787
        my @vals;
1788
        foreach(@tstamps) {push @vals, int(100*$diskWrites{$_})/100;};
1789
        $item->{"diskWrites"} = \@vals || \@a;
1790
    }
1791
    elsif ($params{"metric"} eq "networkactivityrx" || $params{'networkactivityrx'}) {
1792
        my @vals;
1793
        foreach(@tstamps) {push @vals, int(100*$networkActivityRX{$_})/100;};
1794
        $item->{"networkactivityrx"} = \@vals || \@a;
1795
    }
1796
    elsif ($params{"metric"} eq "networkactivitytx" || $params{'networkactivitytx'}) {
1797
        my @vals;
1798
        foreach(@tstamps) {push @vals, int(100*$networkActivityTX{$_})/100;};
1799
        $item->{"networkactivitytx"} = \@vals || \@a;
1800
    }
1801
    push @items, $item;
1802
    $postreply .= to_json(\@items, {pretty=>1});
1803
    return $postreply;
1804
}
1805

    
1806
sub hasRRD {
1807
	my($uuid) = @_;
1808
	my $rrd_file = "/var/cache/rrdtool/".$uuid."_highres.rrd";
1809

    
1810
	if ((not -e $rrd_file) and ($uuid)) {
1811
		return(0);
1812
	} else {
1813
		return(1);
1814
	}
1815
}
1816

    
1817
sub do_packages_remove {
1818
    my ($uuid, $action, $obj) = @_;
1819
    if ($help) {
1820
        return <<END
1821
DELETE:uuid:
1822
Remove packages belonging to server or system with given uuid.
1823
END
1824
    }
1825
    my $issystem = $obj->{"issystem"} || $register{$uuid};
1826
    unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access package register"};
1827
    my @domains;
1828
    if ($issystem) {
1829
        foreach my $valref (values %domreg) {
1830
            if (($valref->{'system'} eq $uuid || $uuid eq '*')
1831
                    && ($valref->{'user'} eq $user || $fulllist)) {
1832
                push(@domains, $valref->{'uuid'});
1833
            }
1834
        }
1835
    } else { # Allow if domain no longer exists or belongs to user
1836
        push(@domains, $uuid) if (!$domreg{$uuid} || $domreg{$uuid}->{'user'} eq $user || $fulllist);
1837
    }
1838
    foreach my $domuuid (@domains) {
1839
        foreach my $packref (values %packreg) {
1840
            my $id = $packref->{'id'};
1841
            if (substr($id, 0,36) eq $domuuid || ($uuid eq '*' && $packref->{'user'} eq $user)) {
1842
                delete $packreg{$id};
1843
            }
1844
        }
1845
    }
1846
    tied(%packreg)->commit;# if (%packreg);
1847
    if ($issystem && $register{$uuid}) {
1848
        $postreply = "Status=OK Cleared packages for $register{$uuid}->{'name'}\n";
1849
    } elsif ($domreg{$uuid}) {
1850
        $postreply = "Status=OK Cleared packages for $domreg{$uuid}->{'name'}\n";
1851
    } else {
1852
        $postreply = "Status=OK Cleared packages. System not registered\n";
1853
    }
1854
    return $postreply;
1855
}
1856

    
1857
sub Packages_load {
1858
    my ($uuid, $action, $obj) = @_;
1859
    if ($help) {
1860
        return <<END
1861
POST:uuid:
1862
Load list of installed software packages that are installed on the image. Image must contain a valid OS.
1863
END
1864
    }
1865
    if (!$isreadonly) {
1866
        unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access package register"};
1867
        unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1868
        my $curimg;
1869
        my $apps;
1870
        my @domains;
1871
        my $issystem = $obj->{'issystem'};
1872
        if ($issystem) {
1873
            foreach my $valref (values %domreg) {
1874
                if (($valref->{'system'} eq $uuid || $uuid eq '*')
1875
                        && ($valref->{'user'} eq $user || $fulllist)) {
1876
                    push(@domains, $valref->{'uuid'});
1877
                }
1878
            }
1879
        } else {
1880
            push(@domains, $uuid) if ($domreg{$uuid}->{'user'} eq $user || $fulllist);
1881
        }
1882

    
1883
        foreach my $domuuid (@domains) {
1884
            if ($domreg{$domuuid}) {
1885
                $curimg = $domreg{$domuuid}->{'image'};
1886
                $apps = getPackages($curimg);
1887
                if ($apps) {
1888
                    my @packages;
1889
                    my @packages2;
1890
                    open my $fh, '<', \$apps or die $!;
1891
                    my $distro;
1892
                    my $hostname;
1893
                    my $i;
1894
                    while (<$fh>) {
1895
                        if (!$distro) {
1896
                            $distro = $_;
1897
                            chomp $distro;
1898
                        } elsif (!$hostname) {
1899
                            $hostname = $_;
1900
                            chomp $hostname;
1901
                        } elsif ($_ =~ /\[(\d+)\]/) {
1902
                            push @packages2, $packages[$i];
1903
                            $i = $1;
1904
                        } elsif ($_ =~ /(\S+): (.+)/ && $2) {
1905
                            $packages[$i]->{$1} = $2;
1906
                        }
1907
                    }
1908
                    close $fh or die $!;
1909
                    $domreg{$domuuid}->{'os'} = $distro;
1910
                    $domreg{$domuuid}->{'hostname'} = $hostname;
1911
                    foreach $package (@packages) {
1912
                        my $id = "$domuuid-$package->{'app_name'}";
1913
                        $packreg{$id} = $package;
1914
                        $packreg{$id}->{'app_display_name'} = $packreg{$id}->{'app_name'} unless ($packreg{$id}->{'app_display_name'});
1915
                        $packreg{$id}->{'domuuid'} = $domuuid;
1916
                        $packreg{$id}->{'user'} = $user;
1917
                    }
1918
                    $postreply .= "Status=OK Updated packages for $domreg{$domuuid}->{'name'}\n";
1919
                } else {
1920
                    $domreg{$domuuid}->{'os'} = 'unknown';
1921
                    $domreg{$domuuid}->{'hostname'} = 'unknown';
1922
                    $postreply .= "Status=Error Could not update packages for $domreg{$domuuid}->{'name'}";
1923
                }
1924
            }
1925
        }
1926
        tied(%packreg)->commit;
1927
        tied(%domreg)->commit;
1928
        untie %domreg;
1929
        untie %packreg;
1930

    
1931
    } else {
1932
        $postreply .= "Status=Error Not allowed\n";
1933
    }
1934
    return $postreply;
1935
}
1936

    
1937
sub do_packages {
1938
    my ($uuid, $action, $obj) = @_;
1939
    if ($help) {
1940
        return <<END
1941
GET:uuid:
1942
Handling of packages
1943
END
1944
    }
1945

    
1946
    unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access package register"};
1947
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
1948

    
1949
    # List packages
1950
    my @packregvalues = values %packreg;
1951
    my @curregvalues;
1952
    my %packhash;
1953
    my %sysdoms; # Build list of members of system
1954
    foreach $sysdom (values %domreg) {
1955
        if ($sysdom->{'system'} eq $curuuid) {
1956
            $sysdoms{$sysdom->{'uuid'}} = $curuuid;
1957
        }
1958
    }
1959
    foreach my $valref (@packregvalues) {
1960
        if ($valref->{'user'} eq $user || $fulllist) {
1961
            if ((!$curuuid || $curuuid eq '*') # List packages from all servers
1962
                || ($domreg{$curuuid} && $curuuid eq $valref->{'domuuid'}) # List packages from a single server
1963
                || ($register{$curuuid} && $sysdoms{ $valref->{'domuuid'} }) # List packages from multiple servers - a system
1964
            ) {
1965
            #    push(@curregvalues, $valref);
1966
                my $packid = "$valref->{'app_display_name'}:$valref->{'app_version'}";
1967
                if ($packhash{$packid}) {
1968
                    ($packhash{$packid}->{'app_count'})++;
1969
                } else {
1970
                    $packhash{$packid} = {
1971
                        app_display_name=>$valref->{'app_display_name'},
1972
                        app_name=>$valref->{'app_name'},
1973
                        app_release=>$valref->{'app_release'},
1974
                    #    app_publisher=>$valref->{'app_publisher'},
1975
                        app_version=>$valref->{'app_version'},
1976
                        app_count=>1
1977
                    }
1978
                }
1979
            }
1980
        }
1981
    }
1982
    my @sorted_packs = sort {$a->{'app_display_name'} cmp $b->{'app_display_name'}} values %packhash;
1983
    if ($obj->{format} eq 'html') {
1984
        my $res;
1985
        $res .= qq[<tr><th>Name</th><th>Version</th><th>Count</th></tr>\n];
1986
        foreach my $valref (@sorted_packs) {
1987
            $res .= qq[<tr><td>$valref->{'app_display_name'}</td><td>$valref->{'app_version'}</td><td>$valref->{'app_count'}</td></tr>\n];
1988
        }
1989
        $postreply .= qq[<table cellspacing="0" frame="void" rules="rows" class="systemTables">\n$res</table>\n];
1990
    } elsif ($obj->{'format'} eq 'csv') {
1991
        $postreply = header("text/plain");
1992
        csv(in => \@sorted_packs, out => \my $csvdata);
1993
        $postreply .= $csvdata;
1994
    } else {
1995
        $postreply .= to_json(\@sorted_packs);
1996
    }
1997
    untie %domreg;
1998
    untie %packreg;
1999
    return $postreply;
2000
}
2001

    
2002
sub Buildsystem {
2003
    my ($uuid, $action, $obj) = @_;
2004
    if ($help) {
2005
        return <<END
2006
GET:name, master, storagepool, system, instances, networkuuid, bschedule, networktype1, ports, memory, vcpu, diskbus, cdrom, boot, loader, nicmodel1, nicmac1, networkuuid2, nicmac2, storagepool2, monitors, managementlink, start:
2007
Build a complete system from cloned master image.
2008
master is the only required parameter. Set [storagepool2] to -1 if you want data images to be put on node storage.
2009
END
2010
    }
2011
    $curuuid = $uuid unless ($curuuid);
2012
    $postreply = buildSystem(
2013
        $obj->{name},
2014
        $obj->{master},
2015
        $obj->{storagepool},
2016
        $obj->{system},
2017
        $obj->{instances},
2018
        $obj->{networkuuid1},
2019
        $obj->{bschedule},
2020
        $obj->{networktype1},
2021
        $obj->{ports},
2022
        $obj->{memory},
2023
        $obj->{vcpu},
2024
        $obj->{diskbus},
2025
        $obj->{cdrom},
2026
        $obj->{boot},
2027
        $obj->{nicmodel1},
2028
        $obj->{nicmac1},
2029
        $obj->{networkuuid2},
2030
        $obj->{nicmac2},
2031
        $obj->{monitors},
2032
        $obj->{managementlink},
2033
        $obj->{start},
2034
        $obj->{domuuid},
2035
        $obj->{storagepool2},
2036
        $obj->{loader}
2037
    );
2038
    
2039
    return $postreply;
2040
}
2041

    
2042
sub Upgradesystem {
2043
    my ($uuid, $action, $obj) = @_;
2044
    if ($help) {
2045
        return <<END
2046
GET:uuid,internalip:
2047
Upgrades a system
2048
END
2049
    }
2050
    my $internalip = $params{'internalip'};
2051
    $postreply = upgradeSystem($internalip);
2052
    return $postreply;
2053
}
2054

    
2055
sub Removeusersystems {
2056
    my ($uuid, $action, $obj) = @_;
2057
    if ($help) {
2058
        return <<END
2059
GET:username:
2060
Removes all systems belonging to a user, i.e. completely deletes all servers, images and networks belonging to an account.
2061
Use with extreme care.
2062
END
2063
    }
2064
    my $username = $obj->{username};
2065
    $username = $username || $user;
2066
    $postreply = removeusersystems($username); # method performs security check
2067
    return $postreply;
2068
}
2069

    
2070
sub Removesystem {
2071
    my ($uuid, $action, $obj) = @_;
2072
    if ($help) {
2073
        return <<END
2074
GET:uuid:
2075
Removes specified system, i.e. completely deletes all servers, images, networks and backups belonging to a system.
2076
Use with care.
2077
END
2078
    }
2079
    my $duuid = $obj->{uuid} || $uuid;
2080
    $postreply = remove($duuid, 0, 1);
2081
    return $postreply;
2082
}
2083

    
2084
1;
2085

    
2086
# Print list of available actions on objects
2087
sub do_plainhelp {
2088
    my $res;
2089
    $res .= header('text/plain') unless $console;
2090
    $res .= <<END
2091
new [name="name"]
2092
start
2093
suspend
2094
resume
2095
shutdown
2096
destroy
2097
buildsystem [master, storagepool, system (uuid), instances, networkuuid1,bschedule,
2098
networktype1, ports, memory, vcpu, diskbus, cdrom, boot, nicmodel1, nicmac1, networkuuid2,
2099
nicmac2, monitors, start]
2100
removesystem
2101
updateaccountinfo
2102
resettoaccountinfo
2103

    
2104
END
2105
;
2106
}
2107

    
2108
# Save current mon status to /etc/stabile/opstatus, in order to preserve state when reloading mon
2109
sub saveOpstatus {
2110
    my $deleteid = shift;
2111
    my %opstatus = getSavedOpstatus();
2112
    my @monarray = split("\n", `/usr/bin/moncmd list opstatus`);
2113
    my $opfile = "/etc/stabile/opstatus";
2114
    open(FILE, ">$opfile") or {throw Error::Simple("Unable to write $opfile")};
2115
    foreach my $line (@monarray) {
2116
        my @pairs = split(/ /,$line);
2117
        my %h;
2118
        my $ALERT;
2119
        foreach my $pair (@pairs) {
2120
            my ($key, $val) = split(/=/,$pair);
2121
            $obj->{$key} = $val;
2122
        }
2123
        my $ops = $opstatus{"$group:$service"};
2124
        my $group = $obj->{'group'};
2125
        my $service = $obj->{'service'};
2126
        my $curstatus = $ops->{'opstatus'};
2127
        my $curack = $ops->{'ack'};
2128
        my $curackcomment = $ops->{'ackcomment'};
2129
        my $curline = $ops->{'line'};
2130
        if ($deleteid && $deleteid eq "$group:$service") {
2131
            ; # Don't write line for service we are deleting
2132
        } elsif (($obj->{'opstatus'} eq '0' || $obj->{'opstatus'} eq '7') && $curack && $curstatus eq '0') {
2133
            # A failure has been acknowledged and service is still down
2134
            print FILE "$curline\n";
2135
            $ALERT = ($obj->{'opstatus'}?'UP':'DOWN');
2136
        } elsif (($obj->{'opstatus'} || $obj->{'opstatus'} eq '0') && $obj->{'opstatus'} ne '7') {
2137
            print FILE "$line\n";
2138
            $ALERT = ($obj->{'opstatus'}?'UP':'DOWN');
2139
        } elsif (($curstatus || $curstatus eq '0') && $curstatus ne '7') {
2140
            print FILE "$curline\n";
2141
            $ALERT = ($obj->{'opstatus'}?'UP':'DOWN');
2142
        } else {
2143
            # Don't write anything if neither is different from 7
2144
        }
2145
    # Create empty log file if it does not exist
2146
        my $oplogfile = "/var/log/stabile/$year-$month:$group:$service";
2147
        unless (-s $oplogfile) {
2148
            if ($group && $service && $ALERT) {
2149
                `/usr/bin/touch "$oplogfile"`;
2150
                `/bin/chown mon:mon "$oplogfile"`;
2151
                my $logline = "$current_time, $ALERT, MARK, $pretty_time";
2152
                `/bin/echo >> $oplogfile "$logline"`;
2153
            }
2154
        }
2155
    }
2156
    close (FILE);
2157
    #if ((!-e $opfile) || ($current_time - (stat($opfile))[9] > 120) ) {
2158
    #    `/usr/bin/moncmd list opstatus > $opfile`;
2159
    #}
2160
}
2161

    
2162
sub getSavedOpstatus {
2163
    my $dounbackslash = shift;
2164
    my $opfile = "/etc/stabile/opstatus";
2165
    my @oparray;
2166
    my %opstatus;
2167
    # Build hash (%opstatus) with opstatus'es etc. to use for services that are in state unknown because of mon reload
2168
    if (-e $opfile) {
2169
        open(FILE, $opfile) or {throw Error::Simple("Unable to read $opfile")};
2170
        @oparray = <FILE>;
2171
        close(FILE);
2172
        foreach my $line (@oparray) {
2173
            my @pairs = split(/ /,$line);
2174
            my %h;
2175
            foreach my $pair (@pairs) {
2176
                my ($key, $val) = split(/=/,$pair);
2177
                if ($key eq 'last_result' || !$dounbackslash) {
2178
                    $obj->{$key} = $val;
2179
                } else {
2180
                    $val =~ s/\\/\\x/g;
2181
                    $obj->{$key} = unbackslash($val);
2182
                }
2183
            }
2184
            $obj->{'line'} = $line;
2185
            $opstatus{"$obj->{'group'}:$obj->{'service'}"} = \%h;
2186
        }
2187
    }
2188
    return %opstatus;
2189
}
2190

    
2191
sub getOpstatus {
2192
    my ($selgroup, $selservice, $usemoncmd) = @_;
2193
    my %opcodes = ("", "checking", "0", "down", "1", "ok", "3", "3", "4", "4", "5", "5", "6", "6", "7", "checking", "9", "disabled");
2194
    my %s;
2195
    my %opstatus;
2196
    my %savedopstatus = getSavedOpstatus(1);
2197
    my %sysdoms;
2198

    
2199
    my %disabled;
2200
    my %desc;
2201
    my @dislist = split(/\n/, `/usr/bin/moncmd list disabled`);
2202
    foreach my $disline (@dislist) {
2203
        my ($a, $b, $c, $d) = split(' ', $disline);
2204
        $disabled{"$b" . ($d?":$d":'')} = 1;
2205
    };
2206
    my %emails;
2207
    my @emaillist = split(/\n/, `/bin/cat /etc/mon/mon.cf`);
2208
    my $emailuuid;
2209
    foreach my $eline (@emaillist) {
2210
        my ($a, $b, $c, $d) = split(/ +/, $eline, 4);
2211
        if ($a eq 'watch') {
2212
            if ($b =~ /\S+-\S+-\S+-\S+-\S+/) {$emailuuid = $b;}
2213
            else {$emailuuid = ''};
2214
        }
2215
        $emails{$emailuuid} = $d if ($emailuuid && $b eq 'alert' && $c eq 'stabile.alert');
2216
    };
2217

    
2218
    # We are dealing with a system group rather than a domain, build hash of domains in system
2219
    if ($selgroup && !$domreg{$selgroup} && $register{$selgroup}) {
2220
        foreach my $valref (values %domreg) {
2221
            $sysdoms{$valref->{'uuid'}} = $selgroup if ($valref->{system} eq $selgroup);
2222
        }
2223
    }
2224
    if ($usemoncmd) {
2225
        my @oparray = split("\n", `/usr/bin/moncmd list opstatus`);
2226
        foreach my $line (@oparray) {
2227
            my @pairs = split(/ /,$line);
2228
            my %h;
2229
            foreach my $pair (@pairs) {
2230
                my ($key, $val) = split(/=/,$pair);
2231
                if ($key eq 'last_result') {
2232
                    $obj->{$key} = $val;
2233
                } else {
2234
                    $val =~ s/\\/\\x/g;
2235
                    $obj->{$key} = unbackslash($val);
2236
                }
2237
            }
2238
            if (!$selgroup || $sysdoms{$obj->{'group'}}
2239
                (!$selservice && $selgroup eq $obj->{'group'}) ||
2240
                ($selgroup eq $obj->{'group'} && $selservice eq $obj->{'service'})
2241
            )
2242
            {
2243
                #$obj->{'line'} = $line;
2244
                #$opstatus{"$obj->{'group'}:$obj->{'service'}"} = \%h;
2245
                $s{$obj->{'group'}}->{$obj->{'service'}} = \%h if($obj->{'group'});
2246
            }
2247
        }
2248

    
2249
    } else {
2250
        my $monc;
2251
        $monc = new Mon::Client (
2252
            host => "127.0.0.1"
2253
        );
2254
        $monc->connect();
2255
        %desc = $monc->list_descriptions; # Get descriptions
2256
        #%disabled = $monc->list_disabled;
2257
        $selgroup = '' if (%sysdoms);
2258
        my @selection = [$selgroup, $selservice];
2259
        if ($selgroup && $selservice) {%s = $monc->list_opstatus( @selection );}
2260
        elsif ($selgroup) {%s = $monc->list_opstatus( (@selection) );}# List selection
2261
        else {%s = $monc->list_opstatus;} # List all
2262
        $monc->disconnect();
2263
    }
2264

    
2265
    foreach my $group (keys %s) {
2266
        if ($domreg{$group} && ($domreg{$group}->{'user'} eq $user || $fulllist)) {
2267
            foreach my $service (values %{$s{$group}}) {
2268

    
2269
                next if (%sysdoms && !$sysdoms{$group});
2270
                next unless ($service->{'monitor'});
2271
                my $ostatus = $service->{'opstatus'};
2272
                my $id = "$group:$service->{'service'}";
2273
                if (%sysdoms) {
2274
                    $service->{'system'} = $sysdoms{$group};
2275
                }
2276
                if ($ostatus == 7 && $savedopstatus{$id}) { # Get status etc. from %savedopstatus because mon has recently been reloaded
2277
                    $service->{'opstatus'} = $savedopstatus{$id}->{'opstatus'};
2278
                    $service->{'last_success'} = $savedopstatus{$id}->{'last_success'};
2279
                    $service->{'last_check'} = $savedopstatus{$id}->{'last_check'};
2280
                    $service->{'last_detail'} = $savedopstatus{$id}->{'last_detail'};
2281
                    $service->{'checking'} = "1";
2282
                }
2283
#                if (($ostatus == 7 || $ostatus == 0) &&  $savedopstatus{$id}->{'ack'}) { # Get ack because mon has recently been reloaded
2284
                if ($ostatus == 7 &&  $savedopstatus{$id}->{'ack'}) { # Get ack because mon has recently been reloaded
2285
                    $service->{'ack'} = $savedopstatus{$id}->{'ack'};
2286
                    $service->{'ackcomment'} = $savedopstatus{$id}->{'ackcomment'};
2287
                    $service->{'first_failure'} = $savedopstatus{$id}->{'first_failure'};
2288
                }
2289
                $service->{'ackcomment'} = $1 if ($service->{'ackcomment'} =~ /^: *(.*)/);
2290
                my $status = $opcodes{$service->{'opstatus'}};
2291
                if ($disabled{$id} || $disabled{$group}){
2292
                    $status = 'disabled';
2293
                    $service->{'opstatus'} = "9";
2294
                }
2295
                $service->{'status'} = $status;
2296
                $service->{'id'} = $id;
2297
                $service->{'name'} = "$domreg{$group}->{'name'} : $service->{'service'}";
2298
                $service->{'servername'} = $domreg{$group}->{'name'};
2299
                $service->{'serveruuid'} = $domreg{$group}->{'uuid'};
2300
                $service->{'serverstatus'} = $domreg{$group}->{'status'};
2301
                my $serverip = `cat /etc/mon/mon.cf |sed -n -e 's/^hostgroup $group //p'`;
2302
                chomp $serverip;
2303
                $service->{'serverip'} = $serverip;
2304

    
2305
                my $desc = $desc{$group}->{$service->{'service'}};
2306
                $desc = '' if ($desc eq '--');
2307
                $service->{'desc'} = $desc;
2308
                $service->{'last_detail'} =~ s/-//g;
2309
                $service->{'last_detail'} =~ s/^\n//;
2310
                $service->{'last_detail'} =~ s/\n+/\n/g;
2311

    
2312
                my $monitor = $service->{'monitor'};
2313

    
2314
                $service->{'request'} = $service->{'okstring'} = $service->{'port'} = $service->{'email'} = '';
2315
                #$monitor = URI::Escape::uri_unescape($monitor);
2316
                #if ( $monitor =~ /stabile-diskspace\.monitor\s+(\S+)\s+(\S+)\s+(\S+)/ ) {
2317
                if ( $monitor =~ /stabile-diskspace\.monitor\s+(\S+)\s+(\S+)/ ) {
2318
                    $service->{'request'} = $2 if ( $monitor =~ /stabile-diskspace\.monitor\s+(\S+)\s+(\S+)/ );
2319
                    $service->{'okstring'} = $3 if ( $monitor =~ /stabile-diskspace\.monitor\s+(\S+)\s+(\S+)\s+(\S+)/ );
2320
                }
2321

    
2322
                $service->{'okstring'} = $1 if ( $monitor =~ /--okstring \"(.*)\"/ );
2323
                $service->{'okstring'} = $1 if ( $monitor =~ /-l \"(.*)\"/ );
2324
#                $service->{'request'} = $2 if ( $monitor =~ /http(s*):\/\/.+\/(.*)/ );
2325
                $service->{'request'} = $2 if ( $monitor =~ /http(s*):\/\/[^\/]+\/(.*)/ );
2326
                $service->{'port'} = $2 if ( $monitor =~ /http(s*):\/\/.+:(\d+)/ );
2327
                $service->{'request'} = $1 if ( $monitor =~ /--from \"(\S*)\"/ );
2328
                $service->{'okstring'} = $1 if ( $monitor =~ /--to \"(\S*)\"/ );
2329
                $service->{'port'} = $1 if ( $monitor =~ /--port (\d+)/ );
2330

    
2331
                $service->{'email'} = $emails{$group};
2332

    
2333
                $opstatus{$id} = $service;
2334
                #push @monitors, $service;
2335
            }
2336
        }
2337
    }
2338
    return %opstatus;
2339
}
2340

    
2341
sub change_monitor_email {
2342
    my $serveruuid = shift;
2343
    my $email = shift;
2344
    my $match;
2345
    if ($email && $serveruuid) {
2346
        unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
2347
        if ($domreg{$serveruuid}->{'user'} eq $user || $isadmin) {
2348
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf"); # $^I is the in-place edit switch
2349
            # undef $/; # This makes <> read in the entire file in one go
2350
            my $uuidmatch;
2351
            while (<>) {
2352
                if (/^watch (\S+)/) {
2353
                    if ($1 eq $serveruuid) {$uuidmatch = $serveruuid}
2354
                    else {$uuidmatch = ''};
2355
                };
2356
                if ($uuidmatch) {
2357
                    $match = 1 if (s/(stabile\.alert) (.*)/$1 $email/);
2358
                }
2359
                print;
2360
                close ARGV if eof;
2361
        #       $match = 1 if (s/(watch $serveruuid\n.+\n.+\n.+\n.+\n.+)$oldemail(\n.+)$oldemail(\n.+)$oldemail/$1$email$2$email$3$email/g);
2362
            }
2363
        #    $/ = "\n";
2364
        }
2365
    }
2366
    return $match;
2367
}
2368

    
2369
# Delete all monitors belonging to a server
2370
sub deleteMonitors {
2371
    my ($serveruuid) = @_;
2372
    my $match;
2373
    if ($serveruuid) {
2374
        if ($domreg{$serveruuid}->{'user'} eq $user || $isadmin) {
2375
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf");
2376
            # undef $/; # This makes <> read in the entire file in one go
2377
            my $uuidmatch;
2378
            while (<>) {
2379
                if (/^watch (\S+)/) {
2380
                    if ($1 eq $serveruuid) {$uuidmatch = $serveruuid}
2381
                    else {$uuidmatch = ''};
2382
                };
2383
                if ($uuidmatch) {
2384
                    $match = 1;
2385
                } else {
2386
                    #chomp;
2387
                    print unless (/^hostgroup $serveruuid/);
2388
                }
2389
                close ARGV if eof;
2390
            }
2391
            #$/ = "\n";
2392
        }
2393
        unlink glob "/var/log/stabile/*:$serveruuid:*";
2394
    }
2395
    `/usr/bin/moncmd reset keepstate` if ($match);
2396
    return $match;
2397
}
2398

    
2399
# Add a monitors to a server when building system
2400
sub addSimpleMonitors {
2401
    my ($serveruuid, $email, $monitors_ref) = @_;
2402
    my @mons = @{$monitors_ref};
2403

    
2404
    my $match;
2405
    my $hmatch1;
2406
    my $hmatch2;
2407
    my $hmatch3;
2408
    if ($serveruuid && $domreg{$serveruuid}) {
2409
        if ($domreg{$serveruuid}->{'user'} eq $user || $isadmin) {
2410
            my $monitors = {
2411
                ping=>"fping.monitor",
2412
                diskspace=>"stabile-diskspace.monitor $serveruuid",
2413
                http=>"http_tppnp.monitor",
2414
                https=>"http_tppnp.monitor",
2415
                smtp=>"smtp3.monitor",
2416
                smtps=>"smtp3.monitor",
2417
                imap=>"imap.monitor",
2418
                imaps=>"imap-ssl.monitor",
2419
                ldap=>"ldap.monitor",
2420
                telnet=>"telnet.monitor"
2421
            };
2422

    
2423
            if (!$email) {$email = $domreg{$serveruuid}->{'alertemail'}};
2424
            if (!$email && $register{$domreg{$serveruuid}->{'system'}}) {$email = $register{$domreg{$serveruuid}->{'system'}}->{'alertemail'}};
2425
            if (!$email) {$email = $userreg{$user}->{'alertemail'}};
2426

    
2427
            unless (tie %networkreg,'Tie::DBI', {
2428
                db=>'mysql:steamregister',
2429
                table=>'networks',
2430
                key=>'uuid',
2431
                autocommit=>0,
2432
                CLOBBER=>3,
2433
                user=>$dbiuser,
2434
                password=>$dbipasswd}) {throw Error::Simple("Stroke=Error Register could not be accessed")};
2435

    
2436
            my $networkuuid1 = $domreg{$serveruuid}->{'networkuuid1'};
2437
            my $networktype = $networkreg{$networkuuid1}->{'type'};
2438
            my $ip = $networkreg{$networkuuid1}->{'internalip'};
2439
            $ip = $networkreg{$networkuuid1}->{'externalip'} if ($networktype eq 'externalip');
2440
            $ip = '127.0.0.1' if ($networktype eq 'gateway'); #Dummy IP - we only support diskspace checks
2441
            untie %networkreg;
2442

    
2443
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf");
2444
            my $uuidmatch;
2445
            while (<>) {
2446
                $hmatch1=1 if (/^hostgroup/);
2447
                $hmatch2=1 if ($hmatch1 && !/^hostgroup/);
2448
                if ($hmatch1 && $hmatch2 && !$hmatch3) {
2449
                    print "hostgroup $serveruuid $ip\n";
2450
                    $hmatch3 = 1;
2451
                }
2452
                print;
2453
                if (eof) {
2454
                    print "watch $serveruuid\n";
2455
                    foreach $service (@mons) {
2456
                        print <<END;
2457
    service $service
2458
        interval 1m
2459
        monitor $monitors->{$service}
2460
        description --
2461
        period
2462
            alert stabile.alert $email
2463
            upalert stabile.alert $email
2464
            startupalert stabile.alert $email
2465
            numalerts 2
2466
            no_comp_alerts
2467
END
2468
;
2469
                        my $oplogfile = "/var/log/stabile/$year-$month:$serveruuid:$service";
2470
                        unless (-e $oplogfile) {
2471
                            `/usr/bin/touch "$oplogfile"`;
2472
                            `/bin/chown mon:mon "$oplogfile"`;
2473
                            my $logline = "$current_time, UP, STARTUP, $pretty_time";
2474
                            `/bin/echo >> $oplogfile "$logline"`;
2475
                        }
2476
                    }
2477
                    close ARGV;
2478
                }
2479
            }
2480
        } else {
2481
            return "Server $serveruuid not available";
2482
        }
2483
    } else {
2484
        return "Invalid uuid $serveruuid";
2485
    }
2486
    return "OK";
2487
}
2488

    
2489
sub Monitors_save {
2490
    my ($id, $action, $obj) = @_;
2491
    if ($help) {
2492
        return <<END
2493
PUT:id:
2494
Enable, disable or acknowledge a monitor. Id is of the form serveruuid:service
2495
END
2496
    }
2497

    
2498
    my $delete = ($action eq 'monitors_remove'); # Delete an existing monitor
2499
    $id = $obj->{'id'} || $id; # ID in params supersedes id in path
2500
    my $update; # Update an existing monitor?
2501
    my $postmsg;
2502

    
2503
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
2504
    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
2505
    foreign_require("mon", "mon-lib.pl");
2506
    $conf = mon::get_mon_config();
2507
#    my @ogroups = mon::find("hostgroup", $conf);
2508
#    my @owatches = mon::find("watch", $conf);
2509
    my $doreset;
2510
    my $email;
2511

    
2512
    my $serveruuid;
2513
    my $servicename;
2514
    if ($id =~ /(.+):(.+)/){ # List specific monitor for specific server
2515
        $serveruuid = $1;
2516
        $servicename = $2;
2517
    }
2518
    $serveruuid = $serveruuid || $obj->{'serveruuid'};
2519
    my $desc = $obj->{'desc'};
2520
    my $okstring = $obj->{'okstring'};
2521
    my $request = $obj->{'request'};
2522
    my $port = $obj->{'port'};
2523
    $servicename = $servicename || $obj->{'service'};
2524
    my $interval = '1'; # Number of minutes between checks
2525
    $interval = '20' if ($servicename eq 'diskspace');
2526
    $email = $obj->{'alertemail'} || $obj->{'email'};
2527
    my $serv = $domreg{$serveruuid};
2528
    if (!$email) {$email = $serv->{'alertemail'}};
2529
    if (!$email && $serv->{'system'}) {$email = $register{$serv->{'system'}}->{'alertemail'}};
2530
    if (!$email) {$email = $userreg{$user}->{'alertemail'}};
2531
    my $networkuuid1 = $serv->{'networkuuid1'};
2532
    my $networktype = $networkreg{$networkuuid1}->{'type'};
2533
    my $deleteid;
2534
    
2535
    if (!$serveruuid || !$servicename) {
2536
        $postmsg = qq|No monitor specified|;
2537
        $postreply = "Status=Error $postmsg\n";
2538
        return $postreply;
2539
    }
2540

    
2541
    if (!$delete && $networktype eq 'gateway' && $servicename ne 'diskspace'
2542
            && (!$obj->{'serverip'} || !($obj->{'serverip'} =~ /^\d+\.\d+\.\d+\.\d+$/) )) {
2543
        $postmsg = qq|Invalid IP address|;
2544
    } elsif (!$domreg{$serveruuid}) {
2545
        $postmsg = qq|Unknown server $serveruuid|;
2546
# Security check
2547
    } elsif ($domreg{$serveruuid}->{'user'} ne $user) {
2548
        $postmsg = qq|Bad server|;
2549
    } else {
2550
        my $monitors = {
2551
            ping=>"fping.monitor",
2552
            diskspace=>"stabile-diskspace.monitor",
2553
            http=>"http_tppnp.monitor",
2554
            https=>"http_tppnp.monitor",
2555
            smtp=>"smtp3.monitor",
2556
            smtps=>"smtp3.monitor",
2557
            imap=>"imap.monitor",
2558
            imaps=>"imap-ssl.monitor",
2559
            ldap=>"ldap.monitor",
2560
            telnet=>"telnet.monitor"
2561
        };
2562
        my $args = '';
2563
        my $ip = $networkreg{$networkuuid1}->{'internalip'};
2564
        $ip = $networkreg{$networkuuid1}->{'externalip'} if ($networktype eq 'externalip');
2565
        $ip = '127.0.0.1' if ($networktype eq 'gateway' && $servicename eq 'diskspace'); #Dummy IP - we only support diskspace checks
2566
        if ($networktype eq 'gateway' && $servicename eq 'ping') {
2567
            $ip = $obj->{'serverip'};
2568
        # We can only check 10.x.x.x addresses on vlan because of routing
2569
            if ($ip =~ /^10\./) {
2570
                $monitors->{'ping'} = "stabile-arping.monitor";
2571
                my $id = $networkreg{$networkuuid1}->{'id'};
2572
                if ($id > 1) {
2573
                    my $if = $datanic . "." . $id;
2574
                    $args = " $if";
2575
                } else {
2576
                    $args = " $extnic";
2577
                }
2578
                $args .= " $ip";
2579
            }
2580
        }
2581

    
2582
        if ($servicename eq 'ping') {
2583
            ;
2584
        } elsif ($servicename eq 'diskspace'){
2585
            #my $macip = $domreg{$serveruuid}->{'macip'};
2586
            #my $image = URI::Escape::uri_escape($domreg{$serveruuid}->{'image'});
2587
            #$args .= " $macip $image $serveruuid";
2588
            $args .= " $serveruuid";
2589
            $args .= ($request)?" $request":" 10"; #min free %
2590
            $args .= " $okstring" if ($okstring); #Comma-separated partion list, e.g. 0,1
2591
        } elsif ($servicename eq 'http'){
2592
            $args .= " --okcodes \"200,403\" --debuglog -";
2593
            $args .= " --okstring \"$okstring\"" if ($okstring);
2594
            $args .= " http://$ip";
2595
            $args .= ":$port" if ($port && $port>10 && $port<65535);
2596
            $request = substr($request,1) if ($request =~ /^\//);
2597
            $args .= "/$request" if ($request);
2598
        } elsif ($servicename eq 'https'){
2599
            $args .= " --okcodes \"200,403\" --debuglog -";
2600
            $args .= " --okstring \"$okstring\"" if ($okstring);
2601
            $args .= " https://$ip";
2602
            $args .= ":$port" if ($port && $port>10 && $port<65535);
2603
            $request = substr($request,1) if ($request =~ /^\//);
2604
            $args .= "/$request" if ($request);
2605
        } elsif ($servicename eq 'smtp'){
2606
            $args .= " --from \"$request\"" if ($request);
2607
            $args .= " --to \"$okstring\"" if ($okstring);
2608
            $args .= " --port $port" if ($port && $port>10 && $port<65535);
2609
        } elsif ($servicename eq 'smtps'){
2610
            $args .= " --requiretls";
2611
            $args .= " --from \"$request\"" if ($request);
2612
            $args .= " --to \"$okstring\"" if ($okstring);
2613
            $args .= " --port $port" if ($port && $port>10 && $port<65535);
2614
        } elsif ($servicename eq 'imap'){
2615
            $args .= " -p $port" if ($port && $port>10 && $port<65535);
2616
        } elsif ($servicename eq 'imaps'){
2617
            $args .= " -p $port" if ($port && $port>10 && $port<65535);
2618
        } elsif ($servicename eq 'ldap'){
2619
            $args .= " --port $port" if ($port && $port>10 && $port<65535);
2620
            $args .= " --basedn \"$request\"" if ($request);
2621
            $args .= " --attribute \"$okstring\"" if ($okstring);
2622
        } elsif ($servicename eq 'telnet'){
2623
            $args .= " -l \"$okstring\"" if ($okstring);
2624
            $args .= " -p $port" if ($port && $port>10 && $port<65535);
2625
        }
2626

    
2627
        my @ogroups = mon::find("hostgroup", $conf);
2628
        my @owatches = mon::find("watch", $conf);
2629

    
2630
        $group = { 'name' => 'hostgroup', 'values' => [ $serveruuid, $ip ] };
2631
        my $ogroup = undef;
2632
        my $i;
2633
        for($i=0; $i<scalar @ogroups; $i++) {
2634
            if ($ogroups[$i]->{'values'}[0] eq  $serveruuid) {
2635
                $ogroup = $ogroups[$i];
2636
                last;
2637
            }
2638
        }
2639
        mon::save_directive($conf, $ogroup, $group); #Update host hostgroup
2640

    
2641
        $watch = { 'name' => 'watch','values' => [ $serveruuid ], 'members' => [ ] };
2642
        my $owatch = undef;
2643
        my $oservice = undef;
2644
        my $widx = undef;
2645
        for($i=0; $i<scalar @owatches; $i++) { # Run through all watches and locate match
2646
            if ($owatches[$i]->{'values'}[0] eq  $serveruuid) {
2647
                $owatch = $watch = $owatches[$i];
2648
                $widx = $owatch->{'index'};
2649
                my @oservices = mon::find("service", $watch->{'members'});
2650
                for($j=0; $j<@oservices; $j++) { # Run through all services for watch and locate match
2651
                    if ($oservices[$j]->{'values'}[0] eq $servicename) {
2652
                        $oservice = $oservices[$j];
2653
                        my $newmonargs = "$monitors->{$servicename}$args";
2654
                        $newmonargs =~ s/\s+$//; # Remove trailing spaces
2655
                        my $oldmonargs = "$oservices[$j]->{'members'}[2]->{'values'}[0] $oservices[$j]->{'members'}[2]->{'values'}[1]";
2656
                        $oldmonargs =~ s/\s+$//; # Remove trailing spaces
2657
                        if ($newmonargs ne $oldmonargs) {
2658
                            $update = 1; #We are changing an existing service definition
2659
                        };
2660
                        last;
2661
                    }
2662
                }
2663
                last;
2664
            }
2665
        }
2666
        my $in = {
2667
            args=>undef,
2668
            desc=>"$desc",
2669
            idx=>$widx,
2670
            interval=>$interval,
2671
            interval_u=>'m',
2672
            monitor=>$monitors->{$servicename} . $args,
2673
            monitor_def=>1,
2674
            name=>$servicename,
2675
            other=>undef,
2676
            sidx=>undef,
2677
            delete=>$delete,
2678
            email=>$email
2679
        };
2680

    
2681
        if ($update || $delete) {
2682
            unlink glob "/var/log/stabile/*:$serveruuid:$servicename";
2683
        } else {
2684
            my $oplogfile = "/var/log/stabile/$year-$month:$serveruuid:$servicename";
2685
            unless (-e $oplogfile) {
2686
                `/usr/bin/touch "$oplogfile"`;
2687
                `/bin/chown mon:mon "$oplogfile"`;
2688
                my $logline = "$current_time, UP, STARTUP, $pretty_time";
2689
                `/bin/echo >> $oplogfile "$logline"`;
2690
            }
2691
        }
2692
        $deleteid = (($delete || $update)?"$serveruuid:$servicename":'');
2693
        save_service($in, $owatch, $oservice);
2694
        $doreset = 1;
2695
        $obj->{'last_check'} = '--';
2696
        $obj->{'opstatus'} = '7';
2697
        $obj->{'status'} = 'checking';
2698
        $obj->{'alertemail'} = $email;
2699
        mon::flush_file_lines();
2700
        $main::syslogit->($user, 'info', "updating monitor $serveruuid:$servicename" .  (($delete)?" delete":""));
2701
        saveOpstatus($deleteid);
2702
        `/usr/bin/moncmd reset keepstate`;
2703
    }
2704

    
2705
    untie %networkreg;
2706
    untie %domreg;
2707

    
2708
    $postreply = to_json(\%h, {pretty => 1});
2709
    $postmsg = "OK" unless ($postmsg);
2710
    return $postreply;
2711
}
2712

    
2713
## Copied from save_service.cgi (from webmin) and slightly modified - well heavily perhaps
2714

    
2715
sub save_service {
2716
    my $sin = shift;
2717
    my $owatch = shift;
2718
    my $oservice = shift;
2719
    my %in = %{$sin};
2720
    my $oldservice = undef;
2721
    my $service;
2722
    if ($oservice) {
2723
        # $oldservice = $service = $watch->{'members'}->[$in{'sidx'}];
2724
        $oldservice = $service = $oservice;
2725
    } else {
2726
        $service = { 'name' => 'service',
2727
                 'indent' => '    ',
2728
                 'members' => [ ] };
2729
    }
2730

    
2731
    if ($in{'delete'}) {
2732
        # Delete this service from the watch
2733
        mon::save_directive($watch->{'members'}, $service, undef) if ($oservice);
2734
        my @rservices = mon::find("service", $watch->{'members'});
2735
        # Delete watch and hostgroup if no services left
2736
        if (@rservices==0) {
2737
            mon::save_directive($conf, $watch, undef);
2738
            mon::save_directive($conf, $group, undef);
2739
        }
2740
    } else {
2741
        # Validate and store service inputs
2742
        $in{'name'} =~ /^\S+$/ || {$in{'name'} = 'ping'};
2743
        $service->{'values'} = [ $in{'name'} ];
2744
        $in{'interval'} =~ /^\d+$/ || {$in{'interval'} = 1};
2745

    
2746
        &set_directive($service->{'members'}, "interval", $in{'interval'}.$in{'interval_u'});
2747

    
2748
        if ($in{'monitor_def'}) {
2749
            &set_directive($service->{'members'}, "monitor", $in{'monitor'}.' '.$in{'args'});
2750
        }
2751
        else {
2752
            $in{'other'} =~ /^\S+$/ || return "No other monitor specified";
2753
            &set_directive($service->{'members'}, "monitor", $in{'other'}.' '.$in{'args'});
2754
        }
2755

    
2756
        # Save the description
2757
        if ($in{'desc'}) {
2758
            my $desc = $in{'desc'};
2759
            $desc =~ tr/\n/ /;
2760
            &set_directive($service->{'members'}, "description", $in{'desc'});
2761
        }
2762
        else {
2763
            &set_directive($service->{'members'}, "description", '--');
2764
        }
2765

    
2766
        my $period = { 'name' => 'period', 'members' => [ ] };
2767
        my @alert;
2768
        my @v = ( "stabile.alert", $in{'email'} );
2769
        my @num = (2); # The number of alerts to send
2770
        push(@alert, { 'name' => 'alert', 'values' => \@v });
2771
		&set_directive($period->{'members'}, "alert", @alert);
2772
        my @upalert;
2773
        push(@upalert, { 'name' => 'upalert', 'values' => \@v });
2774
		&set_directive($period->{'members'}, "upalert", @upalert);
2775
        my @startupalert;
2776
        push(@startupalert, { 'name' => 'startupalert', 'values' => \@v });
2777
		&set_directive($period->{'members'}, "startupalert", @startupalert);
2778
        my @numalerts;
2779
        push(@numalerts, { 'name' => 'numalerts', 'values' => \@num });
2780
		&set_directive($period->{'members'}, "numalerts", @numalerts);
2781
        my @no_comp_alerts;
2782
        push(@no_comp_alerts, { 'name' => 'no_comp_alerts', 'values' => 0 });
2783
		&set_directive($period->{'members'}, "no_comp_alerts", @no_comp_alerts);
2784

    
2785
        push(@period, $period);
2786

    
2787
    	&set_directive($service->{'members'}, "period", @period);
2788

    
2789
        if ($owatch) {
2790
            # Store the service in existing watch in the config file
2791
            mon::save_directive($watch->{'members'}, $oldservice, $service);
2792
        } else {
2793
            # Create new watch
2794
            push(@service, $service);
2795
            &set_directive($watch->{'members'}, "service", @service);
2796
            mon::save_directive($conf, undef, $watch);
2797
        }
2798
    }
2799
}
2800

    
2801
# set_directive(&config, name, value, value, ..)
2802
sub set_directive
2803
{
2804
local @o = mon::find($_[1], $_[0]);
2805
local @n = @_[2 .. @_-1];
2806
local $i;
2807
for($i=0; $i<@o || $i<@n; $i++) {
2808
	local $idx = &indexof($o[$i], @{$_[0]}) if ($o[$i]);
2809
	local $nv = ref($n[$i]) ? $n[$i] : { 'name' => $_[1],
2810
					     'values' => [ $n[$i] ] }
2811
						if (defined($n[$i]));
2812
	if ($o[$i] && defined($n[$i])) {
2813
		$_[0]->[$idx] = $nv;
2814
		}
2815
	elsif ($o[$i]) {
2816
		splice(@{$_[0]}, $idx, 1);
2817
		}
2818
	else {
2819
		push(@{$_[0]}, $nv);
2820
		}
2821
	}
2822
}
2823

    
2824
sub getSystemsListing {
2825
    my ($action, $curuuid, $username) = @_;
2826
    $username = $user unless ($username);
2827
    my @domregvalues = values %domreg;
2828
    my @curregvalues;
2829
    my %curreg;
2830

    
2831
    $userfullname = $userreg{$username}->{'fullname'};
2832
    $useremail = $userreg{$username}->{'email'};
2833
    $userphone = $userreg{$username}->{'phone'};
2834
    $useropfullname = $userreg{$username}->{'opfullname'};
2835
    $useropemail = $userreg{$username}->{'opemail'};
2836
    $useropphone = $userreg{$username}->{'opphone'};
2837
    $useralertemail = $userreg{$username}->{'alertemail'};
2838

    
2839
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$postreply = "Unable to access image register"; return;};
2840
    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
2841

    
2842
    # Collect systems from domains and include domains as children
2843
    if ($action ne 'flatlist') { # Dont include children in select
2844
        my @imagenames = qw(image image2 image3 image4);
2845
        foreach my $valref (@domregvalues) {
2846
        # Only include VM's belonging to current user (or all users if specified and user is admin)
2847
            if ($username eq $valref->{'user'} || $fulllist) {
2848
                next unless (!$curuuid || ($valref->{'uuid'} eq $curuuid || $valref->{'system'} eq $curuuid));
2849

    
2850
                my %val = %{$valref}; # Deference and assign to new ass array, effectively cloning object
2851
                my $sysuuid = $val{'system'};
2852
                my $dbobj = $register{$sysuuid};
2853
                $val{'memory'} += 0;
2854
                $val{'vcpu'} += 0;
2855
                $val{'nodetype'} = 'child';
2856
                $val{'fullname'} = $val{'fullname'} || $dbobj->{'fullname'} || $userfullname;
2857
                $val{'email'} = $val{'email'} || $dbobj->{'email'} || $useremail;
2858
                $val{'phone'} = $val{'phone'} || $dbobj->{'phone'} || $userphone;
2859
                $val{'opfullname'} = $val{'opfullname'} || $dbobj->{'opfullname'} || $useropfullname;
2860
                $val{'opemail'} = $val{'opemail'} || $dbobj->{'opemail'} || $useropemail;
2861
                $val{'opphone'} = $val{'opphone'} || $dbobj->{'opphone'} || $useropphone;
2862
                $val{'alertemail'} = $val{'alertemail'} || $dbobj->{'alertemail'} || $useralertemail;
2863
                $val{'autostart'} = ($val{'autostart'})?'1':'';
2864

    
2865
                foreach my $img (@imagenames) {
2866
                    if ($imagereg{$val{$img}} && $imagereg{$val{$img}}->{'storagepool'} == -1) {
2867
                        $val{'nodestorage'} += $imagereg{$val{$img}}->{'virtualsize'};
2868
                    } else {
2869
                        $val{'storage'} += $imagereg{$val{$img}}->{'virtualsize'} if ($imagereg{$val{$img}});
2870
                    }
2871
                }
2872
                $val{'externalips'} += 1 if ($networkreg{$val{'networkuuid1'}} && $networkreg{$val{'networkuuid1'}}->{'type'} =~ /externalip|ipmapping/);
2873
                $val{'externalips'} += 1 if ($networkreg{$val{'networkuuid2'}} && $networkreg{$val{'networkuuid2'}}->{'type'} =~ /externalip|ipmapping/);
2874
                $val{'externalips'} += 1 if ($networkreg{$val{'networkuuid3'}} && $networkreg{$val{'networkuuid3'}}->{'type'} =~ /externalip|ipmapping/);
2875
                $val{'networktype1'} = $networkreg{$val{'networkuuid1'}}->{'type'} if ($networkreg{$val{'networkuuid1'}});
2876
                $val{'imageuuid'} = $imagereg{$val{'image'}}->{'uuid'} if ($imagereg{$val{'image'}});
2877
                $val{'imageuuid2'} = $imagereg{$val{'image2'}}->{'uuid'} if ($imagereg{$val{'image2'}} && $val{'image2'} && $val{'image2'} ne '--');
2878
                $val{'internalip'} = $networkreg{$val{'networkuuid1'}}->{'internalip'} if ($networkreg{$val{'networkuuid1'}});
2879
                $val{'externalip'} = $networkreg{$val{'networkuuid1'}}->{'externalip'} if ($networkreg{$val{'networkuuid1'}});
2880

    
2881
                my $networkuuid1; # needed for generating management url
2882
                if ($sysuuid && $sysuuid ne '--') { # We are dealing with a server that's part of a system
2883
                    if (!$register{$sysuuid}) { #System does not exist - create it
2884
                        $sysname = $val{'name'};
2885
                        $sysname = $1 if ($sysname =~ /(.+)\..*/);
2886
                        $sysname =~ s/server/System/i;
2887
                        $register{$sysuuid} = {
2888
                            uuid => $sysuuid,
2889
                            name => $sysname,
2890
                            user => $username,
2891
                            created => $current_time
2892
                        };
2893
                    }
2894

    
2895
                    my %pval = %{$register{$sysuuid}};
2896
                    $pval{'status'} = '--';
2897
                    $pval{'issystem'} = 1;
2898
                    $pval{'fullname'} = $pval{'fullname'} || $userfullname;
2899
                    $pval{'email'} = $pval{'email'} || $useremail;
2900
                    $pval{'phone'} = $pval{'phone'} || $userphone;
2901
                    $pval{'opfullname'} = $pval{'opfullname'} || $useropfullname;
2902
                    $pval{'opemail'} = $pval{'opemail'} || $useropemail;
2903
                    $pval{'opphone'} = $pval{'opphone'} || $useropphone;
2904
                    $pval{'alertemail'} = $pval{'alertemail'} || $useralertemail;
2905
                    $pval{'autostart'} = ($pval{'autostart'})?'1':'';
2906

    
2907
                    my @children;
2908
                    if ($curreg{$sysuuid}->{'children'}) {
2909
                        @children = @{$curreg{$sysuuid}->{'children'}};
2910
                    }
2911
                    # If system has an admin image, update networkuuid1 with the image's server's info
2912
                    if ($pval{'image'} && $pval{'image'} ne '--') {
2913
                        my $dbimg = $imagereg{$pval{'image'}};
2914
                        $networkuuid1 = $domreg{$dbimg->{'domains'}}->{'networkuuid1'} if ($domreg{$dbimg->{'domains'}});
2915
                        my $externalip = '';
2916
                        my $ports = '';
2917
                        if ($networkreg{$networkuuid1}) {
2918
                            $externalip = $networkreg{$networkuuid1}->{'externalip'};
2919
                            $ports = $networkreg{$networkuuid1}->{'ports'}
2920
                        }
2921
                        $register{$sysuuid}->{'networkuuid1'} = $networkuuid1;
2922
                        $register{$sysuuid}->{'internalip'} = $networkreg{$networkuuid1}->{'internalip'} if ($networkreg{$networkuuid1});
2923
                        $pval{'master'} = $dbimg->{'master'};
2924
                        $pval{'appid'} = $dbimg->{'appid'};
2925
                        $pval{'version'} = $dbimg->{'version'};
2926
                        my $managementurl;
2927
                        $managementurl = $dbimg->{'managementlink'};
2928
                        $managementurl =~ s/\{uuid\}/$networkuuid1/;
2929
                        $managementurl =~ s/\{externalip\}/$externalip/;
2930
                        $pval{'managementurl'} = $managementurl;
2931
                        my $upgradeurl;
2932
                        $upgradeurl = $dbimg->{'upgradelink'};
2933
                        $upgradeurl =~ s/\{uuid\}/$networkuuid1/;
2934
                        $pval{'upgradeurl'} = $upgradeurl;
2935
                        my $terminalurl;
2936
                        $terminalurl = $dbimg->{'terminallink'};
2937
                        $terminalurl =~ s/\{uuid\}/$networkuuid1/;
2938
                        $pval{'terminalurl'} = $terminalurl;
2939
                        $pval{'externalip'} = $externalip;
2940
                        $pval{'ports'} = $ports;
2941
                        $pval{'imageuuid'} = $dbimg->{'uuid'};
2942
                        $pval{'imageuuid2'} = $imagereg{$pval{'image2'}}->{'uuid'} if ($pval{'image2'} && $pval{'image2'} ne '--');
2943
                    }
2944
                    push @children,\%val;
2945
                    $pval{'children'} = \@children;
2946
                    $curreg{$sysuuid} = \%pval;
2947
                } else { # This server is not part of a system
2948
                    $sysuuid = $val{'uuid'};
2949
                    my $dbimg = $imagereg{$val{'image'}};
2950
                    $networkuuid1 = $domreg{$dbimg->{'domains'}}->{'networkuuid1'} if ($domreg{$dbimg->{'domains'}});
2951
                    my $externalip;
2952
                    if ($networkreg{$networkuuid1}) {
2953
                        $externalip = $networkreg{$networkuuid1}->{'externalip'};
2954
                        $val{'internalip'} = $networkreg{$networkuuid1}->{'internalip'};
2955
                        $val{'ports'} = $networkreg{$networkuuid1}->{'ports'};
2956
                    }
2957
                    $val{'networkuuid1'} = $networkuuid1;
2958
                    $val{'master'} = $dbimg->{'master'};
2959
                    $val{'appid'} = $dbimg->{'appid'};
2960
                    $val{'version'} = $dbimg->{'version'};
2961
                    $val{'imageuuid'} = $dbimg->{'uuid'};
2962
                    $val{'imageuuid2'} = $imagereg{$val{'image2'}}->{'uuid'} if ($val{'image2'} && $val{'image2'} ne '--' && $imagereg{$val{'image2'}});
2963

    
2964
                    my $managementurl = $dbimg->{'managementlink'};
2965
                    $managementurl =~ s/\{uuid\}/$networkuuid1/;
2966
                    $managementurl =~ s/\{externalip\}/$externalip/;
2967
                    $val{'managementurl'} = $managementurl;
2968
                    my $upgradeurl;
2969
                    $upgradeurl = $dbimg->{'upgradelink'};
2970
                    $upgradeurl =~ s/\{uuid\}/$networkuuid1/;
2971
                    $val{'upgradeurl'} = $upgradeurl;
2972
                    my $terminalurl;
2973
                    $terminalurl = $dbimg->{'terminallink'};
2974
                    $terminalurl =~ s/\{uuid\}/$networkuuid1/;
2975
                    $val{'terminalurl'} = $terminalurl;
2976
                    $val{'externalip'} = $externalip;
2977
                    $val{'system'} = '--';
2978

    
2979
                    $curreg{$sysuuid} = \%val;
2980
                }
2981
            }
2982
        }
2983
        tied(%register)->commit;
2984
    }
2985
    untie %imagereg;
2986

    
2987
    my @regvalues = values %register;
2988
    # Go through systems register, add empty systems and update statuses
2989
    foreach my $valref (@regvalues) {
2990
    # Only include items belonging to current user (or all users if specified and user is admin)
2991
        if ($username eq $valref->{'user'} || $fulllist) {
2992
            next unless (!$curuuid || $valref->{'uuid'} eq $curuuid);
2993

    
2994
            my %val = %{$valref};
2995
            # add empty system (must be empty since not included from going through servers
2996
            if (!($curreg{$val{'uuid'}})) {
2997
                $val{'issystem'} = 1;
2998
                $val{'status'} = 'inactive';
2999
                $curreg{$val{'uuid'}} = \%val;
3000
            } else {
3001
            # Update status
3002
                my $status = 'running';
3003
                my $externalips = 0;
3004
                foreach my $child (@{$curreg{$val{'uuid'}}-> {'children'}}) {
3005
                    $status = $child->{'status'} unless ($child->{'status'} eq $status);
3006
                    $externalips += $child->{'externalips'} unless ($child->{'externalips'} eq '');
3007
                }
3008
                $status = 'degraded' unless ($status eq 'running' || $status eq 'shutoff');
3009
                $curreg{$val{'uuid'}}->{'status'} = $status;
3010
                $curreg{$val{'uuid'}}->{'externalips'} = $externalips;
3011
                # $networkreg{$domreg{$curdomuuid}->{'networkuuid1'}}->{'internalip'};
3012
                if ($curuuid && !$curreg{$val{'uuid'}}->{'internalip'}) { # Add calling server's own internalip if it's part of an ad-hoc assembled system
3013
                    $curreg{$val{'uuid'}}->{'internalip'} = $networkreg{$domreg{$curdomuuid}->{'networkuuid1'}}->{'internalip'};
3014
                }
3015
            }
3016
        }
3017
    }
3018
    untie %networkreg;
3019

    
3020
    @curregvalues = values %curreg;
3021
    my @sorted_systems = sort {$a->{'name'} cmp $b->{'name'}} @curregvalues;
3022
    @sorted_systems = sort {$a->{'status'} cmp $b->{'status'}} @sorted_systems;
3023

    
3024
    if ($action eq 'tablelist') {
3025
        my $t2 = Text::SimpleTable->new(40,24,14);
3026

    
3027
        $t2->row('uuid', 'name', 'user');
3028
        $t2->hr;
3029
        my $pattern = $options{m};
3030
        foreach $rowref (@sorted_systems){
3031
            if ($pattern) {
3032
                my $rowtext = $rowref->{'uuid'} . " " . $rowref->{'name'} . " " . $rowref->{'user'};
3033
                next unless ($rowtext =~ /$pattern/i);
3034
            }
3035
            $t2->row($rowref->{'uuid'}, $rowref->{'name'}||'--', $rowref->{'user'}||'--');
3036
        }
3037
        return $t2->draw;
3038
    } elsif ($action eq 'removeusersystems') {
3039
        return @sorted_systems;
3040
    } elsif ($action eq 'arraylist') {
3041
        return @sorted_systems;
3042
    } elsif ($console) {
3043
        return Dumper(\@sorted_systems);
3044
    } else {
3045
        my %it = ('uuid','--','name','--', 'issystem', 1);
3046
        push(@sorted_systems, \%it) if ($action eq 'flatlist');
3047
        my $json_text = to_json(\@sorted_systems, {pretty => 1});
3048
        $json_text =~ s/"false"/false/g;
3049
        $json_text =~ s/"true"/true/g;
3050
#        $json_text =~ s/""/"--"/g;
3051
        $json_text =~ s/null/"--"/g;
3052
        $json_text =~ s/\x/ /g;
3053
        if ($action eq 'flatlist') {
3054
            return qq|{"identifier": "uuid", "label": "name", "items": $json_text}|;
3055
        } else {
3056
            return $json_text;
3057
        }
3058
    }
3059
}
3060

    
3061
# Build a complete system around cloned image
3062
sub buildSystem {
3063
    my ($name, $hmaster, $hstoragepool, $hsystem, $hinstances,
3064
        $hnetworkuuid1, $hbschedule, $hnetworktype1, $hports, $hmemory, $hvcpu, $hdiskbus,
3065
        $hcdrom, $hboot, $hnicmodel1, $hnicmac1, $hnetworkuuid2, $hnicmac2, $hmonitors,
3066
        $hmanagementlink, $hstart, $duuid, $hstoragepool2, $hloader ) = @_;
3067

    
3068
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {$postreply = "Unable to access domain register"; return $postreply;};
3069
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$postreply = "Unable to access image register"; return $postreply;};
3070

    
3071
    my $master = $hmaster;
3072

    
3073
    if ($curuuid && !$domreg{$curuuid} && $duuid) { # curuuid is a system uuid
3074
        $curuuid = $duuid;
3075
    }
3076

    
3077
    if (!$master && $curuuid && $domreg{$curuuid} && $imagereg{$domreg{$curuuid}->{image}}) {
3078
        $master = $imagereg{$domreg{$curuuid}->{image}}->{master};
3079
    }
3080
    my $cdrom = $hcdrom;
3081
    my $storagepool = $hstoragepool;
3082
    my $storagepool2 = $hstoragepool2 || '0';
3083
    my $loader = $hloader || 'bios';
3084
    my $image2;
3085
    $hinstances = 1 unless ($hinstances);
3086
    my $ioffset = 0;
3087
    if (!$name && $curuuid) {
3088
        $ioffset = 1; # Looks like we are called from an existing server - bump
3089
        $name = $domreg{$curuuid}->{'name'};
3090
        $name = $1 if ($name =~ /(.+)\.\d+$/);
3091
        foreach my $dom (values %domreg) { # Sequential naming of related systems
3092
            if ($dom->{'user'} eq $user && $dom->{'name'} =~ /$name\.(\d+)$/) {
3093
                $ioffset = $1+1 if ($1 >= $ioffset);
3094
            }
3095
        }
3096
    }
3097
    if ($master && !$imagereg{"$master"}) {
3098
    # Try to look up master based on file name
3099
        my @spoolpaths = $cfg->param('STORAGE_POOLS_LOCAL_PATHS');
3100
        my @users = ('common', $user);
3101
        foreach my $u (@accounts) {push @users,$u;};
3102
        # Include my sponsors master images
3103
        my $billto = $userreg{$user}->{'billto'};
3104
        push @users, $billto if ($billto);
3105
        # Also include my subusers' master images
3106
        my @userregkeys = (tied %userreg)->select_where("billto = '$user'");
3107
        push @users, @userregkeys if (@userregkeys);
3108

    
3109
        my $match;
3110
        foreach my $u (@users) {
3111
            foreach $sp (@spoolpaths) {
3112
                if ($imagereg{"$sp/$u/$master"}) {
3113
                    $master = "$sp/$u/$master";
3114
                    $match = 1;
3115
                    last;
3116
                }
3117
            }
3118
            last if ($match),
3119
        }
3120
    }
3121

    
3122
    if (!$imagereg{$master} && length $master == 36) {
3123
    # Try to look up master by uuid
3124
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {$postreply = "Unable to access image register"; return $postreply;};
3125
        $master = $imagereg2{$master}->{'path'} if ($imagereg2{$master});
3126
        untie %imagereg2;
3127
    }
3128

    
3129
    if (!$master && $curuuid) {
3130
        $master = $imagereg{$domreg{$curuuid}->{'image'}}->{'master'};
3131
    }
3132

    
3133
    unless ($imagereg{$master}) {$postreply = "Status=Error Invalid master $master"; return $postreply;};
3134
    my $masterimage2 = $imagereg{$master}->{'image2'};
3135
    my $sysuuid = $hsystem;
3136

    
3137
    if ($cdrom && $cdrom ne '--' && !$imagereg{"$cdrom"}) {
3138
    # Try to look up cdrom based on file name
3139
        my @spoolpaths = $cfg->param('STORAGE_POOLS_LOCAL_PATHS');
3140
        my @users = ('common', $user);
3141
        foreach my $u (@accounts) {push @users,$u;};
3142
        my $match;
3143
        foreach my $u (@users) {
3144
            foreach $sp (@spoolpaths) {
3145
                if ($imagereg{"$sp/$u/$cdrom"}) {
3146
                    $cdrom = "$sp/$u/$cdrom";
3147
                    $match = 1;
3148
                    last;
3149
                }
3150
            }
3151
            last if ($match),
3152
        }
3153
    }
3154

    
3155
    #open OUTPUT, '>', "/dev/null"; select OUTPUT;
3156
    $Stabile::Images::console = 1;
3157
    require "$Stabile::basedir/cgi/images.cgi";
3158
    $Stabile::Networks::console = 1;
3159
    require "$Stabile::basedir/cgi/networks.cgi";
3160
    $Stabile::Servers::console = 1;
3161
    require "$Stabile::basedir/cgi/servers.cgi";
3162

    
3163
    #close(OUTPUT); select STDOUT;
3164
    # reset stdout to be the default file handle
3165
    my $oipath; # This var stores admin servers image, if only one server initially
3166
    if ($sysuuid eq 'new') {
3167
        $sysuuid = '';
3168
    } elsif ($sysuuid eq 'auto' || (!$sysuuid && $curuuid)) { # $curuuid means request is coming from a running vm
3169
        my $domuuid = $curuuid || Stabile::Networks::ip2domain( $ENV{'REMOTE_ADDR'} );
3170
        if ($domuuid && $domreg{$domuuid}) {
3171
            if ($domreg{$domuuid}->{'system'}) {
3172
                $sysuuid = $domreg{$domuuid}->{'system'};
3173
            } else {
3174
                my $ug = new Data::UUID;
3175
                $sysuuid = $ug->create_str();
3176
                #$sysuuid = $domuuid; # Make sysuuid same as primary domains uuid
3177
                $domreg{$domuuid}->{'system'} = $sysuuid;
3178
                $oipath = $domreg{$domuuid}->{'image'};
3179
            }
3180
        } else {
3181
            $sysuuid = '';
3182
        }
3183
    }
3184

    
3185
    # Check if images should be moved to node storage
3186
    if ($storagepool eq "-1") {
3187
        if (index($privileges, 'n')==-1 && !$isadmin) {
3188
            $storagepool = '';
3189
        } else {
3190
            $storagepool = -1;
3191
            # %nodereg is needed in order to increment reservedvcpus for nodes
3192
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) ) {$postreply = "Unable to access node register"; return $postreply;};
3193
        }
3194
    }
3195

    
3196
    my @domains;
3197
    my $systemuuid;
3198
    for (my $i=$ioffset; $i<$hinstances+$ioffset; $i++) {
3199
        my $ipath;
3200
        my $mac;
3201
        my $res;
3202
        my $istr = ".$i";
3203
        $istr = '' if ($hinstances==1 && $i==0);
3204

    
3205
    # Clone image
3206
        my $imagename = $name;
3207
        $imagename =~ s/system/Image/i;
3208
        $res = Stabile::Images::Clone($master, 'clone', '', $storagepool, '', "$imagename$istr", $hbschedule, 1, $hmanagementlink, $appid, 1, $hvcpu, $hmemory);
3209
        $postreply .= $res;
3210
        if ($res =~ /path: (.+)/) {
3211
            $ipath = $1;
3212
        } else {
3213
            next;
3214
        }
3215
        $mac = $1 if ($res =~ /mac: (.+)/);
3216
        Stabile::Images::updateBilling();
3217

    
3218
        # Secondary image - clone it
3219
        if ($masterimage2 && $masterimage2 ne '--' && $masterimage2 =~ /\.master\.qcow2$/) {
3220
            $res = Stabile::Images::Clone($masterimage2, 'clone', '', $storagepool2, $mac, "$imagename$istr-data", $hbschedule, 1, '', '', 1);
3221
            $postreply .= $res;
3222
            $image2 = $1 if ($res =~ /path: (.+)/);
3223
        }
3224

    
3225
    # Create network
3226
        my $networkuuid1;
3227
        if ($hnetworkuuid1) { # An existing network was specified
3228
            $networkuuid1 = $hnetworkuuid1;
3229
        } else { # Create new network
3230
            my $networkname = $name;
3231
            $networkname =~ s/system/Connection/i;
3232
            my $type = ($i==0)?$hnetworktype1 : '';
3233
            if (!$type) {
3234
                if ($curuuid && $i==0) { # This should never be true, leaving for now...
3235
                    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {$postreply = "Unable to access networks register"; return $postreply;};
3236
                    $type = $networkreg{$domreg{$curuuid}->{'networkuuid1'}}->{'type'};
3237
                    untie %networkreg;
3238
                } else {
3239
                    $type = 'internalip';
3240
                }
3241
            }
3242
            $main::syslogit->($user, 'info', "saving network $networkname$istr");
3243
            $res = Stabile::Networks::save('', '', "$networkname$istr", 'new', $type, '','',$hports,1,$user);
3244
            $postreply .= $res;
3245
            if ($res =~ /uuid: (.+)/) {
3246
                $networkuuid1 = $1;
3247
            } else {
3248
                next;
3249
            }
3250
            if ($hstart) {
3251
                Stabile::Networks::Activate($networkuuid1, 'activate'); # Ugly hack, seems to be needed
3252
            }
3253
        }
3254

    
3255
    # Create server
3256
        my $servername = $name;
3257
        $servername =~ s/system/Server/i;
3258
        if ($curuuid) {
3259
            $hmemory = $hmemory || $domreg{$curuuid}->{'memory'};
3260
            $hvcpu = $hvcpu || $domreg{$curuuid}->{'vcpu'};
3261
            $hdiskbus = $hdiskbus || $domreg{$curuuid}->{'diskbus'};
3262
            $cdrom = $cdrom || $domreg{$curuuid}->{'cdrom'};
3263
            $hboot = $hboot || $domreg{$curuuid}->{'boot'};
3264
            $hnicmodel1 = $hnicmodel1 || $domreg{$curuuid}->{'nicmodel1'};
3265
        }
3266

    
3267
        $main::syslogit->($user, 'info', "saving server $servername$istr");
3268
        $res =  Stabile::Servers::Save('', '', {
3269
                 uuid => '',
3270
                 name => "$servername$istr",
3271
                 memory => $hmemory,
3272
                 vcpu => $hvcpu,
3273
                 image => $ipath,
3274
                 imagename => '',
3275
                 image2 => $image2,
3276
                 image2name => '',
3277
                 diskbus => $hdiskbus,
3278
                 cdrom => $cdrom,
3279
                 boot => $hboot,
3280
                 loader => $loader,
3281
                 networkuuid1 => $networkuuid1,
3282
                 networkid1 => '',
3283
                 networkname1 => '',
3284
                 nicmodel1 => $hnicmodel1,
3285
                 nicmac1 => $hnicmac1,
3286
                 nicmac2 => $hnicmac2,
3287
                 status => 'new',
3288
                 notes => $notes,
3289
                 system => $sysuuid,
3290
                 newsystem => ($hinstances>1 && !$sysuuid),
3291
                 buildsystem => 1,
3292
                 console => 1
3293
             });
3294

    
3295
        $postreply .= "$res\n";
3296
        $sysuuid = $1 if ($res =~ /sysuuid: (\S+)/);
3297
        my $serveruuid;
3298
        $serveruuid = $1 if ($res =~ /uuid: (\S+)/);
3299
        my $sys = $register{$sysuuid};
3300
        if ($sysuuid && $i==$ioffset) {
3301
            $register{$sysuuid} = {
3302
                uuid => $sysuuid,
3303
                name => $sys->{'name'} || $servername, #Don't rename existing system
3304
                user => $user,
3305
                image => $sys->{'image'} || $oipath || $ipath, #Don't update admin image for existing system
3306
                created => $current_time
3307
            };
3308
        }
3309

    
3310
    # Create monitors
3311
        my @monitors = split(",", $hmonitors);
3312
        if (@monitors) {
3313
            $res = addSimpleMonitors($serveruuid, $alertemail, \@monitors);
3314
            if ( $res eq 'OK' ) {
3315
                `/usr/bin/moncmd reset keepstate &`;
3316
                $postreply .= "Status=OK Saved monitors @monitors\n";
3317
            } else {
3318
                $postreply .= "Status=OK Not saving monitors: $res\n";
3319
            }
3320

    
3321
        }
3322

    
3323
        if ($serveruuid) {
3324
            unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {$postreply = "Unable to access networks register"; return $postreply;};
3325
            $networkreg{$networkuuid1}->{'domains'} = $serveruuid;
3326
            tied(%networkreg)->commit;
3327
            untie %networkreg;
3328

    
3329
            push @domains, $serveruuid;
3330
            $imagereg{$ipath}->{'domains'} = $serveruuid;
3331
            $imagereg{$ipath}->{'domainnames'} = "$servername$istr";
3332
            if ($storagepool == -1) {
3333
                # my $mac = $imagereg{$ipath}->{'mac'};
3334
                # Increment reserved vcpus in order for location of target node to spread out
3335
                $postreply .= "Status=OK Cloned image to node $mac: $nodereg{$mac}->{'reservedvcpus'}";
3336
                $nodereg{$mac}->{'reservedvcpus'} += $hvcpu;
3337
                $postreply .= ":$nodereg{$mac}->{'reservedvcpus'}\n";
3338
                tied(%nodereg)->commit;
3339
                if (!$hstart) { # If we are not starting servers, wake up node anyway to perform clone operation
3340
                    if ($nodereg{$mac}->{'status'} eq 'asleep') {
3341
                        require "$Stabile::basedir/cgi/nodes.cgi";
3342
                        $Stabile::Nodes::console = 1;
3343
                        Stabile::Nodes::wake($mac);
3344
                    }
3345
                }
3346
            }
3347
        }
3348
        $systemuuid = (($sysuuid)? $sysuuid : $serveruuid) unless ($systemuuid);
3349
    }
3350
    if ($storagepool == -1) {
3351
        untie %nodereg;
3352
    }
3353

    
3354
    $postreply .= "Status=OK sysuuid: $systemuuid\n" if ($systemuuid);
3355
    if ($hstart) {
3356
        foreach my $serveruuid (@domains) {
3357
            $postreply .= Stabile::Servers::Start($serveruuid, 'start',{buildsystem=>1});
3358
        }
3359
    } else {
3360
        $main::updateUI->({tab=>'servers', user=>$user, uuid=>$serveruuid, status=>'shutoff'});
3361
    }
3362
    untie %imagereg;
3363
    #if (@domains) {
3364
    #    return to_json(\@domains, {pretty=>1});
3365
    #} else {
3366
        return $postreply;
3367
    #}
3368
}
3369

    
3370
sub upgradeSystem {
3371
    my $internalip = shift;
3372

    
3373
    unless (tie %imagereg,'Tie::DBI', { # Needed for ValidateItem
3374
        db=>'mysql:steamregister',
3375
        table=>'images',
3376
        key=>'path',
3377
        autocommit=>0,
3378
        CLOBBER=>3,
3379
        user=>$dbiuser,
3380
        password=>$dbipasswd}) {throw Error::Simple("Stroke=ERROR Image register could not be accessed")};
3381

    
3382
    my $appid;
3383
    my $appversion;
3384
    my $appname;
3385
    my $master;
3386
    my $progress;
3387
    my $currentversion;
3388

    
3389
# Locate the system we should upgrade
3390
    if ($internalip) {
3391
        foreach my $network (values %networkreg) {
3392
            if ($internalip =~ /^10\.\d+\.\d+\.\d+/
3393
                && $network->{'internalip'} eq $internalip
3394
                && $network->{'user'} eq $user
3395
            ) {
3396
                $curuuid = $domreg{$network->{'domains'}}->{'uuid'};
3397
                $cursysuuid = $domreg{$curuuid}->{'system'};
3398
                $master = $imagereg{$domreg{$curuuid}->{'image'}}->{'master'};
3399
                $appid = $imagereg{$master}->{'appid'};
3400
                $appversion = $imagereg{$master}->{'version'};
3401
                $appname = $imagereg{$master}->{'name'};
3402
                last;
3403
            }
3404
        }
3405
    }
3406
# Locate the newest version of master image
3407
    my $currentmaster;
3408
    foreach my $imgref (values %imagereg) {
3409
        if ($imgref->{'path'} =~ /\.master\.qcow2$/
3410
            && $imgref->{'path'} !~ /-data\.master\.qcow2$/
3411
            && $imgref->{'appid'} eq $appid
3412
        ) {
3413
            if ($imgref->{'version'} > $currentversion) {
3414
                $currentmaster = $imgref;
3415
                $currentversion = $imgref->{'version'};
3416
            }
3417
        }
3418
    }
3419
# Build list of system members
3420
    my @doms;
3421
    if ($cursysuuid && $register{$cursysuuid}) {
3422
        $register{$cursysuuid}->{'status'} = 'upgrading';
3423
        foreach my $domref (values %domreg) {
3424
            push( @doms, $domref ) if ($domref->{'system'} eq $cursysuuid && $domref->{'user'} eq $user);
3425
        }
3426
    } else {
3427
        push( @doms, $domreg{$curuuid} ) if ($domreg{$curuuid}->{'user'} eq $user);
3428
    }
3429
    $membs = int @doms;
3430

    
3431
    my $problem = 0;
3432
    foreach my $dom (@doms) {
3433
        if ($dom->{'status'} ne 'running') {
3434
            $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user,
3435
            status=>qq|Server $dom->{name} is not running. All member servers must be running when upgrading an app.|});
3436
            $problem = 1;
3437
            last;
3438
        }
3439
    }
3440
# First dump each servers data to nfs
3441
    unless ($problem) {
3442
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"Already newest version, reinstalling version $currentversion!", title=>'Reinstalling, hold on...'});
3443
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>'Beginning data dump!'});
3444

    
3445
        my $browser = LWP::UserAgent->new;
3446
        $browser->agent('movepiston/1.0b');
3447
        $browser->protocols_allowed( [ 'http','https'] );
3448

    
3449
        foreach my $dom (@doms) {
3450
            my $upgradelink = $imagereg{$dom->{'image'}}->{'upgradelink'};
3451
            if ($upgradelink) {
3452
                my $res;
3453
                my $networkuuid1 = $dom->{'networkuuid1'};
3454
                my $ip = $networkreg{$networkuuid1}->{'internalip'};
3455
                $upgradelink = "http://internalip$upgradelink" unless ($upgradelink =~ s/\{internalip\}/$ip/);
3456
                $domreg{$dom->{'uuid'}}->{'status'} = 'upgrading';
3457
                $main::updateUI->({tab=>'servers', user=>$user, uuid=>$dom->{'uuid'}, status=>'upgrading'});
3458
                my $content = $browser->get($upgradelink)->content();
3459
                if ($content =~ /^\{/) { # Looks like json
3460
                    $jres = from_json($content);
3461
                    $res = $jres->{'message'};
3462
                    unless (lc $jres->{'status'} eq 'ok') {
3463
                        $problem = 2;
3464
                    }
3465
                } else { # no json returned, assume things went hayward
3466
                    $res = $content;
3467
                    $res =~ s/</&lt;/g;
3468
                    $res =~ s/>/&gt;/g;
3469
                    $problem = "Data dump failed ($upgradelink)";
3470
                }
3471
                $res =~ s/\n/ /;
3472
                $progress += 10;
3473
                $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"$ip: $res", progress=>$progress});
3474
            }
3475
        }
3476
    }
3477
    tied(%domreg)->commit;
3478

    
3479
# Shut down all servers
3480
    unless ($problem) {
3481
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>'Beginning shutdown of servers!'});
3482
        require "$Stabile::basedir/cgi/servers.cgi";
3483
        $Stabile::Servers::console = 1;
3484
        foreach my $dom (@doms) {
3485
            $progress += 10;
3486
            my $networkuuid1 = $dom->{'networkuuid1'};
3487
            my $ip = $networkreg{$networkuuid1}->{'internalip'};
3488
            $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"$ip: Shutting down...", progress=>$progress});
3489
            if ($dom->{'status'} eq 'shutoff' || $dom->{'status'} eq 'inactive') {
3490
                next;
3491
            } else {
3492
                my $res = Stabile::Servers::destroyUserServers($user, 1, $dom->{'uuid'});
3493
                if ($dom->{'status'} ne 'shutoff' && $dom->{'status'} ne 'inactive') {
3494
                    $problem = "ERROR $res"; # We could not shut down a server, fail...
3495
                    last;
3496
                }
3497
            }
3498
        }
3499
    }
3500
# Then replace each image with new version
3501
    unless ($problem) {
3502
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>'Attaching new images!'});
3503
        require "$Stabile::basedir/cgi/images.cgi";
3504
        $Stabile::Images::console = 1;
3505
        foreach my $dom (@doms) {
3506
            $progress += 10;
3507
            my $networkuuid1 = $dom->{'networkuuid1'};
3508
            my $ip = $networkreg{$networkuuid1}->{'internalip'};
3509
            $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"$ip: Attaching image...", progress=>$progress});
3510
            my $image = $imagereg{$dom->{'image'}};
3511
            my $ipath;
3512
            # Clone image
3513
            my $imagename = $image->{'name'};
3514
            my $res = Stabile::Images::Clone($currentmaster->{'path'}, 'clone', '', $image->{'storagepool'}, '', $imagename, $image->{'bschedule'}, 1, $currentmaster->{'managementlink'}, $appid, 1);
3515
            $postreply .= $res;
3516
            if ($res =~ /path: (.+)/) {
3517
                $ipath = $1;
3518
            } else {
3519
                $problem = 5;
3520
            }
3521

    
3522
            if ($ipath =~ /\.qcow2$/) {
3523
                Stabile::Images::updateBilling();
3524
                # Attach new image to server
3525
                $main::syslogit->($user, 'info', "attaching new image to server $dom->{'name'} ($dom->{'uuid'})");
3526
                $res =  Stabile::Servers::Save({
3527
                         uuid => $dom->{'uuid'},
3528
                         image => $ipath,
3529
                         imagename => $imagename,
3530
                     });
3531
                # Update systems admin image
3532
                $register{$cursysuuid}->{'image'} = $ipath if ($register{$cursysuuid} && $dom->{'uuid'} eq $curuuid);
3533
                # Update image properties
3534
                $imagereg{$ipath}->{'domains'} = $dom->{'uuid'};
3535
                $imagereg{$ipath}->{'domainnames'} = $dom->{'name'};
3536
            } else {
3537
                $problem = 6;
3538
            }
3539
        }
3540
    }
3541

    
3542
# Finally start all servers with new image
3543
    unless ($problem) {
3544
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>'Starting servers!'});
3545
        require "$Stabile::basedir/cgi/servers.cgi";
3546
        $Stabile::Servers::console = 1;
3547
        foreach my $dom (@doms) {
3548
            $progress += 10;
3549
            my $networkuuid1 = $dom->{'networkuuid1'};
3550
            my $ip = $networkreg{$networkuuid1}->{'internalip'};
3551
            $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, status=>"$ip: Starting...", progress=>$progress});
3552
            if ($dom->{'status'} eq 'shutoff' || $dom->{'status'} eq 'inactive') {
3553
                Stabile::Servers::Start($dom->{'uuid'}, 'start', {uistatus=>'upgrading'});
3554
                $main::updateUI->({ tab=>'servers',
3555
                                    user=>$user,
3556
                                    uuid=>$dom->{'uuid'},
3557
                                    status=>'upgrading'})
3558
            }
3559
        }
3560
    } else {
3561
        foreach my $dom (@doms) {
3562
            $dom->{'status'} = 'inactive'; # Prevent servers from being stuck in upgrading status
3563
        }
3564
    }
3565

    
3566
    my $nlink = $imagereg{$doms[0]->{'image'}}->{'managementlink'}; # There might be a new managementlink for image
3567
    my $nuuid = $doms[0]->{'networkuuid1'};
3568
    $nlink =~ s/\{uuid\}/$nuuid/;
3569

    
3570
    unless ($problem) {
3571
# All servers successfully upgraded
3572
        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.|;
3573
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, progress=>100, status=>$status, managementlink=>$nlink, message=>"All done!"});
3574
    } else {
3575
        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.|;
3576
        $main::updateUI->({tab=>'upgrade', type=>'update', user=>$user, progress=>100, status=>$status, managementlink=>$nlink, message=>"Something went wrong :("});
3577
    }
3578
    untie %imagereg;
3579

    
3580
    my $reply = qq|{"message": "Upgrading $domreg{$curuuid}->{name} with $membs members"}|;
3581
    return "$reply\n";
3582
}
3583

    
3584
sub removeusersystems {
3585
    my $username = shift;
3586
    return $postreply unless (($isadmin || $user eq $username) && !$isreadonly);
3587
    $user = $username;
3588
    my @allsystems = getSystemsListing('removeusersystems');
3589
    foreach my $sys (@allsystems) {
3590
        next unless $sys->{'uuid'};
3591
#        $postreply .= "Status=OK Removing $username system $sys->{'name'} ($sys->{'uuid'})\n";
3592
        remove($sys->{'uuid'}, $sys->{'issystem'}, 1);
3593
    }
3594
    return $postreply || "[]";
3595
}
3596

    
3597

    
3598
# Remove every trace of a system including servers, images, etc.
3599
sub remove {
3600
    my ($uuid, $issystem, $destroy) = @_;
3601
    my $sysuuid = $uuid;
3602
    my $reguser = $register{$uuid}->{'user'} if ($register{$uuid});
3603
    $reguser = $domreg{$uuid}->{'user'} if (!$reguser && $domreg{$uuid});
3604

    
3605
    $Stabile::Images::user = $user;
3606
    require "$Stabile::basedir/cgi/images.cgi";
3607
    $Stabile::Images::console = 1;
3608

    
3609
    $Stabile::Networks::user = $user;
3610
    require "$Stabile::basedir/cgi/networks.cgi";
3611
    $Stabile::Networks::console = 1;
3612

    
3613
    $Stabile::Servers::user = $user;
3614
    require "$Stabile::basedir/cgi/servers.cgi";
3615
    $Stabile::Servers::console = 1;
3616

    
3617
    $issystem = 1 if ($register{$uuid});
3618
    my @domains;
3619
    my $res;
3620

    
3621
    if ($issystem) {
3622
    # Delete child servers
3623
        if (($user eq $reguser || $isadmin) && $register{$uuid}){ # Existing system
3624
        # First delete any linked networks
3625
            if ($register{$uuid}->{'networkuuids'} && $register{$uuid}->{'networkuuids'} ne '--') {
3626
                my @lnetworks = split /, ?/, $register{$uuid}->{'networkuuids'};
3627
                foreach my $networkuuid (@lnetworks) {
3628
                    if ($networkuuid) {
3629
                        Stabile::Networks::Deactivate($networkuuid);
3630
                        $res .= Stabile::Networks::Remove($networkuuid, 'remove', {force=>1});
3631
                    }
3632
                }
3633
            }
3634
            foreach my $domvalref (values %domreg) {
3635
                if ($domvalref->{'system'} eq $uuid && ($domvalref->{'user'} eq $user || $isadmin)) {
3636
                    if ($domvalref->{'status'} eq 'shutoff' || $domvalref->{'status'} eq 'inactive') {
3637
                        push @domains, $domvalref->{'uuid'};
3638
                    } elsif ($destroy) {
3639
                        Stabile::Servers::destroyUserServers($reguser, 1, $domvalref->{'uuid'});
3640
                        push @domains, $domvalref->{'uuid'} if ($domvalref->{'status'} eq 'shutoff' || $domvalref->{'status'} eq 'inactive');
3641
                    }
3642
                }
3643
            }
3644
        }
3645
        $postreply .= "Status=removing OK Removing system $register{$uuid}->{'name'} ($uuid)\n";
3646
        delete $register{$uuid};
3647
        tied(%register)->commit;
3648
    } elsif ($domreg{$uuid} && $domreg{$uuid}->{uuid}) {
3649
    # Delete single server
3650
        if ($domreg{$uuid}->{'status'} eq 'shutoff' || $domreg{$uuid}->{'status'} eq 'inactive') {
3651
            push @domains, $uuid;
3652
        } elsif ($destroy) {
3653
            Stabile::Servers::destroyUserServers($reguser, 1, $uuid);
3654
            push @domains, $uuid if ($domreg{$uuid}->{'status'} eq 'shutoff' || $domreg{$uuid}->{'status'} eq 'inactive');
3655
        }
3656
     #   $postreply .= "Status=OK Removing server $domreg{$uuid}->{'name'} ($uuid)\n";
3657
    } else {
3658
        $postreply .= "Status=Error System $uuid not found\n";
3659
        return $postreply;
3660
    }
3661
    my $duuid;
3662
    foreach my $domuuid (@domains) {
3663
        if ($domreg{$domuuid}->{'status'} ne 'shutoff' && $domreg{$domuuid}->{'status'} ne 'inactive' ) {
3664
            $postreply .= "Status=ERROR Cannot delete server (active)\n";
3665
        } else {
3666
            my $imagepath = $domreg{$domuuid}->{'image'};
3667
            my $image2path = $domreg{$domuuid}->{'image2'};
3668
            my $networkuuid1 = $domreg{$domuuid}->{'networkuuid1'};
3669
            my $networkuuid2 = $domreg{$domuuid}->{'networkuuid2'};
3670

    
3671
            # Delete packages from software register
3672
        #    $postreply .= deletePackages($domuuid);
3673
            # Delete monitors
3674
        #    $postreply .= deleteMonitors($domuuid)?"Stream=OK Deleted monitors for $domreg{$domuuid}->{'name'}\n":"Stream=OK No monitors to delete for $domreg{$domuuid}->{'name'}\n";
3675
            # Delete server
3676
            $res .= Stabile::Servers::Remove($domuuid);
3677

    
3678
            # Delete images
3679
            $res .= Stabile::Images::Remove($imagepath);
3680
            if ($image2path && $image2path ne '--') {
3681
                $res .= Stabile::Images::Remove($image2path);
3682
            }
3683
            # Delete networks
3684
            if ($networkuuid1 && $networkuuid1 ne '--' && $networkuuid1 ne '0' && $networkuuid1 ne '1') {
3685
                Stabile::Networks::Deactivate($networkuuid1);
3686
                $res .= Stabile::Networks::Remove($networkuuid1);
3687
            }
3688
            if ($networkuuid2 && $networkuuid2 ne '--' && $networkuuid2 ne '0' && $networkuuid2 ne '1') {
3689
                Stabile::Networks::Deactivate($networkuuid2);
3690
                $res .= Stabile::Networks::Remove($networkuuid2);
3691
            }
3692
        }
3693
        $duuid = $domuuid;
3694
    }
3695
    if ($register{$uuid}) {
3696
        delete $register{$uuid};
3697
        tied(%register)->commit;
3698
    }
3699
    if (@domains) {
3700
        $main::updateUI->(
3701
                        {tab=>'servers',
3702
                        user=>$user,
3703
                        type=>'update',
3704
                        message=>((scalar @domains==1)?"Server has been removed":"Stack has been removed!")
3705
                        },
3706
                        {tab=>'images',
3707
                        user=>$user
3708
                        },
3709
                        {tab=>'networks',
3710
                        user=>$user
3711
                        },
3712
                        {tab=>'home',
3713
                        user=>$user,
3714
                        type=>'removal',
3715
                        uuid=>$uuid,
3716
                        domuuid=>$duuid
3717
                        }
3718
                    );
3719
    } else {
3720
        $main::updateUI->(
3721
                        {tab=>'servers',
3722
                        user=>$user,
3723
                        type=>'update',
3724
                        message=>"Nothing to remove!"
3725
                        }
3726
                    );
3727
    }
3728

    
3729
    if ($engineid && $enginelinked) {
3730
        # Remove domain from origo.io
3731
        my $json_text = qq|{"uuid": "$sysuuid" , "status": "delete"}|;
3732
        $main::postAsyncToOrigo->($engineid, 'updateapps', "[$json_text]");
3733
    }
3734
    return $postreply || qq|Content-type: application/json\n\n|;
3735
}
3736

    
3737
sub getPackages {
3738
    my $curimg = shift;
3739

    
3740
    unless (tie %imagereg,'Tie::DBI', { # Needed for ValidateItem
3741
        db=>'mysql:steamregister',
3742
        table=>'images',
3743
        key=>'path',
3744
        autocommit=>0,
3745
        CLOBBER=>0,
3746
        user=>$dbiuser,
3747
        password=>$dbipasswd}) {throw Error::Simple("Stroke=ERROR Image register could not be accessed")};
3748

    
3749
    my $mac = $imagereg{$curimg}->{'mac'};
3750
    untie %imagereg;
3751

    
3752
    my $macip;
3753
    if ($mac && $mac ne '--') {
3754
        unless (tie %nodereg,'Tie::DBI', {
3755
            db=>'mysql:steamregister',
3756
            table=>'nodes',
3757
            key=>'mac',
3758
            autocommit=>0,
3759
            CLOBBER=>1,
3760
            user=>$dbiuser,
3761
            password=>$dbipasswd}) {return 0};
3762
        $macip = $nodereg{$mac}->{'ip'};
3763
        untie %nodereg;
3764
    }
3765
    $curimg =~ /(.+)/; $curimg = $1;
3766
    my $sshcmd;
3767
    if ($macip && $macip ne '--') {
3768
        $sshcmd = "/usr/bin/ssh -q -l irigo -i /var/www/.ssh/id_rsa_www -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $macip";
3769
    }
3770
    my $apps;
3771

    
3772
    if ($sshcmd) {
3773
        my $cmd = qq[eval \$(/usr/bin/guestfish --ro -a "$curimg" --i --listen); ]; # sets $GUESTFISH_PID shell var
3774
        $cmd .= qq[root="\$(/usr/bin/guestfish --remote inspect-get-roots)"; ];
3775
        $cmd .= qq[guestfish --remote inspect-get-product-name "\$root"; ];
3776
        $cmd .= qq[guestfish --remote inspect-get-hostname "\$root"; ];
3777
        $cmd .= qq[guestfish --remote inspect-list-applications "\$root"; ];
3778
        $cmd .= qq[guestfish --remote exit];
3779
        $cmd = "$sshcmd '$cmd'";
3780
        $apps = `$cmd`;
3781
    } else {
3782
        my $cmd;
3783
#        my $pid = open my $cmdpipe, "-|",qq[/usr/bin/guestfish --ro -a "$curimg" --i --listen];
3784
            $cmd .= qq[eval \$(/usr/bin/guestfish --ro -a "$curimg" --i --listen); ];
3785
        # Start listening guestfish
3786
        my $daemon = Proc::Daemon->new(
3787
                work_dir => '/usr/local/bin',
3788
                setuid => 'www-data',
3789
                exec_command => $cmd
3790
            ) or do {$posterror .= "Stream=ERROR $@\n";};
3791
        my $pid = $daemon->Init();
3792
        while ($daemon->Status($pid)) {
3793
            sleep 1;
3794
        }
3795
        # Find pid of the listening guestfish
3796
        my $pid2;
3797
        my $t = new Proc::ProcessTable;
3798
        foreach $p ( @{$t->table} ){
3799
            my $pcmd = $p->cmndline;
3800
            if ($pcmd =~ /guestfish.+$curimg/) {
3801
                $pid2 = $p->pid;
3802
                last;
3803
            }
3804
        }
3805
        my $cmd2;
3806
        if ($pid2) {
3807
            $cmd2 .= qq[root="\$(/usr/bin/guestfish --remote=$pid2 inspect-get-roots)"; ];
3808
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-get-product-name "\$root"; ];
3809
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-get-hostname "\$root"; ];
3810
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-list-applications "\$root"; ];
3811
            $cmd2 .= qq[guestfish --remote=$pid2 exit];
3812
        }
3813
        $apps = `$cmd2`;
3814
        $apps .= $cmd2;
3815
    }
3816
    return $apps;
3817
}
(7-7/9)