Project

General

Profile

Download (119 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::Servers;
9

    
10
use Error qw(:try);
11
use Data::UUID;
12
use Proc::Daemon;
13
use File::Basename;
14
use lib dirname (__FILE__);
15
use File::Basename;
16
use lib dirname (__FILE__);
17
use Stabile;
18
#use Encode::Escape;
19

    
20
$\ = ''; # Some of the above seems to set this to \n, resulting in every print appending a line feed
21

    
22
$cpuovercommision = $Stabile::config->get('CPU_OVERCOMMISION') || 1;
23
$dpolicy = $Stabile::config->get('DISTRIBUTION_POLICY') || 'disperse'; #"disperse" or "pack"
24
$amtpasswd = $Stabile::config->get('AMT_PASSWD') || "";
25
$brutalsleep = $Stabile::config->get('BRUTAL_SLEEP') || "";
26
$sshcmd = $sshcmd || $Stabile::sshcmd;
27

    
28
my %ahash; # A hash of accounts and associated privileges current user has access to
29

    
30
#my %options=();
31
#Getopt::Std::getopts("a:hfu:m:k:", \%options); # -a action -h help -f full-list (all users) -u uuid -m match pattern -k keywords
32

    
33
try {
34
    Init(); # Perform various initalization tasks
35
    process() if ($package);
36

    
37
    if ($action || %params) {
38
    	untie %register;
39
    	untie %networkreg;
40
        untie %nodereg;
41
        untie %xmlreg;
42
    }
43

    
44
} catch Error with {
45
	my $ex = shift;
46
    print $Stabile::q->header('text/html', '500 Internal Server Error') unless ($console);
47
	if ($ex->{-text}) {
48
        print "Got error: ", $ex->{-text}, " on line ", $ex->{-line}, "\n";
49
	} else {
50
	    print "Status=ERROR\n";
51
	}
52
} finally {
53
};
54

    
55
1;
56

    
57
sub getObj {
58
    my %h = %{@_[0]};
59
    $console = 1 if $h{"console"};
60
    $api = 1 if $h{"api"};
61
    my $uuid = $h{"uuid"};
62
    $uuid = $curuuid if ($uuid eq 'this');
63
    my $obj;
64
    $action = $action || $h{'action'};
65

    
66
    if ($h{'action'} eq 'destroy' || $action eq 'destroy' || $action eq 'attach' || $action eq 'detach' || $action =~ /changepassword|sshaccess/) {
67
        $obj = \%h;
68
        return $obj;
69
    }
70

    
71
    # Allow specifying nicmac1 instead of uuid if known
72
    if (!$uuid) {
73
        $uuid = nicmac1ToUuid($h{"nicmac1"});
74
    }
75
    my $status = 'new';
76
    $status = $register{$uuid}->{'status'} if ($register{$uuid});
77

    
78
    my $objaction = lc $h{"action"};
79
    $objaction = "" if ($status eq "new");
80

    
81
    if ((!$uuid) && $status eq 'new') {
82
        my $ug = new Data::UUID;
83
        $uuid = $ug->create_str();
84
        if ($uripath =~ /servers(\.cgi)?\/(.+)/) {
85
            my $huuid = $2;
86
            if ($ug->to_string($ug->from_string($huuid)) eq $huuid) { # Check for valid uuid
87
                $uuid = $huuid;
88
            }
89
        }
90
    };
91
    unless ($uuid && length $uuid == 36) {
92
        $posterror .= "Status=Error Invalid uuid.\n";
93
        return;
94
    }
95

    
96
    my $dbobj = $register{$uuid} || {};
97

    
98
    my $name = $h{"name"} || $dbobj->{'name'};
99
    utf8::decode($name);
100
    my $memory = $h{"memory"} || $dbobj->{'memory'};
101
    my $vcpu = $h{"vcpu"} || $dbobj->{'vcpu'};
102
    my $boot = $h{"boot"} || $dbobj->{'boot'};
103
    my $image = $h{"image"} || $dbobj->{'image'};
104
    my $imagename = $h{"imagename"} || $dbobj->{'imagename'};
105
    if ($image && $image ne '--' && !($image =~ /^\//)) { # Image is registered by uuid - we find the path
106
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {$posterror = "Unable to access image uuid register"; return;};
107
        $image = $imagereg2{$image}->{'path'};
108
        $imagename = $imagereg2{$image}->{'name'};
109
        untie %imagereg2;
110
        return unless ($image);
111
    }
112
    my $image2 = $h{"image2"} || $dbobj->{'image2'};
113
    my $image3 = $h{"image3"} || $dbobj->{'image3'};
114
    my $image4 = $h{"image4"} || $dbobj->{'image4'};
115
    my $image2name = $h{"image2name"} || $dbobj->{'image2name'};
116
    my $image3name = $h{"image3name"} || $dbobj->{'image3name'};
117
    my $image4name = $h{"image4name"} || $dbobj->{'image4name'};
118
    if ($image2 && $image2 ne '--' && !($image2 =~ /^\//)) { # Image2 is registered by uuid - we find the path
119
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {$postreply = "Unable to access image uuid register"; return $postreply;};
120
        $image2 = $imagereg2{$image2}->{'path'};
121
        $image2name = $imagereg2{$image2}->{'name'};
122
        untie %imagereg2;
123
    }
124
    my $diskbus = $h{"diskbus"} || $dbobj->{'diskbus'};
125
    my $diskdev = "vda";
126
    my $diskdev2 = "vdb";
127
    my $diskdev3 = "vdc";
128
    my $diskdev4 = "vdd";
129
    if ($diskbus eq "ide") {$diskdev = "hda"; $diskdev2 = "hdb"; $diskdev3 = "hdc"; $diskdev4 = "hdd"};
130
    my $cdrom = $h{"cdrom"} || $dbobj->{'cdrom'};
131
    if ($cdrom && $cdrom ne '--' && !($cdrom =~ /^\//)) {
132
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {$postreply = "Unable to access image uuid register"; return $postreply;};
133
        $cdrom = $imagereg2{$cdrom}->{'path'};
134
        untie %imagereg2;
135
    }
136

    
137
    my $networkuuid1 = $h{"networkuuid1"} || $dbobj->{'networkuuid1'};
138
    if ($h{"networkuuid1"} eq "0") {$networkuuid1 = "0"}; #Stupid perl... :-)
139
    my $networkid1 = $h{"networkid1"} || $dbobj->{'networkid1'};
140
    my $networkname1 = $h{"networkname1"} || $dbobj->{'networkname1'};
141
    my $nicmodel1 = $h{"nicmodel1"} || $dbobj->{'nicmodel1'};
142
    my $nicmac1 = $h{"nicmac1"} || $dbobj->{'nicmac1'};
143
    if (!$nicmac1 || $nicmac1 eq "--") {$nicmac1 = randomMac();}
144

    
145
    my $networkuuid2 = $h{"networkuuid2"} || $dbobj->{'networkuuid2'};
146
    if ($h{"networkuuid2"} eq "0") {$networkuuid2 = "0"};
147
    my $networkid2 = $h{"networkid2"} || $dbobj->{'networkid2'};
148
    my $networkname2 = $h{"networkname2"} || $dbobj->{'networkname2'};
149
    my $nicmac2 = $h{"nicmac2"} || $dbobj->{'nicmac2'};
150
    if (!$nicmac2 || $nicmac2 eq "--") {$nicmac2 = randomMac();}
151

    
152
    my $networkuuid3 = $h{"networkuuid3"} || $dbobj->{'networkuuid3'};
153
    if ($h{"networkuuid3"} eq "0") {$networkuuid3 = "0"};
154
    my $networkid3 = $h{"networkid3"} || $dbobj->{'networkid3'};
155
    my $networkname3 = $h{"networkname3"} || $dbobj->{'networkname3'};
156
    my $nicmac3 = $h{"nicmac3"} || $dbobj->{'nicmac3'};
157
    if (!$nicmac3 || $nicmac3 eq "--") {$nicmac3 = randomMac();}
158

    
159
    my $action = $h{"action"};
160
    my $notes = $h{"notes"};
161
    $notes = $dbobj->{'notes'} if (!$notes || $notes eq '--');
162
    my $reguser = $dbobj->{'user'};
163
    my $autostart = ($h{"autostart"} ."") || $dbobj->{'autostart'};
164
    if ($autostart && $autostart ne "false") {$autostart = "true";}
165
    my $locktonode = ($h{"locktonode"} ."") || $dbobj->{'locktonode'};
166
    if ($locktonode && $locktonode ne "false") {$locktonode = "true";}
167
    my $mac;
168
    $mac = $dbobj->{'mac'} unless ($objaction eq 'start');
169
#    $mac = $h{"mac"} if ($isadmin && $locktonode eq 'true' && $h{"mac"});
170
    $mac = $h{"mac"} if ($isadmin && $h{"mac"});
171
    my $domuser = $h{"user"} || $user; # Set if user is trying to move server to another account
172

    
173
    # Sanity checks
174
    if (
175
        ($name && length $name > 255)
176
            || ($networkuuid1<0)
177
            || ($networkuuid2<0)
178
            || ($networkuuid3<0)
179
            || ($networkuuid1>1 && length $networkuuid1 != 36)
180
            || ($networkuuid2>1 && length $networkuuid2 != 36)
181
            || ($networkuuid3>1 && length $networkuuid3 != 36)
182
            || ($image && length $image > 255)
183
            || ($imagename && length $imagename > 255)
184
            || ($image2 && length $image2 > 255)
185
            || ($image3 && length $image3 > 255)
186
            || ($image4 && length $image4 > 255)
187
            || ($image2name && length $image2name > 255)
188
            || ($image3name && length $image3name > 255)
189
            || ($image4name && length $image4name > 255)
190
            || ($cdrom && length $cdrom > 255)
191
            || ($memory && ($memory<64 || $memory >1024*64))
192
    ) {
193
        $postreply .= "Status=ERROR Invalid server data: $name\n";
194
        return 0;
195
    }
196

    
197
    # Security check
198
    if ($status eq 'new' && (($action && $action ne '--' && $action ne 'save') || !$image || $image eq '--')) {
199
        $postreply .= "Status=ERROR Bad server data: $name\n";
200
        $postmsg = "Bad server data";
201
        return 0;
202
    }
203
    if (!$reguser && $status ne 'new'
204
        && !($name && $memory && $vcpu && $boot && $image && $diskbus && $networkuuid1 && $nicmodel1)) {
205
        $posterror .= "Status=ERROR Insufficient data: $name\n";
206
        return 0;
207
    }
208
    if (!$isadmin) {
209
        if (($networkuuid1>1 && $networkreg{$networkuuid1}->{'user'} ne $user)
210
            || ($networkuuid2>1 && $networkreg{$networkuuid2}->{'user'} ne $user)
211
            || ($networkuuid3>1 && $networkreg{$networkuuid3}->{'user'} ne $user)
212
        )
213
        {
214
            $postreply .= "Status=ERROR No privileges: $networkname1 $networkname2\n";
215
            return 0;
216
        }
217
        if ( ($reguser && ($user ne $reguser) && $action ) || ($reguser && $status eq "new"))
218
        {
219
            $postreply .= "Status=ERROR No privileges: $name\n";
220
            return 0;
221
        }
222
        if (!($image =~ /\/$user\//)
223
            || ($image2 && $image2 ne "--" && !($image2 =~ /\/$user\//))
224
            || ($image3 && $image3 ne "--" && !($image3 =~ /\/$user\//))
225
            || ($image4 && $image4 ne "--" && !($image4 =~ /\/$user\//))
226
        )
227
        {
228
            $postreply .= "Status=ERROR No image privileges: $name\n";
229
            return 0;
230
        }
231
    }
232

    
233
    # No action - regular save of domain properties
234
    $cdrom = '--' if ($cdrom eq 'virtio');
235

    
236
    $obj = {
237
        uuid => $uuid,
238
        status => $status,
239
        name => $name,
240
        memory => $memory,
241
        vcpu => $vcpu,
242
        image => $image,
243
        imagename => $imagename,
244
        image2 => $image2,
245
        image2name => $image2name,
246
        image3 => $image3,
247
        image3name => $image3name,
248
        image4 => $image4,
249
        image4name => $image4name,
250
        diskbus => $diskbus,
251
        cdrom => $cdrom,
252
        boot => $boot,
253
        networkuuid1 => $networkuuid1,
254
        networkid1 => $networkid1,
255
        networkname1 => $networkname1,
256
        nicmodel1 => $nicmodel1,
257
        nicmac1 => $nicmac1,
258
        networkuuid2 => $networkuuid2,
259
        networkid2 => $networkid2,
260
        networkname2 => $networkname2,
261
        nicmac2 => $nicmac2,
262
        networkuuid3 => $networkuuid3,
263
        networkid3 => $networkid3,
264
        networkname3 => $networkname3,
265
        nicmac3 => $nicmac3,
266
        notes => $notes,
267
        autostart => $autostart,
268
        locktonode => $locktonode,
269
        mac => $mac,
270
        user => $domuser
271
    };
272
    return $obj;
273
}
274

    
275
sub Init {
276
    # Tie database tables to hashes
277
    unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access image register"};
278
    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access network register"};
279
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) ) {return "Unable to access nodes register"};
280
    unless ( tie(%xmlreg,'Tie::DBI', Hash::Merge::merge({table=>'domainxml'}, $Stabile::dbopts)) ) {return "Unable to access domainxml register"};
281

    
282
    # simplify globals initialized in Stabile.pm
283
    $tktuser = $tktuser || $Stabile::tktuser;
284
    $user = $user || $Stabile::user;
285
    $isadmin = $isadmin || $Stabile::isadmin;
286
    $privileges = $privileges || $Stabile::privileges;
287

    
288
    # Create aliases of functions
289
    *header = \&CGI::header;
290
    *to_json = \&JSON::to_json;
291

    
292
    *Showautostart = \&Autostartall;
293

    
294
    *do_save = \&Save;
295
    *do_tablelist = \&do_list;
296
    *do_jsonlist = \&do_list;
297
    *do_showautostart = \&action;
298
    *do_autostartall = \&privileged_action;
299
    *do_help = \&action;
300

    
301
    *do_start = \&privileged_action;
302
    *do_destroy = \&action;
303
    *do_shutdown = \&action;
304
    *do_suspend = \&action;
305
    *do_resume = \&action;
306
    *do_remove = \&privileged_action;
307
    *do_move = \&action;
308
    *do_mountcd = \&action;
309
    *do_changepassword = \&privileged_action;
310
    *do_sshaccess = \&privileged_action;
311

    
312
    *do_gear_start = \&do_gear_action;
313
    *do_gear_autostart = \&do_gear_action;
314
    *do_gear_showautostart = \&do_gear_action;
315
    *do_gear_autostartall = \&do_gear_action;
316
    *do_gear_remove = \&do_gear_action;
317
    *do_gear_changepassword = \&do_gear_action;
318
    *do_gear_sshaccess = \&do_gear_action;
319

    
320
}
321

    
322
sub do_list {
323
    my ($uuid, $action) = @_;
324
    if ($help) {
325
        return <<END
326
GET:uuid:
327
List servers current user has access to.
328
END
329
    }
330

    
331
    my $res;
332
    my $filter;
333
    my $statusfilter;
334
    my $uuidfilter;
335
    my $curserv = $register{$curuuid};
336
    if ($curuuid && ($isadmin || $curserv->{'user'} eq $user) && $uripath =~ /servers(\.cgi)?\/(\?|)(this)/) {
337
        $uuidfilter = $curuuid;
338
    } elsif ($uripath =~ /servers(\.cgi)?\/(\?|)(name|status)/) {
339
        $filter = $3 if ($uripath =~ /servers(\.cgi)?\/\??name(:|=)(.+)/);
340
        $filter = $1 if ($filter =~ /(.*)\*$/);
341
        $statusfilter = $4 if ($uripath =~ /servers(\.cgi)?\/\??(.+ AND )?status(:|=)(\w+)/);
342
    } elsif ($uripath =~ /servers(\.cgi)?\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})/) {
343
        $uuidfilter = $2;
344
    }
345
    $filter = $1 if ($filter =~ /(.*)\*/);
346

    
347
    my $sysuuid;
348
    if ($params{'system'}) {
349
        $sysuuid = $params{'system'};
350
        $sysuuid = $cursysuuid || $curuuid if ($params{'system'} eq 'this');
351
    }
352
    my @curregvalues;
353
    my @regkeys;
354
    if ($fulllist && $isadmin) {
355
        @regkeys = keys %register;
356
    } elsif ($uuidfilter && $isadmin) {
357
        @regkeys = (tied %register)->select_where("uuid = '$uuidfilter'");
358
    } elsif ($sysuuid) {
359
        @regkeys = (tied %register)->select_where("system = '$sysuuid' OR uuid = '$sysuuid'");
360
    } else {
361
        @regkeys = (tied %register)->select_where("user = '$user'");
362
    }
363

    
364
    unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
365
    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;};
366

    
367
    foreach my $k (@regkeys) {
368
        $valref = $register{$k};
369
        # Only include VM's belonging to current user (or all users if specified and user is admin)
370
        if ($user eq $valref->{'user'} || $fulllist || ($uuidfilter && $isadmin)) {
371
            next unless (!$sysuuid || $valref->{'system'} eq $sysuuid || $valref->{'uuid'} eq $sysuuid);
372

    
373
            my $validatedref = validateItem($valref);
374
            my %val = %{$validatedref}; # Deference and assign to new ass array, effectively cloning object
375
            $val{'memory'} += 0;
376
            $val{'vcpu'} += 0;
377
            $val{'nodetype'} = 'parent';
378
            $val{'internalip'} = $networkreg{$val{'networkuuid1'}}->{'internalip'};
379
            $val{'self'} = 1 if ($curuuid && $curuuid eq $val{'uuid'});
380
            if ($action eq 'treelist') {
381
                if ($val{'system'} && $val{'system'} ne '') {
382
                    my $sysuuid = $val{'system'};
383
                    my $sysname = $sysreg{$sysuuid}->{'name'};
384
                    if (!$sysname) {
385
                        $sysname = $1 if ($sysname =~ /(.+)\..*/);
386
                        $sysname = $val{'name'};
387
                        $sysname =~ s/server/System/i;
388
                    }
389
                    $sysreg{$sysuuid} = {
390
                        uuid => $sysuuid,
391
                        name => $sysname,
392
                        user => 'irigo'
393
                    };
394

    
395
                    my %pval = %{$sysreg{$sysuuid}};
396
                    $pval{'nodetype'} = 'parent';
397
                    $pval{'status'} = '--';
398
                    $val{'nodetype'} = 'child';
399

    
400
                    my @children;
401
                    push @children,\%val;
402
                    $pval{'children'} = \@children;
403
                    push @curregvalues,\%pval;
404
                } else {
405
                    push @curregvalues,\%val;
406
                }
407
            } elsif ($filter || $statusfilter || $uuidfilter) { # List filtered servers
408
                my $fmatch;
409
                my $smatch;
410
                my $umatch;
411
                $fmatch = 1 if (!$filter || $val{'name'}=~/$filter/i);
412
                $smatch = 1 if (!$statusfilter || $statusfilter eq 'all'
413
                    || $statusfilter eq $val{'status'}
414
                );
415
                $umatch = 1 if ($val{'uuid'} eq $uuidfilter);
416
                if ($fmatch && $smatch && !$uuidfilter) {
417
                    push @curregvalues,\%val;
418
                } elsif ($umatch) {
419
                    push @curregvalues,\%val;
420
                    last;
421
                }
422
            } else {
423
                push @curregvalues,\%val;
424
            }
425
        }
426
    }
427
    tied(%sysreg)->commit;
428
    untie(%sysreg);
429
    untie %imagereg;
430
    @curregvalues = (sort {$a->{'status'} cmp $b->{'status'}} @curregvalues); # Sort by status
431

    
432
    # Sort @curregvalues
433
    @curregvalues = (sort {$b->{'name'} <=> $a->{'name'}} @curregvalues); # Always sort by name first
434
    my $sort = 'status';
435
    $sort = $2 if ($uripath =~ /sort\((\+|\-)(\S+)\)/);
436
    my $reverse;
437
    $reverse = 1 if ($1 eq '-');
438
    if ($reverse) { # sort reverse
439
        if ($sort =~ /memory|vcpu/) {
440
            @curregvalues = (sort {$b->{$sort} <=> $a->{$sort}} @curregvalues); # Sort as number
441
        } else {
442
            @curregvalues = (sort {$b->{$sort} cmp $a->{$sort}} @curregvalues); # Sort as string
443
        }
444
    } else {
445
        if ($sort =~ /memory|vcpu/) {
446
            @curregvalues = (sort {$a->{$sort} <=> $b->{$sort}} @curregvalues); # Sort as number
447
        } else {
448
            @curregvalues = (sort {$a->{$sort} cmp $b->{$sort}} @curregvalues); # Sort as string
449
        }
450
    }
451

    
452
    if ($action eq 'tablelist') {
453
        my $t2;
454

    
455
        if ($isadmin) {
456
            $t2 = Text::SimpleTable->new(36,20,20,10,10,12,7);
457
            $t2->row('uuid', 'name', 'imagename', 'memory', 'user', 'mac', 'status');
458
        } else {
459
            $t2 = Text::SimpleTable->new(36,20,20,10,10,7);
460
            $t2->row('uuid', 'name', 'imagename', 'memory', 'user', 'status');
461
        }
462
        $t2->hr;
463
        my $pattern = $options{m};
464
        foreach $rowref (@curregvalues){
465
            if ($pattern) {
466
                my $rowtext = $rowref->{'uuid'} . " " . $rowref->{'name'} . " " . $rowref->{'imagename'} . " " . $rowref->{'memory'}
467
                    . " " .  $rowref->{'user'} . " " . $rowref->{'status'};
468
                $rowtext .= " " . $rowref->{'mac'} if ($isadmin);
469
                next unless ($rowtext =~ /$pattern/i);
470
            }
471
            if ($isadmin) {
472
                $t2->row($rowref->{'uuid'}, $rowref->{'name'}, $rowref->{'imagename'}, $rowref->{'memory'},
473
                    $rowref->{'user'}, $rowref->{'mac'}, $rowref->{'status'});
474
            } else {
475
                $t2->row($rowref->{'uuid'}, $rowref->{'name'}, $rowref->{'imagename'}, $rowref->{'memory'},
476
                    $rowref->{'user'}, $rowref->{'status'});
477
            }
478
        }
479
        $res .= $t2->draw;
480
    } elsif ($console) {
481
        $res .= Dumper(\@curregvalues);
482
    } else {
483
        my $json_text;
484
        if ($uuidfilter && @curregvalues) {
485
            $json_text = to_json($curregvalues[0], {pretty => 1});
486
        } else {
487
            $json_text = to_json(\@curregvalues, {pretty => 1});
488
        }
489

    
490
        $json_text =~ s/\x/ /g;
491
        $json_text =~ s/\"\"/"--"/g;
492
        $json_text =~ s/null/"--"/g;
493
        $json_text =~ s/"autostart":"true"/"autostart":true/g;
494
        $json_text =~ s/"autostart":"--"/"autostart":false/g;
495
        $json_text =~ s/"locktonode":"true"/"locktonode":true/g;
496
        $json_text =~ s/"locktonode":"--"/"locktonode":false/g;
497
        if ($action eq 'jsonlist' || $action eq 'list' || !$action) {
498
            $res .= $json_text;
499
        } else {
500
            $res .= qq|{"action": "$action", "identifier": "uuid", "label": "uuid", "items" : $json_text}|;
501
        }
502
    }
503
    return $res;
504
}
505

    
506
sub do_uuidshow {
507
    my ($uuid, $action) = @_;
508
    if ($help) {
509
        return <<END
510
GET:uuid:
511
Simple action for showing a single server.
512
END
513
    }
514
    my $res;
515
    $res .= $Stabile::q->header('text/plain') unless $console;
516
    my $u = $uuid || $options{u};
517
    if ($u || $u eq '0') {
518
        foreach my $uuid (keys %register) {
519
            if (($register{$uuid}->{'user'} eq $user || $register{$uuid}->{'user'} eq 'common' || $isadmin)
520
                && $uuid =~ /^$u/) {
521
                my %hash = %{$register{$uuid}};
522
                delete $hash{'action'};
523
                my $dump = Dumper(\%hash);
524
                $dump =~ s/undef/"--"/g;
525
                $res .= $dump;
526
                last;
527
            }
528
        }
529
    }
530
    return $res;
531
}
532

    
533
sub do_uuidlookup {
534
    if ($help) {
535
        return <<END
536
GET:uuid:
537
Simple action for looking up a uuid or part of a uuid and returning the complete uuid.
538
END
539
    }
540
    my $res;
541
    $res .= header('text/plain') unless $console;
542
    my $u = $options{u};
543
    $u = $curuuid unless ($u || $u eq '0');
544
    my $ruuid;
545
    if ($u || $u eq '0') {
546
        my $match;
547
        foreach my $uuid (keys %register) {
548
            if ($uuid =~ /^$u/) {
549
                $ruuid = $uuid if ($register{$uuid}->{'user'} eq $user || index($privileges,"a")!=-1);
550
                $match = 1;
551
                last;
552
            }
553
        }
554
        if (!$match && $isadmin) { # If no match and user is admin, do comprehensive lookup
555
            foreach my $uuid (keys %register) {
556
                if ($uuid =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/) {
557
                    $ruuid = $uuid;
558
                    last;
559
                }
560
            }
561
        }
562
    }
563
    $res .= "$ruuid\n" if ($ruuid);
564
    return $res;
565
}
566

    
567
sub do_destroyuserservers {
568
    if ($help) {
569
        return <<END
570
GET::
571
Simple action for destroying all servers belonging to a user
572
END
573
    }
574
    my $res;
575
    $res .= $Stabile::q->header('text/plain') unless $console;
576
    destroyUserServers($user);
577
    $res .= $postreply;
578
    return $res;
579
}
580

    
581
sub do_removeuserservers {
582
    if ($help) {
583
        return <<END
584
GET::
585
Simple action for removing all servers belonging to a user
586
END
587
    }
588
    my $res;
589
    $res .= $Stabile::q->header('text/plain') unless $console;
590
    removeUserServers($user);
591
    $res .= $postreply;
592
    return $res;
593
}
594

    
595
sub do_getappid {
596
    my ($uuid, $action) = @_;
597
    if ($help) {
598
        return <<END
599
GET:uuid:
600
Simple action for getting the app id
601
END
602
    }
603
    my $res;
604
    $res .= $Stabile::q->header('text/plain') unless $console;
605
    $uuid = $uuid || $options{u};
606
    $uuid = $curuuid unless ($uuid);
607
    if ($uuid && $register{$uuid}) {
608
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access image register"};
609
        $res .= "appid: ". $imagereg{$register{$uuid}->{image}}->{appid}, "\n";
610
        untie %imagereg;
611
    }
612
    return $res;
613
}
614

    
615
sub do_setrunning {
616
    my ($uuid, $action) = @_;
617
    if ($help) {
618
        return <<END
619
GET:uuid:
620
Simple action for setting status back to running after e.g. an upgrade
621
END
622
    }
623
    my $res;
624
    $res .= $Stabile::q->header('text/plain') unless $console;
625
    $uuid = $uuid || $options{u};
626
    $uuid = $curuuid unless ($uuid);
627
    if ($uuid && $register{$uuid}) {
628
        $register{$uuid}->{'status'} = 'running';
629
        $main::updateUI->({ tab => 'servers',
630
            user                => $user,
631
            uuid                => $uuid,
632
            status              => 'running' })
633

    
634
    };
635
    $res .= "Status=OK Set status of $register{$uuid}->{'name'} to running\n";
636
    return $res;
637
}
638

    
639
sub do_getappinfo {
640
    my ($uuid, $action) = @_;
641
    if ($help) {
642
        return <<END
643
GET:uuid:
644
Simple action for getting the apps basic info
645
END
646
    }
647
    my $res;
648
    $res .= $Stabile::q->header('application/json') unless $console;
649
    $uuid = $uuid || $options{u};
650
    $uuid = $curuuid unless ($uuid);
651
    my %appinfo;
652
    if ($uuid && $register{$uuid}) {
653
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access image register"};
654
        $appinfo{'appid'} = $imagereg{$register{$uuid}->{image}}->{appid} || '';
655
        $appinfo{'managementlink'} = $imagereg{$register{$uuid}->{image}}->{managementlink} || '';
656
        $appinfo{'managementlink'} =~ s/{uuid}/$register{$uuid}->{networkuuid1}/;
657

    
658
        my $termlink = $imagereg{$register{$uuid}->{image}}->{terminallink} || '';
659
        $termlink =~ s/{uuid}/$register{$uuid}->{networkuuid1}/;
660
        my $burl = $baseurl;
661
        $burl = $1 if ($termlink =~ /\/stabile/ && $baseurl =~ /(.+)\/stabile/); # Unpretty, but works for now
662
        # $termlink = $1 if ($termlink =~ /\/(.+)/);
663
        # $termlink = "$burl/$termlink" unless ($termlink =~ /^http/ || !$termlink); # || $termlink =~ /^\//
664
        $appinfo{'terminallink'} = $termlink;
665

    
666
        $appinfo{'upgradelink'} = $imagereg{$register{$uuid}->{image}}->{upgradelink} || '';
667
        $appinfo{'upgradelink'} =~ s/{uuid}/$register{$uuid}->{networkuuid1}/;
668
        $appinfo{'version'} = $imagereg{$register{$uuid}->{image}}->{version} || '';
669
        $appinfo{'status'} = $register{$uuid}->{status} || '';
670
        $appinfo{'name'} = $register{$uuid}->{name} || '';
671
        $appinfo{'system'} = $register{$uuid}->{system} || '';
672

    
673
        if ($appinfo{'system'}) {
674
            unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
675
            $appinfo{'systemname'} = $sysreg{$appinfo{'system'}}->{name} || '';
676
            untie(%sysreg);
677
        } else {
678
            $appinfo{'systemname'} = $appinfo{'name'};
679
        }
680

    
681

    
682
        if ($appinfo{'appid'}) {
683
            my @regkeys = (tied %imagereg)->select_where("appid = '$appinfo{appid}'");
684
            foreach my $k (@regkeys) {
685
                my $imgref = $imagereg{$k};
686
                if ($imgref->{'path'} =~ /\.master\.qcow2$/ && $imgref->{'appid'} eq $appinfo{'appid'}
687
                     && $imgref->{'installable'} && $imgref->{'installable'} ne 'false'
688
                ) {
689
                    if ($imgref->{'version'} > $appinfo{'currentversion'}) {
690
                        $appinfo{'currentversion'} = $imgref->{'version'};
691
                        $appinfo{'appname'} = $imgref->{'name'};
692
                    }
693
                }
694
            }
695
        }
696

    
697
        untie %imagereg;
698
    }
699
    $appinfo{'appstoreurl'} = $appstoreurl;
700
    $appinfo{'dnsdomain'} = ($enginelinked)?$dnsdomain:'';
701
    $appinfo{'dnssubdomain'} = ($enginelinked)?substr($engineid, 0, 8):'';
702
    $appinfo{'uuid'} = $uuid;
703
    $appinfo{'user'} = $user;
704
    $appinfo{'remoteip'} = $remoteip;
705
    $res .= to_json(\%appinfo, { pretty => 1 });
706
    return $res;
707
}
708

    
709
sub do_removeserver {
710
    if ($help) {
711
        return <<END
712
GET:uuid:
713
Simple action for destroying and removing a single server
714
END
715
    }
716
    my $res;
717
    $res .= $Stabile::q->header('text/plain') unless $console;
718
    if ($curuuid) {
719
        removeUserServers($user, $curuuid, 1);
720
    }
721
    else {
722
        $postreply .= "Status=Error Unable to uninstall\n";
723
    }
724
    $res .= $postreply;
725
    return $res;
726
}
727

    
728
sub do_updateregister {
729
    if ($help) {
730
        return <<END
731
GET::
732
Update server register
733
END
734
    }
735
    my $res;
736
    $res .= $Stabile::q->header('text/plain') unless $console;
737
    return unless $isadmin;
738
    updateRegister();
739
    $res .= "Status=OK Updated server registry for all users\n";
740
    return $res;
741
}
742

    
743
sub Autostartall {
744
    my ($uuid, $action) = @_;
745
    if ($help) {
746
        return <<END
747
GET::
748
Start all servers marked for autostart. When called as showautostart only shows which would be started.
749
END
750
    }
751
    my $res;
752
    $res .= $Stabile::q->header('text/plain') unless $console;
753
    my $mes;
754
    return $res if ($isreadonly);
755

    
756
    # Wait for all pistons to be online
757
    my $nodedown;
758
    my $nodecount;
759
    for (my $i = 0; $i < 10; $i++) {
760
        $nodedown = 0;
761
        foreach my $node (values %nodereg) {
762
            if ($node->{'status'} ne 'running' && $node->{'status'} ne 'maintenance') {
763
                $nodedown = 1;
764
            }
765
            else {
766
                $nodecount++ unless ($node->{'status'} eq 'maintenance');
767
            }
768
        }
769
        if ($nodedown) {
770
            # Wait and see if nodes come online
771
            $mes = "Waiting for nodes...(" . (10 - $i) . ")\n";
772
            print $mes if ($console);
773
            $res .= $mes;
774
            sleep 5;
775
        }
776
        else {
777
            last;
778
        }
779
    }
780

    
781
    if (!%nodereg || $nodedown || !$nodecount) {
782
        $mes = "Not autostarting servers - not all nodes ready!\n";
783
        print $mes if ($console);
784
        $res .= $mes;
785
    }
786
    else {
787
        $mes = "$nodecount nodes ready - autostarting servers...\n";
788
        print $mes if ($console);
789
        $res .= $mes;
790
        if ($action eq "showautostart") {
791
            $mes = "Only showing which servers would be starting!\n";
792
            print $mes if ($console);
793
            $res .= $mes;
794
        }
795

    
796
        $Stabile::Networks::user = $user;
797
        require "$Stabile::basedir/cgi/networks.cgi";
798
        $Stabile::Networks::console = 1;
799

    
800
        foreach my $dom (values %register) {
801
            if ($dom->{'autostart'} eq '1' || $dom->{'autostart'} eq 'true') {
802
                $res .= "Checking if $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'}) should be started\n";
803
                my $networkstatus1 = $networkreg{$dom->{'networkuuid1'}}->{status};
804
                my $networkstatus2 = ($networkreg{$dom->{'networkuuid2'}})?$networkreg{$dom->{'networkuuid2'}}->{status}:'';
805
                my $networkstatus3 = ($networkreg{$dom->{'networkuuid3'}})?$networkreg{$dom->{'networkuuid3'}}->{status}:'';
806
                my @dnets;
807
                push @dnets, $dom->{'networkuuid1'} if ($dom->{'networkuuid1'} && $dom->{'networkuuid1'} ne '--' && $networkstatus1 ne 'up');
808
                push @dnets, $dom->{'networkuuid2'} if ($dom->{'networkuuid2'} && $dom->{'networkuuid2'} ne '--' && $networkstatus2 ne 'up');
809
                push @dnets, $dom->{'networkuuid3'} if ($dom->{'networkuuid3'} && $dom->{'networkuuid3'} ne '--' && $networkstatus3 ne 'up');
810
                my $i;
811
                for ($i=0; $i<5; $i++) { # wait for status newer than 10 secs
812
                    validateItem($dom);
813
                    last if (time() - $dom->{timestamp} < 10);
814
                    $mes = "Waiting for newer timestamp, current is " . (time() - $dom->{timestamp}) . " old\n";
815
                    print $mes if ($console);
816
                    $res .= $mes;
817
                    sleep 2;
818
                }
819
                if (
820
                    $dom->{'status'} eq 'shutoff' || $dom->{'status'} eq 'inactive'
821
                ) {
822
                    if ($action eq "showautostart") { # Dry run
823
                        $mes = "Starting $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
824
                        print $mes if ($console);
825
                        $res .= $mes;
826
                    }
827
                    else {
828
                        $mes = "Starting $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
829
                        print $mes if ($console);
830
                        $res .= $mes;
831
                        $postreply = Start($dom->{'uuid'});
832
                        print $postreply if ($console);
833
                        $res .= $postreply;
834
#                        $mes = `REMOTE_USER=$dom->{'user'} $base/cgi/servers.cgi -a start -u $dom->{'uuid'}`;
835
                        print $mes if ($console);
836
                        $res .= $mes;
837
                        sleep 1;
838
                    }
839
                }
840
                elsif (@dnets) {
841
                    if ($action eq "showautostart") { # Dry run
842
                        foreach my $networkuuid (@dnets) {
843
                            $mes = "Would bring network $networkreg{$networkuuid}->{name} up for $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
844
                            print $mes if ($console);
845
                            $res .= $mes;
846
                        }
847
                    }
848
                    else {
849
                        foreach my $networkuuid (@dnets) {
850
                            $mes = "Bringing network $networkreg{$networkuuid}->{name} up for $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
851
                            print $mes if ($console);
852
                            $res .= $mes;
853
                            $mes = Stabile::Networks::Activate($networkuuid, 'activate');
854
                            print $mes if ($console);
855
                            $res .= $mes;
856
                            sleep 1;
857
                        }
858
                    }
859
                }
860
            } else {
861
                $res .= "Not marked for autostart $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
862
            }
863
        }
864
    }
865
    return $res;
866
}
867

    
868
sub do_listnodeavailability {
869
    if ($help) {
870
        return <<END
871
GET::
872
Utility call - only informational. Shows availability of nodes for starting servers.
873
END
874
    }
875
    my $res;
876
    $res .= $Stabile::q->header('application/json') unless ($console);
877
    my ($temp1, $temp2, $temp3, $temp4, $ahashref) = locateTargetNode();
878
    my @avalues = values %$ahashref;
879
    my @sorted_values = (sort {$b->{'index'} <=> $a->{'index'}} @avalues);
880
    $res .= to_json(\@sorted_values, { pretty => 1 });
881
    return $res;
882
}
883

    
884
sub do_listbillingdata {
885
    if ($help) {
886
        return <<END
887
GET::
888
List current billing data.
889
END
890
    }
891
    my $res;
892
    $res .= $Stabile::q->header('application/json') unless ($console);
893
    my $buser = URI::Escape::uri_unescape($params{'user'}) || $user;
894
    my %b;
895
    my @bmonths;
896
    if ($isadmin || $buser eq $user) {
897
        my $bmonth = URI::Escape::uri_unescape($params{'month'}) || $month;
898
        my $byear = URI::Escape::uri_unescape($params{'year'}) || $year;
899
        if ($bmonth eq "all") {
900
            @bmonths = ("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12");
901
        }
902
        else {
903
            @bmonths = ($bmonth);
904
        }
905

    
906
        unless ( tie(%billingreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_domains', key=>'usernodetime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
907

    
908
        my @nkeys = keys %nodereg;
909
        foreach my $bm (@bmonths) {
910
            my $vcpuavg = 0;
911
            my $memoryavg = 0;
912
            foreach my $nmac (@nkeys) {
913
                $vcpuavg += $billingreg{"$buser-$nmac-$byear-$bm"}->{'vcpuavg'};
914
                $memoryavg += $billingreg{"$buser-$nmac-$byear-$bm"}->{'memoryavg'};
915
            }
916
            $b{"$buser-$byear-$bm"} = {
917
                id        => "$buser-$byear-$bm",
918
                vcpuavg   => $vcpuavg,
919
                memoryavg => $memoryavg,
920
                month     => $bm + 0,
921
                year      => $byear + 0
922
            }
923
        }
924
        untie %billingreg;
925
    }
926
    my @bvalues = values %b;
927
    $res .= "{\"identifier\": \"id\", \"label\": \"id\", \"items\":" . to_json(\@bvalues) . "}";
928
    return $res;
929
}
930

    
931
# Print list of available actions on objects
932
sub do_plainhelp {
933
    my $res;
934
    $res .= $Stabile::q->header('text/plain') unless $console;
935
    $res .= <<END
936
new [name="name"]
937
* start: Starts a server
938
* destroy: Destroys a server, i.e. terminates the VM, equivalent of turning the power off a physical computer
939
* shutdown: Asks the operating system of a server to shut down via ACPI
940
* suspend: Suspends the VM, effectively putting the server to sleep
941
* resume: Resumes a suspended VM, effectively waking the server from sleep
942
* move [mac="mac"]: Moves a server to specified node. If no node is specified, moves to other node with highest availability
943
index
944
* delete: Deletes a server. Image and network are not deleted, only information about the server. Server cannot be
945
runing
946
* mountcd [cdrom="path"]: Mounts a cd rom
947
END
948
    ;
949
    return $res;
950
}
951

    
952
# Helper function
953
sub recurse($) {
954
	my($path) = @_;
955
	my @files;
956
	## append a trailing / if it's not there
957
	$path .= '/' if($path !~ /\/$/);
958
	## loop through the files contained in the directory
959
	for my $eachFile (glob($path.'*')) {
960
		## if the file is a directory
961
		if( -d $eachFile) {
962
			## pass the directory to the routine ( recursion )
963
			push(@files,recurse($eachFile));
964
		} else {
965
			push(@files,$eachFile);
966
		}
967
	}
968
	return @files;
969
}
970

    
971
sub Start {
972
    my ($uuid, $action, $obj) = @_;
973
    $dmac = $obj->{mac};
974
    $buildsystem = $obj->{buildsystem};
975
    $uistatus = $obj->{uistatus};
976
    if ($help) {
977
        return <<END
978
GET:uuid,mac:
979
Start a server. Supply mac for starting on specific node.
980
END
981
    }
982
    $dmac = $dmac || $params{'mac'};
983
    return "Status=ERROR No uuid\n" unless ($register{$uuid});
984
    my $serv = $register{$uuid};
985
    $postreply = '' if ($buildsystem);
986

    
987
    my $name = $serv->{'name'};
988
    utf8::decode($name);
989
    my $image = $serv->{'image'};
990
    my $image2 = $serv->{'image2'};
991
    my $image3 = $serv->{'image3'};
992
    my $image4 = $serv->{'image4'};
993
    my $memory = $serv->{'memory'};
994
    my $vcpu = $serv->{'vcpu'};
995
    my $vgpu = $serv->{'vgpu'};
996
    my $dbstatus = $serv->{'status'};
997
    my $mac = $serv->{'mac'};
998
    my $macname = $serv->{'macname'};
999
    my $networkuuid1 = $serv->{'networkuuid1'};
1000
    my $networkuuid2 = $serv->{'networkuuid2'};
1001
    my $networkuuid3 = $serv->{'networkuuid3'};
1002
    my $nicmodel1 = $serv->{'nicmodel1'};
1003
    my $nicmac1 = $serv->{'nicmac1'};
1004
    my $nicmac2 = $serv->{'nicmac2'};
1005
    my $nicmac3 = $serv->{'nicmac3'};
1006
    my $boot = $serv->{'boot'};
1007
    my $diskbus = $serv->{'diskbus'};
1008
    my $cdrom = $serv->{'cdrom'};
1009
    my $diskdev = "vda";
1010
    my $diskdev2 = "vdb";
1011
    my $diskdev3 = "vdc";
1012
    my $diskdev4 = "vdd";
1013
    if ($diskbus eq "ide") {$diskdev = "hda"; $diskdev2 = "hdb"; $diskdev3 = "hdc"; $diskdev4 = "hdd"};
1014

    
1015
    my $mem = $memory * 1024;
1016

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

    
1019
    my $img = $imagereg{$image};
1020
    my $imagename = $img->{'name'};
1021
    my $imagestatus = $img->{'status'};
1022
    my $img2 = $imagereg{$image2};
1023
    my $image2status = $img2->{'status'};
1024
    my $img3 = $imagereg{$image3};
1025
    my $image3status = $img3->{'status'};
1026
    my $img4 = $imagereg{$image4};
1027
    my $image4status = $img4->{'status'};
1028

    
1029
    if (!$imagereg{$image}) {
1030
        $postreply .= "Status=Error Image $image not found - please select a new image for your server, not starting $name\n";
1031
        untie %imagereg;
1032
        return $postreply;
1033
    }
1034
    untie %imagereg;
1035

    
1036
    if ($imagestatus ne "used" && $imagestatus ne "cloning") {
1037
        $postreply .= "Status=ERROR Image $imagename $image is $imagestatus, not starting $name\n";
1038
    } elsif ($image2 && $image2 ne '--' && $image2status ne "used" && $image2status ne "cloning") {
1039
        $postreply .= "Status=ERROR Image2 is $image2status, not starting $name\n";
1040
    } elsif ($image3 && $image3 ne '--' && $image3status ne "used" && $image3status ne "cloning") {
1041
        $postreply .= "Status=ERROR Image3 is $image3status, not starting $name\n";
1042
    } elsif ($image4 && $image4 ne '--' && $image4status ne "used" && $image4status ne "cloning") {
1043
        $postreply .= "Status=ERROR Image4 is $image4status, not starting $name\n";
1044
    } elsif (overQuotas($memory,$vcpu)) {
1045
        $main::syslogit->($user, "info", "Over quota starting a $dbstatus domain: $uuid");
1046
        $postreply .= "Status=ERROR Over quota - not starting $name\n";
1047
    # Status inactive is typically caused by a movepiston having problems. We should not start inactive servers since
1048
    # they could possibly be running even if movepiston is down. Movepiston on the node should be brought up to update
1049
    # the status, or the node should be removed from the stabile.
1050
    # We now allow to force start of inactive server when dmac is specified
1051
    } elsif ((!$dmac || $dmac eq $mac) && $dbstatus eq 'inactive' && $nodereg{$mac} && ($nodereg{$mac}->{'status'} eq 'inactive' || $nodereg{$mac}->{'status'} eq 'shutdown')) {
1052
        $main::syslogit->($user, "info", "Not starting inactive domain: $uuid (last seen on $mac)");
1053
        $postreply .= "Status=ERROR Not starting $name - Please bring up node $macname\n";
1054
    } elsif ($dbstatus eq 'inactive' || $dbstatus eq 'shutdown' || $dbstatus eq 'shutoff' || $dbstatus eq 'new') {
1055
        unless ($dmac && $isadmin) {
1056
            $dmac = $mac if ($dbstatus eq 'inactive'); # If movepiston crashed while shutting down, allow server to start on same node
1057
        }
1058
        $uistatus = "starting" unless ($uistatus);
1059
        my $hypervisor = getHypervisor($image);
1060
        my ($targetmac, $targetname, $targetip, $port) = locateTargetNode($uuid, $dmac, $mem, $vcpu, $image, $image2 ,$image3, $image4, $hypervisor);
1061

    
1062
        # Build XML for starting domain
1063
        my $graphics = "vnc";
1064
        $graphics = "rdp" if ($hypervisor eq "vbox");
1065
        my $net1 = $networkreg{$networkuuid1};
1066
        my $networkid1 = $net1->{'id'}; # Get the current vlan id of the network
1067
        my $net2 = $networkreg{$networkuuid2};
1068
        my $networkid2 = $net2->{'id'}; # Get the current vlan id of the network
1069
        my $net3 = $networkreg{$networkuuid2};
1070
        my $networkid3 = $net3->{'id'}; # Get the current vlan id of the network
1071
        my $networkid1ip = $net1->{'internalip'};
1072
        $networkid1ip = $net1->{'externalip'} if ($net1->{'type'} eq 'externalip');
1073

    
1074
        my $uname = $name . substr($uuid,0,8); # We don't enforce unique names, so we make them
1075
        $uname =~ s/[^[:ascii:]]/_/g; # Get rid of funny chars - they mess up Guacamole
1076
        $uname =~ s/\W/_/g;
1077

    
1078
        my $driver1;
1079
        my $driver2;
1080
        if ($hypervisor eq 'kvm') {
1081
            my $fmt1 = ($image =~ /\.qcow2$/)?'qcow2':'raw';
1082
            my $fmt2 = ($image2 =~ /\.qcow2$/)?'qcow2':'raw';
1083
            my $fmt3 = ($image3 =~ /\.qcow2$/)?'qcow2':'raw';
1084
            my $fmt4 = ($image4 =~ /\.qcow2$/)?'qcow2':'raw';
1085
            my $cache1 = ($image =~ /\/node\//)?'default':'writeback';
1086
            my $cache2 = ($image2 =~ /\/node\//)?'default':'writeback';
1087
            my $cache3 = ($image3 =~ /\/node\//)?'default':'writeback';
1088
            my $cache4 = ($image4 =~ /\/node\//)?'default':'writeback';
1089
            $driver1 = "\n      <driver name='qemu' type='$fmt1' cache='$cache1'/>";
1090
            $driver2 = "\n      <driver name='qemu' type='$fmt2' cache='$cache2'/>";
1091
            $driver3 = "\n      <driver name='qemu' type='$fmt3' cache='$cache3'/>";
1092
            $driver4 = "\n      <driver name='qemu' type='$fmt4' cache='$cache4'/>";
1093
        }
1094

    
1095
        my $networktype1 = "user";
1096
        my $networksource1 = "default";
1097
        my $networkforward1 = "bridge";
1098
        my $networkisolated1 = "no";
1099
        $networksource1 = "vboxnet0" if ($hypervisor eq "vbox");
1100
        if ($networkid1 eq '0') {
1101
            $networktype1 = "user";
1102
            $networkforward1 = "nat";
1103
            $networkisolated1 = "yes"
1104
        } elsif ($networkid1 == 1) {
1105
            $networktype1 = "network" ;
1106
            $networkforward1 = "nat";
1107
            $networkisolated1 = "yes"
1108
        } elsif ($networkid1 > 1) {
1109
            $networktype1 = "bridge";
1110
            $networksource1 = "br$networkid1";
1111
        }
1112
        my $networktype2 = "user";
1113
        my $networksource2 = "default";
1114
        my $networkforward2 = "bridge";
1115
        my $networkisolated2 = "no";
1116
        $networksource2 = "vboxnet0" if ($hypervisor eq "vbox");
1117
        if ($networkid2 eq '0') {
1118
            $networktype2 = "user";
1119
            $networkforward2 = "nat";
1120
            $networkisolated2 = "yes"
1121
        } elsif ($networkid2 == 1) {
1122
            $networktype2 = "network" ;
1123
            $networkforward2 = "nat";
1124
            $networkisolated2 = "yes"
1125
        } elsif ($networkid2 > 1) {
1126
            $networktype2 = "bridge";
1127
            $networksource2 = "br$networkid2";
1128
        }
1129
        my $networktype3 = "user";
1130
        my $networksource3 = "default";
1131
        my $networkforward3 = "bridge";
1132
        my $networkisolated3 = "no";
1133
        $networksource3 = "vboxnet0" if ($hypervisor eq "vbox");
1134
        if ($networkid3 eq '0') {
1135
            $networktype3 = "user";
1136
            $networkforward3 = "nat";
1137
            $networkisolated3 = "yes"
1138
        } elsif ($networkid3 == 1) {
1139
            $networktype3 = "network" ;
1140
            $networkforward3 = "nat";
1141
            $networkisolated3 = "yes"
1142
        } elsif ($networkid3 > 1) {
1143
            $networktype3 = "bridge";
1144
            $networksource3 = "br$networkid3";
1145
        }
1146

    
1147
        my $xml = "<domain type='$hypervisor' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>\n";
1148
#        if ($vgpu && $vgpu ne "--") {
1149
#            $xml .= <<ENDXML2
1150
#  <qemu:commandline>
1151
#    <qemu:arg value='-device'/>
1152
#    <qemu:arg value='vfio-pci,host=01:00.0,x-vga=on'/>
1153
#    <qemu:arg value='-device'/>
1154
#    <qemu:arg value='vfio-pci,host=02:00.0,x-vga=on'/>
1155
#  </qemu:commandline>
1156
#ENDXML2
1157
#            ;
1158
#        }
1159

    
1160
#    <qemu:arg value='-set'/>
1161
#    <qemu:arg value='device.hostdev1.x-vga=on'/>
1162
#    <qemu:arg value='-cpu'/>
1163
#	<qemu:arg value='host,kvm=off'/>
1164
#    <qemu:arg value='-device'/>
1165
#	<qemu:arg value='pci-assign,host=01:00.0,id=hostdev0,configfd=20,bus=pci.0,addr=0x6,x-pci-vendor-id=0x10DE,x-pci-device-id=0x11BA,x-pci-sub-vendor-id=0x10DE,x-pci-sub-device-id=0x0965'/>
1166

    
1167
#  <cpu mode='host-model'>
1168
#    <vendor>Intel</vendor>
1169
#    <model>core2duo</model>
1170
#  </cpu>
1171

    
1172
#    <loader readonly='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE.fd</loader>
1173
#    <nvram template='/usr/share/OVMF/OVMF_VARS.fd'/>
1174

    
1175
        if ($vgpu && $vgpu ne "--") {
1176
            $xml .= <<ENDXML
1177
  <cpu mode='host-passthrough'>
1178
    <feature policy='disable' name='hypervisor'/>
1179
  </cpu>
1180
ENDXML
1181
;
1182
        } else {
1183
            $xml .= <<ENDXML
1184
  <cpu mode='host-model'>
1185
  </cpu>
1186
ENDXML
1187
            ;
1188
        }
1189
        $xml .=  <<ENDXML
1190
  <name>$uname</name>
1191
  <uuid>$uuid</uuid>
1192
  <memory>$mem</memory>
1193
  <vcpu>$vcpu</vcpu>
1194
  <os>
1195
    <type arch='x86_64' machine='pc'>hvm</type>
1196
    <boot dev='$boot'/>
1197
    <bootmenu enable='yes' timeout='200'/>
1198
    <smbios mode='sysinfo'/>
1199
  </os>
1200
  <sysinfo type='smbios'>
1201
    <bios>
1202
      <entry name='vendor'>Origo</entry>
1203
    </bios>
1204
    <system>
1205
      <entry name='manufacturer'>Origo</entry>
1206
      <entry name='sku'>$networkid1ip</entry>
1207
    </system>
1208
  </sysinfo>
1209
  <features>
1210
ENDXML
1211
;
1212
        if ($vgpu && $vgpu ne "--") { $xml .= <<ENDXML
1213
    <kvm>
1214
      <hidden state='on'/>
1215
    </kvm>
1216
ENDXML
1217
;
1218
        }
1219
        $xml .= <<ENDXML
1220
    <pae/>
1221
    <acpi/>
1222
    <apic/>
1223
  </features>
1224
  <clock offset='localtime'>
1225
    <timer name='rtc' tickpolicy='catchup' track='guest'/>
1226
    <timer name='pit' tickpolicy='delay'/>
1227
    <timer name='hpet' present='no'/>
1228
  </clock>
1229
  <on_poweroff>destroy</on_poweroff>
1230
  <on_reboot>restart</on_reboot>
1231
  <on_crash>restart</on_crash>
1232
  <devices>
1233
  <sound model='ac97'/>
1234
ENDXML
1235
;
1236
#        if ($vgpu && $vgpu ne "--") {
1237
#            $xml .= <<ENDXML2
1238
#  <hostdev mode='subsystem' type='pci' managed='yes'>
1239
#    <source>
1240
#      <address domain='0x0000' bus='0x01' slot='0x00' function='0x0' multifunction='on'/>
1241
#    </source>
1242
#  </hostdev>
1243
#  <hostdev mode='subsystem' type='pci' managed='yes'>
1244
#    <source>
1245
#      <address domain='0x0000' bus='0x02' slot='0x00' function='0x0' multifunction='on'/>
1246
#    </source>
1247
#  </hostdev>
1248
#ENDXML2
1249
#;
1250
#        }
1251
        if ($image && $image ne "" && $image ne "--") {
1252
						$xml .= <<ENDXML2
1253
    <disk type='file' device='disk'>
1254
      <source file='$image'/>$driver1
1255
      <target dev='$diskdev' bus='$diskbus'/>
1256
    </disk>
1257
ENDXML2
1258
;
1259
        };
1260

    
1261
        if ($image2 && $image2 ne "" && $image2 ne "--") {
1262
						$xml .= <<ENDXML2
1263
    <disk type='file' device='disk'>$driver2
1264
      <source file='$image2'/>
1265
      <target dev='$diskdev2' bus='$diskbus'/>
1266
    </disk>
1267
ENDXML2
1268
;
1269
        };
1270
        if ($image3 && $image3 ne "" && $image3 ne "--") {
1271
						$xml .= <<ENDXML2
1272
    <disk type='file' device='disk'>$driver3
1273
      <source file='$image3'/>
1274
      <target dev='$diskdev3' bus='$diskbus'/>
1275
    </disk>
1276
ENDXML2
1277
;
1278
        };
1279
        if ($image4 && $image4 ne "" && $image4 ne "--") {
1280
						$xml .= <<ENDXML2
1281
    <disk type='file' device='disk'>$driver4
1282
      <source file='$image4'/>
1283
      <target dev='$diskdev4' bus='$diskbus'/>
1284
    </disk>
1285
ENDXML2
1286
;
1287
        };
1288

    
1289
        unless ($image4 && $image4 ne '--' && $diskbus eq 'ide') {
1290
            if ($cdrom && $cdrom ne "" && $cdrom ne "--") {
1291
						$xml .= <<ENDXML3
1292
    <disk type='file' device='cdrom'>
1293
      <source file='$cdrom'/>
1294
      <target dev='hdd' bus='ide'/>
1295
      <readonly/>
1296
    </disk>
1297
ENDXML3
1298
;
1299
            } elsif ($hypervisor ne "vbox") {
1300
						$xml .= <<ENDXML3
1301
    <disk type='file' device='cdrom'>
1302
      <target dev='hdd' bus='ide'/>
1303
      <readonly/>
1304
    </disk>
1305
ENDXML3
1306
;
1307
            }
1308
        }
1309

    
1310
        $xml .= <<ENDXML4
1311
    <interface type='$networktype1'>
1312
      <source $networktype1='$networksource1'/>
1313
      <forward mode='$networkforward1'/>
1314
      <port isolated='$networkisolated1'/>
1315
      <model type='$nicmodel1'/>
1316
      <mac address='$nicmac1'/>
1317
    </interface>
1318
ENDXML4
1319
;
1320

    
1321
        if (($networkuuid2 && $networkuuid2 ne '--') || $networkuuid2 eq '0') {
1322
            $xml .= <<ENDXML5
1323
    <interface type='$networktype2'>
1324
      <source $networktype2='$networksource2'/>
1325
      <forward mode='$networkforward2'/>
1326
      <port isolated='$networkisolated2'/>
1327
      <model type='$nicmodel1'/>
1328
      <mac address='$nicmac2'/>
1329
    </interface>
1330
ENDXML5
1331
;
1332
        }
1333
        if (($networkuuid3 && $networkuuid3 ne '--') || $networkuuid3 eq '0') {
1334
            $xml .= <<ENDXML5
1335
    <interface type='$networktype3'>
1336
      <source $networktype3='$networksource3'/>
1337
      <forward mode='$networkforward3'/>
1338
      <port isolated='$networkisolated3'/>
1339
      <model type='$nicmodel1'/>
1340
      <mac address='$nicmac3'/>
1341
    </interface>
1342
ENDXML5
1343
;
1344
        }
1345
        $xml .= <<ENDXML6
1346
     <serial type='pty'>
1347
       <source path='/dev/pts/0'/>
1348
       <target port='0'/>
1349
     </serial>
1350
    <input type='tablet' bus='usb'/>
1351
    <graphics type='$graphics' port='$port'/>
1352
  </devices>
1353
</domain>
1354
ENDXML6
1355
;
1356

    
1357

    
1358
#    <graphics type='$graphics' port='$port' keymap='en-us'/>
1359
#     <console type='pty' tty='/dev/pts/0'>
1360
#       <source path='/dev/pts/0'/>
1361
#       <target port='0'/>
1362
#     </console>
1363
#     <graphics type='$graphics' port='-1' autoport='yes'/>
1364

    
1365
        $xmlreg{$uuid} = {
1366
            xml=>URI::Escape::uri_escape($xml)
1367
        };
1368

    
1369
        # Actually ask node to start domain
1370
        if ($targetmac) {
1371
            $register{$uuid}->{'mac'} = $targetmac;
1372
            $register{$uuid}->{'macname'} = $targetname;
1373
            $register{$uuid}->{'macip'} = $targetip;
1374

    
1375
            my $tasks = $nodereg{$targetmac}->{'tasks'};
1376
            $tasks .= "START $uuid $user\n";
1377
    # Also update allowed port forwards - obsolete
1378
    #        $tasks .= "PERMITOPEN $user\n";
1379
            $nodereg{$targetmac}->{'tasks'} = $tasks;
1380
            tied(%nodereg)->commit;
1381
            $uiuuid = $uuid;
1382
            $uidisplayip = $targetip;
1383
            $uidisplayport = $port;
1384
            $register{$uuid}->{'status'} = $uistatus;
1385
            $register{$uuid}->{'statustime'} = $current_time;
1386
            tied(%register)->commit;
1387

    
1388
            # Activate networks
1389
            require "$Stabile::basedir/cgi/networks.cgi";
1390
            Stabile::Networks::Activate($networkuuid1, 'activate');
1391
            Stabile::Networks::Activate($networkuuid2, 'activate') if ($networkuuid2 && $networkuuid2 ne '--');
1392
            Stabile::Networks::Activate($networkuuid3, 'activate') if ($networkuuid3 && $networkuuid3 ne '--');
1393

    
1394
            $main::syslogit->($user, "info", "Marked $name ($uuid) for ". $serv->{'status'} . " on $targetname ($targetmac)");
1395
            $postreply .= "Status=starting OK $uistatus ". $serv->{'name'} . "\n";
1396
        } else {
1397
            $main::syslogit->($user, "info", "Could not find $hypervisor target for creating $uuid ($image)");
1398
            $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . " (unable to locate target node)\n";
1399
        };
1400
    } else {
1401
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
1402
        $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . "\n";
1403
    }
1404
    #return ($uiuuid, $uidisplayip, $uidisplayport, $postreply, $targetmac);
1405
    return $postreply;
1406
}
1407

    
1408
sub do_attach {
1409
    my ($uuid, $action, $obj) = @_;
1410
    if ($help) {
1411
        return <<END
1412
GET:uuid,image:
1413
Attaches an image to a server as a disk device. Image must not be in use.
1414
END
1415
    }
1416
    my $dev = '';
1417
    my $imagenum = 0;
1418
    my $serv = $register{$uuid};
1419

    
1420
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1421
        return "Status=Error Server must exist and be running\n";
1422
    }
1423
    my $macip = $serv->{macip};
1424
    my $image = $obj->{image} || $obj->{path};
1425
    if ($image && !($image =~ /^\//)) { # We have a uuid
1426
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Status=Error Unable to access images register\n"};
1427
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1428
        untie %imagereg2;
1429
    }
1430
    unless (tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$postreply .= "Status=Error Unable to access images register\n"; return $postreply;};
1431
    unless ($macip && $imagereg{$image} && $imagereg{$image}->{'user'} eq $user && $serv->{'user'} eq $user)  {$postreply .= "Status=Error Invalid image or server\n"; return $postreply;};
1432
    if ($imagereg{$image}->{'status'} ne 'unused') {return "Status=Error Image $image is already in use ($imagereg{$image}->{'status'})\n"};
1433

    
1434
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1435
    my $res = `$cmd`;
1436
    unless ($res =~ /vdb\s+.+/) {$dev = 'vdb'; $imagenum = 2};
1437
    unless ($dev || $res =~ /vdc\s+.+/)  {$dev = 'vdc'; $imagenum = 3};
1438
    unless ($dev || $res =~ /vdd\s+.+/)  {$dev = 'vdd'; $imagenum = 4};
1439
    if (!$dev) {
1440
        $postreply = "Status=Error No more images can be attached\n";
1441
    } else {
1442
        my $xml = <<END
1443
<disk type='file' device='disk'>
1444
  <driver type='qcow2' name='qemu' cache='default'/>
1445
  <source file='$image'/>
1446
  <target dev='$dev' bus='virtio'/>
1447
</disk>
1448
END
1449
;
1450
        $cmd = qq|$sshcmd $macip "echo \\"$xml\\" > /tmp/attach-device-$uuid.xml"|;
1451
        $res = `$cmd`;
1452
        $res .= `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh attach-device $uuid /tmp/attach-device-$uuid.xml`;
1453
        chomp $res;
1454
        if ($res =~ /successfully/) {
1455
            $postreply .= "Status=OK Attaching $image to $dev\n";
1456
            $imagereg{$image}->{'status'} = 'active';
1457
            $imagereg{$image}->{'domains'} = $uuid;
1458
            $imagereg{$image}->{'domainnames'} = $serv->{'name'};
1459
            $serv->{"image$imagenum"} = $image;
1460
            $serv->{"image$imagenum"."name"} = $imagereg{$image}->{'name'};
1461
            $serv->{"image$imagenum"."type"} = 'qcow2';
1462
        } else {
1463
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1464
        }
1465
    }
1466
    untie %imagereg;
1467
    return $postreply;
1468
}
1469

    
1470
sub do_detach {
1471
    my ($uuid, $action, $obj) = @_;
1472
    if ($help) {
1473
        return <<END
1474
GET:uuid,image:
1475
Detaches a disk device and the associated image from a running server. All associated file-systems within the server should be unmounted before detaching, otherwise data loss i very probable. Use with care.
1476
END
1477
    }
1478
    my $dev = '';
1479
    my $serv = $register{$uuid};
1480

    
1481
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1482
        return "Status=Error Server must exist and be running\n";
1483
    }
1484
    my $macip = $serv->{macip};
1485

    
1486
    my $image = $obj->{image} || $obj->{path} || $serv->{'image2'};
1487
    if ($image && !($image =~ /^\//)) { # We have a uuid
1488
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1489
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1490
        untie %imagereg2;
1491
    }
1492
    unless (tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$postreply .= "Status=Error Unable to access images register\n"; return $postreply;};
1493
    unless ($macip && $imagereg{$image} && $imagereg{$image}->{'user'} eq $user && $serv->{'user'} eq $user)  {$postreply .= "Status=Error Invalid image or server. Server must have a secondary image attached.\n"; return $postreply;};
1494

    
1495
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1496
    my $res = `$cmd`;
1497
    $dev = $1 if ($res =~ /(vd.)\s+.+$image/);
1498
    if (!$dev) {
1499
        $postreply =  qq|Status=Error Image $image, $cmd, is not currently attached\n|;
1500
    } elsif ($dev eq 'vda') {
1501
        $postreply = "Status=Error You cannot detach the primary image\n";
1502
    } else {
1503
        $res = `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh detach-disk $uuid $dev`;
1504
        chomp $res;
1505
        if ($res =~ /successfully/) {
1506
            $postreply .= "Status=OK Detaching image $image, $imagereg{$image}->{'uuid'} from $dev\n";
1507
            my $imagenum;
1508
            $imagenum = 2 if ($serv->{'image2'} eq $image);
1509
            $imagenum = 3 if ($serv->{'image3'} eq $image);
1510
            $imagenum = 4 if ($serv->{'image4'} eq $image);
1511
            $imagereg{$image}->{'status'} = 'unused';
1512
            $imagereg{$image}->{'domains'} = '';
1513
            $imagereg{$image}->{'domainnames'} = '';
1514
            if ($imagenum) {
1515
                $serv->{"image$imagenum"} = '';
1516
                $serv->{"image$imagenum"."name"} = '';
1517
                $serv->{"image$imagenum"."type"} = '';
1518
            }
1519
        } else {
1520
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1521
        }
1522
    }
1523
    untie %imagereg;
1524
    return $postreply;
1525
}
1526

    
1527
sub Destroy {
1528
    my ($uuid, $action, $obj) = @_;
1529
    if ($help) {
1530
        return <<END
1531
GET:uuid,wait:
1532
Marks a server for halt, i.e. pull the plug if regular shutdown does not work or is not desired. Server and storage is preserved.
1533
END
1534
    }
1535
    my $uistatus = 'destroying';
1536
    my $name = $register{$uuid}->{'name'};
1537
    my $mac = $register{$uuid}->{'mac'};
1538
    my $macname = $register{$uuid}->{'macname'};
1539
    my $dbstatus = $register{$uuid}->{'status'};
1540
    my $wait = $obj->{'wait'};
1541
    if ($dbstatus eq 'running' or $dbstatus eq 'paused'
1542
        or $dbstatus eq 'shuttingdown' or $dbstatus eq 'starting'
1543
        or $dbstatus eq 'destroying' or $dbstatus eq 'upgrading'
1544
        or $dbstatus eq 'suspending' or $dbstatus eq 'resuming') {
1545
        if ($wait) {
1546
            $postreply = destroyUserServers($user, 1, $uuid);
1547
        } else {
1548
            my $tasks = $nodereg{$mac}->{'tasks'};
1549
            $nodereg{$mac}->{'tasks'} = $tasks . "DESTROY $uuid $user\n";
1550
            tied(%nodereg)->commit;
1551
            $register{$uuid}->{'status'} = $uistatus;
1552
            $register{$uuid}->{'statustime'} = $current_time;
1553
            $uiuuid = $uuid;
1554
            $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus on $macname ($mac)");
1555
            $postreply .= "Status=destroying $uistatus ". $register{$uuid}->{'name'} . "\n";
1556
        }
1557
    } else {
1558
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $name ($uuid)");
1559
        $postreply .= "Status=ERROR problem $uistatus $name\n";
1560
    }
1561
    return $postreply;
1562
}
1563

    
1564
sub getHypervisor {
1565
	my $image = shift;
1566
	# Produce a mapping of image file suffixes to hypervisors
1567
	my %idreg;
1568
    unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access nodeidentities register"};
1569
    my @idvalues = values %idreg;
1570
	my %formats;
1571
	foreach my $val (@idvalues) {
1572
		my %h = %$val;
1573
		foreach (split(/,/,$h{'formats'})) {
1574
			$formats{lc $_} = $h{'hypervisor'}
1575
		}
1576
	}
1577
	untie %idreg;
1578

    
1579
	# and then determine the hypervisor in question
1580
	my $hypervisor = "vbox";
1581
	my ($pathname, $path, $suffix) = fileparse($image, '\.[^\.]*');
1582
	$suffix = substr $suffix, 1;
1583
	my $hypervisor = $formats{lc $suffix};
1584
	return $hypervisor;
1585
}
1586

    
1587
sub nicmac1ToUuid {
1588
    my $nicmac1 = shift;
1589
    my $uuid;
1590
    return $uuid unless $nicmac1;
1591
    my @regkeys = (tied %register)->select_where("user = '$user' AND nicmac1 = '$nicmac1");
1592
	foreach my $k (@regkeys) {
1593
	    my $val = $register{$k};
1594
		my %h = %$val;
1595
		if (lc $h{'nicmac1'} eq lc $nicmac1 && $user eq $h{'user'}) {
1596
    		$uuid =  $h{'uuid'};
1597
    		last;
1598
		}
1599
	}
1600
	return $uuid;
1601
}
1602

    
1603
sub randomMac {
1604
	my ( %vendor, $lladdr, $i );
1605
#	$lladdr = '00';
1606
	$lladdr = '52:54:00';# KVM vendor string
1607
	while ( ++$i )
1608
#	{ last if $i > 10;
1609
	{ last if $i > 6;
1610
		$lladdr .= ':' if $i % 2;
1611
		$lladdr .= sprintf "%" . ( qw (X x) [int ( rand ( 2 ) ) ] ), int ( rand ( 16 ) );
1612
	}
1613
	return $lladdr;
1614
}
1615

    
1616
sub overQuotas {
1617
    my $meminc = shift;
1618
    my $vcpuinc = shift;
1619
	my $usedmemory = 0;
1620
	my $usedvcpus = 0;
1621
	my $overquota = 0;
1622
    return $overquota if ($isadmin || $Stabile::userprivileges =~ /a/); # Don't enforce quotas for admins
1623

    
1624
	my $memoryquota = $usermemoryquota;
1625
	my $vcpuquota = $uservcpuquota;
1626

    
1627
	if (!$memoryquota || !$vcpuquota) { # 0 or empty quota means use defaults
1628
        $memoryquota = $memoryquota || $Stabile::config->get('MEMORY_QUOTA');
1629
        $vcpuquota = $vcpuquota || $Stabile::config->get('VCPU_QUOTA');
1630
    }
1631

    
1632
    my @regkeys = (tied %register)->select_where("user = '$user'");
1633
	foreach my $k (@regkeys) {
1634
	    my $val = $register{$k};
1635
		if ($val->{'user'} eq $user && $val->{'status'} ne "shutoff" &&
1636
		    $val->{'status'} ne "inactive" && $val->{'status'} ne "shutdown" ) {
1637

    
1638
		    $usedmemory += $val->{'memory'};
1639
		    $usedvcpus += $val->{'vcpu'};
1640
		}
1641
	}
1642
	$overquota = $usedmemory+$meminc if ($memoryquota!=-1 && $usedmemory+$meminc > $memoryquota); # -1 means no quota
1643
	$overquota = $usedvcpus+$vcpuinc if ($vcpuquota!=-1 && $usedvcpus+$vcpuinc > $vcpuquota);
1644
	return $overquota;
1645
}
1646

    
1647
sub validateItem {
1648
    my $valref = shift;
1649
    my $img = $imagereg{$valref->{'image'}};
1650
    my $imagename = $img->{'name'};
1651
    $valref->{'imagename'} = $imagename if ($imagename);
1652
    my $imagetype = $img->{'type'};
1653
    $valref->{'imagetype'} = $imagetype if ($imagetype);
1654

    
1655
    # imagex may be registered by uuid instead of path - find the path
1656
    # We now support up to 4 images
1657
    for (my $i=2; $i<=4; $i++) {
1658
        if ($valref->{"image$i"} && $valref->{"image$i"} ne '--' && !($valref->{"image$i"} =~ /^\//)) {
1659
            unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1660
            $valref->{"image$i"} = $imagereg2{$valref->{"image$i"}}->{'path'};
1661
            untie %imagereg2;
1662
        }
1663

    
1664
        my $imgi = $imagereg{$valref->{"image$i"}};
1665
        $valref->{"image$i" . 'name'} = $imgi->{'name'} || $valref->{"image$i" . 'name'};
1666
        $valref->{"image$i" . 'type'} = $imgi->{'type'} || $valref->{"image$i" . 'type'};
1667
    }
1668

    
1669
    my $net1 = $networkreg{$valref->{'networkuuid1'}};
1670
    my $networkname1 = $net1->{'name'};
1671
    $valref->{'networkname1'} = $networkname1 if ($networkname1);
1672
    my $net2 = $networkreg{$valref->{'networkuuid2'}};
1673
    my $networkname2 = $net2->{'name'};
1674
    $valref->{'networkname2'} = $networkname2 if ($networkname2);
1675
    my $name = $valref->{'name'};
1676
    $valref->{'name'} = $imagename unless $name;
1677

    
1678
    if ($valref->{'status'} eq "shutoff" || $valref->{'status'} eq "inactive") {
1679
        my $node = $nodereg{$valref->{'mac'}};
1680
        if ($valref->{'image'} =~ /\/mnt\/stabile\/node\//) {
1681
            $valref->{'mac'} = $img->{'mac'};
1682
            $valref->{'macname'} = $node->{'name'};
1683
            $valref->{'macip'} = $node->{'ip'};
1684
        } elsif ($valref->{'image2'} =~ /\/mnt\/stabile\/node\//) {
1685
            $valref->{'mac'} = $imagereg{$valref->{'image2'}}->{'mac'};
1686
            $valref->{'macname'} = $node->{'name'};
1687
            $valref->{'macip'} = $node->{'ip'};
1688
        } elsif ($valref->{'image3'} =~ /\/mnt\/stabile\/node\//) {
1689
            $valref->{'mac'} = $imagereg{$valref->{'image3'}}->{'mac'};
1690
            $valref->{'macname'} = $node->{'name'};
1691
            $valref->{'macip'} = $node->{'ip'};
1692
        } elsif ($valref->{'image4'} =~ /\/mnt\/stabile\/node\//) {
1693
            $valref->{'mac'} = $imagereg{$valref->{'image4'}}->{'mac'};
1694
            $valref->{'macname'} = $node->{'name'};
1695
            $valref->{'macip'} = $node->{'ip'};
1696
        }
1697
    }
1698
# Mark domains we have heard from in the last 20 secs as inactive
1699
    my $dbtimestamp = 0;
1700
    $dbtimestamp = $register{$valref->{'uuid'}}->{'timestamp'} if ($register{$valref->{'uuid'}});
1701
    my $timediff = $current_time - $dbtimestamp;
1702
    if ($timediff >= 20) {
1703
        if  (! ($valref->{'status'} eq "shutoff"
1704
                || $valref->{'status'} eq "starting"
1705
            #    || $valref->{'status'} eq "shuttingdown"
1706
            #    || $valref->{'status'} eq "destroying"
1707
                || ($valref->{'status'} eq "moving" && $timediff<40)
1708
            )) { # Move has probably failed
1709
            $valref->{'status'} = "inactive";
1710
            $imagereg{$valref->{'image'}}->{'status'} = "used" if ($valref->{'image'} && $imagereg{$valref->{'image'}});
1711
            $imagereg{$valref->{'image2'}}->{'status'} = "used" if ($valref->{'image2'} && $imagereg{$valref->{'imag2'}});
1712
            $imagereg{$valref->{'image3'}}->{'status'} = "used" if ($valref->{'image3'} && $imagereg{$valref->{'image3'}});
1713
            $imagereg{$valref->{'image4'}}->{'status'} = "used" if ($valref->{'image4'} && $imagereg{$valref->{'image4'}});
1714
        }
1715
    };
1716
    return $valref;
1717
}
1718

    
1719
# Run through all domains and mark domains we have heard from in the last 20 secs as inactive
1720
sub updateRegister {
1721
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access user register"};
1722
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1723

    
1724
    my @regkeys = (tied %register)->select_where("user = '$user'");
1725

    
1726
    foreach my $k (@regkeys) {
1727
        my $valref = $register{$k};
1728
        next unless ($userreg{$valref->{'user'}});
1729
        my $dbtimestamp = $valref->{'timestamp'};
1730
        my $dbstatus = $valref->{'status'};
1731
        my $timediff = $current_time - $dbtimestamp;
1732
        my $imgstatus;
1733
        my $domstatus;
1734
        if ($timediff >= 20) {
1735
            if  ( $valref->{'status'} eq "shutoff" ) {
1736
                $imgstatus = 'used';
1737
            } elsif ((  $valref->{'status'} eq "starting"
1738
                            || $valref->{'status'} eq "shuttingdown"
1739
                        ) && $timediff>50) {
1740
                $imgstatus = 'used';
1741
                $domstatus = 'inactive';
1742
            } elsif ($valref->{'status'} eq "destroying" || $valref->{'status'} eq "moving") {
1743
                ;
1744
            } else {
1745
                $domstatus = 'inactive';
1746
                $imgstatus = 'used';
1747
            }
1748
            $valref->{'status'} = $domstatus if ($domstatus);
1749
            my $image = $valref->{'image'};
1750
            my $image2 = $valref->{'image2'};
1751
            my $image3 = $valref->{'image3'};
1752
            my $image4 = $valref->{'image4'};
1753
            $imagereg{$image}->{'status'} = $imgstatus if ($imgstatus);
1754
            $imagereg{$image2}->{'status'} = $imgstatus if ($image2 && $imgstatus);
1755
            $imagereg{$image3}->{'status'} = $imgstatus if ($image3 && $imgstatus);
1756
            $imagereg{$image4}->{'status'} = $imgstatus if ($image4 && $imgstatus);
1757
            if ($domstatus eq 'inactive ' && $dbstatus ne 'inactive') {
1758
                $main::updateUI->({ tab=>'servers',
1759
                                    user=>$valref->{'user'},
1760
                                    uuid=>$valref->{'uuid'},
1761
                                    sender=>'updateRegister',
1762
                                    status=>'inactive'})
1763
            }
1764
        };
1765

    
1766
    }
1767
    untie %userreg;
1768
    untie %imagereg;
1769
}
1770

    
1771

    
1772
sub locateTargetNode {
1773
    my ($uuid, $dmac, $mem, $vcpu, $image, $image2, $image3, $image4, $hypervisor, $smac)= @_;
1774
    my $targetname;
1775
    my $targetip;
1776
    my $port;
1777
    my $targetnode;
1778
    my $targetindex; # Availability index of located target node
1779
    my %avhash;
1780

    
1781
    my $mnode = $register{$uuid};
1782
    $dmac = $mnode->{'mac'}
1783
        if (!$dmac
1784
            && $mnode->{'locktonode'} eq 'true'
1785
            && $mnode->{'mac'}
1786
            && $mnode->{'mac'} ne '--'
1787
            );
1788

    
1789
    $dmac = '' unless ($isadmin); # Only allow admins to select specific node
1790
    if ($dmac && !$nodereg{$dmac}) {
1791
        $main::syslogit->($user, "info", "The target node $dmac no longer exists, starting $uuid on another node if possible");
1792
        $dmac = '';
1793
    }
1794

    
1795
    my $imageonnode = ($image =~ /\/mnt\/stabile\/node\//
1796
                                          || $image2 =~ /\/mnt\/stabile\/node\//
1797
                                          || $image3 =~ /\/mnt\/stabile\/node\//
1798
                                          || $image4 =~ /\/mnt\/stabile\/node\//
1799
                                          );
1800

    
1801
    foreach $node (values %nodereg) {
1802
        my $nstatus = $node->{'status'};
1803
        my $maintenance = $node->{'maintenance'};
1804
        my $nmac = $node->{'mac'};
1805

    
1806
        if (($nstatus eq 'running' || $nstatus eq 'asleep' || $nstatus eq 'maintenance' || $nstatus eq 'waking')
1807
         && $smac ne $nmac
1808
         && (( ($node->{'memfree'} > $mem+512*1024)
1809
         && (($node->{'vmvcpus'} + $vcpu) <= ($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'})) ) || $action eq 'listnodeavailability')
1810
        ) {
1811
        # Determine how available this node is
1812
        # Available memory
1813
            my $memweight = 0.2; # memory weighing factor
1814
            my $memindex = $avhash{$nmac}->{'memindex'} = int(100* $memweight* $node->{'memfree'} / (1024*1024) )/100;
1815
        # Free cores
1816
            my $cpuindex = $avhash{$nmac}->{'cpuindex'} = int(100*($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'} - $node->{'vmvcpus'} - $node->{'reservedvcpus'}))/100;
1817
        # Asleep - not asleep gives a +3
1818
            my $sleepindex = $avhash{$nmac}->{'sleepindex'} = ($node->{'status'} eq 'asleep' || $node->{'status'} eq 'waking')?'0':'3';
1819
            $avhash{$nmac}->{'vmvcpus'} = $node->{'vmvcpus'};
1820
#            $avhash{$nmac}->{'cpucommision'} = $cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'};
1821
#            $avhash{$nmac}->{'cpureservation'} = $node->{'vmvcpus'} + $node->{'reservedvcpus'};
1822

    
1823
            $avhash{$nmac}->{'name'} = $node->{'name'};
1824
            $avhash{$nmac}->{'mac'} = $node->{'mac'};
1825

    
1826
            my $aindex = $memindex + $cpuindex + $sleepindex;
1827
        # Don't use nodes that are out of memory of cores
1828
            $aindex = 0 if ($memindex <= 0 || $cpuindex <= 0);
1829
            $avhash{$nmac}->{'index'} = $aindex;
1830

    
1831
            $avhash{$nmac}->{'storfree'} = $node->{'storfree'};
1832
            $avhash{$nmac}->{'memfree'} = $node->{'memfree'};
1833
            $avhash{$nmac}->{'ip'} = $node->{'ip'};
1834
            $avhash{$nmac}->{'identity'} = $node->{'identity'};
1835
            $avhash{$nmac}->{'status'} = $node->{'status'};
1836
            $avhash{$nmac}->{'maintenance'} = $maintenance;
1837
            $avhash{$nmac}->{'reservedvcpus'} = $node->{'reservedvcpus'};
1838
            my $nodeidentity = $node->{'identity'};
1839
            $nodeidentity = 'kvm' if ($nodeidentity eq 'local_kvm');
1840

    
1841
            if ($hypervisor eq $nodeidentity) {
1842
                # If image is on node, we must start on same node - registered when moving image
1843
                if ($imageonnode) {
1844
                    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1845
                    $dmac = $imagereg{$image}->{'mac'};
1846
                    $dmac = $imagereg{$image2}->{'mac'} unless ($dmac);
1847
                    $dmac = $imagereg{$image3}->{'mac'} unless ($dmac);
1848
                    $dmac = $imagereg{$image4}->{'mac'} unless ($dmac);
1849

    
1850
                    untie %imagereg;
1851
                    if (!$dmac) {
1852
                        $postreply .= "Status=ERROR Image node not found\n";
1853
                        last;
1854
                    }
1855
                }
1856
                $dmac = "" if ($dmac eq "--");
1857
            # If a specific node is asked for, match mac addresses
1858
                if ($dmac eq $nmac) {
1859
                    $targetnode = $node;
1860
                    last;
1861
                } elsif (!$dmac && $nstatus ne "maintenance" && !$maintenance) {
1862
            # pack or disperse
1863
                    if (!$targetindex) {
1864
                        $targetindex = $aindex;
1865
                        $targetnode = $node;
1866
                    } elsif ($dpolicy eq 'pack') {
1867
                        if ($aindex < $targetindex) {
1868
                            $targetnode = $node;
1869
                            $targetindex = $aindex;
1870
                        }
1871
                    } else {
1872
                        if ($aindex > $targetindex) {
1873
                            $targetnode = $node;
1874
                            $targetindex = $aindex;
1875
                        }
1876
                    }
1877
                }
1878
            }
1879
        }
1880
    }
1881

    
1882
    if ($targetnode && $uuid) {
1883
        if ($targetnode->{'status'} eq 'asleep') {
1884
            my $nmac = $targetnode->{'mac'};
1885
            my $realmac = substr($nmac,0,2).":".substr($nmac,2,2).":".substr($nmac,4,2).":".substr($nmac,6,2).":".substr($nmac,8,2).":".substr($nmac,10,2);
1886
            my $nlogmsg = "Node $nmac marked for wake ";
1887
            if ($brutalsleep && (
1888
                    ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--')
1889
                || ($targetnode->{'ipmiip'} && $targetnode->{'ipmiip'} ne '--')
1890
                )) {
1891
                my $wakecmd;
1892
                if ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--') {
1893
                    $wakecmd = "echo 'y' | AMT_PASSWORD='$amtpasswd' /usr/bin/amttool $targetnode->{'amtip'} powerup pxe";
1894
                } else {
1895
                    $wakecmd = "ipmitool -I lanplus -H $targetnode->{'ipmiip'} -U ADMIN -P ADMIN power on";
1896
                }
1897
                $nlogmsg .= `$wakecmd`;
1898
            } else {
1899
                my $broadcastip = $targetnode->{'ip'};
1900
                $broadcastip =~ s/\.\d{1,3}$/.255/;
1901
                $nlogmsg .= 'on lan ' . `/usr/bin/wakeonlan -i $broadcastip $realmac`;
1902
            }
1903
            $targetnode->{'status'} = "waking";
1904
            $nlogmsg =~ s/\n/ /g;
1905
            $main::syslogit->($user, "info", $nlogmsg);
1906
            $postreply .= "Status=OK waking $targetnode->{'name'}\n";
1907
        }
1908
        $targetname = $targetnode->{'name'};
1909
        $targetmac = $targetnode->{'mac'};
1910
        $targetip = $targetnode->{'ip'};
1911
        $targetip = $targetnode->{'ip'};
1912
        my $porttaken = 1;
1913
        while ($porttaken) {
1914
            $porttaken = 0;
1915
            $port = $targetnode->{'vms'} + (($hypervisor eq "vbox")?3389:5900);
1916
            $port += int(rand(200));
1917
            my @regkeys = (tied %register)->select_where("port = '$port' AND macip = '$targetip'");
1918
            foreach my $k (@regkeys) {
1919
                $r = $register{$k};
1920
                if ($r->{'port'} eq $port && $r->{'macip'} eq $targetip) {
1921
                    $porttaken = 1;
1922
                }
1923
            }
1924
        }
1925
        $targetnode->{'vms'}++;
1926
        $targetnode->{'vmvcpus'} += $vcpu;
1927
        $register{$uuid}->{'port'} = $port;
1928
#        $register{$uuid}->{'mac'} = $targetmac;
1929
#        $register{$uuid}->{'macname'} = $targetname;
1930
#        $register{$uuid}->{'macip'} = $targetip;
1931
        $register{$uuid}->{'display'} = (($hypervisor eq "vbox")?'rdp':'vnc');
1932
    } else {
1933
        my $macstatus;
1934
        $macstatus = $nodereg{$dmac}->{status} if ($nodereg{$dmac});
1935
        $main::syslogit->($user, "info", "Could not find target for $uuid, $dmac, $mem, $vcpu, $image, $image2,$image3,$image4, $hypervisor, $smac, dmac-status: $macstatus") if ($uuid);
1936
    }
1937
    return ($targetmac, $targetname, $targetip, $port, \%avhash);
1938
}
1939

    
1940
sub destroyUserServers {
1941
    my $username = shift;
1942
    my $wait = shift; # Should we wait for servers do die
1943
    my $duuid = shift;
1944
    return unless ($isadmin || $user eq $username);
1945
    my @updateList;
1946

    
1947
    my @regkeys = (tied %register)->select_where("user = '$username'");
1948
    foreach my $uuid (@regkeys) {
1949
        if ($register{$uuid}->{'user'} eq $username
1950
            && $register{$uuid}->{'status'} ne 'shutoff'
1951
            && (!$duuid || $duuid eq $uuid)
1952
        ) {
1953
            $postreply .= "Destroying $username server $register{$uuid}->{'name'}, $uuid\n";
1954
            Destroy($uuid);
1955
            push (@updateList,{ tab=>'servers',
1956
                                user=>$user,
1957
                                uuid=>$duuid,
1958
                                status=>'destroying'});
1959
        }
1960
    }
1961
    $main::updateUI->(@updateList) if (@updateList);
1962
    if ($wait) {
1963
        my @regkeys = (tied %register)->select_where("user = '$username'");
1964
        my $activeservers = 1;
1965
        my $i = 0;
1966
        while ($activeservers && $i<10) {
1967
            $activeservers = 0;
1968
            foreach my $k (@regkeys) {
1969
                my $valref = $register{$k};
1970
                if ($username eq $valref->{'user'}
1971
                    && ($valref->{'status'} ne 'shutoff'
1972
                    && $valref->{'status'} ne 'inactive')
1973
                    && (!$duuid || $duuid eq $valref->{'uuid'})
1974
                ) {
1975
                    $activeservers = $valref->{'uuid'};
1976
                }
1977
            }
1978
            $i++;
1979
            if ($activeservers) {
1980
                my $res .= "Status=OK Waiting $i for server $register{$activeservers}->{'name'}, $register{$activeservers}->{'status'} to die...\n";
1981
                print $res if ($console);
1982
                $postreply .= $res;
1983
                sleep 2;
1984
            }
1985
        }
1986
        $postreply .= "Status=OK Servers halted for $username\n" unless ($activeservers);
1987
    }
1988
    return $postreply;
1989
}
1990

    
1991
sub removeUserServers {
1992
    my $username = shift;
1993
    my $uuid = shift;
1994
    my $destroy = shift; # Should running servers be destroyed before removing
1995
    return unless (($isadmin || $user eq $username) && !$isreadonly);
1996
    $user = $username;
1997
    my @regkeys = (tied %register)->select_where("user = '$username'");
1998
    foreach my $ruuid (@regkeys) {
1999
        next if ($uuid && $ruuid ne $uuid);
2000
        if ($destroy && $register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} ne 'shutoff' && $register{$ruuid}->{'status'} ne 'inactive')) {
2001
            destroyUserServers($username, 1, $ruuid);
2002
        }
2003

    
2004
        if ($register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} eq 'shutoff' || $register{$ruuid}->{'status'} eq 'inactive')) {
2005
            $postreply .= "Removing $username server $register{$ruuid}->{'name'}, $ruuid" . ($console?'':'<br>') . "\n";
2006
            Remove($ruuid);
2007
        }
2008
    }
2009
}
2010

    
2011
sub Remove {
2012
    my ($uuid, $action) = @_;
2013
    if ($help) {
2014
        return <<END
2015
DELETE:uuid:
2016
Removes a server. Server must be shutoff. Does not remove associated images or networks.
2017
END
2018
    }
2019
    my $reguser = $register{$uuid}->{'user'};
2020
    my $dbstatus = $register{$uuid}->{'status'};
2021
    my $image = $register{$uuid}->{'image'};
2022
    my $image2 = $register{$uuid}->{'image2'};
2023
    my $image3 = $register{$uuid}->{'image3'};
2024
    my $image4 = $register{$uuid}->{'image4'};
2025
    my $name = $register{$uuid}->{'name'};
2026
    $image2 = '' if ($image2 eq '--');
2027
    $image3 = '' if ($image3 eq '--');
2028
    $image4 = '' if ($image4 eq '--');
2029

    
2030
    if ($reguser ne $user) {
2031
        $postreply .= "Status=ERROR You cannot delete a vm you don't own\n";
2032
    } elsif ($dbstatus eq 'inactive' || $dbstatus eq 'shutdown' || $dbstatus eq 'shutoff') {
2033

    
2034
        # Delete software packages and monitors from register
2035
        $postmsg .= deletePackages($uuid);
2036
        my $sname = $register{$uuid}->{'name'};
2037
        utf8::decode($sname);
2038
        $postmsg .= deleteMonitors($uuid)?" deleted monitors for $sname ":'';
2039

    
2040
        delete $register{$uuid};
2041
        delete $xmlreg{$uuid};
2042

    
2043
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2044
        $imagereg{$image}->{'status'} = "unused" if ($imagereg{$image});
2045
        $imagereg{$image2}->{'status'} = "unused" if ($image2 && $imagereg{$image2});
2046
        $imagereg{$image3}->{'status'} = "unused" if ($image3 && $imagereg{$image3});
2047
        $imagereg{$image4}->{'status'} = "unused" if ($image4 && $imagereg{$image4});
2048
        untie %imagereg;
2049

    
2050
        # Delete metrics
2051
        my $metricsdir = "/var/lib/graphite/whisper/domains/$uuid";
2052
        `rm -r $metricsdir` if (-e $metricsdir);
2053
        my $rrdfile = "/var/cache/rrdtool/".$uuid."_highres.rrd";
2054
        `rm $rrdfile` if (-e $rrdfile);
2055

    
2056
        $main::syslogit->($user, "info", "Deleted domain $uuid from db");
2057
        utf8::decode($name);
2058
        $postmsg .= " deleted server $name";
2059
        $postreply = "[]";
2060
        sleep 1;
2061
    } else {
2062
        $postreply .= "Status=ERROR Cannot delete a $dbstatus server\n";
2063
    }
2064
    return $postreply;
2065
}
2066

    
2067
# Delete all monitors belonging to a server
2068
sub deleteMonitors {
2069
    my ($serveruuid) = @_;
2070
    my $match;
2071
    if ($serveruuid) {
2072
        if ($register{$serveruuid}->{'user'} eq $user || $isadmin) {
2073
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf");
2074
            # undef $/; # This makes <> read in the entire file in one go
2075
            my $uuidmatch;
2076
            while (<>) {
2077
                if (/^watch (\S+)/) {
2078
                    if ($1 eq $serveruuid) {$uuidmatch = $serveruuid}
2079
                    else {$uuidmatch = ''};
2080
                };
2081
                if ($uuidmatch) {
2082
                    $match = 1;
2083
                } else {
2084
                    #chomp;
2085
                    print unless (/^hostgroup $serveruuid/);
2086
                }
2087
                close ARGV if eof;
2088
            }
2089
            #$/ = "\n";
2090
        }
2091
        unlink glob "/var/log/stabile/*:$serveruuid:*";
2092
    }
2093
    `/usr/bin/moncmd reset keepstate` if ($match);
2094
    return $match;
2095
}
2096

    
2097
sub deletePackages {
2098
    my ($uuid, $issystem, %packreg) = @_;
2099
    unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2100

    
2101
    my @domains;
2102
    if ($issystem) {
2103
        foreach my $valref (values %register) {
2104
            if (($valref->{'system'} eq $uuid || $uuid eq '*')
2105
                    && ($valref->{'user'} eq $user || $fulllist)) {
2106
                push(@domains, $valref->{'uuid'});
2107
            }
2108
        }
2109
    } else { # Allow if domain no longer exists or belongs to user
2110
        push(@domains, $uuid) if (!$register{$uuid} || $register{$uuid}->{'user'} eq $user || $fulllist);
2111
    }
2112

    
2113
    foreach my $domuuid (@domains) {
2114
        foreach my $packref (values %packreg) {
2115
            my $id = $packref->{'id'};
2116
            if (substr($id, 0,36) eq $domuuid || ($uuid eq '*' && $packref->{'user'} eq $user)) {
2117
                delete $packreg{$id};
2118
            }
2119
        }
2120
    }
2121
    tied(%packreg)->commit;# if (%packreg);
2122
    if ($issystem) {
2123
        my $sname = $register{$uuid}->{'name'};
2124
        utf8::decode($sname);
2125
        return "Status=OK Cleared packages for $sname\n";
2126
    } elsif ($register{$uuid}) {
2127
        my $sname = $register{$uuid}->{'name'};
2128
        utf8::decode($sname);
2129
        return "Status=OK Cleared packages for $sname\n";
2130
    } else {
2131
        return "Status=OK Cleared packages. System not registered\n";
2132
    }
2133
}
2134

    
2135
sub Save {
2136
    my ($uuid, $action, $obj) = @_;
2137
    if ($help) {
2138
        return <<END
2139
POST:uuid, name, user, system, autostart, locktonode, mac, memory, vcpu, boot, diskbus, nicmodel1, vgpu, cdrom, image, image2, image3, image4, networkuuid2, networkuuid3, networkuuid1, nicmac1, nicmac2, nicmac3:
2140
To save a servers of networks you either PUT or POST a JSON array to the main endpoint with objects representing the servers with the changes you want.
2141
Depending on your privileges not all changes are permitted. If you save without specifying a uuid, a new server is created.
2142
If you pass [user] parameter it is assumed you want to move server to this user's account.
2143
Supported parameters:
2144

    
2145
uuid: UUID
2146
name: string
2147
user: string
2148
system: UUID of stack this server belongs to
2149
autostart: true|false
2150
locktonode: true|false
2151
mac: MAC address of target node
2152

    
2153
memory: int bytes
2154
vcpu: int
2155
boot: hd|cdrom|network
2156
diskbus: virtio|ide|scsi
2157
nicmodel1: virtio|rtl8139|ne2k_pci|e1000|i82551|i82557b|i82559er|pcnet
2158
vgpu: int
2159

    
2160
cdrom: string path
2161
image: string path
2162
image2: string path
2163
image3: string path
2164
image4: string path
2165

    
2166
networkuuid1: UUID of network connection
2167
networkuuid2: UUID of network connection
2168
networkuuid3: UUID of network connection
2169

    
2170
END
2171
    }
2172

    
2173
# notes, opemail, opfullname, opphone, email, fullname, phone, services, recovery, alertemail
2174
# notes: string
2175
# opemail: string
2176
# opfullname: string
2177
# opphone: string
2178
# email: string
2179
# fullname: string
2180
# phone: string
2181
# services: string
2182
# recovery: string
2183
# alertemail: string
2184

    
2185
    my $system = $obj->{system};
2186
    my $newsystem = $obj->{newsystem};
2187
    my $buildsystem = $obj->{buildsystem};
2188
    my $nicmac1 = $obj->{nicmac1};
2189
    $console = $console || $obj->{console};
2190

    
2191
    $postmsg = '' if ($buildsystem);
2192
    if (!$uuid && $nicmac1) {
2193
        $uuid = nicmac1ToUuid($nicmac1); # If no uuid try to locate based on mac
2194
    }
2195
    if (!$uuid && $uripath =~ /servers(\.cgi)?\/(.+)/) { # Try to parse uuid out of URI
2196
        my $huuid = $2;
2197
        if ($ug->to_string($ug->from_string($huuid)) eq $huuid) { # Check for valid uuid
2198
            $uuid = $huuid;
2199
        }
2200
    }
2201
    my $regserv = $register{$uuid};
2202
    my $status = $regserv->{'status'} || 'new';
2203
    if ((!$uuid) && $status eq 'new') {
2204
        my $ug = new Data::UUID;
2205
        $uuid = $ug->create_str();
2206
    };
2207
    unless ($uuid && length $uuid == 36){
2208
        $postmsg = "Status=Error No valid uuid ($uuid), $obj->{image}";
2209
        return $postmsg;
2210
    }
2211
    $nicmac1 = $nicmac1 || $regserv->{'nicmac1'};
2212
    my $name = $obj->{name} || $regserv->{'name'};
2213
    my $memory = $obj->{memory} || $regserv->{'memory'};
2214
    my $vcpu = $obj->{vcpu} || $regserv->{'vcpu'};
2215
    my $image = $obj->{image} || $regserv->{'image'};
2216
    my $imagename = $obj->{imagename} || $regserv->{'imagename'};
2217
    my $image2 = $obj->{image2} || $regserv->{'image2'};
2218
    my $image2name = $obj->{image2name} || $regserv->{'image2name'};
2219
    my $image3 = $obj->{image3} || $regserv->{'image3'};
2220
    my $image3name = $obj->{image3name} || $regserv->{'image3name'};
2221
    my $image4 = $obj->{image4} || $regserv->{'image4'};
2222
    my $image4name = $obj->{image4name} || $regserv->{'image4name'};
2223
    my $diskbus = $obj->{diskbus} || $regserv->{'diskbus'};
2224
    my $cdrom = $obj->{cdrom} || $regserv->{'cdrom'};
2225
    my $boot = $obj->{boot} || $regserv->{'boot'};
2226
    my $networkuuid1 = ($obj->{networkuuid1} || $obj->{networkuuid1} eq '0')?$obj->{networkuuid1}:$regserv->{'networkuuid1'};
2227
    my $networkid1 = $obj->{networkid1} || $regserv->{'networkid1'};
2228
    my $networkname1 = $obj->{networkname1} || $regserv->{'networkname1'};
2229
    my $nicmodel1 = $obj->{nicmodel1} || $regserv->{'nicmodel1'};
2230
    my $networkuuid2 = ($obj->{networkuuid2} || $obj->{networkuuid2} eq '0')?$obj->{networkuuid2}:$regserv->{'networkuuid2'};
2231
    my $networkid2 = $obj->{networkid2} || $regserv->{'networkid2'};
2232
    my $networkname2 = $obj->{networkname2} || $regserv->{'networkname2'};
2233
    my $nicmac2 = $obj->{nicmac2} || $regserv->{'nicmac2'};
2234
    my $networkuuid3 = ($obj->{networkuuid3} || $obj->{networkuuid3} eq '0')?$obj->{networkuuid3}:$regserv->{'networkuuid3'};
2235
    my $networkid3 = $obj->{networkid3} || $regserv->{'networkid3'};
2236
    my $networkname3 = $obj->{networkname3} || $regserv->{'networkname3'};
2237
    my $nicmac3 = $obj->{nicmac3} || $regserv->{'nicmac3'};
2238
    my $notes = $obj->{notes} || $regserv->{'notes'};
2239
    my $autostart = $obj->{autostart} || $regserv->{'autostart'};
2240
    my $locktonode = $obj->{locktonode} || $regserv->{'locktonode'};
2241
    my $mac = $obj->{mac} || $regserv->{'mac'};
2242
    my $created = $regserv->{'created'} || time;
2243
    # Sanity checks
2244
    my $tenderpaths = $Stabile::config->get('STORAGE_POOLS_LOCAL_PATHS') || "/mnt/stabile/images";
2245
    my @tenderpathslist = split(/,\s*/, $tenderpaths);
2246

    
2247
    $networkid1 = $networkreg{$networkuuid1}->{'id'};
2248
    my $networktype1 = $networkreg{$networkuuid1}->{'type'};
2249
    my $networktype2;
2250
    if (!$nicmac1 || $nicmac1 eq "--") {$nicmac1 = randomMac();}
2251
    if ($networkuuid2 && $networkuuid2 ne "--") {
2252
        $networkid2 = $networkreg{$networkuuid2}->{'id'};
2253
        $nicmac2 = randomMac() if (!$nicmac2 || $nicmac2 eq "--");
2254
        $networktype2 = $networkreg{$networkuuid2}->{'type'};
2255
    }
2256
    if ($networkuuid3 && $networkuuid3 ne "--") {
2257
        $networkid3 = $networkreg{$networkuuid3}->{'id'};
2258
        $networkname3 = $networkreg{$networkuuid3}->{'name'};
2259
        $nicmac3 = randomMac() if (!$nicmac3 || $nicmac3 eq "--");
2260
        $networktype3 = $networkreg{$networkuuid3}->{'type'};
2261
    }
2262

    
2263
    my $imgdup;
2264
    my $netdup;
2265
    my $json_text; # returned if all goes well
2266

    
2267
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2268

    
2269
    if ($networkid1 > 1 && $networkid2 > 1 && $networktype1 ne 'gateway' && $networktype2 ne 'gateway'
2270
        && $networkuuid1 eq $networkuuid2) {
2271
        $netdup = 1;
2272
    }
2273
    if ($networkid1 > 1 && $networkid3 > 1 && $networktype1 ne 'gateway' && $networktype3 ne 'gateway'
2274
        && $networkuuid1 eq $networkuuid3) {
2275
        $netdup = 11;
2276
    }
2277

    
2278
    if ($image eq $image2
2279
        || $image eq $image3
2280
        || $image eq $image4
2281
        || $image2 && $image2 ne '--' && $image2 eq $image3
2282
        || $image2 && $image2 ne '--' && $image2 eq $image4
2283
        || $image3 && $image3 ne '--' && $image3 eq $image4
2284
    ) {
2285
        $imgdup = 1;
2286
    } elsif ($image =~ m/\.master\.qcow2/
2287
        || $image2 =~ m/\.master\.qcow2/
2288
        || $image3 =~ m/\.master\.qcow2/
2289
        || $image4 =~ m/\.master\.qcow2/
2290
    ) {
2291
        $imgdup = 2;
2292
    } else {
2293
        # Check if another server is using image
2294
        my @regkeys = (tied %register)->select_where("user = '$user' OR user = 'common'");
2295
        foreach my $k (@regkeys) {
2296
            my $val = $register{$k};
2297
            my %h = %$val;
2298
            if ($h{'uuid'} ne $uuid) {
2299
                if (
2300
                    $image eq $h{'image'} || $image eq $h{'image2'}|| $image eq $h{'image3'}|| $image eq $h{'image4'}
2301
                ) {
2302
                    $imgdup = 51;
2303
                } elsif ($image2 && $image2 ne "--" &&
2304
                    ($image2 eq $h{'image'} || $image2 eq $h{'image2'} || $image2 eq $h{'image3'} || $image2 eq $h{'image4'})
2305
                ) {
2306
                    $imgdup = 52;
2307
                } elsif ($image3 && $image3 ne "--" &&
2308
                    ($image3 eq $h{'image'} || $image3 eq $h{'image2'} || $image3 eq $h{'image3'} || $image3 eq $h{'image4'})
2309
                ) {
2310
                    $imgdup = 53;
2311
                } elsif ($image4 && $image4 ne "--" &&
2312
                    ($image4 eq $h{'image'} || $image4 eq $h{'image2'} || $image4 eq $h{'image3'} || $image4 eq $h{'image4'})
2313
                ) {
2314
                    $imgdup = 54;
2315
                }
2316

    
2317
                if ($networkid1>1) {
2318
                    if ($networktype1 ne 'gateway' &&
2319
                        ($networkuuid1 eq $h{'networkuuid1'} || $networkuuid1 eq $h{'networkuuid2'})
2320
                    ) {
2321
                        $netdup = 51;
2322
                    }
2323
                }
2324
                if ($networkid2>1) {
2325
                    if ($networktype2 ne 'gateway' && $networkuuid2 && $networkuuid2 ne "--" &&
2326
                        ($networkuuid2 eq $h{'networkuuid1'} || $networkuuid2 eq $h{'networkuuid2'})
2327
                    ) {
2328
                        $netdup = 52;
2329
                    }
2330
                }
2331
            }
2332
        }
2333
        my $legalpath;
2334
        if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2335
            $legalpath = 1;
2336
        } else {
2337
            foreach my $path (@tenderpathslist) {
2338
                if ($image =~ m/$path\/$user/) {
2339
                    $legalpath = 1;
2340
                    last;
2341
                }
2342
            }
2343
        }
2344
        $imgdup = 6 unless $legalpath;
2345
        if ($image2 && $image2 ne "--") { # TODO: We should probably check for conflicting nodes for image3 and image 4 too
2346
            if ($image2 =~ m/\/mnt\/stabile\/node\/$user/) {
2347
                if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2348
                    if ($imagereg{$image}->{'mac'} eq $imagereg{$image2}->{'mac'}) {
2349
                        $legalpath = 1;
2350
                    } else {
2351
                        $legalpath = 0; # Images are on two different nodes
2352
                    }
2353
                } else {
2354
                    $legalpath = 1;
2355
                }
2356
            } else {
2357
                $legalpath = 0;
2358
                foreach my $path (@tenderpathslist) {
2359
                    if ($image2 =~ m/$path\/$user/) {
2360
                        $legalpath = 1;
2361
                        last;
2362
                    }
2363
                }
2364
            }
2365
            $imgdup = 7 unless $legalpath;
2366
        }
2367
    }
2368

    
2369
    if (!$imgdup && !$netdup) {
2370
        if ($status eq "new") {
2371
            $status = "shutoff";
2372
            $name = $name || 'New Server';
2373
            $memory = $memory || 1024;
2374
            $vcpu = $vcpu || 1;
2375
            $imagename = $imagename || '--';
2376
            $image2 = $image2 || '--';
2377
            $image2name = $image2name || '--';
2378
            $image3 = $image3 || '--';
2379
            $image3name = $image3name || '--';
2380
            $image4 = $image4 || '--';
2381
            $image4name = $image4name || '--';
2382
            $diskbus = $diskbus || 'ide';
2383
            $cdrom = $cdrom || '--';
2384
            $boot = $boot || 'hd';
2385
            $networkuuid1 = $networkuuid1 || 1;
2386
            $networkid1 = $networkid1 || 1;
2387
            $networkname1 = $networkname1 || '--';
2388
            $nicmodel1 = $nicmodel1 || 'rtl8139';
2389
            $nicmac1 = $nicmac1 || randomMac();
2390
            $networkuuid2 = $networkuuid2 || '--';
2391
            $networkid2 = $networkid2 || '--';
2392
            $networkname2 = $networkname2 || '--';
2393
            $nicmac2 = $nicmac2 || randomMac();
2394
            $networkuuid3 = $networkuuid3 || '--';
2395
            $networkid3 = $networkid3 || '--';
2396
            $networkname3 = $networkname3 || '--';
2397
            $nicmac3 = $nicmac3 || randomMac();
2398
            #    $uiuuid = $uuid; # No need to update ui for new server with jsonreststore
2399
            $postmsg .= "OK Created new server: $name";
2400
            $postmsg .= ", uuid: $uuid " if ($console);
2401
        }
2402
        # Update status of images
2403
        my @imgs = ($image, $image2, $image3, $image4);
2404
        my @imgkeys = ('image', 'image2', 'image3', 'image4');
2405
        for (my $i=0; $i<4; $i++) {
2406
            my $img = $imgs[$i];
2407
            my $k = $imgkeys[$i];
2408
            my $regimg = $imagereg{$img};
2409
            # if ($img && $img ne '--' && ($status eq 'new' || $img ne $regserv->{$k})) { # Servers image changed - update image status
2410
            if ($img && $img ne '--') { # Always update image status
2411
                $regimg->{'status'} = 'used' if (
2412
                    $regimg->{'status'} eq 'unused'
2413
                        # Image cannot be active if server is shutoff
2414
                        || ($regimg->{'status'} eq 'active' && $status eq 'shutoff')
2415
                );
2416
                $regimg->{'domains'} = $uuid;
2417
                $regimg->{'domainnames'} = $name;
2418
            }
2419
            # If image has changed, release the old image
2420
            if ($status ne 'new' && $img ne $regserv->{$k} && $imagereg{$regserv->{$k}}) {
2421
                $imagereg{$regserv->{$k}}->{'status'} = 'unused';
2422
                delete $imagereg{$regserv->{$k}}->{'domains'};
2423
                delete $imagereg{$regserv->{$k}}->{'domainnames'};
2424
            }
2425
        }
2426

    
2427
        my $valref = {
2428
            uuid=>$uuid,
2429
            user=>$user,
2430
            name=>$name,
2431
            memory=>$memory,
2432
            vcpu=>$vcpu,
2433
            image=>$image,
2434
            imagename=>$imagename,
2435
            image2=>$image2,
2436
            image2name=>$image2name,
2437
            image3=>$image3,
2438
            image3name=>$image3name,
2439
            image4=>$image4,
2440
            image4name=>$image4name,
2441
            diskbus=>$diskbus,
2442
            cdrom=>$cdrom,
2443
            boot=>$boot,
2444
            networkuuid1=>$networkuuid1,
2445
            networkid1=>$networkid1,
2446
            networkname1=>$networkname1,
2447
            nicmodel1=>$nicmodel1,
2448
            nicmac1=>$nicmac1,
2449
            networkuuid2=>$networkuuid2,
2450
            networkid2=>$networkid2,
2451
            networkname2=>$networkname2,
2452
            nicmac2=>$nicmac2,
2453
            networkuuid3=>$networkuuid3,
2454
            networkid3=>$networkid3,
2455
            networkname3=>$networkname3,
2456
            nicmac3=>$nicmac3,
2457
            status=>$status,
2458
            notes=>$notes,
2459
            autostart=>$autostart,
2460
            locktonode=>$locktonode,
2461
            action=>"",
2462
            created=>$created
2463
        };
2464
        $valref->{'system'} = $system if ($system);
2465
        if ($mac && $locktonode eq 'true') {
2466
            $valref->{'mac'} = $mac;
2467
            $valref->{'macip'} = $nodereg{$mac}->{'ip'};
2468
            $valref->{'macname'} = $nodereg{$mac}->{'name'};
2469
        }
2470
        if ($newsystem) {
2471
            my $ug = new Data::UUID;
2472
            $sysuuid = $ug->create_str();
2473
            $valref->{'system'} = $sysuuid;
2474
            $postmsg .= "OK sysuuid: $sysuuid " if ($console);
2475
        }
2476

    
2477
        # Remove domain uuid from old networks. Leave gateways alone - they get updated on next listing
2478
        my $oldnetworkuuid1 = $regserv->{'networkuuid1'};
2479
        if ($oldnetworkuuid1 ne $networkuuid1 && $networkreg{$oldnetworkuuid1}) {
2480
            $networkreg{$oldnetworkuuid1}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2481
        }
2482

    
2483
        $register{$uuid} = validateItem($valref);
2484

    
2485
        if ($networkreg{$networkuuid1}->{'type'} eq 'gateway') {
2486
            my $domains = $networkreg{$networkuuid1}->{'domains'};
2487
            $networkreg{$networkuuid1}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2488
            my $domainnames = $networkreg{$networkuuid1}->{'domainnames'};
2489
            $networkreg{$networkuuid1}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2490
        } else {
2491
            $networkreg{$networkuuid1}->{'domains'}  = $uuid;
2492
            $networkreg{$networkuuid1}->{'domainnames'}  = $name;
2493
        }
2494

    
2495
        if ($networkuuid2 && $networkuuid2 ne '--') {
2496
            if ($networkreg{$networkuuid2}->{'type'} eq 'gateway') {
2497
                my $domains = $networkreg{$networkuuid2}->{'domains'};
2498
                $networkreg{$networkuuid2}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2499
                my $domainnames = $networkreg{$networkuuid2}->{'domainnames'};
2500
                $networkreg{$networkuuid2}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2501
            } else {
2502
                $networkreg{$networkuuid2}->{'domains'}  = $uuid;
2503
                $networkreg{$networkuuid2}->{'domainnames'}  = $name;
2504
            }
2505
        }
2506

    
2507
        if ($networkuuid3 && $networkuuid3 ne '--') {
2508
            if ($networkreg{$networkuuid3}->{'type'} eq 'gateway') {
2509
                my $domains = $networkreg{$networkuuid3}->{'domains'};
2510
                $networkreg{$networkuuid3}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2511
                my $domainnames = $networkreg{$networkuuid3}->{'domainnames'};
2512
                $networkreg{$networkuuid3}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2513
            } else {
2514
                $networkreg{$networkuuid3}->{'domains'}  = $uuid;
2515
                $networkreg{$networkuuid3}->{'domainnames'}  = $name;
2516
            }
2517
        }
2518
        my %jitem = %{$register{$uuid}};
2519
        $json_text = to_json(\%jitem, {pretty=>1});
2520
        $json_text =~ s/null/"--"/g;
2521
        $uiuuid = $uuid;
2522
        $uiname = $name;
2523

    
2524
        tied(%register)->commit;
2525
        tied(%imagereg)->commit;
2526
        tied(%networkreg)->commit;
2527

    
2528
    } else {
2529
        $postmsg .= "ERROR This image ($image) cannot be used ($imgdup) " if ($imgdup);
2530
        $postmsg .= "ERROR This network ($networkname1) cannot be used ($netdup)" if ($netdup);
2531
    }
2532

    
2533
    my $domuser = $obj->{'user'};
2534
    # We were asked to move server to another account
2535
    if ($domuser && $domuser ne '--' && $domuser ne $user) {
2536
        unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>0}, $Stabile::dbopts)) ) {throw Error::Simple("Stroke=Error User register could not be  accessed")};
2537
        if ($status eq 'shutoff' || $status eq 'inactive') {
2538
            unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {$posterror =  "Unable to access user register"; return 0;};
2539
            my @accounts = split(/,\s*/, $userreg{$tktuser}->{'accounts'});
2540
            my @accountsprivs = split(/,\s*/, $userreg{$tktuser}->{'accountsprivileges'});
2541
            %ahash = ($tktuser, $userreg{$tktuser}->{'privileges'}); # Include tktuser in accounts hash
2542
            for my $i (0 .. scalar @accounts)
2543
            {
2544
                next unless $accounts[$i];
2545
                $ahash{$accounts[$i]} = $accountsprivs[$i] || 'r';
2546
            }
2547
            untie %userreg;
2548

    
2549
            if (!$isreadonly && $ahash{$domuser} && !($ahash{$domuser} =~ /r/)) { # Check if user is allow to access account
2550
                my $imgdone;
2551
                my $netdone;
2552
                # First move main image
2553
                $Stabile::Images::user = $user;
2554
                require "$Stabile::basedir/cgi/images.cgi";
2555
                $Stabile::Images::console = 1;
2556
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving image $imagename to account: $domuser"});
2557
                my $nimage = Stabile::Images::Move($image, $domuser);
2558
                chomp $nimage;
2559
                if ($nimage) {
2560
                    $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2561
                    $register{$uuid}->{'image'} = $nimage;
2562
                    $imgdone = 1;
2563
                } else {
2564
                    $main::syslogit->($user, "info", "Unable to move image $imagename to account: $domuser");
2565
                }
2566
                # Move other attached images
2567
                my @images = ($image2, $image3, $image4);
2568
                my @imagenames = ($image2name, $image3name, $image4name);
2569
                my @imagekeys = ('image2', 'image3', 'image4');
2570
                for (my $i=0; $i<3; $i++) {
2571
                    my $img = $images[$i];
2572
                    my $imgname = $imagenames[$i];
2573
                    my $imgkey = $imagekeys[$i];
2574
                    if ($img && $img ne '--') {
2575
                        $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving $imgkey $imgname to account: $domuser"});
2576
                        $nimage = Stabile::Images::Move($img, $domuser);
2577
                        chomp $nimage;
2578
                        if ($nimage) {
2579
                            $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2580
                            $register{$uuid}->{$imgkey} = $nimage;
2581
                        } else {
2582
                            $main::syslogit->($user, "info", "Unable to move $imagekeys[$i] $img to account: $domuser");
2583
                        }
2584
                    }
2585
                }
2586
                # Then move network(s)
2587
                if ($imgdone) {
2588
                    $Stabile::Networks::user = $user;
2589
                    require "$Stabile::basedir/cgi/networks.cgi";
2590
                    $Stabile::Networks::console = 1;
2591
                    my @networks = ($networkuuid1, $networkuuid2, $networkuuid3);
2592
                    my @netkeys = ('networkuuid1', 'networkuuid2', 'networkuuid3');
2593
                    my @netnamekeys = ('networkname1', 'networkname2', 'networkname3');
2594
                    for (my $i=0; $i<scalar @networks; $i++) {
2595
                        my $net = $networks[$i];
2596
                        my $netkey = $netkeys[$i];
2597
                        my $netnamekey = $netnamekeys[$i];
2598
                        my $regnet = $networkreg{$net};
2599
                        my $oldid = $regnet->{'id'};
2600
                        next if ($net eq '' || $net eq '--');
2601
                        if ($regnet->{'type'} eq 'gateway') {
2602
                            if ($oldid > 1) { # Private gateway
2603
                                foreach my $networkvalref (values %networkreg) { # use gateway with same id if it exists
2604
                                    if ($networkvalref->{'user'} eq $domuser
2605
                                        && $networkvalref->{'type'} eq 'gateway'
2606
                                        && $networkvalref->{'id'} == $oldid) {
2607
                                        # We found an existing gateway with same id - use it
2608
                                        $register{$uuid}->{$netkey} = $networkvalref->{'uuid'};
2609
                                        $register{$uuid}->{$netnamekey} = $networkvalref->{'name'};
2610
                                        $netdone = 1;
2611
                                        $main::updateUI->({tab=>"networks", user=>$user, message=>"Using network $networkvalref->{'name'} from account: $domuser"});
2612
                                        last;
2613
                                    }
2614
                                }
2615
                                if (!($netdone)) {
2616
                                    # Make a new gateway
2617
                                    my $ug = new Data::UUID;
2618
                                    my $newuuid = $ug->create_str();
2619
                                    Stabile::Networks::save($oldid, $newuuid, $regnet->{'name'}, 'new', 'gateway', '', '', $regnet->{'ports'}, 0, $domuser);
2620
                                    $register{$uuid}->{$netkey} = $newuuid;
2621
                                    $register{$uuid}->{$netnamekey} = $regnet->{'name'};
2622
                                    $netdone = 1;
2623
                                    $main::updateUI->({tab=>"networks", user=>$user, message=>"Created gateway $regnet->{'name'} for account: $domuser"});
2624
                                    $main::syslogit->($user, "info", "Created gateway $regnet->{'name'} for account: $domuser");
2625
                                }
2626
                            } elsif ($oldid==0 || $oldid==1) {
2627
                                $netdone = 1; # Use common gateway
2628
                                $main::updateUI->({tab=>"networks", user=>$user, message=>"Reused network $regnet->{'name'} for account: $domuser"});
2629
                            }
2630
                        } else {
2631
                            my $newid = Stabile::Networks::getNextId('', $domuser);
2632
                            $networkreg{$net}->{'id'} = $newid;
2633
                            $networkreg{$net}->{'user'} = $domuser;
2634
                        #    if ($regnet->{'type'} eq 'internalip' || $regnet->{'type'} eq 'ipmapping') {
2635
                                # Deactivate network and assign new internal ip
2636
                                Stabile::Networks::Deactivate($regnet->{'uuid'});
2637
                                $networkreg{$net}->{'internalip'} =
2638
                                    Stabile::Networks::getNextInternalIP('',$regnet->{'uuid'}, $newid, $domuser);
2639
                        #    }
2640
                            $netdone = 1;
2641
                            $main::updateUI->({tab=>"networks", user=>$user, message=>"Moved network $regnet->{'name'} to account: $domuser"});
2642
                            $main::syslogit->($user, "info", "Moved network $regnet->{'name'} to account: $domuser");
2643
                        }
2644
                    }
2645
                    if ($netdone) {
2646
                        # Finally move the server
2647
                        $register{$uuid}->{'user'} = $domuser;
2648
                        $postmsg .= "OK Moved server $name to account: $domuser";
2649
                        $main::syslogit->($user, "info", "Moved server $name ($uuid) to account: $domuser");
2650
                        $main::updateUI->({tab=>"servers", user=>$user, type=>"update"});
2651
                    } else {
2652
                        $postmsg .= "ERROR Unable to move network to account: $domuser";
2653
                        $main::updateUI->({tab=>"image", user=>$user, message=>"Unable to move network to account: $domuser"});
2654
                    }
2655
                } else {
2656
                    $main::updateUI->({tab=>"image", user=>$user, message=>"Could not move image to account: $domuser"});
2657
                }
2658
            } else {
2659
                $postmsg .= "ERROR No access to move server";
2660
            }
2661
        } else {
2662
            $postmsg .= "Error Unable to move $status server";
2663
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Please shut down before moving server"});
2664
        }
2665
        untie %userreg;
2666
    }
2667

    
2668
    if ($console) {
2669
        $postreply = $postmsg;
2670
    } else {
2671
        $postreply = $json_text || $postmsg;
2672
    }
2673
    return $postreply;
2674
    untie %imagereg;
2675
}
2676

    
2677

    
2678
sub Shutdown {
2679
    my ($uuid, $action, $obj) = @_;
2680
    if ($help) {
2681
        return <<END
2682
GET:uuid:
2683
Marks a server for shutdown, i.e. send and ACPI shutdown event to the server. If OS supports ACPI, it begins a shutdown.
2684
END
2685
    }
2686
    $uistatus = "shuttingdown";
2687
    my $dbstatus = $obj->{status};
2688
    my $mac = $obj->{mac};
2689
    my $macname = $obj->{macname};
2690
    my $name = $obj->{name};
2691
    if ($dbstatus eq 'running') {
2692
        my $tasks;
2693
        $tasks = $nodereg{$mac}->{'tasks'} if ($nodereg{$mac});
2694
        $nodereg{$mac}->{'tasks'} = $tasks . "SHUTDOWN $uuid $user\n";
2695
        tied(%nodereg)->commit;
2696
        $register{$uuid}->{'status'} = $uistatus;
2697
        $register{$uuid}->{'statustime'} = $current_time;
2698
        $uiuuid = $uuid;
2699
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2700
        $postreply .= "Status=$uistatus OK $uistatus $name\n";
2701
    } else {
2702
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2703
        $postreply .= "Status=ERROR problem $uistatus $name...\n";
2704
    }
2705
    return $postreply;
2706
}
2707

    
2708
sub Suspend {
2709
    my ($uuid, $action, $obj) = @_;
2710
    if ($help) {
2711
        return <<END
2712
GET:uuid:
2713
Marks a server for suspend, i.e. pauses the server. Server must be running
2714
END
2715
    }
2716
#    my $obj = getObj(('uuid', $uuid));
2717
    $uistatus = "suspending";
2718
    my $dbstatus = $obj->{status};
2719
    my $mac = $obj->{mac};
2720
    my $macname = $obj->{macname};
2721
    my $name = $obj->{name};
2722
    if ($dbstatus eq 'running') {
2723
        my $tasks = $nodereg{$mac}->{'tasks'};
2724
        $nodereg{$mac}->{'tasks'} = $tasks . "SUSPEND $uuid $user\n";
2725
        tied(%nodereg)->commit;
2726
        $register{$uuid}->{'status'} = $uistatus;
2727
        $register{$uuid}->{'statustime'} = $current_time;
2728
        $uiuuid = $uuid;
2729
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2730
        $postreply .= "Status=$uistatus OK $uistatus $name.\n";
2731
    } else {
2732
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2733
        $postreply .= "Status=ERROR problem $uistatus $name.\n";
2734
    }
2735
    return $postreply;
2736
}
2737

    
2738
sub Resume {
2739
    my ($uuid, $action, $obj) = @_;
2740
    if ($help) {
2741
        return <<END
2742
GET:uuid:
2743
Marks a server for resume running. Server must be paused.
2744
END
2745
    }
2746
    my $dbstatus = $obj->{status};
2747
    my $mac = $obj->{mac};
2748
    my $macname = $obj->{macname};
2749
    my $name = $obj->{name};
2750
    my $image = $obj->{image};
2751
    my $image2 = $obj->{image2};
2752
    my $image3 = $obj->{image3};
2753
    my $image4 = $obj->{image4};
2754
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$posterror = "Unable to access image register"; return;};
2755
    if ($imagereg{$image}->{'status'} ne "paused"
2756
        || ($image2 && $image2 ne '--' && $imagereg{$image}->{'status'} ne "paused")
2757
        || ($image3 && $image3 ne '--' && $imagereg{$image3}->{'status'} ne "paused")
2758
        || ($image4 && $image4 ne '--' && $imagereg{$image4}->{'status'} ne "paused")
2759
    ) {
2760
        $postreply .= "Status=ERROR Image $uuid busy ($imagereg{$image}->{'status'}), please wait 30 sec.\n";
2761
        untie %imagereg;
2762
        return $postreply   ;
2763
    } else {
2764
        untie %imagereg;
2765
    }
2766
    $uistatus = "resuming";
2767
    if ($dbstatus eq 'paused') {
2768
        my $tasks = $nodereg{$mac}->{'tasks'};
2769
        $nodereg{$mac}->{'tasks'} = $tasks . "RESUME $uuid $user\n";
2770
        tied(%nodereg)->commit;
2771
        $register{$uuid}->{'status'} = $uistatus;
2772
        $register{$uuid}->{'statustime'} = $current_time;
2773
        $uiuuid = $uuid;
2774
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2775
        $postreply .= "Status=$uistatus OK $uistatus ". $register{$uuid}->{'name'} . "\n";
2776
    } else {
2777
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2778
        $postreply .= "Status=ERROR problem $uistatus ". $register{$uuid}->{'name'} . "\n";
2779
    }
2780
    return $postreply;
2781
}
2782

    
2783
sub Move {
2784
    my ($uuid, $action, $obj) = @_;
2785
    if ($help) {
2786
        return <<END
2787
GET:uuid,mac:
2788
Moves a server to a different node (Qemu live migration). Server must be running
2789
END
2790
    }
2791
    my $dbstatus = $obj->{status};
2792
    my $dmac = $obj->{mac};
2793
    my $name = $obj->{name};
2794
    my $mem = $obj->{memory};
2795
    my $vcpu = $obj->{vcpu};
2796
    my $image = $obj->{image};
2797
    my $image2 = $obj->{image2};
2798
    my $image3 = $obj->{image3};
2799
    my $image4 = $obj->{image4};
2800
    $uistatus = "moving";
2801
    if ($dbstatus eq 'running' && $isadmin) {
2802
        my $hypervisor = getHypervisor($image);
2803
        my $mac = $register{$uuid}->{'mac'};
2804
        $dmac = "" if ($dmac eq "--");
2805
        $mac = "" if ($mac eq "--");
2806

    
2807
        if ($image =~ /\/mnt\/stabile\/node\//
2808
            || $image2 =~ /\/mnt\/stabile\/node\//
2809
            || $image3 =~ /\/mnt\/stabile\/node\//
2810
            || $image4 =~ /\/mnt\/stabile\/node\//
2811
        ) {
2812
            # We do not support moving locally stored VM's yet...
2813
            $postreply = qq|{"error": 1, "message": "Moving servers with local storage not supported"}|;
2814
        } else {
2815
            my ($targetmac, $targetname, $targetip, $port) =
2816
                locateTargetNode($uuid, $dmac, $mem, $vcpu, $image, $image2, $image3, $image4, $hypervisor, $mac);
2817
            if ($targetmac) {
2818
                my $tasks = $nodereg{$targetmac}->{'tasks'};
2819
                $tasks = $tasks . "RECEIVE $uuid $user\n";
2820
                # Also update allowed port forwards
2821
                $nodereg{$targetmac}->{'tasks'} = $tasks . "PERMITOPEN $user\n";
2822
                $register{$uuid}->{'status'} = "moving";
2823
                $register{$uuid}->{'statustime'} = $current_time;
2824
                $uiuuid = $uuid;
2825
                $uidisplayip = $targetip;
2826
                $uidisplayport = $port;
2827
                $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus to $targetname ($targetmac)");
2828
                $postreply .= "Status=OK $uistatus ". $register{$uuid}->{'name'} . "\n";
2829

    
2830
                if ($params{'PUTDATA'}) {
2831
                    my %jitem = %{$register{$uuid}};
2832
                    my $json_text = to_json(\%jitem);
2833
                    $json_text =~ s/null/"--"/g;
2834
                    $postreply = $json_text;
2835
                }
2836
                $main::updateUI->({tab=>"servers", user=>$user, status=>'moving', uuid=>$uuid, type=>'update', message=>"Moving $register{$uuid}->{name} to $targetmac"});
2837
            } else {
2838
                $main::syslogit->($user, "info", "Could not find $hypervisor target for $uistatus $uuid ($image)");
2839
                $postreply = qq|{"error": 1, "message": "Could not find target for $uistatus $register{$uuid}->{'name'}"}|;
2840
            }
2841
        }
2842
    } else {
2843
        $main::syslogit->($user, "info", "Problem moving a $dbstatus domain: $uuid");
2844
        $postreply .= qq|{"error": 1, "message": "ERROR problem moving $register{$uuid}->{'name'} ($dbstatus)"}|;
2845
    }
2846
    return $postreply;
2847
}
2848

    
2849
sub Changepassword {
2850
    my ($uuid, $action, $obj) = @_;
2851
    if ($help) {
2852
        return <<END
2853
POST:uuid,username,password:
2854
Attempts to set password for [username] to [password] using guestfish. If no username is specified, user 'stabile' is assumed.
2855
END
2856
    }
2857
    my $img = $register{$uuid}->{'image'};
2858
    my $username = $obj->{'username'} || 'stabile';
2859
    my $password = $obj->{'password'};
2860
    return "Status=Error Please supply a password\n" unless ($password);
2861
    return "Status=Error Please shut down the server before changing password\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
2862
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
2863

    
2864
    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;};
2865
    my $cmd = qq/guestfish --rw -a $img -i command "bash -c 'echo $username:$password | chpasswd'" 2>\&1/;
2866
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
2867
        my $mac = $imagereg{$img}->{'mac'};
2868
        my $macip = $nodereg{$mac}->{'ip'};
2869
        $cmd = "$sshcmd $macip $cmd";
2870
    }
2871
    my $res = `$cmd`;
2872
    $res = $1 if ($res =~ /guestfish: (.*)/);
2873
    chomp $res;
2874
    return "Status=OK Ran chpasswd for user $username in server $register{$uuid}->{'name'}: $res\n";
2875
}
2876

    
2877
sub Sshaccess {
2878
    my ($uuid, $action, $obj) = @_;
2879
    if ($help) {
2880
        return <<END
2881
POST:uuid,address:
2882
Attempts to change the ip addresses you can access the server over SSH (port 22) from, by adding [address] to /etc/hosts.allow.
2883
[address] should either be an IP address or a range in CIDR notation. Please note that no validation of [address] is performed.
2884
END
2885
    }
2886
    my $img = $register{$uuid}->{'image'};
2887
    my $address = $obj->{'address'};
2888
    return "Status=Error Please supply an aaddress\n" unless ($address);
2889
    return "Status=Error Please shut down the server before changing SSH access\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
2890
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
2891

    
2892
    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;};
2893

    
2894
    my $isshcmd = '';
2895
    my $cmd = qq[guestfish --rw -a $img -i command "sed -i -re 's|(sshd: .*)#stabile|\\1 $address #stabile|' /etc/hosts.allow"];
2896
#    my $cmd = qq[guestfish --rw -a $img -i command "bash -c 'echo sshd: $address >> /etc/hosts.allow'"];
2897
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
2898
        my $mac = $imagereg{$img}->{'mac'};
2899
        my $macip = $nodereg{$mac}->{'ip'};
2900
        $isshcmd = "$sshcmd $macip ";
2901
    }
2902
    my $res = `$isshcmd$cmd`;
2903
    chomp $res;
2904
    #$cmd = qq[guestfish --rw -a $img -i command "bash -c 'cat /etc/hosts.allow'"];
2905
    #$res .= `$isshcmd$cmd`;
2906
    #chomp $res;
2907
    return "Status=OK Tried to add sshd: $address to /etc/hosts.allow in server $register{$uuid}->{'name'}\n";
2908
}
2909

    
2910
sub Mountcd {
2911
    my ($uuid, $action, $obj) = @_;
2912
    if ($help) {
2913
        return <<END
2914
GET:uuid,cdrom:
2915
Mounts a cdrom on a server. Server must be running. Mounting the special cdrom named '--' unomunts any currently mounted cdrom.
2916
END
2917
    }
2918
    my $dbstatus = $obj->{status};
2919
    my $mac = $obj->{mac};
2920
    my $cdrom = $obj->{cdrom};
2921
    unless ($cdrom && $dbstatus eq 'running') {
2922
        $main::updateUI->({tab=>"servers", user=>$user, uuid=>$uuid, type=>'update', message=>"Unable to mount cdrom"});
2923
        $postreply = qq|{"Error": 1, "message": "Problem mounting cdrom on $obj->{name}"}|;
2924
        return;
2925
    }
2926
    my $tasks = $nodereg{$mac}->{'tasks'};
2927
    # $user is in the middle here, because $cdrom may contain spaces...
2928
    $nodereg{$mac}->{'tasks'} = $tasks . "MOUNT $uuid $user \"$cdrom\"\n";
2929
    tied(%nodereg)->commit;
2930
    if ($cdrom eq "--") {
2931
        $postreply = qq|{"OK": 1, "message": "OK unmounting cdrom from $obj->{name}"}|;
2932
    } else {
2933
        $postreply = qq|{"OK": 1, "message": "OK mounting cdrom $cdrom on $obj->{name}"}|;
2934
    }
2935
    $register{$uuid}->{'cdrom'} = $cdrom unless ($cdrom eq 'virtio');
2936
    return $postreply;
2937
}
(5-5/9)