Project

General

Profile

Download (126 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 Config::Simple;
17
use lib dirname (__FILE__);
18
use Stabile;
19
#use Encode::Escape;
20

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

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

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

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

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

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

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

    
56
1;
57

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
294
    *Showautostart = \&Autostartall;
295
    *Stormove = \&Move;
296

    
297
    *do_save = \&Save;
298
    *do_tablelist = \&do_list;
299
    *do_jsonlist = \&do_list;
300
    *do_showautostart = \&action;
301
    *do_autostartall = \&privileged_action;
302
    *do_help = \&action;
303

    
304
    *do_start = \&privileged_action;
305
    *do_destroy = \&action;
306
    *do_shutdown = \&action;
307
    *do_suspend = \&action;
308
    *do_resume = \&action;
309
    *do_remove = \&privileged_action;
310
    *do_move = \&action;
311
    *do_abort = \&action;
312
    *do_stormove = \&action;
313
    *do_mountcd = \&action;
314
    *do_changepassword = \&privileged_action;
315
    *do_sshaccess = \&privileged_action;
316

    
317
    *do_gear_start = \&do_gear_action;
318
    *do_gear_autostart = \&do_gear_action;
319
    *do_gear_showautostart = \&do_gear_action;
320
    *do_gear_autostartall = \&do_gear_action;
321
    *do_gear_remove = \&do_gear_action;
322
    *do_gear_changepassword = \&do_gear_action;
323
    *do_gear_sshaccess = \&do_gear_action;
324

    
325
}
326

    
327
sub do_list {
328
    my ($uuid, $action) = @_;
329
    if ($help) {
330
        return <<END
331
GET:uuid:
332
List servers current user has access to.
333
END
334
    }
335

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

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

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

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

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

    
400
                    my %pval = %{$sysreg{$sysuuid}};
401
                    $pval{'nodetype'} = 'parent';
402
                    $pval{'status'} = '--';
403
                    $val{'nodetype'} = 'child';
404

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

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

    
457
    if ($action eq 'tablelist') {
458
        my $t2;
459

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

    
495
        $json_text =~ s/\x/ /g;
496
        $json_text =~ s/\"\"/"--"/g;
497
        $json_text =~ s/null/"--"/g;
498
        $json_text =~ s/"autostart"\s?:\s?"true"/"autostart": true/g;
499
        $json_text =~ s/"autostart"\s?:\s?"--"/"autostart": false/g;
500
        $json_text =~ s/"locktonode"\s?:\s?"true"/"locktonode": true/g;
501
        $json_text =~ s/"locktonode"\s?:\s?"--"/"locktonode": false/g;
502
        $json_text =~ s/"loader"\s?:\s?"--"/"loader": "bios"/g;
503
        if ($action eq 'jsonlist' || $action eq 'list' || !$action) {
504
            $res .= $json_text;
505
        } else {
506
            $res .= qq|{"action": "$action", "identifier": "uuid", "label": "uuid", "items" : $json_text}|;
507
        }
508
    }
509
    return $res;
510
}
511

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

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

    
573
sub do_destroyuserservers {
574
    my ($uuid, $action, $obj) = @_;
575
    if ($help) {
576
        return <<END
577
GET:username:
578
Simple action for destroying all servers belonging to a user
579
END
580
    }
581
    $username = $obj->{username};
582
    my $res;
583
    $res .= $Stabile::q->header('text/plain') unless $console;
584

    
585
    destroyUserServers($username);
586
    $res .= $postreply;
587
    return $res;
588
}
589

    
590
sub do_removeuserservers {
591
    if ($help) {
592
        return <<END
593
GET::
594
Simple action for removing all servers belonging to a user
595
END
596
    }
597
    my $res;
598
    $res .= $Stabile::q->header('text/plain') unless $console;
599
    removeUserServers($user);
600
    $res .= $postreply;
601
    return $res;
602
}
603

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

    
624
sub do_setrunning {
625
    my ($uuid, $action) = @_;
626
    if ($help) {
627
        return <<END
628
GET:uuid:
629
Simple action for setting status back to running after e.g. an upgrade
630
END
631
    }
632
    my $res;
633
    $res .= $Stabile::q->header('text/plain') unless $console;
634
    $uuid = $uuid || $options{u};
635
    $uuid = $curuuid unless ($uuid);
636
    if ($uuid && $register{$uuid}) {
637
        $register{$uuid}->{'status'} = 'running';
638
        $main::updateUI->({ tab => 'servers',
639
            user                => $user,
640
            uuid                => $uuid,
641
            status              => 'running' })
642

    
643
    };
644
    $res .= "Status=OK Set status of $register{$uuid}->{'name'} to running\n";
645
    return $res;
646
}
647

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

    
667
        my $termlink = $imagereg{$register{$uuid}->{image}}->{terminallink} || '';
668
        $termlink =~ s/{uuid}/$register{$uuid}->{networkuuid1}/;
669
        my $burl = $baseurl;
670
        $burl = $1 if ($termlink =~ /\/stabile/ && $baseurl =~ /(.+)\/stabile/); # Unpretty, but works for now
671
        # $termlink = $1 if ($termlink =~ /\/(.+)/);
672
        # $termlink = "$burl/$termlink" unless ($termlink =~ /^http/ || !$termlink); # || $termlink =~ /^\//
673
        $appinfo{'terminallink'} = $termlink;
674

    
675
        $appinfo{'upgradelink'} = $imagereg{$register{$uuid}->{image}}->{upgradelink} || '';
676
        $appinfo{'upgradelink'} =~ s/{uuid}/$register{$uuid}->{networkuuid1}/;
677
        $appinfo{'version'} = $imagereg{$register{$uuid}->{image}}->{version} || '';
678
        $appinfo{'status'} = $register{$uuid}->{status} || '';
679
        $appinfo{'name'} = $register{$uuid}->{name} || '';
680
        $appinfo{'system'} = $register{$uuid}->{system} || '';
681

    
682
        if ($appinfo{'system'}) {
683
            unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
684
            $appinfo{'systemname'} = $sysreg{$appinfo{'system'}}->{name} || '';
685
            untie(%sysreg);
686
        } else {
687
            $appinfo{'systemname'} = $appinfo{'name'};
688
        }
689

    
690

    
691
        if ($appinfo{'appid'}) {
692
            my @regkeys = (tied %imagereg)->select_where("appid = '$appinfo{appid}'");
693
            foreach my $k (@regkeys) {
694
                my $imgref = $imagereg{$k};
695
                if ($imgref->{'path'} =~ /\.master\.qcow2$/ && $imgref->{'appid'} eq $appinfo{'appid'}
696
                     && $imgref->{'installable'} && $imgref->{'installable'} ne 'false'
697
                ) {
698
                    if ($imgref->{'version'} > $appinfo{'currentversion'}) {
699
                        $appinfo{'currentversion'} = $imgref->{'version'};
700
                        $appinfo{'appname'} = $imgref->{'name'};
701
                    }
702
                }
703
            }
704
        }
705

    
706
        untie %imagereg;
707
    }
708
    $appinfo{'appstoreurl'} = $appstoreurl;
709
    $appinfo{'dnsdomain'} = ($enginelinked)?$dnsdomain:'';
710
    $appinfo{'dnssubdomain'} = ($enginelinked)?substr($engineid, 0, 8):'';
711
    $appinfo{'uuid'} = $uuid;
712
    $appinfo{'user'} = $user;
713
    $appinfo{'remoteip'} = $remoteip;
714
    $res .= to_json(\%appinfo, { pretty => 1 });
715
    return $res;
716
}
717

    
718
sub do_removeserver {
719
    if ($help) {
720
        return <<END
721
GET:uuid:
722
Simple action for destroying and removing a single server
723
END
724
    }
725
    my $res;
726
    $res .= $Stabile::q->header('text/plain') unless $console;
727
    if ($curuuid) {
728
        removeUserServers($user, $curuuid, 1);
729
    }
730
    else {
731
        $postreply .= "Status=Error Unable to uninstall\n";
732
    }
733
    $res .= $postreply;
734
    return $res;
735
}
736

    
737
sub do_updateregister {
738
    if ($help) {
739
        return <<END
740
GET::
741
Update server register
742
END
743
    }
744
    my $res;
745
    $res .= $Stabile::q->header('text/plain') unless $console;
746
    return unless $isadmin;
747
    updateRegister();
748
    $res .= "Status=OK Updated server registry for all users\n";
749
    return $res;
750
}
751

    
752
sub Autostartall {
753
    my ($uuid, $action) = @_;
754
    if ($help) {
755
        return <<END
756
GET::
757
Start all servers marked for autostart. When called as showautostart only shows which would be started.
758
END
759
    }
760
    my $res;
761
    $res .= $Stabile::q->header('text/plain') unless $console;
762
    my $mes;
763
    return $res if ($isreadonly);
764

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

    
790
    $mes = "$nodecount nodes ready - autostarting servers...\n";
791
    print $mes if ($console);
792
    $res .= $mes;
793
    if (!%nodereg || $nodedown || !$nodecount) {
794
        $mes = "Only autostarting servers on local node - not all nodes ready!\n";
795
        print $mes if ($console);
796
        $res .= $mes;
797
    }
798
    if ($action eq "showautostart") {
799
        $mes = "Only showing which servers would be starting!\n";
800
        print $mes if ($console);
801
        $res .= $mes;
802
    }
803

    
804
    $Stabile::Networks::user = $user;
805
    require "$Stabile::basedir/cgi/networks.cgi";
806
    $Stabile::Networks::console = 1;
807

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

    
884
sub do_listnodeavailability {
885
    if ($help) {
886
        return <<END
887
GET::
888
Utility call - only informational. Shows availability of nodes for starting servers.
889
END
890
    }
891
    my $res;
892
    $res .= $Stabile::q->header('application/json') unless ($console);
893
    my ($temp1, $temp2, $temp3, $temp4, $ahashref) = locateTargetNode();
894
    my @avalues = values %$ahashref;
895
    my @sorted_values = (sort {$b->{'index'} <=> $a->{'index'}} @avalues);
896
    $res .= to_json(\@sorted_values, { pretty => 1 });
897
    return $res;
898
}
899

    
900
sub do_listbillingdata {
901
    if ($help) {
902
        return <<END
903
GET::
904
List current billing data.
905
END
906
    }
907
    my $res;
908
    $res .= $Stabile::q->header('application/json') unless ($console);
909
    my $buser = URI::Escape::uri_unescape($params{'user'}) || $user;
910
    my %b;
911
    my @bmonths;
912
    if ($isadmin || $buser eq $user) {
913
        my $bmonth = URI::Escape::uri_unescape($params{'month'}) || $month;
914
        my $byear = URI::Escape::uri_unescape($params{'year'}) || $year;
915
        if ($bmonth eq "all") {
916
            @bmonths = ("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12");
917
        }
918
        else {
919
            @bmonths = ($bmonth);
920
        }
921

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

    
924
        my @nkeys = keys %nodereg;
925
        foreach my $bm (@bmonths) {
926
            my $vcpuavg = 0;
927
            my $memoryavg = 0;
928
            foreach my $nmac (@nkeys) {
929
                $vcpuavg += $billingreg{"$buser-$nmac-$byear-$bm"}->{'vcpuavg'};
930
                $memoryavg += $billingreg{"$buser-$nmac-$byear-$bm"}->{'memoryavg'};
931
            }
932
            $b{"$buser-$byear-$bm"} = {
933
                id        => "$buser-$byear-$bm",
934
                vcpuavg   => $vcpuavg,
935
                memoryavg => $memoryavg,
936
                month     => $bm + 0,
937
                year      => $byear + 0
938
            }
939
        }
940
        untie %billingreg;
941
    }
942
    my @bvalues = values %b;
943
    $res .= "{\"identifier\": \"id\", \"label\": \"id\", \"items\":" . to_json(\@bvalues) . "}";
944
    return $res;
945
}
946

    
947
# Print list of available actions on objects
948
sub do_plainhelp {
949
    my $res;
950
    $res .= $Stabile::q->header('text/plain') unless $console;
951
    $res .= <<END
952
new [name="name"]
953
* start: Starts a server
954
* destroy: Destroys a server, i.e. terminates the VM, equivalent of turning the power off a physical computer
955
* shutdown: Asks the operating system of a server to shut down via ACPI
956
* suspend: Suspends the VM, effectively putting the server to sleep
957
* resume: Resumes a suspended VM, effectively waking the server from sleep
958
* move [mac="mac"]: Moves a server to specified node. If no node is specified, moves to other node with highest availability
959
index
960
* delete: Deletes a server. Image and network are not deleted, only information about the server. Server cannot be
961
runing
962
* mountcd [cdrom="path"]: Mounts a cd rom
963
END
964
    ;
965
    return $res;
966
}
967

    
968
# Helper function
969
sub recurse($) {
970
	my($path) = @_;
971
	my @files;
972
	## append a trailing / if it's not there
973
	$path .= '/' if($path !~ /\/$/);
974
	## loop through the files contained in the directory
975
	for my $eachFile (glob($path.'*')) {
976
		## if the file is a directory
977
		if( -d $eachFile) {
978
			## pass the directory to the routine ( recursion )
979
			push(@files,recurse($eachFile));
980
		} else {
981
			push(@files,$eachFile);
982
		}
983
	}
984
	return @files;
985
}
986

    
987
sub Start {
988
    my ($uuid, $action, $obj) = @_;
989
    $dmac = $obj->{mac};
990
    $buildsystem = $obj->{buildsystem};
991
    $uistatus = $obj->{uistatus};
992
    if ($help) {
993
        return <<END
994
GET:uuid,mac:
995
Start a server. Supply mac for starting on specific node.
996
END
997
    }
998
    $dmac = $dmac || $params{'mac'};
999
    return "Status=ERROR No uuid\n" unless ($register{$uuid});
1000
    my $serv = $register{$uuid};
1001
    $postreply = '' if ($buildsystem);
1002

    
1003
    my $name = $serv->{'name'};
1004
    utf8::decode($name);
1005
    my $image = $serv->{'image'};
1006
    my $image2 = $serv->{'image2'};
1007
    my $image3 = $serv->{'image3'};
1008
    my $image4 = $serv->{'image4'};
1009
    my $memory = $serv->{'memory'};
1010
    my $vcpu = $serv->{'vcpu'};
1011
    my $vgpu = $serv->{'vgpu'};
1012
    my $dbstatus = $serv->{'status'};
1013
    my $mac = $serv->{'mac'};
1014
    my $macname = $serv->{'macname'};
1015
    my $networkuuid1 = $serv->{'networkuuid1'};
1016
    my $networkuuid2 = $serv->{'networkuuid2'};
1017
    my $networkuuid3 = $serv->{'networkuuid3'};
1018
    my $nicmodel1 = $serv->{'nicmodel1'};
1019
    my $nicmac1 = $serv->{'nicmac1'};
1020
    my $nicmac2 = $serv->{'nicmac2'};
1021
    my $nicmac3 = $serv->{'nicmac3'};
1022
    my $boot = $serv->{'boot'};
1023
    my $loader = $serv->{'loader'};
1024
    my $diskbus = $serv->{'diskbus'};
1025
    my $cdrom = $serv->{'cdrom'};
1026
    my $diskdev = "vda";
1027
    my $diskdev2 = "vdb";
1028
    my $diskdev3 = "vdc";
1029
    my $diskdev4 = "vdd";
1030
    if ($diskbus eq "ide") {$diskdev = "hda"; $diskdev2 = "hdb"; $diskdev3 = "hdc"; $diskdev4 = "hdd"};
1031

    
1032
    my $mem = $memory * 1024;
1033

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

    
1036
    my $img = $imagereg{$image};
1037
    my $imagename = $img->{'name'};
1038
    my $imagestatus = $img->{'status'};
1039
    my $img2 = $imagereg{$image2};
1040
    my $image2status = $img2->{'status'};
1041
    my $img3 = $imagereg{$image3};
1042
    my $image3status = $img3->{'status'};
1043
    my $img4 = $imagereg{$image4};
1044
    my $image4status = $img4->{'status'};
1045

    
1046
    if (!$imagereg{$image}) {
1047
        $postreply .= "Status=Error Image $image not found - please select a new image for your server, not starting $name\n";
1048
        untie %imagereg;
1049
        return $postreply;
1050
    }
1051
    untie %imagereg;
1052

    
1053
    if ($imagestatus ne "used" && $imagestatus ne "cloning") {
1054
        $postreply .= "Status=ERROR Image $imagename $image is $imagestatus, not starting $name\n";
1055
    } elsif ($image2 && $image2 ne '--' && $image2status ne "used" && $image2status ne "cloning") {
1056
        $postreply .= "Status=ERROR Image2 is $image2status, not starting $name\n";
1057
    } elsif ($image3 && $image3 ne '--' && $image3status ne "used" && $image3status ne "cloning") {
1058
        $postreply .= "Status=ERROR Image3 is $image3status, not starting $name\n";
1059
    } elsif ($image4 && $image4 ne '--' && $image4status ne "used" && $image4status ne "cloning") {
1060
        $postreply .= "Status=ERROR Image4 is $image4status, not starting $name\n";
1061
    } elsif (Stabile::Servers::overQuotas($memory,$vcpu)) {
1062
        $main::syslogit->($user, "info", "Over quota ($memory, $vcpu, " . Stabile::Servers::overQuotas($memory,$vcpu) .  ") starting a $dbstatus domain: $uuid");
1063
        $postreply .= "Status=ERROR Over quota - not starting $name\n";
1064
    # Status inactive is typically caused by a movepiston having problems. We should not start inactive servers since
1065
    # they could possibly be running even if movepiston is down. Movepiston on the node should be brought up to update
1066
    # the status, or the node should be removed from the stabile.
1067
    # We now allow to force start of inactive server when dmac is specified
1068
    } elsif ((!$dmac || $dmac eq $mac) && $dbstatus eq 'inactive' && $nodereg{$mac} && ($nodereg{$mac}->{'status'} eq 'inactive' || $nodereg{$mac}->{'status'} eq 'shutdown')) {
1069
        $main::syslogit->($user, "info", "Not starting inactive domain: $uuid (last seen on $mac)");
1070
        $postreply .= "Status=ERROR Not starting $name - Please bring up node $macname\n";
1071
    } elsif ($dbstatus eq 'inactive' || $dbstatus eq 'shutdown' || $dbstatus eq 'shutoff' || $dbstatus eq 'new') {
1072
        unless ($dmac && $isadmin) {
1073
            $dmac = $mac if ($dbstatus eq 'inactive'); # If movepiston crashed while shutting down, allow server to start on same node
1074
        }
1075
        $uistatus = "starting" unless ($uistatus);
1076
        my $hypervisor = getHypervisor($image);
1077
        my ($targetmac, $targetname, $targetip, $port) = locateTargetNode($uuid, $dmac, $mem, $vcpu, $image, $image2 ,$image3, $image4, $hypervisor);
1078

    
1079
        # Read limits from nodeconfig
1080
        my $vm_readlimit = '';
1081
        my $vm_writelimit = '';
1082
        my $vm_iopsreadlimit = ''; # e.g. 1000 IOPS
1083
        my $vm_iopswritelimit = '';
1084
        if  (-e "/etc/stabile/nodeconfig.cfg") {
1085
            my $nodecfg = new Config::Simple("/etc/stabile/nodeconfig.cfg");
1086
            $vm_readlimit = $nodecfg->param('VM_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
1087
            $vm_writelimit = $nodecfg->param('VM_WRITE_LIMIT');
1088
            $vm_iopsreadlimit = $nodecfg->param('VM_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
1089
            $vm_iopswritelimit = $nodecfg->param('VM_IOPS_WRITE_LIMIT');
1090
        }
1091

    
1092
        # Build XML for starting domain
1093
        my $graphics = "vnc";
1094
        $graphics = "rdp" if ($hypervisor eq "vbox");
1095
        my $net1 = $networkreg{$networkuuid1};
1096
        my $networkid1 = $net1->{'id'}; # Get the current vlan id of the network
1097
        my $net2 = $networkreg{$networkuuid2};
1098
        my $networkid2 = $net2->{'id'}; # Get the current vlan id of the network
1099
        my $net3 = $networkreg{$networkuuid2};
1100
        my $networkid3 = $net3->{'id'}; # Get the current vlan id of the network
1101
        my $networkid1ip = $net1->{'internalip'};
1102
        $networkid1ip = $net1->{'externalip'} if ($net1->{'type'} eq 'externalip');
1103

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

    
1108
        my $driver1;
1109
        my $driver2;
1110
        if ($hypervisor eq 'kvm') {
1111
            my $fmt1 = ($image =~ /\.qcow2$/)?'qcow2':'raw';
1112
            my $fmt2 = ($image2 =~ /\.qcow2$/)?'qcow2':'raw';
1113
            my $fmt3 = ($image3 =~ /\.qcow2$/)?'qcow2':'raw';
1114
            my $fmt4 = ($image4 =~ /\.qcow2$/)?'qcow2':'raw';
1115
            my $cache1 = ($image =~ /\/node\//)?'default':'writeback';
1116
            my $cache2 = ($image2 =~ /\/node\//)?'default':'writeback';
1117
            my $cache3 = ($image3 =~ /\/node\//)?'default':'writeback';
1118
            my $cache4 = ($image4 =~ /\/node\//)?'default':'writeback';
1119
            $driver1 = "\n      <driver name='qemu' type='$fmt1' cache='$cache1'/>";
1120
            $driver2 = "\n      <driver name='qemu' type='$fmt2' cache='$cache2'/>";
1121
            $driver3 = "\n      <driver name='qemu' type='$fmt3' cache='$cache3'/>";
1122
            $driver4 = "\n      <driver name='qemu' type='$fmt4' cache='$cache4'/>";
1123
        }
1124

    
1125
        my $networktype1 = "user";
1126
        my $networksource1 = "default";
1127
        my $networkforward1 = "bridge";
1128
        my $networkisolated1 = "no";
1129
        $networksource1 = "vboxnet0" if ($hypervisor eq "vbox");
1130
        if ($networkid1 eq '0') {
1131
            $networktype1 = "user";
1132
            $networkforward1 = "nat";
1133
            $networkisolated1 = "yes"
1134
        } elsif ($networkid1 == 1) {
1135
            $networktype1 = "network" ;
1136
            $networkforward1 = "nat";
1137
            $networkisolated1 = "yes"
1138
        } elsif ($networkid1 > 1) {
1139
            $networktype1 = "bridge";
1140
            $networksource1 = "br$networkid1";
1141
        }
1142
        my $networktype2 = "user";
1143
        my $networksource2 = "default";
1144
        my $networkforward2 = "bridge";
1145
        my $networkisolated2 = "no";
1146
        $networksource2 = "vboxnet0" if ($hypervisor eq "vbox");
1147
        if ($networkid2 eq '0') {
1148
            $networktype2 = "user";
1149
            $networkforward2 = "nat";
1150
            $networkisolated2 = "yes"
1151
        } elsif ($networkid2 == 1) {
1152
            $networktype2 = "network" ;
1153
            $networkforward2 = "nat";
1154
            $networkisolated2 = "yes"
1155
        } elsif ($networkid2 > 1) {
1156
            $networktype2 = "bridge";
1157
            $networksource2 = "br$networkid2";
1158
        }
1159
        my $networktype3 = "user";
1160
        my $networksource3 = "default";
1161
        my $networkforward3 = "bridge";
1162
        my $networkisolated3 = "no";
1163
        $networksource3 = "vboxnet0" if ($hypervisor eq "vbox");
1164
        if ($networkid3 eq '0') {
1165
            $networktype3 = "user";
1166
            $networkforward3 = "nat";
1167
            $networkisolated3 = "yes"
1168
        } elsif ($networkid3 == 1) {
1169
            $networktype3 = "network" ;
1170
            $networkforward3 = "nat";
1171
            $networkisolated3 = "yes"
1172
        } elsif ($networkid3 > 1) {
1173
            $networktype3 = "bridge";
1174
            $networksource3 = "br$networkid3";
1175
        }
1176

    
1177
        my $xml = "<domain type='$hypervisor' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>\n";
1178
#        if ($vgpu && $vgpu ne "--") {
1179
#            $xml .= <<ENDXML2
1180
#  <qemu:commandline>
1181
#    <qemu:arg value='-device'/>
1182
#    <qemu:arg value='vfio-pci,host=01:00.0,x-vga=on'/>
1183
#    <qemu:arg value='-device'/>
1184
#    <qemu:arg value='vfio-pci,host=02:00.0,x-vga=on'/>
1185
#  </qemu:commandline>
1186
#ENDXML2
1187
#            ;
1188
#        }
1189

    
1190
#    <qemu:arg value='-set'/>
1191
#    <qemu:arg value='device.hostdev1.x-vga=on'/>
1192
#    <qemu:arg value='-cpu'/>
1193
#	<qemu:arg value='host,kvm=off'/>
1194
#    <qemu:arg value='-device'/>
1195
#	<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'/>
1196

    
1197
#  <cpu mode='host-model'>
1198
#    <vendor>Intel</vendor>
1199
#    <model>core2duo</model>
1200
#  </cpu>
1201

    
1202
#    <loader readonly='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE.fd</loader>
1203
#    <nvram template='/usr/share/OVMF/OVMF_VARS.fd'/>
1204
        my $loader_xml = <<ENDXML
1205
    <bootmenu enable='yes' timeout='200'/>
1206
    <smbios mode='sysinfo'/>
1207
ENDXML
1208
        ;
1209
        if ($loader eq 'uefi') {
1210
            $loader_xml = <<ENDXML
1211
  <loader readonly='yes' secure='no' type='pflash'>/usr/share/ovmf/OVMF.fd</loader>
1212
  <nvram template='/usr/share/OVMF/OVMF_VARS.fd'>/tmp/guest_VARS.fd</nvram>
1213
ENDXML
1214
    ;
1215
        }
1216
        my $iotune_xml = <<ENDXML
1217
      <iotune>
1218
        <read_bytes_sec>$vm_readlimit</read_bytes_sec>
1219
        <write_bytes_sec>$vm_writelimit</write_bytes_sec>
1220
        <read_iops_sec>$vm_iopsreadlimit</read_iops_sec>
1221
        <write_iops_sec>$vm_iopswritelimit</write_iops_sec>
1222
      </iotune>
1223
ENDXML
1224
;
1225
        $iotune_xml = '' unless ($enforceiolimits);
1226

    
1227
        if ($vgpu && $vgpu ne "--") {
1228
            $xml .= <<ENDXML
1229
  <cpu mode='host-passthrough'>
1230
    <feature policy='disable' name='hypervisor'/>
1231
  </cpu>
1232
ENDXML
1233
;
1234
        } else {
1235
            $xml .= <<ENDXML
1236
  <cpu mode='host-model'>
1237
  </cpu>
1238
ENDXML
1239
            ;
1240
        }
1241
        $xml .=  <<ENDXML
1242
  <name>$uname</name>
1243
  <uuid>$uuid</uuid>
1244
  <memory>$mem</memory>
1245
  <vcpu>$vcpu</vcpu>
1246
  <os>
1247
    <type arch='x86_64' machine='pc'>hvm</type>
1248
    <boot dev='$boot'/>
1249
$loader_xml
1250
  </os>
1251
  <sysinfo type='smbios'>
1252
    <bios>
1253
      <entry name='vendor'>Origo</entry>
1254
    </bios>
1255
    <system>
1256
      <entry name='manufacturer'>Origo</entry>
1257
      <entry name='sku'>$networkid1ip</entry>
1258
    </system>
1259
  </sysinfo>
1260
  <features>
1261
ENDXML
1262
;
1263
        if ($vgpu && $vgpu ne "--") { $xml .= <<ENDXML
1264
    <kvm>
1265
      <hidden state='on'/>
1266
    </kvm>
1267
ENDXML
1268
;
1269
        }
1270
        $xml .= <<ENDXML
1271
    <pae/>
1272
    <acpi/>
1273
    <apic/>
1274
  </features>
1275
  <clock offset='localtime'>
1276
    <timer name='rtc' tickpolicy='catchup' track='guest'/>
1277
    <timer name='pit' tickpolicy='delay'/>
1278
    <timer name='hpet' present='no'/>
1279
  </clock>
1280
  <on_poweroff>destroy</on_poweroff>
1281
  <on_reboot>restart</on_reboot>½
1282
  <on_crash>restart</on_crash>
1283
  <devices>
1284
  <sound model='ich6'/>
1285
ENDXML
1286
;
1287
#        if ($vgpu && $vgpu ne "--") {
1288
#            $xml .= <<ENDXML2
1289
#  <hostdev mode='subsystem' type='pci' managed='yes'>
1290
#    <source>
1291
#      <address domain='0x0000' bus='0x01' slot='0x00' function='0x0' multifunction='on'/>
1292
#    </source>
1293
#  </hostdev>
1294
#  <hostdev mode='subsystem' type='pci' managed='yes'>
1295
#    <source>
1296
#      <address domain='0x0000' bus='0x02' slot='0x00' function='0x0' multifunction='on'/>
1297
#    </source>
1298
#  </hostdev>
1299
#ENDXML2
1300
#;
1301
#        }
1302
        if ($image && $image ne "" && $image ne "--") {
1303
						$xml .= <<ENDXML2
1304
    <disk type='file' device='disk'>
1305
      <source file='$image'/>$driver1
1306
      <target dev='$diskdev' bus='$diskbus'/>
1307
$iotune_xml
1308
    </disk>
1309
ENDXML2
1310
;
1311
        };
1312

    
1313
        if ($image2 && $image2 ne "" && $image2 ne "--") {
1314
						$xml .= <<ENDXML2
1315
    <disk type='file' device='disk'>$driver2
1316
      <source file='$image2'/>
1317
      <target dev='$diskdev2' bus='$diskbus'/>
1318
$iotune_xml
1319
    </disk>
1320
ENDXML2
1321
;
1322
        };
1323
        if ($image3 && $image3 ne "" && $image3 ne "--") {
1324
						$xml .= <<ENDXML2
1325
    <disk type='file' device='disk'>$driver3
1326
      <source file='$image3'/>
1327
      <target dev='$diskdev3' bus='$diskbus'/>
1328
$iotune_xml
1329
    </disk>
1330
ENDXML2
1331
;
1332
        };
1333
        if ($image4 && $image4 ne "" && $image4 ne "--") {
1334
						$xml .= <<ENDXML2
1335
    <disk type='file' device='disk'>$driver4
1336
      <source file='$image4'/>
1337
      <target dev='$diskdev4' bus='$diskbus'/>
1338
$iotune_xml
1339
    </disk>
1340
ENDXML2
1341
;
1342
        };
1343

    
1344
        unless ($image4 && $image4 ne '--' && $diskbus eq 'ide') {
1345
            if ($cdrom && $cdrom ne "" && $cdrom ne "--") {
1346
						$xml .= <<ENDXML3
1347
    <disk type='file' device='cdrom'>
1348
      <source file='$cdrom'/>
1349
      <target dev='hdd' bus='ide'/>
1350
      <readonly/>
1351
    </disk>
1352
ENDXML3
1353
;
1354
            } elsif ($hypervisor ne "vbox") {
1355
						$xml .= <<ENDXML3
1356
    <disk type='file' device='cdrom'>
1357
      <target dev='hdd' bus='ide'/>
1358
      <readonly/>
1359
    </disk>
1360
ENDXML3
1361
;
1362
            }
1363
        }
1364

    
1365
        $xml .= <<ENDXML4
1366
    <interface type='$networktype1'>
1367
      <source $networktype1='$networksource1'/>
1368
      <forward mode='$networkforward1'/>
1369
      <port isolated='$networkisolated1'/>
1370
      <model type='$nicmodel1'/>
1371
      <mac address='$nicmac1'/>
1372
    </interface>
1373
ENDXML4
1374
;
1375

    
1376
        if (($networkuuid2 && $networkuuid2 ne '--') || $networkuuid2 eq '0') {
1377
            $xml .= <<ENDXML5
1378
    <interface type='$networktype2'>
1379
      <source $networktype2='$networksource2'/>
1380
      <forward mode='$networkforward2'/>
1381
      <port isolated='$networkisolated2'/>
1382
      <model type='$nicmodel1'/>
1383
      <mac address='$nicmac2'/>
1384
    </interface>
1385
ENDXML5
1386
;
1387
        }
1388
        if (($networkuuid3 && $networkuuid3 ne '--') || $networkuuid3 eq '0') {
1389
            $xml .= <<ENDXML5
1390
    <interface type='$networktype3'>
1391
      <source $networktype3='$networksource3'/>
1392
      <forward mode='$networkforward3'/>
1393
      <port isolated='$networkisolated3'/>
1394
      <model type='$nicmodel1'/>
1395
      <mac address='$nicmac3'/>
1396
    </interface>
1397
ENDXML5
1398
;
1399
        }
1400
        $xml .= <<ENDXML6
1401
     <serial type='pty'>
1402
       <source path='/dev/pts/0'/>
1403
       <target port='0'/>
1404
     </serial>
1405
    <input type='tablet' bus='usb'/>
1406
    <graphics type='$graphics' port='$port'/>
1407
  </devices>
1408
</domain>
1409
ENDXML6
1410
;
1411

    
1412

    
1413
#    <graphics type='$graphics' port='$port' keymap='en-us'/>
1414
#     <console type='pty' tty='/dev/pts/0'>
1415
#       <source path='/dev/pts/0'/>
1416
#       <target port='0'/>
1417
#     </console>
1418
#     <graphics type='$graphics' port='-1' autoport='yes'/>
1419

    
1420
        $xmlreg{$uuid} = {
1421
            xml=>URI::Escape::uri_escape($xml)
1422
        };
1423

    
1424
        # Actually ask node to start domain
1425
        if ($targetmac) {
1426
            $register{$uuid}->{'mac'} = $targetmac;
1427
            $register{$uuid}->{'macname'} = $targetname;
1428
            $register{$uuid}->{'macip'} = $targetip;
1429

    
1430
            my $tasks = $nodereg{$targetmac}->{'tasks'};
1431
            $tasks .= "START $uuid $user\n";
1432
            $nodereg{$targetmac}->{'tasks'} = $tasks;
1433
            tied(%nodereg)->commit;
1434
            $uiuuid = $uuid;
1435
            $uidisplayip = $targetip;
1436
            $uidisplayport = $port;
1437
            $register{$uuid}->{'status'} = $uistatus;
1438
            $register{$uuid}->{'statustime'} = $current_time;
1439
            tied(%register)->commit;
1440

    
1441
            # Activate networks
1442
            require "$Stabile::basedir/cgi/networks.cgi";
1443
            Stabile::Networks::Activate($networkuuid1, 'activate');
1444
            Stabile::Networks::Activate($networkuuid2, 'activate') if ($networkuuid2 && $networkuuid2 ne '--');
1445
            Stabile::Networks::Activate($networkuuid3, 'activate') if ($networkuuid3 && $networkuuid3 ne '--');
1446

    
1447
            $main::syslogit->($user, "info", "Marked $name ($uuid) for ". $serv->{'status'} . " on $targetname ($targetmac)");
1448
            $postreply .= "Status=starting OK $uistatus ". $serv->{'name'} . "\n";
1449
        } else {
1450
            $main::syslogit->($user, "info", "Could not find $hypervisor target for creating $uuid ($image)");
1451
            $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . " (unable to locate target node)\n";
1452
        };
1453
    } else {
1454
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
1455
        $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . "\n";
1456
    }
1457
    #return ($uiuuid, $uidisplayip, $uidisplayport, $postreply, $targetmac);
1458
    return $postreply;
1459
}
1460

    
1461
sub do_attach {
1462
    my ($uuid, $action, $obj) = @_;
1463
    if ($help) {
1464
        return <<END
1465
GET:uuid,image:
1466
Attaches an image to a server as a disk device. Image must not be in use.
1467
END
1468
    }
1469
    my $dev = '';
1470
    my $imagenum = 0;
1471
    my $serv = $register{$uuid};
1472

    
1473
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1474
        return "Status=Error Server must exist and be running\n";
1475
    }
1476
    my $macip = $serv->{macip};
1477
    my $image = $obj->{image} || $obj->{path};
1478
    if ($image && !($image =~ /^\//)) { # We have a uuid
1479
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Status=Error Unable to access images register\n"};
1480
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1481
        untie %imagereg2;
1482
    }
1483
    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;};
1484
    unless ($macip && $imagereg{$image} && $imagereg{$image}->{'user'} eq $user && $serv->{'user'} eq $user)  {$postreply .= "Status=Error Invalid image or server\n"; return $postreply;};
1485
    if ($imagereg{$image}->{'status'} ne 'unused') {return "Status=Error Image $image is already in use ($imagereg{$image}->{'status'})\n"};
1486

    
1487
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1488
    my $res = `$cmd`;
1489
    unless ($res =~ /vdb\s+.+/) {$dev = 'vdb'; $imagenum = 2};
1490
    unless ($dev || $res =~ /vdc\s+.+/)  {$dev = 'vdc'; $imagenum = 3};
1491
    unless ($dev || $res =~ /vdd\s+.+/)  {$dev = 'vdd'; $imagenum = 4};
1492
    if (!$dev) {
1493
        $postreply = "Status=Error No more images can be attached\n";
1494
    } else {
1495
        my $xml = <<END
1496
<disk type='file' device='disk'>
1497
  <driver type='qcow2' name='qemu' cache='default'/>
1498
  <source file='$image'/>
1499
  <target dev='$dev' bus='virtio'/>
1500
</disk>
1501
END
1502
;
1503
        $cmd = qq|$sshcmd $macip "echo \\"$xml\\" > /tmp/attach-device-$uuid.xml"|;
1504
        $res = `$cmd`;
1505
        $res .= `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh attach-device $uuid /tmp/attach-device-$uuid.xml`;
1506
        chomp $res;
1507
        if ($res =~ /successfully/) {
1508
            $postreply .= "Status=OK Attaching $image to $dev\n";
1509
            $imagereg{$image}->{'status'} = 'active';
1510
            $imagereg{$image}->{'domains'} = $uuid;
1511
            $imagereg{$image}->{'domainnames'} = $serv->{'name'};
1512
            $serv->{"image$imagenum"} = $image;
1513
            $serv->{"image$imagenum"."name"} = $imagereg{$image}->{'name'};
1514
            $serv->{"image$imagenum"."type"} = 'qcow2';
1515
        } else {
1516
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1517
        }
1518
    }
1519
    untie %imagereg;
1520
    return $postreply;
1521
}
1522

    
1523
sub do_detach {
1524
    my ($uuid, $action, $obj) = @_;
1525
    if ($help) {
1526
        return <<END
1527
GET:uuid,image:
1528
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.
1529
END
1530
    }
1531
    my $dev = '';
1532
    my $serv = $register{$uuid};
1533

    
1534
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1535
        return "Status=Error Server must exist and be running\n";
1536
    }
1537
    my $macip = $serv->{macip};
1538

    
1539
    my $image = $obj->{image} || $obj->{path} || $serv->{'image2'};
1540
    if ($image && !($image =~ /^\//)) { # We have a uuid
1541
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1542
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1543
        untie %imagereg2;
1544
    }
1545
    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;};
1546
    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;};
1547

    
1548
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1549
    my $res = `$cmd`;
1550
    $dev = $1 if ($res =~ /(vd.)\s+.+$image/);
1551
    if (!$dev) {
1552
        $postreply =  qq|Status=Error Image $image, $cmd, is not currently attached\n|;
1553
    } elsif ($dev eq 'vda') {
1554
        $postreply = "Status=Error You cannot detach the primary image\n";
1555
    } else {
1556
        $res = `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh detach-disk $uuid $dev`;
1557
        chomp $res;
1558
        if ($res =~ /successfully/) {
1559
            $postreply .= "Status=OK Detaching image $image, $imagereg{$image}->{'uuid'} from $dev\n";
1560
            my $imagenum;
1561
            $imagenum = 2 if ($serv->{'image2'} eq $image);
1562
            $imagenum = 3 if ($serv->{'image3'} eq $image);
1563
            $imagenum = 4 if ($serv->{'image4'} eq $image);
1564
            $imagereg{$image}->{'status'} = 'unused';
1565
            $imagereg{$image}->{'domains'} = '';
1566
            $imagereg{$image}->{'domainnames'} = '';
1567
            if ($imagenum) {
1568
                $serv->{"image$imagenum"} = '';
1569
                $serv->{"image$imagenum"."name"} = '';
1570
                $serv->{"image$imagenum"."type"} = '';
1571
            }
1572
        } else {
1573
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1574
        }
1575
    }
1576
    untie %imagereg;
1577
    return $postreply;
1578
}
1579

    
1580
sub Destroy {
1581
    my ($uuid, $action, $obj) = @_;
1582
    if ($help) {
1583
        return <<END
1584
GET:uuid,wait:
1585
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.
1586
END
1587
    }
1588
    my $uistatus = 'destroying';
1589
    my $name = $register{$uuid}->{'name'};
1590
    my $mac = $register{$uuid}->{'mac'};
1591
    my $macname = $register{$uuid}->{'macname'};
1592
    my $dbstatus = $register{$uuid}->{'status'};
1593
    my $wait = $obj->{'wait'};
1594
    if ($dbstatus eq 'running' or $dbstatus eq 'paused'
1595
        or $dbstatus eq 'shuttingdown' or $dbstatus eq 'starting'
1596
        or $dbstatus eq 'destroying' or $dbstatus eq 'upgrading'
1597
        or $dbstatus eq 'suspending' or $dbstatus eq 'resuming') {
1598
        if ($wait) {
1599
            my $username = $register{$uuid}->{'user'} || $user;
1600
            $username = $user unless ($isadmin);
1601
            $postreply = destroyUserServers($username, 1, $uuid);
1602
        } else {
1603
            my $node = $nodereg{$mac};
1604
            my $tasks = $node->{'tasks'};
1605
            $node->{'tasks'} = $tasks . "DESTROY $uuid $user\n";
1606
            tied(%nodereg)->commit;
1607
            $register{$uuid}->{'status'} = $uistatus;
1608
            $register{$uuid}->{'statustime'} = $current_time;
1609
            $uiuuid = $uuid;
1610
            $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus on $macname ($mac)");
1611
            $postreply .= "Status=destroying $uistatus ". $register{$uuid}->{'name'} . "\n";
1612
        }
1613
    } else {
1614
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $name ($uuid)");
1615
        $postreply .= "Status=ERROR problem $uistatus $name\n";
1616
    }
1617
    return $postreply;
1618
}
1619

    
1620
sub getHypervisor {
1621
	my $image = shift;
1622
	# Produce a mapping of image file suffixes to hypervisors
1623
	my %idreg;
1624
    unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access nodeidentities register"};
1625
    my @idvalues = values %idreg;
1626
	my %formats;
1627
	foreach my $val (@idvalues) {
1628
		my %h = %$val;
1629
		foreach (split(/,/,$h{'formats'})) {
1630
			$formats{lc $_} = $h{'hypervisor'}
1631
		}
1632
	}
1633
	untie %idreg;
1634

    
1635
	# and then determine the hypervisor in question
1636
	my $hypervisor = "vbox";
1637
	my ($pathname, $path, $suffix) = fileparse($image, '\.[^\.]*');
1638
	$suffix = substr $suffix, 1;
1639
	my $hypervisor = $formats{lc $suffix};
1640
	return $hypervisor;
1641
}
1642

    
1643
sub nicmac1ToUuid {
1644
    my $nicmac1 = shift;
1645
    my $uuid;
1646
    return $uuid unless $nicmac1;
1647
    my @regkeys = (tied %register)->select_where("user = '$user' AND nicmac1 = '$nicmac1");
1648
	foreach my $k (@regkeys) {
1649
	    my $val = $register{$k};
1650
		my %h = %$val;
1651
		if (lc $h{'nicmac1'} eq lc $nicmac1 && $user eq $h{'user'}) {
1652
    		$uuid =  $h{'uuid'};
1653
    		last;
1654
		}
1655
	}
1656
	return $uuid;
1657
}
1658

    
1659
sub randomMac {
1660
	my ( %vendor, $lladdr, $i );
1661
#	$lladdr = '00';
1662
	$lladdr = '52:54:00';# KVM vendor string
1663
	while ( ++$i )
1664
#	{ last if $i > 10;
1665
	{ last if $i > 6;
1666
		$lladdr .= ':' if $i % 2;
1667
		$lladdr .= sprintf "%" . ( qw (X x) [int ( rand ( 2 ) ) ] ), int ( rand ( 16 ) );
1668
	}
1669
	return $lladdr;
1670
}
1671

    
1672
sub overQuotas {
1673
    my $meminc = shift;
1674
    my $vcpuinc = shift;
1675
	my $usedmemory = 0;
1676
	my $usedvcpus = 0;
1677
	my $overquota = 0;
1678
    return $overquota if ($isadmin || $Stabile::userprivileges =~ /a/); # Don't enforce quotas for admins
1679

    
1680
	my $memoryquota = $Stabile::usermemoryquota;
1681
	my $vcpuquota = $Stabile::uservcpuquota;
1682

    
1683
	if (!$memoryquota || !$vcpuquota) { # 0 or empty quota means use defaults
1684
        $memoryquota = $memoryquota || $Stabile::config->get('MEMORY_QUOTA');
1685
        $vcpuquota = $vcpuquota || $Stabile::config->get('VCPU_QUOTA');
1686
    }
1687

    
1688
    my @regkeys = (tied %register)->select_where("user = '$user'");
1689
	foreach my $k (@regkeys) {
1690
	    my $val = $register{$k};
1691
		if ($val->{'user'} eq $user && $val->{'status'} ne "shutoff" &&
1692
		    $val->{'status'} ne "inactive" && $val->{'status'} ne "shutdown" ) {
1693

    
1694
		    $usedmemory += $val->{'memory'};
1695
		    $usedvcpus += $val->{'vcpu'};
1696
		}
1697
	}
1698
	$overquota = $usedmemory+$meminc if ($memoryquota!=-1 && $usedmemory+$meminc > $memoryquota); # -1 means no quota
1699
	$overquota = $usedvcpus+$vcpuinc if ($vcpuquota!=-1 && $usedvcpus+$vcpuinc > $vcpuquota);
1700
	return $overquota;
1701
}
1702

    
1703
sub validateItem {
1704
    unless (%imagereg) {
1705
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1706
    }
1707
    my $valref = shift;
1708
    my $img = $imagereg{$valref->{'image'}};
1709
    my $imagename = $img->{'name'};
1710
    $valref->{'imagename'} = $imagename if ($imagename);
1711
    my $imagetype = $img->{'type'};
1712
    $valref->{'imagetype'} = $imagetype if ($imagetype);
1713

    
1714
    # imagex may be registered by uuid instead of path - find the path
1715
    # We now support up to 4 images
1716
    for (my $i=2; $i<=4; $i++) {
1717
        if ($valref->{"image$i"} && $valref->{"image$i"} ne '--' && !($valref->{"image$i"} =~ /^\//)) {
1718
            unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1719
            $valref->{"image$i"} = $imagereg2{$valref->{"image$i"}}->{'path'};
1720
            untie %imagereg2;
1721
        }
1722

    
1723
        my $imgi = $imagereg{$valref->{"image$i"}};
1724
        $valref->{"image$i" . 'name'} = $imgi->{'name'} || $valref->{"image$i" . 'name'};
1725
        $valref->{"image$i" . 'type'} = $imgi->{'type'} || $valref->{"image$i" . 'type'};
1726
    }
1727

    
1728
    my $net1 = $networkreg{$valref->{'networkuuid1'}};
1729
    my $networkname1 = $net1->{'name'};
1730
    $valref->{'networkname1'} = $networkname1 if ($networkname1);
1731
    my $net2 = $networkreg{$valref->{'networkuuid2'}};
1732
    my $networkname2 = $net2->{'name'};
1733
    $valref->{'networkname2'} = $networkname2 if ($networkname2);
1734
    my $name = $valref->{'name'};
1735
    $valref->{'name'} = $imagename unless $name;
1736

    
1737
    # Make sure we start shutoff servers on the node their image is on
1738
    if ($valref->{'status'} eq "shutoff" || $valref->{'status'} eq "inactive") {
1739
        my $node = $nodereg{$valref->{'mac'}};
1740
        if ($valref->{'image'} =~ /\/mnt\/stabile\/node\//) {
1741
            $valref->{'mac'} = $img->{'mac'};
1742
            $valref->{'macname'} = $node->{'name'};
1743
            $valref->{'macip'} = $node->{'ip'};
1744
        } elsif ($valref->{'image2'} =~ /\/mnt\/stabile\/node\//) {
1745
            $valref->{'mac'} = $imagereg{$valref->{'image2'}}->{'mac'};
1746
            $valref->{'macname'} = $node->{'name'};
1747
            $valref->{'macip'} = $node->{'ip'};
1748
        } elsif ($valref->{'image3'} =~ /\/mnt\/stabile\/node\//) {
1749
            $valref->{'mac'} = $imagereg{$valref->{'image3'}}->{'mac'};
1750
            $valref->{'macname'} = $node->{'name'};
1751
            $valref->{'macip'} = $node->{'ip'};
1752
        } elsif ($valref->{'image4'} =~ /\/mnt\/stabile\/node\//) {
1753
            $valref->{'mac'} = $imagereg{$valref->{'image4'}}->{'mac'};
1754
            $valref->{'macname'} = $node->{'name'};
1755
            $valref->{'macip'} = $node->{'ip'};
1756
        }
1757
    }
1758
# Mark domains we have heard from in the last 20 secs as inactive
1759
    my $dbtimestamp = 0;
1760
    $dbtimestamp = $register{$valref->{'uuid'}}->{'timestamp'} if ($register{$valref->{'uuid'}});
1761
    my $timediff = $current_time - $dbtimestamp;
1762
    if ($timediff >= 20) {
1763
        if  (! ($valref->{'status'} eq "shutoff"
1764
                || $valref->{'status'} eq "starting"
1765
            #    || $valref->{'status'} eq "shuttingdown"
1766
            #    || $valref->{'status'} eq "destroying"
1767
                || ($valref->{'status'} =~ /moving/ && $timediff<40)
1768
            )) { # Move has probably failed
1769
            $valref->{'status'} = "inactive";
1770
            $imagereg{$valref->{'image'}}->{'status'} = "used" if ($valref->{'image'} && $imagereg{$valref->{'image'}});
1771
            $imagereg{$valref->{'image2'}}->{'status'} = "used" if ($valref->{'image2'} && $imagereg{$valref->{'image2'}});
1772
            $imagereg{$valref->{'image3'}}->{'status'} = "used" if ($valref->{'image3'} && $imagereg{$valref->{'image3'}});
1773
            $imagereg{$valref->{'image4'}}->{'status'} = "used" if ($valref->{'image4'} && $imagereg{$valref->{'image4'}});
1774
        }
1775
    };
1776
#    untie %imagereg;
1777
    return $valref;
1778
}
1779

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

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

    
1787
    foreach my $k (@regkeys) {
1788
        my $valref = $register{$k};
1789
        next unless ($userreg{$valref->{'user'}});
1790
        my $dbtimestamp = $valref->{'timestamp'};
1791
        my $dbstatus = $valref->{'status'};
1792
        my $timediff = $current_time - $dbtimestamp;
1793
        my $imgstatus;
1794
        my $domstatus;
1795
        if ($timediff >= 20) {
1796
            if  ( $valref->{'status'} eq "shutoff" ) {
1797
                $imgstatus = 'used';
1798
            } elsif ((  $valref->{'status'} eq "starting"
1799
                            || $valref->{'status'} eq "shuttingdown"
1800
                        ) && $timediff>50) {
1801
                $imgstatus = 'used';
1802
                $domstatus = 'inactive';
1803
            } elsif ($valref->{'status'} eq "destroying" || $valref->{'status'} eq "moving") {
1804
                ;
1805
            } else {
1806
                $domstatus = 'inactive';
1807
                $imgstatus = 'used';
1808
            }
1809
            $valref->{'status'} = $domstatus if ($domstatus);
1810
            my $image = $valref->{'image'};
1811
            my $image2 = $valref->{'image2'};
1812
            my $image3 = $valref->{'image3'};
1813
            my $image4 = $valref->{'image4'};
1814
            $imagereg{$image}->{'status'} = $imgstatus if ($imgstatus);
1815
            $imagereg{$image2}->{'status'} = $imgstatus if ($image2 && $imgstatus);
1816
            $imagereg{$image3}->{'status'} = $imgstatus if ($image3 && $imgstatus);
1817
            $imagereg{$image4}->{'status'} = $imgstatus if ($image4 && $imgstatus);
1818
            if ($domstatus eq 'inactive ' && $dbstatus ne 'inactive') {
1819
                $main::updateUI->({ tab=>'servers',
1820
                                    user=>$valref->{'user'},
1821
                                    uuid=>$valref->{'uuid'},
1822
                                    sender=>'updateRegister',
1823
                                    status=>'inactive'})
1824
            }
1825
        };
1826

    
1827
    }
1828
    untie %userreg;
1829
    untie %imagereg;
1830
}
1831

    
1832

    
1833
sub locateTargetNode {
1834
    my ($uuid, $dmac, $mem, $vcpu, $image, $image2, $image3, $image4, $hypervisor, $smac, $stormove)= @_;
1835
    my $targetname;
1836
    my $targetip;
1837
    my $port;
1838
    my $targetnode;
1839
    my $targetindex; # Availability index of located target node
1840
    my %avhash;
1841

    
1842
    $dmac = '' unless ($isadmin); # Only allow admins to select specific node
1843
    my $mnode = $register{$uuid};
1844
    if (!$dmac
1845
            && $mnode->{'locktonode'} eq 'true'
1846
            && $mnode->{'mac'}
1847
            && $mnode->{'mac'} ne '--'
1848
            ) {
1849
        $dmac = $mnode->{'mac'}; # Server is locked to specific node
1850
    }
1851
    if ($dmac && !$nodereg{$dmac}) {
1852
        $main::syslogit->($user, "info", "The target node $dmac no longer exists, starting $uuid on another node if possible");
1853
        $dmac = '';
1854
    }
1855
    my $imageonnode = ((!$stormove) && ($image =~ /\/mnt\/stabile\/node\//
1856
                                          || $image2 =~ /\/mnt\/stabile\/node\//
1857
                                          || $image3 =~ /\/mnt\/stabile\/node\//
1858
                                          || $image4 =~ /\/mnt\/stabile\/node\//
1859
                                          ));
1860

    
1861
    foreach $node (values %nodereg) {
1862
        my $nstatus = $node->{'status'};
1863
        my $maintenance = $node->{'maintenance'};
1864
        my $nmac = $node->{'mac'};
1865

    
1866
        if (($nstatus eq 'running' || $nstatus eq 'asleep' || $nstatus eq 'maintenance' || $nstatus eq 'waking')
1867
         && $smac ne $nmac
1868
         && (( ($node->{'memfree'} > $mem+512*1024)
1869
         && (($node->{'vmvcpus'} + $vcpu) <= ($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'})) ) || $action eq 'listnodeavailability')
1870
        ) {
1871
        # Determine how available this node is
1872
        # Available memory
1873
            my $memweight = 0.2; # memory weighing factor
1874
            my $memindex = $avhash{$nmac}->{'memindex'} = int(100* $memweight* $node->{'memfree'} / (1024*1024) )/100;
1875
        # Free cores
1876
            my $cpuindex = $avhash{$nmac}->{'cpuindex'} = int(100*($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'} - $node->{'vmvcpus'} - $node->{'reservedvcpus'}))/100;
1877
        # Asleep - not asleep gives a +3
1878
            my $sleepindex = $avhash{$nmac}->{'sleepindex'} = ($node->{'status'} eq 'asleep' || $node->{'status'} eq 'waking')?'0':'3';
1879
            $avhash{$nmac}->{'vmvcpus'} = $node->{'vmvcpus'};
1880
#            $avhash{$nmac}->{'cpucommision'} = $cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'};
1881
#            $avhash{$nmac}->{'cpureservation'} = $node->{'vmvcpus'} + $node->{'reservedvcpus'};
1882
            $avhash{$nmac}->{'name'} = $node->{'name'};
1883
            $avhash{$nmac}->{'mac'} = $node->{'mac'};
1884

    
1885
            my $aindex = $memindex + $cpuindex + $sleepindex;
1886
        # Don't use nodes that are out of memory of cores
1887
            $aindex = 0 if ($memindex <= 0 || $cpuindex <= 0);
1888
            $avhash{$nmac}->{'index'} = $aindex;
1889
            $avhash{$nmac}->{'storfree'} = $node->{'storfree'};
1890
            $avhash{$nmac}->{'memfree'} = $node->{'memfree'};
1891
            $avhash{$nmac}->{'ip'} = $node->{'ip'};
1892
            $avhash{$nmac}->{'identity'} = $node->{'identity'};
1893
            $avhash{$nmac}->{'status'} = $node->{'status'};
1894
            $avhash{$nmac}->{'maintenance'} = $maintenance;
1895
            $avhash{$nmac}->{'reservedvcpus'} = $node->{'reservedvcpus'};
1896
            my $nodeidentity = $node->{'identity'};
1897
            $nodeidentity = 'kvm' if ($nodeidentity eq 'local_kvm');
1898
            if ($hypervisor eq $nodeidentity) {
1899
                # If image is on node, we must start on same node - registered when moving image
1900
                if ($imageonnode) {
1901
                    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1902
                    $dmac = $imagereg{$image}->{'mac'};
1903
                    $dmac = $imagereg{$image2}->{'mac'} unless ($dmac);
1904
                    $dmac = $imagereg{$image3}->{'mac'} unless ($dmac);
1905
                    $dmac = $imagereg{$image4}->{'mac'} unless ($dmac);
1906
                    untie %imagereg;
1907
                    if (!$dmac) {
1908
                        $postreply .= "Status=ERROR Image node not found\n";
1909
                        last;
1910
                    }
1911
                }
1912
                $dmac = "" if ($dmac eq "--");
1913
            # If a specific node is asked for, match mac addresses
1914
                if ($dmac eq $nmac) {
1915
                    $targetnode = $node;
1916
                    last;
1917
                } elsif (!$dmac && $nstatus ne "maintenance" && !$maintenance) {
1918
            # pack or disperse
1919
                    if (!$targetindex) {
1920
                        $targetindex = $aindex;
1921
                        $targetnode = $node;
1922
                    } elsif ($dpolicy eq 'pack') {
1923
                        if ($aindex < $targetindex) {
1924
                            $targetnode = $node;
1925
                            $targetindex = $aindex;
1926
                        }
1927
                    } else {
1928
                        if ($aindex > $targetindex) {
1929
                            $targetnode = $node;
1930
                            $targetindex = $aindex;
1931
                        }
1932
                    }
1933
                }
1934
            }
1935
        }
1936
    }
1937
    if ($targetnode && $uuid) {
1938
        if ($targetnode->{'status'} eq 'asleep') {
1939
            my $nmac = $targetnode->{'mac'};
1940
            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);
1941
            my $nlogmsg = "Node $nmac marked for wake ";
1942
            if ($brutalsleep && (
1943
                    ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--')
1944
                || ($targetnode->{'ipmiip'} && $targetnode->{'ipmiip'} ne '--')
1945
                )) {
1946
                my $wakecmd;
1947
                if ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--') {
1948
                    $wakecmd = "echo 'y' | AMT_PASSWORD='$amtpasswd' /usr/bin/amttool $targetnode->{'amtip'} powerup pxe";
1949
                } else {
1950
                    $wakecmd = "ipmitool -I lanplus -H $targetnode->{'ipmiip'} -U ADMIN -P ADMIN power on";
1951
                }
1952
                $nlogmsg .= `$wakecmd`;
1953
            } else {
1954
                my $broadcastip = $targetnode->{'ip'};
1955
                $broadcastip =~ s/\.\d{1,3}$/.255/;
1956
                $nlogmsg .= 'on lan ' . `/usr/bin/wakeonlan -i $broadcastip $realmac`;
1957
            }
1958
            $targetnode->{'status'} = "waking";
1959
            $nlogmsg =~ s/\n/ /g;
1960
            $main::syslogit->($user, "info", $nlogmsg);
1961
            $postreply .= "Status=OK waking $targetnode->{'name'}\n";
1962
        }
1963
        $targetname = $targetnode->{'name'};
1964
        $targetmac = $targetnode->{'mac'};
1965
        $targetip = $targetnode->{'ip'};
1966
        $targetip = $targetnode->{'ip'};
1967
        my $porttaken = 1;
1968
        while ($porttaken) {
1969
            $porttaken = 0;
1970
            $port = $targetnode->{'vms'} + (($hypervisor eq "vbox")?3389:5900);
1971
            $port += int(rand(200));
1972
            my @regkeys = (tied %register)->select_where("port = '$port' AND macip = '$targetip'");
1973
            foreach my $k (@regkeys) {
1974
                $r = $register{$k};
1975
                if ($r->{'port'} eq $port && $r->{'macip'} eq $targetip) {
1976
                    $porttaken = 1;
1977
                }
1978
            }
1979
        }
1980
        $targetnode->{'vms'}++;
1981
        $targetnode->{'vmvcpus'} += $vcpu;
1982
        $register{$uuid}->{'port'} = $port;
1983
#        $register{$uuid}->{'mac'} = $targetmac;
1984
#        $register{$uuid}->{'macname'} = $targetname;
1985
#        $register{$uuid}->{'macip'} = $targetip;
1986
        $register{$uuid}->{'display'} = (($hypervisor eq "vbox")?'rdp':'vnc');
1987
    } else {
1988
        my $macstatus;
1989
        $macstatus = $nodereg{$dmac}->{status} if ($nodereg{$dmac});
1990
        $main::syslogit->($user, "info", "Could not find target for $uuid, $dmac, $imageonnode, $mem, $vcpu, $image, $image2,$image3,$image4, $hypervisor, $smac, dmac-status: $macstatus") if ($uuid);
1991
    }
1992
    return ($targetmac, $targetname, $targetip, $port, \%avhash);
1993
}
1994

    
1995
sub destroyUserServers {
1996
    my $username = shift;
1997
    my $wait = shift; # Should we wait for servers do die
1998
    my $duuid = shift;
1999
    return unless ($username && ($isadmin || $user eq $username));
2000
    my @updateList;
2001

    
2002
    my @regkeys = (tied %register)->select_where("user = '$username'");
2003
    foreach my $uuid (@regkeys) {
2004
        if ($register{$uuid}->{'user'} eq $username
2005
            && $register{$uuid}->{'status'} ne 'shutoff'
2006
            && (!$duuid || $duuid eq $uuid)
2007
        ) {
2008
            $postreply .= "Destroying $username server $register{$uuid}->{'name'}, $uuid\n";
2009
            Destroy($uuid);
2010
            push (@updateList,{ tab=>'servers',
2011
                                user=>$user,
2012
                                uuid=>$duuid,
2013
                                status=>'destroying'});
2014
        }
2015
    }
2016
    $main::updateUI->(@updateList) if (@updateList);
2017
    if ($wait) {
2018
        my @regkeys = (tied %register)->select_where("user = '$username'");
2019
        my $activeservers = 1;
2020
        my $i = 0;
2021
        while ($activeservers && $i<30) {
2022
            $activeservers = 0;
2023
            foreach my $k (@regkeys) {
2024
                my $valref = $register{$k};
2025
                if ($username eq $valref->{'user'}
2026
                    && ($valref->{'status'} ne 'shutoff'
2027
                    && $valref->{'status'} ne 'inactive')
2028
                    && (!$duuid || $duuid eq $valref->{'uuid'})
2029
                ) {
2030
                    $activeservers = $valref->{'uuid'};
2031
                }
2032
            }
2033
            $i++;
2034
            if ($activeservers) {
2035
                my $res .= "Status=OK Waiting $i for server $register{$activeservers}->{'name'}, $register{$activeservers}->{'status'} to die...\n";
2036
            #    print $res if ($console);
2037
                $postreply .= $res;
2038
                sleep 2;
2039
            }
2040
        }
2041
        $postreply .= "Status=OK Servers halted for $username\n" unless ($activeservers);
2042
    }
2043
    return $postreply;
2044
}
2045

    
2046
sub removeUserServers {
2047
    my $username = shift;
2048
    my $uuid = shift;
2049
    my $destroy = shift; # Should running servers be destroyed before removing
2050
    return unless (($isadmin || $user eq $username) && !$isreadonly);
2051
    $user = $username;
2052
    my @regkeys = (tied %register)->select_where("user = '$username'");
2053
    foreach my $ruuid (@regkeys) {
2054
        next if ($uuid && $ruuid ne $uuid);
2055
        if ($destroy && $register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} ne 'shutoff' && $register{$ruuid}->{'status'} ne 'inactive')) {
2056
            destroyUserServers($username, 1, $ruuid);
2057
        }
2058

    
2059
        if ($register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} eq 'shutoff' || $register{$ruuid}->{'status'} eq 'inactive')) {
2060
            $postreply .= "Removing $username server $register{$ruuid}->{'name'}, $ruuid" . ($console?'':'<br>') . "\n";
2061
            Remove($ruuid);
2062
        }
2063
    }
2064
}
2065

    
2066
sub Remove {
2067
    my ($uuid, $action) = @_;
2068
    if ($help) {
2069
        return <<END
2070
DELETE:uuid:
2071
Removes a server. Server must be shutoff. Does not remove associated images or networks.
2072
END
2073
    }
2074
    my $reguser = $register{$uuid}->{'user'};
2075
    my $dbstatus = $register{$uuid}->{'status'};
2076
    my $image = $register{$uuid}->{'image'};
2077
    my $image2 = $register{$uuid}->{'image2'};
2078
    my $image3 = $register{$uuid}->{'image3'};
2079
    my $image4 = $register{$uuid}->{'image4'};
2080
    my $name = $register{$uuid}->{'name'};
2081
    $image2 = '' if ($image2 eq '--');
2082
    $image3 = '' if ($image3 eq '--');
2083
    $image4 = '' if ($image4 eq '--');
2084

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

    
2089
        # Delete software packages and monitors from register
2090
        $postmsg .= deletePackages($uuid);
2091
        my $sname = $register{$uuid}->{'name'};
2092
        utf8::decode($sname);
2093
        $postmsg .= deleteMonitors($uuid)?" deleted monitors for $sname ":'';
2094

    
2095
        delete $register{$uuid};
2096
        delete $xmlreg{$uuid};
2097

    
2098
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2099
        $imagereg{$image}->{'status'} = "unused" if ($imagereg{$image});
2100
        $imagereg{$image2}->{'status'} = "unused" if ($image2 && $imagereg{$image2});
2101
        $imagereg{$image3}->{'status'} = "unused" if ($image3 && $imagereg{$image3});
2102
        $imagereg{$image4}->{'status'} = "unused" if ($image4 && $imagereg{$image4});
2103
        untie %imagereg;
2104

    
2105
        # Delete metrics
2106
        my $metricsdir = "/var/lib/graphite/whisper/domains/$uuid";
2107
        `rm -r $metricsdir` if (-e $metricsdir);
2108
        my $rrdfile = "/var/cache/rrdtool/".$uuid."_highres.rrd";
2109
        `rm $rrdfile` if (-e $rrdfile);
2110

    
2111
        $main::syslogit->($user, "info", "Deleted domain $uuid from db");
2112
        utf8::decode($name);
2113
        $postmsg .= " deleted server $name";
2114
        $postreply = "[]";
2115
        sleep 1;
2116
    } else {
2117
        $postreply .= "Status=ERROR Cannot delete a $dbstatus server\n";
2118
    }
2119
    return $postreply;
2120
}
2121

    
2122
# Delete all monitors belonging to a server
2123
sub deleteMonitors {
2124
    my ($serveruuid) = @_;
2125
    my $match;
2126
    if ($serveruuid) {
2127
        if ($register{$serveruuid}->{'user'} eq $user || $isadmin) {
2128
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf");
2129
            # undef $/; # This makes <> read in the entire file in one go
2130
            my $uuidmatch;
2131
            while (<>) {
2132
                if (/^watch (\S+)/) {
2133
                    if ($1 eq $serveruuid) {$uuidmatch = $serveruuid}
2134
                    else {$uuidmatch = ''};
2135
                };
2136
                if ($uuidmatch) {
2137
                    $match = 1;
2138
                } else {
2139
                    #chomp;
2140
                    print unless (/^hostgroup $serveruuid/);
2141
                }
2142
                close ARGV if eof;
2143
            }
2144
            #$/ = "\n";
2145
        }
2146
        unlink glob "/var/log/stabile/*:$serveruuid:*";
2147
    }
2148
    `/usr/bin/moncmd reset keepstate` if ($match);
2149
    return $match;
2150
}
2151

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

    
2156
    my @domains;
2157
    if ($issystem) {
2158
        foreach my $valref (values %register) {
2159
            if (($valref->{'system'} eq $uuid || $uuid eq '*')
2160
                    && ($valref->{'user'} eq $user || $fulllist)) {
2161
                push(@domains, $valref->{'uuid'});
2162
            }
2163
        }
2164
    } else { # Allow if domain no longer exists or belongs to user
2165
        push(@domains, $uuid) if (!$register{$uuid} || $register{$uuid}->{'user'} eq $user || $fulllist);
2166
    }
2167

    
2168
    foreach my $domuuid (@domains) {
2169
        foreach my $packref (values %packreg) {
2170
            my $id = $packref->{'id'};
2171
            if (substr($id, 0,36) eq $domuuid || ($uuid eq '*' && $packref->{'user'} eq $user)) {
2172
                delete $packreg{$id};
2173
            }
2174
        }
2175
    }
2176
    tied(%packreg)->commit;# if (%packreg);
2177
    if ($issystem) {
2178
        my $sname = $register{$uuid}->{'name'};
2179
        utf8::decode($sname);
2180
        return "Status=OK Cleared packages for $sname\n";
2181
    } elsif ($register{$uuid}) {
2182
        my $sname = $register{$uuid}->{'name'};
2183
        utf8::decode($sname);
2184
        return "Status=OK Cleared packages for $sname\n";
2185
    } else {
2186
        return "Status=OK Cleared packages. System not registered\n";
2187
    }
2188
}
2189

    
2190
sub Save {
2191
    my ($uuid, $action, $obj) = @_;
2192
    if ($help) {
2193
        return <<END
2194
POST:uuid, name, user, system, autostart, locktonode, mac, memory, vcpu, boot, loader, diskbus, nicmodel1, vgpu, cdrom, image, image2, image3, image4, networkuuid2, networkuuid3, networkuuid1, nicmac1, nicmac2, nicmac3:
2195
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.
2196
Depending on your privileges not all changes are permitted. If you save without specifying a uuid, a new server is created.
2197
If you pass [user] parameter it is assumed you want to move server to this user's account.
2198
Supported parameters:
2199

    
2200
uuid: UUID
2201
name: string
2202
user: string
2203
system: UUID of stack this server belongs to
2204
autostart: true|false
2205
locktonode: true|false
2206
mac: MAC address of target node
2207

    
2208
memory: int bytes
2209
vcpu: int
2210
boot: hd|cdrom|network
2211
loader: bios|uefi
2212
diskbus: virtio|ide|scsi
2213
nicmodel1: virtio|rtl8139|ne2k_pci|e1000|i82551|i82557b|i82559er|pcnet
2214
vgpu: int
2215

    
2216
cdrom: string path
2217
image: string path
2218
image2: string path
2219
image3: string path
2220
image4: string path
2221

    
2222
networkuuid1: UUID of network connection
2223
networkuuid2: UUID of network connection
2224
networkuuid3: UUID of network connection
2225

    
2226
END
2227
    }
2228

    
2229
# notes, opemail, opfullname, opphone, email, fullname, phone, services, recovery, alertemail
2230
# notes: string
2231
# opemail: string
2232
# opfullname: string
2233
# opphone: string
2234
# email: string
2235
# fullname: string
2236
# phone: string
2237
# services: string
2238
# recovery: string
2239
# alertemail: string
2240

    
2241
    my $system = $obj->{system};
2242
    my $newsystem = $obj->{newsystem};
2243
    my $buildsystem = $obj->{buildsystem};
2244
    my $nicmac1 = $obj->{nicmac1};
2245
    $console = $console || $obj->{console};
2246

    
2247
    $postmsg = '' if ($buildsystem);
2248
    if (!$uuid && $nicmac1) {
2249
        $uuid = nicmac1ToUuid($nicmac1); # If no uuid try to locate based on mac
2250
    }
2251
    if (!$uuid && $uripath =~ /servers(\.cgi)?\/(.+)/) { # Try to parse uuid out of URI
2252
        my $huuid = $2;
2253
        if ($ug->to_string($ug->from_string($huuid)) eq $huuid) { # Check for valid uuid
2254
            $uuid = $huuid;
2255
        }
2256
    }
2257
    my $regserv = $register{$uuid};
2258
    my $status = $regserv->{'status'} || 'new';
2259
    if ((!$uuid) && $status eq 'new') {
2260
        my $ug = new Data::UUID;
2261
        $uuid = $ug->create_str();
2262
    };
2263
    unless ($uuid && length $uuid == 36){
2264
        $postmsg = "Status=Error No valid uuid ($uuid), $obj->{image}";
2265
        return $postmsg;
2266
    }
2267
    $nicmac1 = $nicmac1 || $regserv->{'nicmac1'};
2268
    my $name = $obj->{name} || $regserv->{'name'};
2269
    my $memory = $obj->{memory} || $regserv->{'memory'};
2270
    my $vcpu = $obj->{vcpu} || $regserv->{'vcpu'};
2271
    my $image = $obj->{image} || $regserv->{'image'};
2272
    my $imagename = $obj->{imagename} || $regserv->{'imagename'};
2273
    my $image2 = $obj->{image2} || $regserv->{'image2'};
2274
    my $image2name = $obj->{image2name} || $regserv->{'image2name'};
2275
    my $image3 = $obj->{image3} || $regserv->{'image3'};
2276
    my $image3name = $obj->{image3name} || $regserv->{'image3name'};
2277
    my $image4 = $obj->{image4} || $regserv->{'image4'};
2278
    my $image4name = $obj->{image4name} || $regserv->{'image4name'};
2279
    my $diskbus = $obj->{diskbus} || $regserv->{'diskbus'};
2280
    my $cdrom = $obj->{cdrom} || $regserv->{'cdrom'};
2281
    my $boot = $obj->{boot} || $regserv->{'boot'};
2282
    my $loader = $obj->{loader} || $regserv->{'loader'};
2283
    my $networkuuid1 = ($obj->{networkuuid1} || $obj->{networkuuid1} eq '0')?$obj->{networkuuid1}:$regserv->{'networkuuid1'};
2284
    my $networkid1 = $obj->{networkid1} || $regserv->{'networkid1'};
2285
    my $networkname1 = $obj->{networkname1} || $regserv->{'networkname1'};
2286
    my $nicmodel1 = $obj->{nicmodel1} || $regserv->{'nicmodel1'};
2287
    my $networkuuid2 = ($obj->{networkuuid2} || $obj->{networkuuid2} eq '0')?$obj->{networkuuid2}:$regserv->{'networkuuid2'};
2288
    my $networkid2 = $obj->{networkid2} || $regserv->{'networkid2'};
2289
    my $networkname2 = $obj->{networkname2} || $regserv->{'networkname2'};
2290
    my $nicmac2 = $obj->{nicmac2} || $regserv->{'nicmac2'};
2291
    my $networkuuid3 = ($obj->{networkuuid3} || $obj->{networkuuid3} eq '0')?$obj->{networkuuid3}:$regserv->{'networkuuid3'};
2292
    my $networkid3 = $obj->{networkid3} || $regserv->{'networkid3'};
2293
    my $networkname3 = $obj->{networkname3} || $regserv->{'networkname3'};
2294
    my $nicmac3 = $obj->{nicmac3} || $regserv->{'nicmac3'};
2295
    my $notes = $obj->{notes} || $regserv->{'notes'};
2296
    my $autostart = $obj->{autostart} || $regserv->{'autostart'};
2297
    my $locktonode = $obj->{locktonode} || $regserv->{'locktonode'};
2298
    my $mac = $obj->{mac} || $regserv->{'mac'};
2299
    my $created = $regserv->{'created'} || time;
2300
    # Sanity checks
2301
    my $tenderpaths = $Stabile::config->get('STORAGE_POOLS_LOCAL_PATHS') || "/mnt/stabile/images";
2302
    my @tenderpathslist = split(/,\s*/, $tenderpaths);
2303

    
2304
    $networkid1 = $networkreg{$networkuuid1}->{'id'};
2305
    my $networktype1 = $networkreg{$networkuuid1}->{'type'};
2306
    my $networktype2;
2307
    if (!$nicmac1 || $nicmac1 eq "--") {$nicmac1 = randomMac();}
2308
    if ($networkuuid2 && $networkuuid2 ne "--") {
2309
        $networkid2 = $networkreg{$networkuuid2}->{'id'};
2310
        $nicmac2 = randomMac() if (!$nicmac2 || $nicmac2 eq "--");
2311
        $networktype2 = $networkreg{$networkuuid2}->{'type'};
2312
    }
2313
    if ($networkuuid3 && $networkuuid3 ne "--") {
2314
        $networkid3 = $networkreg{$networkuuid3}->{'id'};
2315
        $networkname3 = $networkreg{$networkuuid3}->{'name'};
2316
        $nicmac3 = randomMac() if (!$nicmac3 || $nicmac3 eq "--");
2317
        $networktype3 = $networkreg{$networkuuid3}->{'type'};
2318
    }
2319

    
2320
    my $imgdup;
2321
    my $netdup;
2322
    my $json_text; # returned if all goes well
2323

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

    
2326
    if ($networkid1 > 1 && $networkid2 > 1 && $networktype1 ne 'gateway' && $networktype2 ne 'gateway'
2327
        && $networkuuid1 eq $networkuuid2) {
2328
        $netdup = 1;
2329
    }
2330
    if ($networkid1 > 1 && $networkid3 > 1 && $networktype1 ne 'gateway' && $networktype3 ne 'gateway'
2331
        && $networkuuid1 eq $networkuuid3) {
2332
        $netdup = 11;
2333
    }
2334
    if ($image eq $image2
2335
        || $image eq $image3
2336
        || $image eq $image4
2337
        || $image2 && $image2 ne '--' && $image2 eq $image3
2338
        || $image2 && $image2 ne '--' && $image2 eq $image4
2339
        || $image3 && $image3 ne '--' && $image3 eq $image4
2340
    ) {
2341
        $imgdup = 1;
2342
    } elsif ($image =~ m/\.master\.qcow2/
2343
        || $image2 =~ m/\.master\.qcow2/
2344
        || $image3 =~ m/\.master\.qcow2/
2345
        || $image4 =~ m/\.master\.qcow2/
2346
    ) {
2347
        $imgdup = 2;
2348
    } else {
2349
        # Check if another server is using image
2350
        my @regkeys = (tied %register)->select_where("user = '$user' OR user = 'common'");
2351
        foreach my $k (@regkeys) {
2352
            my $val = $register{$k};
2353
            if ($val->{'uuid'} ne $uuid) {
2354
                if (
2355
                    $image eq $val->{'image'} || $image eq $val->{'image2'}|| $image eq $val->{'image3'}|| $image eq $val->{'image4'}
2356
                ) {
2357
                    $imgdup = 51;
2358
                } elsif ($image2 && $image2 ne "--" &&
2359
                    ($image2 eq $val->{'image'} || $image2 eq $val->{'image2'} || $image2 eq $val->{'image3'} || $image2 eq $val->{'image4'})
2360
                ) {
2361
                    $imgdup = 52;
2362
                } elsif ($image3 && $image3 ne "--" &&
2363
                    ($image3 eq $val->{'image'} || $image3 eq $val->{'image2'} || $image3 eq $val->{'image3'} || $image3 eq $val->{'image4'})
2364
                ) {
2365
                    $imgdup = 53;
2366
                } elsif ($image4 && $image4 ne "--" &&
2367
                    ($image4 eq $val->{'image'} || $image4 eq $val->{'image2'} || $image4 eq $val->{'image3'} || $image4 eq $val->{'image4'})
2368
                ) {
2369
                    $imgdup = 54;
2370
                }
2371

    
2372
                if ($networkid1>1) {
2373
                    if ($networktype1 ne 'gateway' &&
2374
                        ($networkuuid1 eq $val->{'networkuuid1'} || $networkuuid1 eq $val->{'networkuuid2'})
2375
                    ) {
2376
                        $netdup = 51;
2377
                    }
2378
                }
2379
                if ($networkid2>1) {
2380
                    if ($networktype2 ne 'gateway' && $networkuuid2 && $networkuuid2 ne "--" &&
2381
                        ($networkuuid2 eq $val->{'networkuuid1'} || $networkuuid2 eq $val->{'networkuuid2'})
2382
                    ) {
2383
                        $netdup = 52;
2384
                    }
2385
                }
2386
            }
2387
        }
2388
        my $legalpath;
2389
        if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2390
            $legalpath = 1;
2391
        } else {
2392
            foreach my $path (@tenderpathslist) {
2393
                if ($image =~ m/$path\/$user/) {
2394
                    $legalpath = 1;
2395
                    last;
2396
                }
2397
            }
2398
        }
2399
        $imgdup = 6 unless $legalpath;
2400
        if ($image2 && $image2 ne "--") { # TODO: We should probably check for conflicting nodes for image3 and image 4 too
2401
            if ($image2 =~ m/\/mnt\/stabile\/node\/$user/) {
2402
                if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2403
                    if ($imagereg{$image}->{'mac'} eq $imagereg{$image2}->{'mac'}) {
2404
                        $legalpath = 1;
2405
                    } else {
2406
                        $legalpath = 0; # Images are on two different nodes
2407
                    }
2408
                } else {
2409
                    $legalpath = 1;
2410
                }
2411
            } else {
2412
                $legalpath = 0;
2413
                foreach my $path (@tenderpathslist) {
2414
                    if ($image2 =~ m/$path\/$user/) {
2415
                        $legalpath = 1;
2416
                        last;
2417
                    }
2418
                }
2419
            }
2420
            $imgdup = 7 unless $legalpath;
2421
        }
2422
    }
2423

    
2424
    if (!$imgdup && !$netdup) {
2425
        if ($status eq "new") {
2426
            $status = "shutoff";
2427
            $name = $name || 'New Server';
2428
            $memory = $memory || 1024;
2429
            $vcpu = $vcpu || 1;
2430
            $imagename = $imagename || '--';
2431
            $image2 = $image2 || '--';
2432
            $image2name = $image2name || '--';
2433
            $image3 = $image3 || '--';
2434
            $image3name = $image3name || '--';
2435
            $image4 = $image4 || '--';
2436
            $image4name = $image4name || '--';
2437
            $diskbus = $diskbus || 'ide';
2438
            $cdrom = $cdrom || '--';
2439
            $boot = $boot || 'hd';
2440
            $loader = $loader || 'bios';
2441
            $networkuuid1 = $networkuuid1 || 1;
2442
            $networkid1 = $networkid1 || 1;
2443
            $networkname1 = $networkname1 || '--';
2444
            $nicmodel1 = $nicmodel1 || 'rtl8139';
2445
            $nicmac1 = $nicmac1 || randomMac();
2446
            $networkuuid2 = $networkuuid2 || '--';
2447
            $networkid2 = $networkid2 || '--';
2448
            $networkname2 = $networkname2 || '--';
2449
            $nicmac2 = $nicmac2 || randomMac();
2450
            $networkuuid3 = $networkuuid3 || '--';
2451
            $networkid3 = $networkid3 || '--';
2452
            $networkname3 = $networkname3 || '--';
2453
            $nicmac3 = $nicmac3 || randomMac();
2454
            #    $uiuuid = $uuid; # No need to update ui for new server with jsonreststore
2455
            $postmsg .= "OK Created new server: $name";
2456
            $postmsg .= ", uuid: $uuid " if ($console);
2457
        }
2458
        # Update status of images
2459
        my @imgs = ($image, $image2, $image3, $image4);
2460
        my @imgkeys = ('image', 'image2', 'image3', 'image4');
2461
        for (my $i=0; $i<4; $i++) {
2462
            my $img = $imgs[$i];
2463
            my $k = $imgkeys[$i];
2464
            my $regimg = $imagereg{$img};
2465
            # if ($img && $img ne '--' && ($status eq 'new' || $img ne $regserv->{$k})) { # Servers image changed - update image status
2466
            if ($img && $img ne '--') { # Always update image status
2467
                $regimg->{'status'} = 'used' if (
2468
                    $regimg->{'status'} eq 'unused'
2469
                        # Image cannot be active if server is shutoff
2470
                        || ($regimg->{'status'} eq 'active' && $status eq 'shutoff')
2471
                );
2472
                $regimg->{'domains'} = $uuid;
2473
                $regimg->{'domainnames'} = $name;
2474
            }
2475
            # If image has changed, release the old image
2476
            if ($status ne 'new' && $img ne $regserv->{$k} && $imagereg{$regserv->{$k}}) {
2477
                $imagereg{$regserv->{$k}}->{'status'} = 'unused';
2478
                delete $imagereg{$regserv->{$k}}->{'domains'};
2479
                delete $imagereg{$regserv->{$k}}->{'domainnames'};
2480
            }
2481
        }
2482

    
2483
        my $valref = {
2484
            uuid=>$uuid,
2485
            user=>$user,
2486
            name=>$name,
2487
            memory=>$memory,
2488
            vcpu=>$vcpu,
2489
            image=>$image,
2490
            imagename=>$imagename,
2491
            image2=>$image2,
2492
            image2name=>$image2name,
2493
            image3=>$image3,
2494
            image3name=>$image3name,
2495
            image4=>$image4,
2496
            image4name=>$image4name,
2497
            diskbus=>$diskbus,
2498
            cdrom=>$cdrom,
2499
            boot=>$boot,
2500
            loader=>$loader,
2501
            networkuuid1=>$networkuuid1,
2502
            networkid1=>$networkid1,
2503
            networkname1=>$networkname1,
2504
            nicmodel1=>$nicmodel1,
2505
            nicmac1=>$nicmac1,
2506
            networkuuid2=>$networkuuid2,
2507
            networkid2=>$networkid2,
2508
            networkname2=>$networkname2,
2509
            nicmac2=>$nicmac2,
2510
            networkuuid3=>$networkuuid3,
2511
            networkid3=>$networkid3,
2512
            networkname3=>$networkname3,
2513
            nicmac3=>$nicmac3,
2514
            status=>$status,
2515
            notes=>$notes,
2516
            autostart=>$autostart,
2517
            locktonode=>$locktonode,
2518
            action=>"",
2519
            created=>$created
2520
        };
2521
        $valref->{'system'} = $system if ($system);
2522
        if ($mac && $locktonode eq 'true') {
2523
            $valref->{'mac'} = $mac;
2524
            $valref->{'macip'} = $nodereg{$mac}->{'ip'};
2525
            $valref->{'macname'} = $nodereg{$mac}->{'name'};
2526
        }
2527
        if ($newsystem) {
2528
            my $ug = new Data::UUID;
2529
            $sysuuid = $ug->create_str();
2530
            $valref->{'system'} = $sysuuid;
2531
            $postmsg .= "OK sysuuid: $sysuuid " if ($console);
2532
        }
2533

    
2534
        # Remove domain uuid from old networks. Leave gateways alone - they get updated on next listing
2535
        my $oldnetworkuuid1 = $regserv->{'networkuuid1'};
2536
        if ($oldnetworkuuid1 ne $networkuuid1 && $networkreg{$oldnetworkuuid1}) {
2537
            $networkreg{$oldnetworkuuid1}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2538
        }
2539
        $register{$uuid} = validateItem($valref);
2540

    
2541
        if ($networkreg{$networkuuid1}->{'type'} eq 'gateway') {
2542
            # We now remove before adding to support API calls that dont necessarily list afterwards
2543
            $networkreg{$networkuuid1}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2544
            my $domains = $networkreg{$networkuuid1}->{'domains'};
2545
            $networkreg{$networkuuid1}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2546

    
2547
            $networkreg{$networkuuid1}->{'domainnames'} =~ s/($name)(,?)( ?)//;
2548
            my $domainnames = $networkreg{$networkuuid1}->{'domainnames'};
2549
            $networkreg{$networkuuid1}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2550
        } else {
2551
            $networkreg{$networkuuid1}->{'domains'}  = $uuid;
2552
            $networkreg{$networkuuid1}->{'domainnames'}  = $name;
2553
        }
2554

    
2555
        if ($networkuuid2 && $networkuuid2 ne '--') {
2556
            if ($networkreg{$networkuuid2}->{'type'} eq 'gateway') {
2557
                $networkreg{$networkuuid2}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2558
                my $domains = $networkreg{$networkuuid2}->{'domains'};
2559
                $networkreg{$networkuuid2}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2560

    
2561
                $networkreg{$networkuuid2}->{'domainnames'} =~ s/($name)(,?)( ?)//;
2562
                my $domainnames = $networkreg{$networkuuid2}->{'domainnames'};
2563
                $networkreg{$networkuuid2}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2564
            } else {
2565
                $networkreg{$networkuuid2}->{'domains'}  = $uuid;
2566
                $networkreg{$networkuuid2}->{'domainnames'}  = $name;
2567
            }
2568
        }
2569

    
2570
        if ($networkuuid3 && $networkuuid3 ne '--') {
2571
            if ($networkreg{$networkuuid3}->{'type'} eq 'gateway') {
2572
                my $domains = $networkreg{$networkuuid3}->{'domains'};
2573
                $networkreg{$networkuuid3}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2574
                my $domainnames = $networkreg{$networkuuid3}->{'domainnames'};
2575
                $networkreg{$networkuuid3}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2576
            } else {
2577
                $networkreg{$networkuuid3}->{'domains'}  = $uuid;
2578
                $networkreg{$networkuuid3}->{'domainnames'}  = $name;
2579
            }
2580
        }
2581
        my %jitem = %{$register{$uuid}};
2582
        $json_text = to_json(\%jitem, {pretty=>1});
2583
        $json_text =~ s/null/"--"/g;
2584
        $uiuuid = $uuid;
2585
        $uiname = $name;
2586

    
2587
        tied(%register)->commit;
2588
        tied(%networkreg)->commit;
2589
        tied(%imagereg)->commit;
2590

    
2591
    } else {
2592
        $postmsg .= "ERROR This image ($image) cannot be used ($imgdup) " if ($imgdup);
2593
        $postmsg .= "ERROR This network ($networkname1) cannot be used ($netdup)" if ($netdup);
2594
    }
2595

    
2596
    my $domuser = $obj->{'user'};
2597
    # We were asked to move server to another account
2598
    if ($domuser && $domuser ne '--' && $domuser ne $user) {
2599
        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")};
2600
        if ($status eq 'shutoff' || $status eq 'inactive') {
2601
            unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {$posterror =  "Unable to access user register"; return 0;};
2602
            my @accounts = split(/,\s*/, $userreg{$tktuser}->{'accounts'});
2603
            my @accountsprivs = split(/,\s*/, $userreg{$tktuser}->{'accountsprivileges'});
2604
            %ahash = ($tktuser, $userreg{$tktuser}->{'privileges'}); # Include tktuser in accounts hash
2605
            for my $i (0 .. scalar @accounts)
2606
            {
2607
                next unless $accounts[$i];
2608
                $ahash{$accounts[$i]} = $accountsprivs[$i] || 'r';
2609
            }
2610
            untie %userreg;
2611

    
2612
            if (!$isreadonly && $ahash{$domuser} && !($ahash{$domuser} =~ /r/)) { # Check if user is allow to access account
2613
                my $imgdone;
2614
                my $netdone;
2615
                # First move main image
2616
                $Stabile::Images::user = $user;
2617
                require "$Stabile::basedir/cgi/images.cgi";
2618
                $Stabile::Images::console = 1;
2619
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving image $imagename to account: $domuser"});
2620
                my $nimage = Stabile::Images::Move($image, $domuser);
2621
                chomp $nimage;
2622
                if ($nimage) {
2623
                    $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2624
                    $register{$uuid}->{'image'} = $nimage;
2625
                    $imgdone = 1;
2626
                } else {
2627
                    $main::syslogit->($user, "info", "Unable to move image $imagename to account: $domuser");
2628
                }
2629
                # Move other attached images
2630
                my @images = ($image2, $image3, $image4);
2631
                my @imagenames = ($image2name, $image3name, $image4name);
2632
                my @imagekeys = ('image2', 'image3', 'image4');
2633
                for (my $i=0; $i<3; $i++) {
2634
                    my $img = $images[$i];
2635
                    my $imgname = $imagenames[$i];
2636
                    my $imgkey = $imagekeys[$i];
2637
                    if ($img && $img ne '--') {
2638
                        $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving $imgkey $imgname to account: $domuser"});
2639
                        $nimage = Stabile::Images::Move($img, $domuser);
2640
                        chomp $nimage;
2641
                        if ($nimage) {
2642
                            $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2643
                            $register{$uuid}->{$imgkey} = $nimage;
2644
                        } else {
2645
                            $main::syslogit->($user, "info", "Unable to move $imagekeys[$i] $img to account: $domuser");
2646
                        }
2647
                    }
2648
                }
2649
                # Then move network(s)
2650
                if ($imgdone) {
2651
                    $Stabile::Networks::user = $user;
2652
                    require "$Stabile::basedir/cgi/networks.cgi";
2653
                    $Stabile::Networks::console = 1;
2654
                    my @networks = ($networkuuid1, $networkuuid2, $networkuuid3);
2655
                    my @netkeys = ('networkuuid1', 'networkuuid2', 'networkuuid3');
2656
                    my @netnamekeys = ('networkname1', 'networkname2', 'networkname3');
2657
                    for (my $i=0; $i<scalar @networks; $i++) {
2658
                        my $net = $networks[$i];
2659
                        my $netkey = $netkeys[$i];
2660
                        my $netnamekey = $netnamekeys[$i];
2661
                        my $regnet = $networkreg{$net};
2662
                        my $oldid = $regnet->{'id'};
2663
                        next if ($net eq '' || $net eq '--');
2664
                        if ($regnet->{'type'} eq 'gateway') {
2665
                            if ($oldid > 1) { # Private gateway
2666
                                foreach my $networkvalref (values %networkreg) { # use gateway with same id if it exists
2667
                                    if ($networkvalref->{'user'} eq $domuser
2668
                                        && $networkvalref->{'type'} eq 'gateway'
2669
                                        && $networkvalref->{'id'} == $oldid) {
2670
                                        # We found an existing gateway with same id - use it
2671
                                        $register{$uuid}->{$netkey} = $networkvalref->{'uuid'};
2672
                                        $register{$uuid}->{$netnamekey} = $networkvalref->{'name'};
2673
                                        $netdone = 1;
2674
                                        $main::updateUI->({tab=>"networks", user=>$user, message=>"Using network $networkvalref->{'name'} from account: $domuser"});
2675
                                        last;
2676
                                    }
2677
                                }
2678
                                if (!($netdone)) {
2679
                                    # Make a new gateway
2680
                                    my $ug = new Data::UUID;
2681
                                    my $newuuid = $ug->create_str();
2682
                                    Stabile::Networks::save($oldid, $newuuid, $regnet->{'name'}, 'new', 'gateway', '', '', $regnet->{'ports'}, 0, $domuser);
2683
                                    $register{$uuid}->{$netkey} = $newuuid;
2684
                                    $register{$uuid}->{$netnamekey} = $regnet->{'name'};
2685
                                    $netdone = 1;
2686
                                    $main::updateUI->({tab=>"networks", user=>$user, message=>"Created gateway $regnet->{'name'} for account: $domuser"});
2687
                                    $main::syslogit->($user, "info", "Created gateway $regnet->{'name'} for account: $domuser");
2688
                                }
2689
                            } elsif ($oldid==0 || $oldid==1) {
2690
                                $netdone = 1; # Use common gateway
2691
                                $main::updateUI->({tab=>"networks", user=>$user, message=>"Reused network $regnet->{'name'} for account: $domuser"});
2692
                            }
2693
                        } else {
2694
                            my $newid = Stabile::Networks::getNextId('', $domuser);
2695
                            $networkreg{$net}->{'id'} = $newid;
2696
                            $networkreg{$net}->{'user'} = $domuser;
2697
                        #    if ($regnet->{'type'} eq 'internalip' || $regnet->{'type'} eq 'ipmapping') {
2698
                                # Deactivate network and assign new internal ip
2699
                                Stabile::Networks::Deactivate($regnet->{'uuid'});
2700
                                $networkreg{$net}->{'internalip'} =
2701
                                    Stabile::Networks::getNextInternalIP('',$regnet->{'uuid'}, $newid, $domuser);
2702
                        #    }
2703
                            $netdone = 1;
2704
                            $main::updateUI->({tab=>"networks", user=>$user, message=>"Moved network $regnet->{'name'} to account: $domuser"});
2705
                            $main::syslogit->($user, "info", "Moved network $regnet->{'name'} to account: $domuser");
2706
                        }
2707
                    }
2708
                    if ($netdone) {
2709
                        # Finally move the server
2710
                        $register{$uuid}->{'user'} = $domuser;
2711
                        $postmsg .= "OK Moved server $name to account: $domuser";
2712
                        $main::syslogit->($user, "info", "Moved server $name ($uuid) to account: $domuser");
2713
                        $main::updateUI->({tab=>"servers", user=>$user, type=>"update"});
2714
                    } else {
2715
                        $postmsg .= "ERROR Unable to move network to account: $domuser";
2716
                        $main::updateUI->({tab=>"image", user=>$user, message=>"Unable to move network to account: $domuser"});
2717
                    }
2718
                } else {
2719
                    $main::updateUI->({tab=>"image", user=>$user, message=>"Could not move image to account: $domuser"});
2720
                }
2721
            } else {
2722
                $postmsg .= "ERROR No access to move server";
2723
            }
2724
        } else {
2725
            $postmsg .= "Error Unable to move $status server";
2726
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Please shut down before moving server"});
2727
        }
2728
        untie %userreg;
2729
    }
2730

    
2731
    if ($console) {
2732
        $postreply = $postmsg;
2733
    } else {
2734
        $postreply = $json_text || $postmsg;
2735
    }
2736
    return $postreply;
2737
    untie %imagereg;
2738
}
2739

    
2740

    
2741
sub Shutdown {
2742
    my ($uuid, $action, $obj) = @_;
2743
    if ($help) {
2744
        return <<END
2745
GET:uuid:
2746
Marks a server for shutdown, i.e. send and ACPI shutdown event to the server. If OS supports ACPI, it begins a shutdown.
2747
END
2748
    }
2749
    $uistatus = "shuttingdown";
2750
    my $dbstatus = $obj->{status};
2751
    my $mac = $obj->{mac};
2752
    my $macname = $obj->{macname};
2753
    my $name = $obj->{name};
2754
    if ($dbstatus eq 'running') {
2755
        my $tasks;
2756
        $tasks = $nodereg{$mac}->{'tasks'} if ($nodereg{$mac});
2757
        $nodereg{$mac}->{'tasks'} = $tasks . "SHUTDOWN $uuid $user\n";
2758
        tied(%nodereg)->commit;
2759
        $register{$uuid}->{'status'} = $uistatus;
2760
        $register{$uuid}->{'statustime'} = $current_time;
2761
        $uiuuid = $uuid;
2762
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2763
        $postreply .= "Status=$uistatus OK $uistatus $name\n";
2764
    } else {
2765
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2766
        $postreply .= "Status=ERROR problem $uistatus $name...\n";
2767
    }
2768
    return $postreply;
2769
}
2770

    
2771
sub Suspend {
2772
    my ($uuid, $action, $obj) = @_;
2773
    if ($help) {
2774
        return <<END
2775
GET:uuid:
2776
Marks a server for suspend, i.e. pauses the server. Server must be running
2777
END
2778
    }
2779
    $uistatus = "suspending";
2780
    my $dbstatus = $obj->{status};
2781
    my $mac = $obj->{mac};
2782
    my $macname = $obj->{macname};
2783
    my $name = $obj->{name};
2784
    my $areply = '';
2785
    if ($dbstatus eq 'running') {
2786
        my $tasks = $nodereg{$mac}->{'tasks'};
2787
        $nodereg{$mac}->{'tasks'} = $tasks . "SUSPEND $uuid $user\n";
2788
        tied(%nodereg)->commit;
2789
        $register{$uuid}->{'status'} = $uistatus;
2790
        $register{$uuid}->{'statustime'} = $current_time;
2791
        $uiuuid = $uuid;
2792
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2793
        $areply .= "Status=$uistatus OK $uistatus $name.\n";
2794
    } else {
2795
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2796
        $areply .= "Status=ERROR problem $uistatus $name.\n";
2797
    }
2798
    return $areply;
2799
}
2800

    
2801
sub Resume {
2802
    my ($uuid, $action, $obj) = @_;
2803
    if ($help) {
2804
        return <<END
2805
GET:uuid:
2806
Marks a server for resume running. Server must be paused.
2807
END
2808
    }
2809
    my $dbstatus = $obj->{status};
2810
    my $mac = $obj->{mac};
2811
    my $macname = $obj->{macname};
2812
    my $name = $obj->{name};
2813
    my $image = $obj->{image};
2814
    my $image2 = $obj->{image2};
2815
    my $image3 = $obj->{image3};
2816
    my $image4 = $obj->{image4};
2817
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$posterror = "Unable to access image register"; return;};
2818
    if ($imagereg{$image}->{'status'} ne "paused"
2819
        || ($image2 && $image2 ne '--' && $imagereg{$image}->{'status'} ne "paused")
2820
        || ($image3 && $image3 ne '--' && $imagereg{$image3}->{'status'} ne "paused")
2821
        || ($image4 && $image4 ne '--' && $imagereg{$image4}->{'status'} ne "paused")
2822
    ) {
2823
        $postreply .= "Status=ERROR Image $uuid busy ($imagereg{$image}->{'status'}), please wait 30 sec.\n";
2824
        untie %imagereg;
2825
        return $postreply   ;
2826
    } else {
2827
        untie %imagereg;
2828
    }
2829
    $uistatus = "resuming";
2830
    if ($dbstatus eq 'paused') {
2831
        my $tasks = $nodereg{$mac}->{'tasks'};
2832
        $nodereg{$mac}->{'tasks'} = $tasks . "RESUME $uuid $user\n";
2833
        tied(%nodereg)->commit;
2834
        $register{$uuid}->{'status'} = $uistatus;
2835
        $register{$uuid}->{'statustime'} = $current_time;
2836
        $uiuuid = $uuid;
2837
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2838
        $postreply .= "Status=$uistatus OK $uistatus ". $register{$uuid}->{'name'} . "\n";
2839
    } else {
2840
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2841
        $postreply .= "Status=ERROR problem $uistatus ". $register{$uuid}->{'name'} . "\n";
2842
    }
2843
    return $postreply;
2844
}
2845

    
2846
sub Abort {
2847
    my ($uuid, $action, $obj) = @_;
2848
    if ($help) {
2849
        return <<END
2850
GET:uuid,mac:
2851
Aborts an ongoing server move between nodes initiated with move or stormove.
2852
END
2853
    }
2854
    my $dbstatus = $obj->{status};
2855
    my $dmac = $obj->{mac};
2856
    my $name = $obj->{name};
2857
    if ($isadmin || $register{$uuid}->{user} eq $user) {
2858
        my $tasks = $nodereg{$dmac}->{'tasks'};
2859
        $tasks .= "ABORT $uuid $user\n";
2860
        $nodereg{$dmac}->{'tasks'} = $tasks;
2861
        tied(%nodereg)->commit;
2862
        $postreply = "Status=aborting Aborting move of server $name ($dbstatus) on node $dmac\n";
2863
    } else {
2864
        $postreply = "Status=OK Insufficient privileges\n";
2865
    }
2866
}
2867

    
2868
sub Move {
2869
    my ($uuid, $action, $obj) = @_;
2870
    if ($help) {
2871
        return <<END
2872
GET:uuid,mac:
2873
Moves a server to a different node (Qemu live migration). Server must be running. When called as stormove, non-shared disks are migrated. This may of course take a lot of time, dependeing on the size of the backing images involved.
2874
END
2875
    }
2876
    my $dbstatus = $obj->{status};
2877
    my $dmac = $obj->{mac};
2878
    my $name = $obj->{name};
2879
    my $mem = $obj->{memory};
2880
    my $vcpu = $obj->{vcpu};
2881
    my $image = $obj->{image};
2882
    my $image2 = $obj->{image2};
2883
    my $image3 = $obj->{image3};
2884
    my $image4 = $obj->{image4};
2885

    
2886
    $uistatus = "moving";
2887
    if ($dbstatus eq 'running' && $isadmin) {
2888
        my $hypervisor = getHypervisor($image);
2889
        my $mac = $register{$uuid}->{'mac'};
2890
        $dmac = "" if ($dmac eq "--");
2891
        $mac = "" if ($mac eq "--");
2892

    
2893
        if (( $image =~ /\/mnt\/stabile\/node\//
2894
            || $image2 =~ /\/mnt\/stabile\/node\//
2895
            || $image3 =~ /\/mnt\/stabile\/node\//
2896
            || $image4 =~ /\/mnt\/stabile\/node\// ) && $action ne 'stormove'
2897
        ) {
2898
            $postreply = qq|{"error": 1, "message": "Servers with local storage must be moved with stormove"}|;
2899
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Servers with local storage must be moved with stormove"});
2900
        } else {
2901
            my ($targetmac, $targetname, $targetip, $port) =
2902
                locateTargetNode($uuid, $dmac, $mem, $vcpu, $image, $image2, $image3, $image4, $hypervisor, $mac, 1);
2903
            if ($targetmac) {
2904
                my $tasks = $nodereg{$targetmac}->{'tasks'};
2905
                if ($action eq 'stormove') {
2906
                    $tasks = $tasks . "RECEIVESTOR $uuid $user\n";
2907
                } else {
2908
                    $tasks = $tasks . "RECEIVE $uuid $user\n";
2909
                }
2910
                # Also update allowed port forwards
2911
                $nodereg{$targetmac}->{'tasks'} = $tasks . "PERMITOPEN $user\n";
2912
                $register{$uuid}->{'status'} = "moving";
2913
                $register{$uuid}->{'statustime'} = $current_time;
2914
                $uiuuid = $uuid;
2915
                $uidisplayip = $targetip;
2916
                $uidisplayport = $port;
2917
                $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus to $targetname ($targetmac)");
2918
                $postreply .= "Status=OK $uistatus ". $register{$uuid}->{'name'} . "\n";
2919

    
2920
                # Precreate images on destination node
2921
                if ($action eq 'stormove') {
2922
                    my $preimages = '';
2923
                    $Stabile::Images::user = $user;
2924
                    require "$Stabile::basedir/cgi/images.cgi";
2925
                    $Stabile::Images::console = 1;
2926
                    if ($targetip eq '10.0.0.1') { # Moving from node
2927
                        if ($image =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2928
                            my $res = Stabile::Images::Move($image, $user, '0', '', 0, 1);
2929
                            $preimages .= " $register{$uuid}->{imagename}";
2930
                        }
2931
                        if ($image2 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2932
                            my $res = Stabile::Images::Move($image2, $user, '0', '', 0, 1);
2933
                            $preimages .= " $register{$uuid}->{image2name}";
2934
                        }
2935
                        if ($image3 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2936
                            my $res = Stabile::Images::Move($image3, $user, '0', '', 0, 1);
2937
                            $preimages .= " $register{$uuid}->{image3name}";
2938
                        }
2939
                        if ($image4 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2940
                            my $res = Stabile::Images::Move($image4, $user, '0', '', 0, 1);
2941
                            $preimages .= " $register{$uuid}->{image4name}";
2942
                        }
2943
                    } else { # Moving to node or between nodes - always move primary image, also if on shared storage
2944
                        my $res = Stabile::Images::Move($image, $user, '-1', $targetmac, 0, 1);
2945
                        $preimages .= " $register{$uuid}->{imagename}";
2946
                        if ($image2 && $image2 ne '--') {
2947
                            # We don't migrate data disks away from shared storage
2948
                            unless ($image2 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
2949
                                my $res = Stabile::Images::Move($image2, $user, '-1', $targetmac, 0, 1);
2950
                                $preimages .= " $register{$uuid}->{image2name}";
2951
                            }
2952
                        }
2953
                        if ($image3 && $image3 ne '--') {
2954
                            unless ($image3 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
2955
                                my $res = Stabile::Images::Move($image3, $user, '-1', $targetmac, 0, 1);
2956
                                $preimages .= " $register{$uuid}->{image3name}";
2957
                            }
2958
                        }
2959
                        if ($image4 && $image4 ne '--') {
2960
                            unless ($image4 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
2961
                                my $res = Stabile::Images::Move($image4, $user, '-1', $targetmac, 0, 1);
2962
                                $preimages .= " $register{$uuid}->{image4name}";
2963
                            }
2964
                        }
2965
                    }
2966
                    if ($preimages) {
2967
                        $main::syslogit->($user, "info", "Precreating images $preimages on node $targetmac");
2968
                        $main::updateUI->({tab=>"servers", user=>$user, message=>"Precreating images $preimages on node $targetmac"});
2969
                    }
2970
                }
2971
                if ($params{'PUTDATA'}) {
2972
                    my %jitem = %{$register{$uuid}};
2973
                    my $json_text = to_json(\%jitem);
2974
                    $json_text =~ s/null/"--"/g;
2975
                    $postreply = $json_text;
2976
                }
2977
#                $main::updateUI->({tab=>"servers", user=>$user, status=>'moving', uuid=>$uuid, type=>'update', message=>"Moving $register{$uuid}->{name} to $targetmac"});
2978
            } else {
2979
                $main::syslogit->($user, "info", "Could not find $hypervisor target for $uistatus $uuid ($image)");
2980
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Could not find target for $uistatus $register{$uuid}->{'name'}"});
2981
                $postreply = qq|{"error": 1, "message": "Could not find target for $uistatus $register{$uuid}->{'name'}"}|;
2982
            }
2983
        }
2984
    } else {
2985
        $main::syslogit->($user, "info", "Problem moving a $dbstatus domain: $uuid");
2986
        my $serv = $register{$uuid};
2987
        $postreply .= qq|{"error": 1, "message": "ERROR problem moving $serv->{'name'} ($dbstatus)"}|;
2988
    }
2989
    return $postreply;
2990
}
2991

    
2992
sub Changepassword {
2993
    my ($uuid, $action, $obj) = @_;
2994
    if ($help) {
2995
        return <<END
2996
POST:uuid,username,password:
2997
Attempts to set password for [username] to [password] using guestfish. If no username is specified, user 'stabile' is assumed.
2998
END
2999
    }
3000
    my $img = $register{$uuid}->{'image'};
3001
    my $username = $obj->{'username'} || 'stabile';
3002
    my $password = $obj->{'password'};
3003
    return "Status=Error Please supply a password\n" unless ($password);
3004
    return "Status=Error Please shut down the server before changing password\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
3005
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
3006

    
3007
    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;};
3008
    my $cmd = qq/guestfish --rw -a $img -i command "bash -c 'echo $username:$password | chpasswd'" 2>\&1/;
3009
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
3010
        my $mac = $imagereg{$img}->{'mac'};
3011
        my $macip = $nodereg{$mac}->{'ip'};
3012
        $cmd = "$sshcmd $macip $cmd";
3013
    }
3014
    my $res = `$cmd`;
3015
    $res = $1 if ($res =~ /guestfish: (.*)/);
3016
    chomp $res;
3017
    return "Status=OK Ran chpasswd for user $username in server $register{$uuid}->{'name'}: $res\n";
3018
}
3019

    
3020
sub Sshaccess {
3021
    my ($uuid, $action, $obj) = @_;
3022
    if ($help) {
3023
        return <<END
3024
POST:uuid,address:
3025
Attempts to change the ip addresses you can access the server over SSH (port 22) from, by adding [address] to /etc/hosts.allow.
3026
[address] should either be an IP address or a range in CIDR notation. Please note that no validation of [address] is performed.
3027
END
3028
    }
3029
    my $img = $register{$uuid}->{'image'};
3030
    my $address = $obj->{'address'};
3031
    return "Status=Error Please supply an aaddress\n" unless ($address);
3032
    return "Status=Error Please shut down the server before changing SSH access\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
3033
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
3034

    
3035
    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;};
3036

    
3037
    my $isshcmd = '';
3038
    my $cmd = qq[guestfish --rw -a $img -i command "sed -i -re 's|(sshd: .*)#stabile|\\1 $address #stabile|' /etc/hosts.allow"];
3039
#    my $cmd = qq[guestfish --rw -a $img -i command "bash -c 'echo sshd: $address >> /etc/hosts.allow'"];
3040
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
3041
        my $mac = $imagereg{$img}->{'mac'};
3042
        my $macip = $nodereg{$mac}->{'ip'};
3043
        $isshcmd = "$sshcmd $macip ";
3044
    }
3045
    my $res = `$isshcmd$cmd`;
3046
    chomp $res;
3047
    #$cmd = qq[guestfish --rw -a $img -i command "bash -c 'cat /etc/hosts.allow'"];
3048
    #$res .= `$isshcmd$cmd`;
3049
    #chomp $res;
3050
    return "Status=OK Tried to add sshd: $address to /etc/hosts.allow in server $register{$uuid}->{'name'}\n";
3051
}
3052

    
3053
sub Mountcd {
3054
    my ($uuid, $action, $obj) = @_;
3055
    if ($help) {
3056
        return <<END
3057
GET:uuid,cdrom:
3058
Mounts a cdrom on a server. Server must be running. Mounting the special cdrom named '--' unomunts any currently mounted cdrom.
3059
END
3060
    }
3061
    my $dbstatus = $obj->{status};
3062
    my $mac = $obj->{mac};
3063
    my $cdrom = $obj->{cdrom};
3064
    unless ($cdrom && $dbstatus eq 'running') {
3065
        $main::updateUI->({tab=>"servers", user=>$user, uuid=>$uuid, type=>'update', message=>"Unable to mount cdrom"});
3066
        $postreply = qq|{"Error": 1, "message": "Problem mounting cdrom on $obj->{name}"}|;
3067
        return;
3068
    }
3069
    my $tasks = $nodereg{$mac}->{'tasks'};
3070
    # $user is in the middle here, because $cdrom may contain spaces...
3071
    $nodereg{$mac}->{'tasks'} = $tasks . "MOUNT $uuid $user \"$cdrom\"\n";
3072
    tied(%nodereg)->commit;
3073
    if ($cdrom eq "--") {
3074
        $postreply = qq|{"OK": 1, "message": "OK unmounting cdrom from $obj->{name}"}|;
3075
    } else {
3076
        $postreply = qq|{"OK": 1, "message": "OK mounting cdrom $cdrom on $obj->{name}"}|;
3077
    }
3078
    $register{$uuid}->{'cdrom'} = $cdrom unless ($cdrom eq 'virtio');
3079
    return $postreply;
3080
}
(5-5/9)