Project

General

Profile

Download (128 KB) Statistics
| Branch: | Revision:
1 95b003ff Origo
#!/usr/bin/perl
2
3
# All rights reserved and Copyright (c) 2020 Origo Systems ApS.
4
# This file is provided with no warranty, and is subject to the terms and conditions defined in the license file LICENSE.md.
5
# The license file is part of this source code package and its content is also available at:
6
# https://www.origo.io/info/stabiledocs/licensing/stabile-open-source-license
7
8
package Stabile::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 a2e0bc7e hq
use Config::Simple;
17 95b003ff Origo
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 c899e439 Origo
    $action = $action || $h{'action'};
66
67 6372a66e hq
    if ($h{'action'} eq 'destroy' || $action eq 'destroy' || $action eq 'destroyuserservers' || $action eq 'attach' || $action eq 'detach' || $action =~ /changepassword|sshaccess/) {
68 95b003ff Origo
        $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 04c16f26 hq
    my $loader = $h{"loader"} || $dbobj->{'loader'};
105 95b003ff Origo
    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 04c16f26 hq
    if ($cdrom && $cdrom ne '--' && !($cdrom =~ /^\//) && $cdrom ne 'virtio') {
134 95b003ff Origo
        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 d3805c61 hq
    $mac = $dbobj->{'mac'} unless ($objaction eq 'start' || $objaction eq 'move' || $objaction eq 'stormove');
171 95b003ff Origo
    $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 a439a9c4 hq
            || ($memory && ($memory<64 || $memory >1024*64))
193 95b003ff Origo
    ) {
194 a439a9c4 hq
        $postreply .= "Status=ERROR Invalid server data: $name\n";
195 95b003ff Origo
        return 0;
196
    }
197
198
    # Security check
199 2a63870a Christian Orellana
    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 95b003ff Origo
        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 91a21c75 hq
        if ( ($reguser && ($user ne $reguser) && $action ) || ($reguser && $status eq "new"))
219 95b003ff Origo
        {
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 04c16f26 hq
    $cdrom = '--' if ($cdrom eq 'virtio' && $action ne 'mountcd');
236 95b003ff Origo
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 04c16f26 hq
        loader=> $loader,
255 95b003ff Origo
        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 d3805c61 hq
    *Stormove = \&Move;
296 95b003ff Origo
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 d3805c61 hq
    *do_abort = \&action;
312
    *do_stormove = \&action;
313 95b003ff Origo
    *do_mountcd = \&action;
314 c899e439 Origo
    *do_changepassword = \&privileged_action;
315
    *do_sshaccess = \&privileged_action;
316 95b003ff Origo
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 c899e439 Origo
    *do_gear_changepassword = \&do_gear_action;
323
    *do_gear_sshaccess = \&do_gear_action;
324 95b003ff Origo
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 2a63870a Christian Orellana
    @curregvalues = (sort {$b->{'name'} <=> $a->{'name'}} @curregvalues); # Always sort by name first
439 95b003ff Origo
    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 c899e439 Origo
        $json_text =~ s/null/"--"/g;
498 04c16f26 hq
        $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 95b003ff Origo
        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 6372a66e hq
    my ($uuid, $action, $obj) = @_;
575 95b003ff Origo
    if ($help) {
576
        return <<END
577 6372a66e hq
GET:username:
578 95b003ff Origo
Simple action for destroying all servers belonging to a user
579
END
580
    }
581 6372a66e hq
    $username = $obj->{username};
582 95b003ff Origo
    my $res;
583
    $res .= $Stabile::q->header('text/plain') unless $console;
584 6372a66e hq
585
    destroyUserServers($username);
586 95b003ff Origo
    $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 6fdc8676 hq
        # $termlink = $1 if ($termlink =~ /\/(.+)/);
672
        # $termlink = "$burl/$termlink" unless ($termlink =~ /^http/ || !$termlink); # || $termlink =~ /^\//
673 95b003ff Origo
        $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 d3d1a2d4 Origo
        $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 95b003ff Origo
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 6fdc8676 hq
    $appinfo{'dnssubdomain'} = ($enginelinked)?substr($engineid, 0, 8):'';
711 95b003ff Origo
    $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 f222b89c hq
    for (my $i = 0; $i < 20; $i++) {
769 95b003ff Origo
        $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 f222b89c hq
            sleep 10;
784 95b003ff Origo
        }
785
        else {
786
            last;
787
        }
788
    }
789
790 a2e0bc7e hq
    $mes = "$nodecount nodes ready - autostarting servers...\n";
791 f222b89c hq
    $main::syslogit->("irigo", "info", "$nodecount nodes ready - autostarting servers...");
792
793 a2e0bc7e hq
    print $mes if ($console);
794
    $res .= $mes;
795 95b003ff Origo
    if (!%nodereg || $nodedown || !$nodecount) {
796 a2e0bc7e hq
        $mes = "Only autostarting servers on local node - not all nodes ready!\n";
797 95b003ff Origo
        print $mes if ($console);
798
        $res .= $mes;
799
    }
800 a2e0bc7e hq
    if ($action eq "showautostart") {
801
        $mes = "Only showing which servers would be starting!\n";
802 95b003ff Origo
        print $mes if ($console);
803
        $res .= $mes;
804 a2e0bc7e hq
    }
805 95b003ff Origo
806 a2e0bc7e hq
    $Stabile::Networks::user = $user;
807
    require "$Stabile::basedir/cgi/networks.cgi";
808
    $Stabile::Networks::console = 1;
809
810
    foreach my $dom (values %register) {
811
        if ($nodedown) { # Only start local servers
812
            unless ($dom->{mac} && $nodereg{$dom->{mac}}->{identity} eq 'local_kvm') {
813
                $mes = "Skipping non-local domain $dom->{name}, $dom->{status}\n";
814
                print $mes if ($console);
815
                $res .= $mes;
816
                next;
817
            }
818
        }
819
        if ($dom->{'autostart'} eq '1' || $dom->{'autostart'} eq 'true') {
820
            $res .= "Checking if $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'}) should be started\n";
821
            my $networkstatus1 = $networkreg{$dom->{'networkuuid1'}}->{status};
822
            my $networkstatus2 = ($networkreg{$dom->{'networkuuid2'}})?$networkreg{$dom->{'networkuuid2'}}->{status}:'';
823
            my $networkstatus3 = ($networkreg{$dom->{'networkuuid3'}})?$networkreg{$dom->{'networkuuid3'}}->{status}:'';
824
            my @dnets;
825
            push @dnets, $dom->{'networkuuid1'} if ($dom->{'networkuuid1'} && $dom->{'networkuuid1'} ne '--' && $networkstatus1 ne 'up');
826
            push @dnets, $dom->{'networkuuid2'} if ($dom->{'networkuuid2'} && $dom->{'networkuuid2'} ne '--' && $networkstatus2 ne 'up');
827
            push @dnets, $dom->{'networkuuid3'} if ($dom->{'networkuuid3'} && $dom->{'networkuuid3'} ne '--' && $networkstatus3 ne 'up');
828
            my $i;
829
            for ($i=0; $i<5; $i++) { # wait for status newer than 10 secs
830
                validateItem($dom);
831
                last if (time() - $dom->{timestamp} < 10);
832
                $mes = "Waiting for newer timestamp, current is " . (time() - $dom->{timestamp}) . " old\n";
833
                print $mes if ($console);
834
                $res .= $mes;
835
                sleep 2;
836
            }
837
            if (
838
                $dom->{'status'} eq 'shutoff' || $dom->{'status'} eq 'inactive'
839
            ) {
840
                if ($action eq "showautostart") { # Dry run
841
                    $mes = "Starting $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
842 95b003ff Origo
                    print $mes if ($console);
843
                    $res .= $mes;
844
                }
845 a2e0bc7e hq
                else {
846
                    $mes = "Starting $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
847
                    print $mes if ($console);
848
                    $res .= $mes;
849
                    $postreply = Start($dom->{'uuid'});
850
                    print $postreply if ($console);
851
                    $res .= $postreply;
852
#                        $mes = `REMOTE_USER=$dom->{'user'} $base/cgi/servers.cgi -a start -u $dom->{'uuid'}`;
853
                    print $mes if ($console);
854
                    $res .= $mes;
855
                    sleep 1;
856
                }
857
            }
858
            elsif (@dnets) {
859
                if ($action eq "showautostart") { # Dry run
860
                    foreach my $networkuuid (@dnets) {
861
                        $mes = "Would bring network $networkreg{$networkuuid}->{name} up for $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
862 95b003ff Origo
                        print $mes if ($console);
863
                        $res .= $mes;
864
                    }
865 a2e0bc7e hq
                }
866
                else {
867
                    foreach my $networkuuid (@dnets) {
868
                        $mes = "Bringing network $networkreg{$networkuuid}->{name} up for $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
869 95b003ff Origo
                        print $mes if ($console);
870
                        $res .= $mes;
871 a2e0bc7e hq
                        $mes = Stabile::Networks::Activate($networkuuid, 'activate');
872 48fcda6b Origo
                        print $mes if ($console);
873
                        $res .= $mes;
874 95b003ff Origo
                        sleep 1;
875
                    }
876
                }
877
            }
878 a2e0bc7e hq
        } else {
879
            $res .= "Not marked for autostart ($dom->{'autostart'}): $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
880
            validateItem($dom);
881 95b003ff Origo
        }
882
    }
883
    return $res;
884
}
885
886
sub do_listnodeavailability {
887
    if ($help) {
888
        return <<END
889
GET::
890
Utility call - only informational. Shows availability of nodes for starting servers.
891
END
892
    }
893
    my $res;
894
    $res .= $Stabile::q->header('application/json') unless ($console);
895
    my ($temp1, $temp2, $temp3, $temp4, $ahashref) = locateTargetNode();
896
    my @avalues = values %$ahashref;
897
    my @sorted_values = (sort {$b->{'index'} <=> $a->{'index'}} @avalues);
898
    $res .= to_json(\@sorted_values, { pretty => 1 });
899
    return $res;
900
}
901
902
sub do_listbillingdata {
903
    if ($help) {
904
        return <<END
905
GET::
906
List current billing data.
907
END
908
    }
909
    my $res;
910
    $res .= $Stabile::q->header('application/json') unless ($console);
911
    my $buser = URI::Escape::uri_unescape($params{'user'}) || $user;
912
    my %b;
913
    my @bmonths;
914
    if ($isadmin || $buser eq $user) {
915
        my $bmonth = URI::Escape::uri_unescape($params{'month'}) || $month;
916
        my $byear = URI::Escape::uri_unescape($params{'year'}) || $year;
917
        if ($bmonth eq "all") {
918
            @bmonths = ("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12");
919
        }
920
        else {
921
            @bmonths = ($bmonth);
922
        }
923
924
        unless ( tie(%billingreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_domains', key=>'usernodetime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
925
926
        my @nkeys = keys %nodereg;
927
        foreach my $bm (@bmonths) {
928
            my $vcpuavg = 0;
929
            my $memoryavg = 0;
930
            foreach my $nmac (@nkeys) {
931
                $vcpuavg += $billingreg{"$buser-$nmac-$byear-$bm"}->{'vcpuavg'};
932
                $memoryavg += $billingreg{"$buser-$nmac-$byear-$bm"}->{'memoryavg'};
933
            }
934
            $b{"$buser-$byear-$bm"} = {
935
                id        => "$buser-$byear-$bm",
936
                vcpuavg   => $vcpuavg,
937
                memoryavg => $memoryavg,
938
                month     => $bm + 0,
939
                year      => $byear + 0
940
            }
941
        }
942
        untie %billingreg;
943
    }
944
    my @bvalues = values %b;
945
    $res .= "{\"identifier\": \"id\", \"label\": \"id\", \"items\":" . to_json(\@bvalues) . "}";
946
    return $res;
947
}
948
949
# Print list of available actions on objects
950
sub do_plainhelp {
951
    my $res;
952
    $res .= $Stabile::q->header('text/plain') unless $console;
953
    $res .= <<END
954
new [name="name"]
955
* start: Starts a server
956
* destroy: Destroys a server, i.e. terminates the VM, equivalent of turning the power off a physical computer
957
* shutdown: Asks the operating system of a server to shut down via ACPI
958
* suspend: Suspends the VM, effectively putting the server to sleep
959
* resume: Resumes a suspended VM, effectively waking the server from sleep
960
* move [mac="mac"]: Moves a server to specified node. If no node is specified, moves to other node with highest availability
961
index
962
* delete: Deletes a server. Image and network are not deleted, only information about the server. Server cannot be
963
runing
964
* mountcd [cdrom="path"]: Mounts a cd rom
965
END
966
    ;
967
    return $res;
968
}
969
970
# Helper function
971
sub recurse($) {
972
	my($path) = @_;
973
	my @files;
974
	## append a trailing / if it's not there
975
	$path .= '/' if($path !~ /\/$/);
976
	## loop through the files contained in the directory
977
	for my $eachFile (glob($path.'*')) {
978
		## if the file is a directory
979
		if( -d $eachFile) {
980
			## pass the directory to the routine ( recursion )
981
			push(@files,recurse($eachFile));
982
		} else {
983
			push(@files,$eachFile);
984
		}
985
	}
986
	return @files;
987
}
988
989
sub Start {
990
    my ($uuid, $action, $obj) = @_;
991
    $dmac = $obj->{mac};
992
    $buildsystem = $obj->{buildsystem};
993
    $uistatus = $obj->{uistatus};
994
    if ($help) {
995
        return <<END
996
GET:uuid,mac:
997
Start a server. Supply mac for starting on specific node.
998
END
999
    }
1000
    $dmac = $dmac || $params{'mac'};
1001
    return "Status=ERROR No uuid\n" unless ($register{$uuid});
1002
    my $serv = $register{$uuid};
1003
    $postreply = '' if ($buildsystem);
1004
1005
    my $name = $serv->{'name'};
1006
    utf8::decode($name);
1007
    my $image = $serv->{'image'};
1008
    my $image2 = $serv->{'image2'};
1009
    my $image3 = $serv->{'image3'};
1010
    my $image4 = $serv->{'image4'};
1011
    my $memory = $serv->{'memory'};
1012
    my $vcpu = $serv->{'vcpu'};
1013
    my $vgpu = $serv->{'vgpu'};
1014
    my $dbstatus = $serv->{'status'};
1015
    my $mac = $serv->{'mac'};
1016
    my $macname = $serv->{'macname'};
1017
    my $networkuuid1 = $serv->{'networkuuid1'};
1018
    my $networkuuid2 = $serv->{'networkuuid2'};
1019
    my $networkuuid3 = $serv->{'networkuuid3'};
1020
    my $nicmodel1 = $serv->{'nicmodel1'};
1021
    my $nicmac1 = $serv->{'nicmac1'};
1022
    my $nicmac2 = $serv->{'nicmac2'};
1023
    my $nicmac3 = $serv->{'nicmac3'};
1024
    my $boot = $serv->{'boot'};
1025 04c16f26 hq
    my $loader = $serv->{'loader'};
1026 95b003ff Origo
    my $diskbus = $serv->{'diskbus'};
1027
    my $cdrom = $serv->{'cdrom'};
1028
    my $diskdev = "vda";
1029
    my $diskdev2 = "vdb";
1030
    my $diskdev3 = "vdc";
1031
    my $diskdev4 = "vdd";
1032
    if ($diskbus eq "ide") {$diskdev = "hda"; $diskdev2 = "hdb"; $diskdev3 = "hdc"; $diskdev4 = "hdd"};
1033
1034
    my $mem = $memory * 1024;
1035
1036
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access image register"};
1037
1038
    my $img = $imagereg{$image};
1039
    my $imagename = $img->{'name'};
1040
    my $imagestatus = $img->{'status'};
1041
    my $img2 = $imagereg{$image2};
1042
    my $image2status = $img2->{'status'};
1043
    my $img3 = $imagereg{$image3};
1044
    my $image3status = $img3->{'status'};
1045
    my $img4 = $imagereg{$image4};
1046
    my $image4status = $img4->{'status'};
1047
1048
    if (!$imagereg{$image}) {
1049
        $postreply .= "Status=Error Image $image not found - please select a new image for your server, not starting $name\n";
1050
        untie %imagereg;
1051
        return $postreply;
1052
    }
1053
    untie %imagereg;
1054
1055
    if ($imagestatus ne "used" && $imagestatus ne "cloning") {
1056
        $postreply .= "Status=ERROR Image $imagename $image is $imagestatus, not starting $name\n";
1057
    } elsif ($image2 && $image2 ne '--' && $image2status ne "used" && $image2status ne "cloning") {
1058
        $postreply .= "Status=ERROR Image2 is $image2status, not starting $name\n";
1059
    } elsif ($image3 && $image3 ne '--' && $image3status ne "used" && $image3status ne "cloning") {
1060
        $postreply .= "Status=ERROR Image3 is $image3status, not starting $name\n";
1061
    } elsif ($image4 && $image4 ne '--' && $image4status ne "used" && $image4status ne "cloning") {
1062
        $postreply .= "Status=ERROR Image4 is $image4status, not starting $name\n";
1063 a2e0bc7e hq
    } elsif (Stabile::Servers::overQuotas($memory,$vcpu)) {
1064
        $main::syslogit->($user, "info", "Over quota ($memory, $vcpu, " . Stabile::Servers::overQuotas($memory,$vcpu) .  ") starting a $dbstatus domain: $uuid");
1065 95b003ff Origo
        $postreply .= "Status=ERROR Over quota - not starting $name\n";
1066
    # Status inactive is typically caused by a movepiston having problems. We should not start inactive servers since
1067
    # they could possibly be running even if movepiston is down. Movepiston on the node should be brought up to update
1068
    # the status, or the node should be removed from the stabile.
1069
    # We now allow to force start of inactive server when dmac is specified
1070
    } elsif ((!$dmac || $dmac eq $mac) && $dbstatus eq 'inactive' && $nodereg{$mac} && ($nodereg{$mac}->{'status'} eq 'inactive' || $nodereg{$mac}->{'status'} eq 'shutdown')) {
1071
        $main::syslogit->($user, "info", "Not starting inactive domain: $uuid (last seen on $mac)");
1072
        $postreply .= "Status=ERROR Not starting $name - Please bring up node $macname\n";
1073
    } elsif ($dbstatus eq 'inactive' || $dbstatus eq 'shutdown' || $dbstatus eq 'shutoff' || $dbstatus eq 'new') {
1074
        unless ($dmac && $isadmin) {
1075
            $dmac = $mac if ($dbstatus eq 'inactive'); # If movepiston crashed while shutting down, allow server to start on same node
1076
        }
1077
        $uistatus = "starting" unless ($uistatus);
1078
        my $hypervisor = getHypervisor($image);
1079
        my ($targetmac, $targetname, $targetip, $port) = locateTargetNode($uuid, $dmac, $mem, $vcpu, $image, $image2 ,$image3, $image4, $hypervisor);
1080
1081 a2e0bc7e hq
        # Read limits from nodeconfig
1082
        my $vm_readlimit = '';
1083
        my $vm_writelimit = '';
1084
        my $vm_iopsreadlimit = ''; # e.g. 1000 IOPS
1085
        my $vm_iopswritelimit = '';
1086
        if  (-e "/etc/stabile/nodeconfig.cfg") {
1087
            my $nodecfg = new Config::Simple("/etc/stabile/nodeconfig.cfg");
1088
            $vm_readlimit = $nodecfg->param('VM_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
1089
            $vm_writelimit = $nodecfg->param('VM_WRITE_LIMIT');
1090
            $vm_iopsreadlimit = $nodecfg->param('VM_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
1091
            $vm_iopswritelimit = $nodecfg->param('VM_IOPS_WRITE_LIMIT');
1092
        }
1093
1094 95b003ff Origo
        # Build XML for starting domain
1095
        my $graphics = "vnc";
1096
        $graphics = "rdp" if ($hypervisor eq "vbox");
1097
        my $net1 = $networkreg{$networkuuid1};
1098
        my $networkid1 = $net1->{'id'}; # Get the current vlan id of the network
1099
        my $net2 = $networkreg{$networkuuid2};
1100
        my $networkid2 = $net2->{'id'}; # Get the current vlan id of the network
1101
        my $net3 = $networkreg{$networkuuid2};
1102
        my $networkid3 = $net3->{'id'}; # Get the current vlan id of the network
1103
        my $networkid1ip = $net1->{'internalip'};
1104
        $networkid1ip = $net1->{'externalip'} if ($net1->{'type'} eq 'externalip');
1105
1106
        my $uname = $name . substr($uuid,0,8); # We don't enforce unique names, so we make them
1107
        $uname =~ s/[^[:ascii:]]/_/g; # Get rid of funny chars - they mess up Guacamole
1108
        $uname =~ s/\W/_/g;
1109
1110
        my $driver1;
1111
        my $driver2;
1112
        if ($hypervisor eq 'kvm') {
1113
            my $fmt1 = ($image =~ /\.qcow2$/)?'qcow2':'raw';
1114
            my $fmt2 = ($image2 =~ /\.qcow2$/)?'qcow2':'raw';
1115
            my $fmt3 = ($image3 =~ /\.qcow2$/)?'qcow2':'raw';
1116
            my $fmt4 = ($image4 =~ /\.qcow2$/)?'qcow2':'raw';
1117 2a63870a Christian Orellana
            my $cache1 = ($image =~ /\/node\//)?'default':'writeback';
1118
            my $cache2 = ($image2 =~ /\/node\//)?'default':'writeback';
1119
            my $cache3 = ($image3 =~ /\/node\//)?'default':'writeback';
1120
            my $cache4 = ($image4 =~ /\/node\//)?'default':'writeback';
1121
            $driver1 = "\n      <driver name='qemu' type='$fmt1' cache='$cache1'/>";
1122
            $driver2 = "\n      <driver name='qemu' type='$fmt2' cache='$cache2'/>";
1123
            $driver3 = "\n      <driver name='qemu' type='$fmt3' cache='$cache3'/>";
1124
            $driver4 = "\n      <driver name='qemu' type='$fmt4' cache='$cache4'/>";
1125 95b003ff Origo
        }
1126
1127
        my $networktype1 = "user";
1128
        my $networksource1 = "default";
1129
        my $networkforward1 = "bridge";
1130
        my $networkisolated1 = "no";
1131
        $networksource1 = "vboxnet0" if ($hypervisor eq "vbox");
1132
        if ($networkid1 eq '0') {
1133
            $networktype1 = "user";
1134
            $networkforward1 = "nat";
1135 f222b89c hq
            $networkisolated1 = "no"
1136 95b003ff Origo
        } elsif ($networkid1 == 1) {
1137
            $networktype1 = "network" ;
1138
            $networkforward1 = "nat";
1139
            $networkisolated1 = "yes"
1140
        } elsif ($networkid1 > 1) {
1141
            $networktype1 = "bridge";
1142
            $networksource1 = "br$networkid1";
1143
        }
1144
        my $networktype2 = "user";
1145
        my $networksource2 = "default";
1146
        my $networkforward2 = "bridge";
1147
        my $networkisolated2 = "no";
1148
        $networksource2 = "vboxnet0" if ($hypervisor eq "vbox");
1149
        if ($networkid2 eq '0') {
1150
            $networktype2 = "user";
1151
            $networkforward2 = "nat";
1152
            $networkisolated2 = "yes"
1153
        } elsif ($networkid2 == 1) {
1154
            $networktype2 = "network" ;
1155
            $networkforward2 = "nat";
1156
            $networkisolated2 = "yes"
1157
        } elsif ($networkid2 > 1) {
1158
            $networktype2 = "bridge";
1159
            $networksource2 = "br$networkid2";
1160
        }
1161
        my $networktype3 = "user";
1162
        my $networksource3 = "default";
1163
        my $networkforward3 = "bridge";
1164
        my $networkisolated3 = "no";
1165
        $networksource3 = "vboxnet0" if ($hypervisor eq "vbox");
1166
        if ($networkid3 eq '0') {
1167
            $networktype3 = "user";
1168
            $networkforward3 = "nat";
1169
            $networkisolated3 = "yes"
1170
        } elsif ($networkid3 == 1) {
1171
            $networktype3 = "network" ;
1172
            $networkforward3 = "nat";
1173
            $networkisolated3 = "yes"
1174
        } elsif ($networkid3 > 1) {
1175
            $networktype3 = "bridge";
1176
            $networksource3 = "br$networkid3";
1177
        }
1178
1179
        my $xml = "<domain type='$hypervisor' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>\n";
1180 51e32e00 hq
        my $vgpuxml = '';
1181
        if ($vgpu && $vgpu ne "--") {
1182
            $Stabile::Nodes::user = $user;
1183
            require "$Stabile::basedir/cgi/networks.cgi";
1184
            $Stabile::Nodes::console = 1;
1185
            my @gpus = Stabile::Nodes::getNextGpus($vgpu);
1186
            if (@gpus) {
1187
                foreach my $gpu (@gpus) {
1188
                    $vgpuxml .= <<ENDXML2
1189
 <hostdev mode='subsystem' type='pci' managed='yes'>
1190
   <source>
1191
     <address domain='0x0000' bus='0x$gpu->{bus}' slot='0x$gpu->{devide}' function='0x$gpu->{function}' multifunction='on'/>
1192
   </source>
1193
 </hostdev>
1194
ENDXML2
1195
                    ;
1196
                }
1197
            }
1198
        }
1199
1200
        #        if ($vgpu && $vgpu ne "--") {
1201 95b003ff Origo
#            $xml .= <<ENDXML2
1202
#  <qemu:commandline>
1203
#    <qemu:arg value='-device'/>
1204
#    <qemu:arg value='vfio-pci,host=01:00.0,x-vga=on'/>
1205
#    <qemu:arg value='-device'/>
1206
#    <qemu:arg value='vfio-pci,host=02:00.0,x-vga=on'/>
1207
#  </qemu:commandline>
1208
#ENDXML2
1209
#            ;
1210
#        }
1211
1212
#    <qemu:arg value='-set'/>
1213
#    <qemu:arg value='device.hostdev1.x-vga=on'/>
1214
#    <qemu:arg value='-cpu'/>
1215
#	<qemu:arg value='host,kvm=off'/>
1216
#    <qemu:arg value='-device'/>
1217
#	<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'/>
1218
1219
#  <cpu mode='host-model'>
1220
#    <vendor>Intel</vendor>
1221
#    <model>core2duo</model>
1222
#  </cpu>
1223
1224
#    <loader readonly='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE.fd</loader>
1225
#    <nvram template='/usr/share/OVMF/OVMF_VARS.fd'/>
1226 04c16f26 hq
        my $loader_xml = <<ENDXML
1227
    <bootmenu enable='yes' timeout='200'/>
1228
    <smbios mode='sysinfo'/>
1229
ENDXML
1230
        ;
1231 d3805c61 hq
        if ($loader eq 'uefi') {
1232
            $loader_xml = <<ENDXML
1233 04c16f26 hq
  <loader readonly='yes' secure='no' type='pflash'>/usr/share/ovmf/OVMF.fd</loader>
1234
  <nvram template='/usr/share/OVMF/OVMF_VARS.fd'>/tmp/guest_VARS.fd</nvram>
1235
ENDXML
1236
    ;
1237 d3805c61 hq
        }
1238
        my $iotune_xml = <<ENDXML
1239
      <iotune>
1240
        <read_bytes_sec>$vm_readlimit</read_bytes_sec>
1241
        <write_bytes_sec>$vm_writelimit</write_bytes_sec>
1242
        <read_iops_sec>$vm_iopsreadlimit</read_iops_sec>
1243
        <write_iops_sec>$vm_iopswritelimit</write_iops_sec>
1244
      </iotune>
1245
ENDXML
1246
;
1247
        $iotune_xml = '' unless ($enforceiolimits);
1248 95b003ff Origo
1249 705b5366 hq
        if ($vgpu && $vgpu ne "--") {
1250
            $xml .= <<ENDXML
1251 95b003ff Origo
  <cpu mode='host-passthrough'>
1252
    <feature policy='disable' name='hypervisor'/>
1253
  </cpu>
1254
ENDXML
1255
;
1256 705b5366 hq
        } else {
1257
            $xml .= <<ENDXML
1258
  <cpu mode='host-model'>
1259
  </cpu>
1260
ENDXML
1261
            ;
1262 95b003ff Origo
        }
1263
        $xml .=  <<ENDXML
1264
  <name>$uname</name>
1265
  <uuid>$uuid</uuid>
1266
  <memory>$mem</memory>
1267
  <vcpu>$vcpu</vcpu>
1268
  <os>
1269
    <type arch='x86_64' machine='pc'>hvm</type>
1270
    <boot dev='$boot'/>
1271 04c16f26 hq
$loader_xml
1272 95b003ff Origo
  </os>
1273
  <sysinfo type='smbios'>
1274
    <bios>
1275
      <entry name='vendor'>Origo</entry>
1276
    </bios>
1277
    <system>
1278
      <entry name='manufacturer'>Origo</entry>
1279
      <entry name='sku'>$networkid1ip</entry>
1280
    </system>
1281
  </sysinfo>
1282
  <features>
1283
ENDXML
1284
;
1285
        if ($vgpu && $vgpu ne "--") { $xml .= <<ENDXML
1286
    <kvm>
1287
      <hidden state='on'/>
1288
    </kvm>
1289
ENDXML
1290
;
1291
        }
1292
        $xml .= <<ENDXML
1293
    <pae/>
1294
    <acpi/>
1295
    <apic/>
1296
  </features>
1297
  <clock offset='localtime'>
1298
    <timer name='rtc' tickpolicy='catchup' track='guest'/>
1299
    <timer name='pit' tickpolicy='delay'/>
1300
    <timer name='hpet' present='no'/>
1301
  </clock>
1302
  <on_poweroff>destroy</on_poweroff>
1303 04c16f26 hq
  <on_reboot>restart</on_reboot>½
1304 95b003ff Origo
  <on_crash>restart</on_crash>
1305
  <devices>
1306 e837d785 hq
  <sound model='ich6'/>
1307 95b003ff Origo
ENDXML
1308
;
1309
#        if ($vgpu && $vgpu ne "--") {
1310
#            $xml .= <<ENDXML2
1311
#  <hostdev mode='subsystem' type='pci' managed='yes'>
1312
#    <source>
1313
#      <address domain='0x0000' bus='0x01' slot='0x00' function='0x0' multifunction='on'/>
1314
#    </source>
1315
#  </hostdev>
1316
#  <hostdev mode='subsystem' type='pci' managed='yes'>
1317
#    <source>
1318
#      <address domain='0x0000' bus='0x02' slot='0x00' function='0x0' multifunction='on'/>
1319
#    </source>
1320
#  </hostdev>
1321
#ENDXML2
1322
#;
1323
#        }
1324 51e32e00 hq
       if ($vgpu && $vgpu ne "--") {
1325
           $xml .= <<ENDXML2
1326
 <hostdev mode='subsystem' type='pci' managed='yes'>
1327
   <source>
1328
     <address domain='0x0000' bus='0x04' slot='0x00' function='0x0' multifunction='on'/>
1329
   </source>
1330
 </hostdev>
1331
ENDXML2
1332
;
1333
       }
1334 95b003ff Origo
        if ($image && $image ne "" && $image ne "--") {
1335
						$xml .= <<ENDXML2
1336
    <disk type='file' device='disk'>
1337
      <source file='$image'/>$driver1
1338
      <target dev='$diskdev' bus='$diskbus'/>
1339 d3805c61 hq
$iotune_xml
1340 95b003ff Origo
    </disk>
1341
ENDXML2
1342
;
1343
        };
1344
1345
        if ($image2 && $image2 ne "" && $image2 ne "--") {
1346
						$xml .= <<ENDXML2
1347
    <disk type='file' device='disk'>$driver2
1348
      <source file='$image2'/>
1349
      <target dev='$diskdev2' bus='$diskbus'/>
1350 d3805c61 hq
$iotune_xml
1351 95b003ff Origo
    </disk>
1352
ENDXML2
1353
;
1354
        };
1355
        if ($image3 && $image3 ne "" && $image3 ne "--") {
1356
						$xml .= <<ENDXML2
1357
    <disk type='file' device='disk'>$driver3
1358
      <source file='$image3'/>
1359
      <target dev='$diskdev3' bus='$diskbus'/>
1360 d3805c61 hq
$iotune_xml
1361 95b003ff Origo
    </disk>
1362
ENDXML2
1363
;
1364
        };
1365
        if ($image4 && $image4 ne "" && $image4 ne "--") {
1366
						$xml .= <<ENDXML2
1367
    <disk type='file' device='disk'>$driver4
1368
      <source file='$image4'/>
1369
      <target dev='$diskdev4' bus='$diskbus'/>
1370 d3805c61 hq
$iotune_xml
1371 95b003ff Origo
    </disk>
1372
ENDXML2
1373
;
1374
        };
1375
1376
        unless ($image4 && $image4 ne '--' && $diskbus eq 'ide') {
1377
            if ($cdrom && $cdrom ne "" && $cdrom ne "--") {
1378
						$xml .= <<ENDXML3
1379
    <disk type='file' device='cdrom'>
1380
      <source file='$cdrom'/>
1381
      <target dev='hdd' bus='ide'/>
1382
      <readonly/>
1383
    </disk>
1384
ENDXML3
1385
;
1386
            } elsif ($hypervisor ne "vbox") {
1387
						$xml .= <<ENDXML3
1388
    <disk type='file' device='cdrom'>
1389
      <target dev='hdd' bus='ide'/>
1390
      <readonly/>
1391
    </disk>
1392
ENDXML3
1393
;
1394
            }
1395
        }
1396
1397
        $xml .= <<ENDXML4
1398
    <interface type='$networktype1'>
1399
      <source $networktype1='$networksource1'/>
1400
      <forward mode='$networkforward1'/>
1401
      <port isolated='$networkisolated1'/>
1402
      <model type='$nicmodel1'/>
1403
      <mac address='$nicmac1'/>
1404
    </interface>
1405
ENDXML4
1406
;
1407
1408
        if (($networkuuid2 && $networkuuid2 ne '--') || $networkuuid2 eq '0') {
1409
            $xml .= <<ENDXML5
1410
    <interface type='$networktype2'>
1411
      <source $networktype2='$networksource2'/>
1412
      <forward mode='$networkforward2'/>
1413
      <port isolated='$networkisolated2'/>
1414
      <model type='$nicmodel1'/>
1415
      <mac address='$nicmac2'/>
1416
    </interface>
1417
ENDXML5
1418
;
1419
        }
1420
        if (($networkuuid3 && $networkuuid3 ne '--') || $networkuuid3 eq '0') {
1421
            $xml .= <<ENDXML5
1422
    <interface type='$networktype3'>
1423
      <source $networktype3='$networksource3'/>
1424
      <forward mode='$networkforward3'/>
1425
      <port isolated='$networkisolated3'/>
1426
      <model type='$nicmodel1'/>
1427
      <mac address='$nicmac3'/>
1428
    </interface>
1429
ENDXML5
1430
;
1431
        }
1432
        $xml .= <<ENDXML6
1433
     <serial type='pty'>
1434
       <source path='/dev/pts/0'/>
1435
       <target port='0'/>
1436
     </serial>
1437
    <input type='tablet' bus='usb'/>
1438
    <graphics type='$graphics' port='$port'/>
1439
  </devices>
1440
</domain>
1441
ENDXML6
1442
;
1443
1444
1445
#    <graphics type='$graphics' port='$port' keymap='en-us'/>
1446
#     <console type='pty' tty='/dev/pts/0'>
1447
#       <source path='/dev/pts/0'/>
1448
#       <target port='0'/>
1449
#     </console>
1450
#     <graphics type='$graphics' port='-1' autoport='yes'/>
1451
1452
        $xmlreg{$uuid} = {
1453
            xml=>URI::Escape::uri_escape($xml)
1454
        };
1455
1456
        # Actually ask node to start domain
1457
        if ($targetmac) {
1458
            $register{$uuid}->{'mac'} = $targetmac;
1459
            $register{$uuid}->{'macname'} = $targetname;
1460
            $register{$uuid}->{'macip'} = $targetip;
1461
1462
            my $tasks = $nodereg{$targetmac}->{'tasks'};
1463
            $tasks .= "START $uuid $user\n";
1464
            $nodereg{$targetmac}->{'tasks'} = $tasks;
1465
            tied(%nodereg)->commit;
1466
            $uiuuid = $uuid;
1467
            $uidisplayip = $targetip;
1468
            $uidisplayport = $port;
1469
            $register{$uuid}->{'status'} = $uistatus;
1470
            $register{$uuid}->{'statustime'} = $current_time;
1471
            tied(%register)->commit;
1472
1473
            # Activate networks
1474
            require "$Stabile::basedir/cgi/networks.cgi";
1475
            Stabile::Networks::Activate($networkuuid1, 'activate');
1476
            Stabile::Networks::Activate($networkuuid2, 'activate') if ($networkuuid2 && $networkuuid2 ne '--');
1477
            Stabile::Networks::Activate($networkuuid3, 'activate') if ($networkuuid3 && $networkuuid3 ne '--');
1478
1479
            $main::syslogit->($user, "info", "Marked $name ($uuid) for ". $serv->{'status'} . " on $targetname ($targetmac)");
1480
            $postreply .= "Status=starting OK $uistatus ". $serv->{'name'} . "\n";
1481
        } else {
1482
            $main::syslogit->($user, "info", "Could not find $hypervisor target for creating $uuid ($image)");
1483
            $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . " (unable to locate target node)\n";
1484
        };
1485
    } else {
1486
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
1487
        $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . "\n";
1488
    }
1489
    #return ($uiuuid, $uidisplayip, $uidisplayport, $postreply, $targetmac);
1490
    return $postreply;
1491
}
1492
1493
sub do_attach {
1494
    my ($uuid, $action, $obj) = @_;
1495
    if ($help) {
1496
        return <<END
1497
GET:uuid,image:
1498
Attaches an image to a server as a disk device. Image must not be in use.
1499
END
1500
    }
1501
    my $dev = '';
1502
    my $imagenum = 0;
1503
    my $serv = $register{$uuid};
1504
1505
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1506
        return "Status=Error Server must exist and be running\n";
1507
    }
1508
    my $macip = $serv->{macip};
1509
    my $image = $obj->{image} || $obj->{path};
1510
    if ($image && !($image =~ /^\//)) { # We have a uuid
1511
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Status=Error Unable to access images register\n"};
1512
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1513
        untie %imagereg2;
1514
    }
1515
    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;};
1516
    unless ($macip && $imagereg{$image} && $imagereg{$image}->{'user'} eq $user && $serv->{'user'} eq $user)  {$postreply .= "Status=Error Invalid image or server\n"; return $postreply;};
1517
    if ($imagereg{$image}->{'status'} ne 'unused') {return "Status=Error Image $image is already in use ($imagereg{$image}->{'status'})\n"};
1518
1519
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1520
    my $res = `$cmd`;
1521
    unless ($res =~ /vdb\s+.+/) {$dev = 'vdb'; $imagenum = 2};
1522
    unless ($dev || $res =~ /vdc\s+.+/)  {$dev = 'vdc'; $imagenum = 3};
1523
    unless ($dev || $res =~ /vdd\s+.+/)  {$dev = 'vdd'; $imagenum = 4};
1524
    if (!$dev) {
1525
        $postreply = "Status=Error No more images can be attached\n";
1526
    } else {
1527
        my $xml = <<END
1528
<disk type='file' device='disk'>
1529
  <driver type='qcow2' name='qemu' cache='default'/>
1530
  <source file='$image'/>
1531
  <target dev='$dev' bus='virtio'/>
1532
</disk>
1533
END
1534
;
1535
        $cmd = qq|$sshcmd $macip "echo \\"$xml\\" > /tmp/attach-device-$uuid.xml"|;
1536
        $res = `$cmd`;
1537
        $res .= `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh attach-device $uuid /tmp/attach-device-$uuid.xml`;
1538
        chomp $res;
1539
        if ($res =~ /successfully/) {
1540
            $postreply .= "Status=OK Attaching $image to $dev\n";
1541
            $imagereg{$image}->{'status'} = 'active';
1542
            $imagereg{$image}->{'domains'} = $uuid;
1543
            $imagereg{$image}->{'domainnames'} = $serv->{'name'};
1544
            $serv->{"image$imagenum"} = $image;
1545
            $serv->{"image$imagenum"."name"} = $imagereg{$image}->{'name'};
1546
            $serv->{"image$imagenum"."type"} = 'qcow2';
1547
        } else {
1548
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1549
        }
1550
    }
1551
    untie %imagereg;
1552
    return $postreply;
1553
}
1554
1555
sub do_detach {
1556
    my ($uuid, $action, $obj) = @_;
1557
    if ($help) {
1558
        return <<END
1559
GET:uuid,image:
1560
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.
1561
END
1562
    }
1563
    my $dev = '';
1564
    my $serv = $register{$uuid};
1565
1566
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1567
        return "Status=Error Server must exist and be running\n";
1568
    }
1569
    my $macip = $serv->{macip};
1570
1571
    my $image = $obj->{image} || $obj->{path} || $serv->{'image2'};
1572
    if ($image && !($image =~ /^\//)) { # We have a uuid
1573
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1574
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1575
        untie %imagereg2;
1576
    }
1577
    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;};
1578
    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;};
1579
1580
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1581
    my $res = `$cmd`;
1582
    $dev = $1 if ($res =~ /(vd.)\s+.+$image/);
1583
    if (!$dev) {
1584
        $postreply =  qq|Status=Error Image $image, $cmd, is not currently attached\n|;
1585
    } elsif ($dev eq 'vda') {
1586
        $postreply = "Status=Error You cannot detach the primary image\n";
1587
    } else {
1588
        $res = `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh detach-disk $uuid $dev`;
1589
        chomp $res;
1590
        if ($res =~ /successfully/) {
1591
            $postreply .= "Status=OK Detaching image $image, $imagereg{$image}->{'uuid'} from $dev\n";
1592
            my $imagenum;
1593
            $imagenum = 2 if ($serv->{'image2'} eq $image);
1594
            $imagenum = 3 if ($serv->{'image3'} eq $image);
1595
            $imagenum = 4 if ($serv->{'image4'} eq $image);
1596
            $imagereg{$image}->{'status'} = 'unused';
1597
            $imagereg{$image}->{'domains'} = '';
1598
            $imagereg{$image}->{'domainnames'} = '';
1599
            if ($imagenum) {
1600
                $serv->{"image$imagenum"} = '';
1601
                $serv->{"image$imagenum"."name"} = '';
1602
                $serv->{"image$imagenum"."type"} = '';
1603
            }
1604
        } else {
1605
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1606
        }
1607
    }
1608
    untie %imagereg;
1609
    return $postreply;
1610
}
1611
1612
sub Destroy {
1613
    my ($uuid, $action, $obj) = @_;
1614
    if ($help) {
1615
        return <<END
1616
GET:uuid,wait:
1617
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.
1618
END
1619
    }
1620
    my $uistatus = 'destroying';
1621
    my $name = $register{$uuid}->{'name'};
1622
    my $mac = $register{$uuid}->{'mac'};
1623
    my $macname = $register{$uuid}->{'macname'};
1624
    my $dbstatus = $register{$uuid}->{'status'};
1625
    my $wait = $obj->{'wait'};
1626
    if ($dbstatus eq 'running' or $dbstatus eq 'paused'
1627
        or $dbstatus eq 'shuttingdown' or $dbstatus eq 'starting'
1628
        or $dbstatus eq 'destroying' or $dbstatus eq 'upgrading'
1629
        or $dbstatus eq 'suspending' or $dbstatus eq 'resuming') {
1630
        if ($wait) {
1631 6372a66e hq
            my $username = $register{$uuid}->{'user'} || $user;
1632
            $username = $user unless ($isadmin);
1633
            $postreply = destroyUserServers($username, 1, $uuid);
1634 95b003ff Origo
        } else {
1635 6372a66e hq
            my $node = $nodereg{$mac};
1636
            my $tasks = $node->{'tasks'};
1637
            $node->{'tasks'} = $tasks . "DESTROY $uuid $user\n";
1638 95b003ff Origo
            tied(%nodereg)->commit;
1639
            $register{$uuid}->{'status'} = $uistatus;
1640
            $register{$uuid}->{'statustime'} = $current_time;
1641
            $uiuuid = $uuid;
1642
            $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus on $macname ($mac)");
1643
            $postreply .= "Status=destroying $uistatus ". $register{$uuid}->{'name'} . "\n";
1644
        }
1645
    } else {
1646
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $name ($uuid)");
1647
        $postreply .= "Status=ERROR problem $uistatus $name\n";
1648
    }
1649
    return $postreply;
1650
}
1651
1652
sub getHypervisor {
1653
	my $image = shift;
1654
	# Produce a mapping of image file suffixes to hypervisors
1655
	my %idreg;
1656
    unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access nodeidentities register"};
1657
    my @idvalues = values %idreg;
1658
	my %formats;
1659
	foreach my $val (@idvalues) {
1660
		my %h = %$val;
1661
		foreach (split(/,/,$h{'formats'})) {
1662
			$formats{lc $_} = $h{'hypervisor'}
1663
		}
1664
	}
1665
	untie %idreg;
1666
1667
	# and then determine the hypervisor in question
1668
	my $hypervisor = "vbox";
1669
	my ($pathname, $path, $suffix) = fileparse($image, '\.[^\.]*');
1670
	$suffix = substr $suffix, 1;
1671
	my $hypervisor = $formats{lc $suffix};
1672
	return $hypervisor;
1673
}
1674
1675
sub nicmac1ToUuid {
1676
    my $nicmac1 = shift;
1677
    my $uuid;
1678
    return $uuid unless $nicmac1;
1679
    my @regkeys = (tied %register)->select_where("user = '$user' AND nicmac1 = '$nicmac1");
1680
	foreach my $k (@regkeys) {
1681
	    my $val = $register{$k};
1682
		my %h = %$val;
1683
		if (lc $h{'nicmac1'} eq lc $nicmac1 && $user eq $h{'user'}) {
1684
    		$uuid =  $h{'uuid'};
1685
    		last;
1686
		}
1687
	}
1688
	return $uuid;
1689
}
1690
1691
sub randomMac {
1692
	my ( %vendor, $lladdr, $i );
1693
#	$lladdr = '00';
1694
	$lladdr = '52:54:00';# KVM vendor string
1695
	while ( ++$i )
1696
#	{ last if $i > 10;
1697
	{ last if $i > 6;
1698
		$lladdr .= ':' if $i % 2;
1699
		$lladdr .= sprintf "%" . ( qw (X x) [int ( rand ( 2 ) ) ] ), int ( rand ( 16 ) );
1700
	}
1701
	return $lladdr;
1702
}
1703
1704
sub overQuotas {
1705
    my $meminc = shift;
1706
    my $vcpuinc = shift;
1707
	my $usedmemory = 0;
1708
	my $usedvcpus = 0;
1709
	my $overquota = 0;
1710
    return $overquota if ($isadmin || $Stabile::userprivileges =~ /a/); # Don't enforce quotas for admins
1711
1712 a2e0bc7e hq
	my $memoryquota = $Stabile::usermemoryquota;
1713
	my $vcpuquota = $Stabile::uservcpuquota;
1714 95b003ff Origo
1715
	if (!$memoryquota || !$vcpuquota) { # 0 or empty quota means use defaults
1716
        $memoryquota = $memoryquota || $Stabile::config->get('MEMORY_QUOTA');
1717
        $vcpuquota = $vcpuquota || $Stabile::config->get('VCPU_QUOTA');
1718
    }
1719
1720
    my @regkeys = (tied %register)->select_where("user = '$user'");
1721
	foreach my $k (@regkeys) {
1722
	    my $val = $register{$k};
1723
		if ($val->{'user'} eq $user && $val->{'status'} ne "shutoff" &&
1724
		    $val->{'status'} ne "inactive" && $val->{'status'} ne "shutdown" ) {
1725
1726
		    $usedmemory += $val->{'memory'};
1727
		    $usedvcpus += $val->{'vcpu'};
1728
		}
1729
	}
1730
	$overquota = $usedmemory+$meminc if ($memoryquota!=-1 && $usedmemory+$meminc > $memoryquota); # -1 means no quota
1731
	$overquota = $usedvcpus+$vcpuinc if ($vcpuquota!=-1 && $usedvcpus+$vcpuinc > $vcpuquota);
1732
	return $overquota;
1733
}
1734
1735
sub validateItem {
1736 a2e0bc7e hq
    unless (%imagereg) {
1737
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1738
    }
1739 95b003ff Origo
    my $valref = shift;
1740
    my $img = $imagereg{$valref->{'image'}};
1741
    my $imagename = $img->{'name'};
1742
    $valref->{'imagename'} = $imagename if ($imagename);
1743
    my $imagetype = $img->{'type'};
1744
    $valref->{'imagetype'} = $imagetype if ($imagetype);
1745
1746
    # imagex may be registered by uuid instead of path - find the path
1747
    # We now support up to 4 images
1748
    for (my $i=2; $i<=4; $i++) {
1749
        if ($valref->{"image$i"} && $valref->{"image$i"} ne '--' && !($valref->{"image$i"} =~ /^\//)) {
1750
            unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1751
            $valref->{"image$i"} = $imagereg2{$valref->{"image$i"}}->{'path'};
1752
            untie %imagereg2;
1753
        }
1754
1755
        my $imgi = $imagereg{$valref->{"image$i"}};
1756
        $valref->{"image$i" . 'name'} = $imgi->{'name'} || $valref->{"image$i" . 'name'};
1757
        $valref->{"image$i" . 'type'} = $imgi->{'type'} || $valref->{"image$i" . 'type'};
1758
    }
1759
1760
    my $net1 = $networkreg{$valref->{'networkuuid1'}};
1761
    my $networkname1 = $net1->{'name'};
1762
    $valref->{'networkname1'} = $networkname1 if ($networkname1);
1763
    my $net2 = $networkreg{$valref->{'networkuuid2'}};
1764
    my $networkname2 = $net2->{'name'};
1765
    $valref->{'networkname2'} = $networkname2 if ($networkname2);
1766
    my $name = $valref->{'name'};
1767
    $valref->{'name'} = $imagename unless $name;
1768
1769 a2e0bc7e hq
    # Make sure we start shutoff servers on the node their image is on
1770 95b003ff Origo
    if ($valref->{'status'} eq "shutoff" || $valref->{'status'} eq "inactive") {
1771
        my $node = $nodereg{$valref->{'mac'}};
1772
        if ($valref->{'image'} =~ /\/mnt\/stabile\/node\//) {
1773
            $valref->{'mac'} = $img->{'mac'};
1774
            $valref->{'macname'} = $node->{'name'};
1775
            $valref->{'macip'} = $node->{'ip'};
1776
        } elsif ($valref->{'image2'} =~ /\/mnt\/stabile\/node\//) {
1777
            $valref->{'mac'} = $imagereg{$valref->{'image2'}}->{'mac'};
1778
            $valref->{'macname'} = $node->{'name'};
1779
            $valref->{'macip'} = $node->{'ip'};
1780
        } elsif ($valref->{'image3'} =~ /\/mnt\/stabile\/node\//) {
1781
            $valref->{'mac'} = $imagereg{$valref->{'image3'}}->{'mac'};
1782
            $valref->{'macname'} = $node->{'name'};
1783
            $valref->{'macip'} = $node->{'ip'};
1784
        } elsif ($valref->{'image4'} =~ /\/mnt\/stabile\/node\//) {
1785
            $valref->{'mac'} = $imagereg{$valref->{'image4'}}->{'mac'};
1786
            $valref->{'macname'} = $node->{'name'};
1787
            $valref->{'macip'} = $node->{'ip'};
1788
        }
1789
    }
1790
# Mark domains we have heard from in the last 20 secs as inactive
1791
    my $dbtimestamp = 0;
1792
    $dbtimestamp = $register{$valref->{'uuid'}}->{'timestamp'} if ($register{$valref->{'uuid'}});
1793
    my $timediff = $current_time - $dbtimestamp;
1794
    if ($timediff >= 20) {
1795
        if  (! ($valref->{'status'} eq "shutoff"
1796
                || $valref->{'status'} eq "starting"
1797
            #    || $valref->{'status'} eq "shuttingdown"
1798
            #    || $valref->{'status'} eq "destroying"
1799 d3805c61 hq
                || ($valref->{'status'} =~ /moving/ && $timediff<40)
1800 95b003ff Origo
            )) { # Move has probably failed
1801
            $valref->{'status'} = "inactive";
1802
            $imagereg{$valref->{'image'}}->{'status'} = "used" if ($valref->{'image'} && $imagereg{$valref->{'image'}});
1803 a2e0bc7e hq
            $imagereg{$valref->{'image2'}}->{'status'} = "used" if ($valref->{'image2'} && $imagereg{$valref->{'image2'}});
1804 95b003ff Origo
            $imagereg{$valref->{'image3'}}->{'status'} = "used" if ($valref->{'image3'} && $imagereg{$valref->{'image3'}});
1805
            $imagereg{$valref->{'image4'}}->{'status'} = "used" if ($valref->{'image4'} && $imagereg{$valref->{'image4'}});
1806
        }
1807
    };
1808 a2e0bc7e hq
#    untie %imagereg;
1809 95b003ff Origo
    return $valref;
1810
}
1811
1812
# Run through all domains and mark domains we have heard from in the last 20 secs as inactive
1813
sub updateRegister {
1814
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access user register"};
1815
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1816
1817
    my @regkeys = (tied %register)->select_where("user = '$user'");
1818
1819
    foreach my $k (@regkeys) {
1820
        my $valref = $register{$k};
1821
        next unless ($userreg{$valref->{'user'}});
1822
        my $dbtimestamp = $valref->{'timestamp'};
1823
        my $dbstatus = $valref->{'status'};
1824
        my $timediff = $current_time - $dbtimestamp;
1825
        my $imgstatus;
1826
        my $domstatus;
1827
        if ($timediff >= 20) {
1828
            if  ( $valref->{'status'} eq "shutoff" ) {
1829
                $imgstatus = 'used';
1830
            } elsif ((  $valref->{'status'} eq "starting"
1831
                            || $valref->{'status'} eq "shuttingdown"
1832
                        ) && $timediff>50) {
1833
                $imgstatus = 'used';
1834
                $domstatus = 'inactive';
1835
            } elsif ($valref->{'status'} eq "destroying" || $valref->{'status'} eq "moving") {
1836
                ;
1837
            } else {
1838
                $domstatus = 'inactive';
1839
                $imgstatus = 'used';
1840
            }
1841
            $valref->{'status'} = $domstatus if ($domstatus);
1842
            my $image = $valref->{'image'};
1843
            my $image2 = $valref->{'image2'};
1844
            my $image3 = $valref->{'image3'};
1845
            my $image4 = $valref->{'image4'};
1846
            $imagereg{$image}->{'status'} = $imgstatus if ($imgstatus);
1847
            $imagereg{$image2}->{'status'} = $imgstatus if ($image2 && $imgstatus);
1848
            $imagereg{$image3}->{'status'} = $imgstatus if ($image3 && $imgstatus);
1849
            $imagereg{$image4}->{'status'} = $imgstatus if ($image4 && $imgstatus);
1850
            if ($domstatus eq 'inactive ' && $dbstatus ne 'inactive') {
1851
                $main::updateUI->({ tab=>'servers',
1852
                                    user=>$valref->{'user'},
1853
                                    uuid=>$valref->{'uuid'},
1854
                                    sender=>'updateRegister',
1855
                                    status=>'inactive'})
1856
            }
1857
        };
1858
1859
    }
1860
    untie %userreg;
1861
    untie %imagereg;
1862
}
1863
1864
1865
sub locateTargetNode {
1866 d3805c61 hq
    my ($uuid, $dmac, $mem, $vcpu, $image, $image2, $image3, $image4, $hypervisor, $smac, $stormove)= @_;
1867 95b003ff Origo
    my $targetname;
1868
    my $targetip;
1869
    my $port;
1870
    my $targetnode;
1871
    my $targetindex; # Availability index of located target node
1872
    my %avhash;
1873
1874 d3805c61 hq
    $dmac = '' unless ($isadmin); # Only allow admins to select specific node
1875 95b003ff Origo
    my $mnode = $register{$uuid};
1876 d3805c61 hq
    if (!$dmac
1877 95b003ff Origo
            && $mnode->{'locktonode'} eq 'true'
1878
            && $mnode->{'mac'}
1879
            && $mnode->{'mac'} ne '--'
1880 d3805c61 hq
            ) {
1881
        $dmac = $mnode->{'mac'}; # Server is locked to specific node
1882
    }
1883 95b003ff Origo
    if ($dmac && !$nodereg{$dmac}) {
1884
        $main::syslogit->($user, "info", "The target node $dmac no longer exists, starting $uuid on another node if possible");
1885
        $dmac = '';
1886
    }
1887 d3805c61 hq
    my $imageonnode = ((!$stormove) && ($image =~ /\/mnt\/stabile\/node\//
1888 95b003ff Origo
                                          || $image2 =~ /\/mnt\/stabile\/node\//
1889
                                          || $image3 =~ /\/mnt\/stabile\/node\//
1890
                                          || $image4 =~ /\/mnt\/stabile\/node\//
1891 d3805c61 hq
                                          ));
1892 95b003ff Origo
1893
    foreach $node (values %nodereg) {
1894
        my $nstatus = $node->{'status'};
1895
        my $maintenance = $node->{'maintenance'};
1896
        my $nmac = $node->{'mac'};
1897
1898
        if (($nstatus eq 'running' || $nstatus eq 'asleep' || $nstatus eq 'maintenance' || $nstatus eq 'waking')
1899
         && $smac ne $nmac
1900
         && (( ($node->{'memfree'} > $mem+512*1024)
1901
         && (($node->{'vmvcpus'} + $vcpu) <= ($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'})) ) || $action eq 'listnodeavailability')
1902
        ) {
1903
        # Determine how available this node is
1904
        # Available memory
1905
            my $memweight = 0.2; # memory weighing factor
1906
            my $memindex = $avhash{$nmac}->{'memindex'} = int(100* $memweight* $node->{'memfree'} / (1024*1024) )/100;
1907
        # Free cores
1908
            my $cpuindex = $avhash{$nmac}->{'cpuindex'} = int(100*($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'} - $node->{'vmvcpus'} - $node->{'reservedvcpus'}))/100;
1909
        # Asleep - not asleep gives a +3
1910
            my $sleepindex = $avhash{$nmac}->{'sleepindex'} = ($node->{'status'} eq 'asleep' || $node->{'status'} eq 'waking')?'0':'3';
1911
            $avhash{$nmac}->{'vmvcpus'} = $node->{'vmvcpus'};
1912
#            $avhash{$nmac}->{'cpucommision'} = $cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'};
1913
#            $avhash{$nmac}->{'cpureservation'} = $node->{'vmvcpus'} + $node->{'reservedvcpus'};
1914
            $avhash{$nmac}->{'name'} = $node->{'name'};
1915
            $avhash{$nmac}->{'mac'} = $node->{'mac'};
1916
1917
            my $aindex = $memindex + $cpuindex + $sleepindex;
1918
        # Don't use nodes that are out of memory of cores
1919
            $aindex = 0 if ($memindex <= 0 || $cpuindex <= 0);
1920
            $avhash{$nmac}->{'index'} = $aindex;
1921
            $avhash{$nmac}->{'storfree'} = $node->{'storfree'};
1922 c899e439 Origo
            $avhash{$nmac}->{'memfree'} = $node->{'memfree'};
1923 95b003ff Origo
            $avhash{$nmac}->{'ip'} = $node->{'ip'};
1924
            $avhash{$nmac}->{'identity'} = $node->{'identity'};
1925
            $avhash{$nmac}->{'status'} = $node->{'status'};
1926
            $avhash{$nmac}->{'maintenance'} = $maintenance;
1927
            $avhash{$nmac}->{'reservedvcpus'} = $node->{'reservedvcpus'};
1928
            my $nodeidentity = $node->{'identity'};
1929
            $nodeidentity = 'kvm' if ($nodeidentity eq 'local_kvm');
1930
            if ($hypervisor eq $nodeidentity) {
1931
                # If image is on node, we must start on same node - registered when moving image
1932
                if ($imageonnode) {
1933
                    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1934
                    $dmac = $imagereg{$image}->{'mac'};
1935
                    $dmac = $imagereg{$image2}->{'mac'} unless ($dmac);
1936
                    $dmac = $imagereg{$image3}->{'mac'} unless ($dmac);
1937
                    $dmac = $imagereg{$image4}->{'mac'} unless ($dmac);
1938
                    untie %imagereg;
1939
                    if (!$dmac) {
1940
                        $postreply .= "Status=ERROR Image node not found\n";
1941
                        last;
1942
                    }
1943
                }
1944
                $dmac = "" if ($dmac eq "--");
1945 a439a9c4 hq
            # If a specific node is asked for, match mac addresses
1946 95b003ff Origo
                if ($dmac eq $nmac) {
1947
                    $targetnode = $node;
1948
                    last;
1949
                } elsif (!$dmac && $nstatus ne "maintenance" && !$maintenance) {
1950
            # pack or disperse
1951
                    if (!$targetindex) {
1952
                        $targetindex = $aindex;
1953
                        $targetnode = $node;
1954
                    } elsif ($dpolicy eq 'pack') {
1955
                        if ($aindex < $targetindex) {
1956
                            $targetnode = $node;
1957
                            $targetindex = $aindex;
1958
                        }
1959
                    } else {
1960
                        if ($aindex > $targetindex) {
1961
                            $targetnode = $node;
1962
                            $targetindex = $aindex;
1963
                        }
1964
                    }
1965
                }
1966
            }
1967
        }
1968
    }
1969
    if ($targetnode && $uuid) {
1970
        if ($targetnode->{'status'} eq 'asleep') {
1971
            my $nmac = $targetnode->{'mac'};
1972
            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);
1973
            my $nlogmsg = "Node $nmac marked for wake ";
1974
            if ($brutalsleep && (
1975
                    ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--')
1976
                || ($targetnode->{'ipmiip'} && $targetnode->{'ipmiip'} ne '--')
1977
                )) {
1978
                my $wakecmd;
1979
                if ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--') {
1980
                    $wakecmd = "echo 'y' | AMT_PASSWORD='$amtpasswd' /usr/bin/amttool $targetnode->{'amtip'} powerup pxe";
1981
                } else {
1982
                    $wakecmd = "ipmitool -I lanplus -H $targetnode->{'ipmiip'} -U ADMIN -P ADMIN power on";
1983
                }
1984
                $nlogmsg .= `$wakecmd`;
1985
            } else {
1986
                my $broadcastip = $targetnode->{'ip'};
1987
                $broadcastip =~ s/\.\d{1,3}$/.255/;
1988
                $nlogmsg .= 'on lan ' . `/usr/bin/wakeonlan -i $broadcastip $realmac`;
1989
            }
1990
            $targetnode->{'status'} = "waking";
1991
            $nlogmsg =~ s/\n/ /g;
1992
            $main::syslogit->($user, "info", $nlogmsg);
1993
            $postreply .= "Status=OK waking $targetnode->{'name'}\n";
1994
        }
1995
        $targetname = $targetnode->{'name'};
1996
        $targetmac = $targetnode->{'mac'};
1997
        $targetip = $targetnode->{'ip'};
1998
        $targetip = $targetnode->{'ip'};
1999
        my $porttaken = 1;
2000
        while ($porttaken) {
2001
            $porttaken = 0;
2002
            $port = $targetnode->{'vms'} + (($hypervisor eq "vbox")?3389:5900);
2003
            $port += int(rand(200));
2004
            my @regkeys = (tied %register)->select_where("port = '$port' AND macip = '$targetip'");
2005
            foreach my $k (@regkeys) {
2006
                $r = $register{$k};
2007
                if ($r->{'port'} eq $port && $r->{'macip'} eq $targetip) {
2008
                    $porttaken = 1;
2009
                }
2010
            }
2011
        }
2012
        $targetnode->{'vms'}++;
2013
        $targetnode->{'vmvcpus'} += $vcpu;
2014
        $register{$uuid}->{'port'} = $port;
2015
#        $register{$uuid}->{'mac'} = $targetmac;
2016
#        $register{$uuid}->{'macname'} = $targetname;
2017
#        $register{$uuid}->{'macip'} = $targetip;
2018
        $register{$uuid}->{'display'} = (($hypervisor eq "vbox")?'rdp':'vnc');
2019
    } else {
2020
        my $macstatus;
2021
        $macstatus = $nodereg{$dmac}->{status} if ($nodereg{$dmac});
2022 d3805c61 hq
        $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);
2023 95b003ff Origo
    }
2024
    return ($targetmac, $targetname, $targetip, $port, \%avhash);
2025
}
2026
2027
sub destroyUserServers {
2028
    my $username = shift;
2029
    my $wait = shift; # Should we wait for servers do die
2030
    my $duuid = shift;
2031 6372a66e hq
    return unless ($username && ($isadmin || $user eq $username));
2032 95b003ff Origo
    my @updateList;
2033
2034
    my @regkeys = (tied %register)->select_where("user = '$username'");
2035
    foreach my $uuid (@regkeys) {
2036
        if ($register{$uuid}->{'user'} eq $username
2037
            && $register{$uuid}->{'status'} ne 'shutoff'
2038
            && (!$duuid || $duuid eq $uuid)
2039
        ) {
2040
            $postreply .= "Destroying $username server $register{$uuid}->{'name'}, $uuid\n";
2041
            Destroy($uuid);
2042
            push (@updateList,{ tab=>'servers',
2043
                                user=>$user,
2044
                                uuid=>$duuid,
2045
                                status=>'destroying'});
2046
        }
2047
    }
2048
    $main::updateUI->(@updateList) if (@updateList);
2049
    if ($wait) {
2050
        my @regkeys = (tied %register)->select_where("user = '$username'");
2051
        my $activeservers = 1;
2052
        my $i = 0;
2053 6372a66e hq
        while ($activeservers && $i<30) {
2054 95b003ff Origo
            $activeservers = 0;
2055
            foreach my $k (@regkeys) {
2056
                my $valref = $register{$k};
2057
                if ($username eq $valref->{'user'}
2058
                    && ($valref->{'status'} ne 'shutoff'
2059
                    && $valref->{'status'} ne 'inactive')
2060
                    && (!$duuid || $duuid eq $valref->{'uuid'})
2061
                ) {
2062
                    $activeservers = $valref->{'uuid'};
2063
                }
2064
            }
2065
            $i++;
2066
            if ($activeservers) {
2067
                my $res .= "Status=OK Waiting $i for server $register{$activeservers}->{'name'}, $register{$activeservers}->{'status'} to die...\n";
2068 9de5a3f1 hq
            #    print $res if ($console);
2069 95b003ff Origo
                $postreply .= $res;
2070
                sleep 2;
2071
            }
2072
        }
2073
        $postreply .= "Status=OK Servers halted for $username\n" unless ($activeservers);
2074
    }
2075
    return $postreply;
2076
}
2077
2078
sub removeUserServers {
2079
    my $username = shift;
2080
    my $uuid = shift;
2081
    my $destroy = shift; # Should running servers be destroyed before removing
2082
    return unless (($isadmin || $user eq $username) && !$isreadonly);
2083
    $user = $username;
2084
    my @regkeys = (tied %register)->select_where("user = '$username'");
2085
    foreach my $ruuid (@regkeys) {
2086
        next if ($uuid && $ruuid ne $uuid);
2087
        if ($destroy && $register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} ne 'shutoff' && $register{$ruuid}->{'status'} ne 'inactive')) {
2088
            destroyUserServers($username, 1, $ruuid);
2089
        }
2090
2091
        if ($register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} eq 'shutoff' || $register{$ruuid}->{'status'} eq 'inactive')) {
2092
            $postreply .= "Removing $username server $register{$ruuid}->{'name'}, $ruuid" . ($console?'':'<br>') . "\n";
2093
            Remove($ruuid);
2094
        }
2095
    }
2096
}
2097
2098
sub Remove {
2099
    my ($uuid, $action) = @_;
2100
    if ($help) {
2101
        return <<END
2102
DELETE:uuid:
2103
Removes a server. Server must be shutoff. Does not remove associated images or networks.
2104
END
2105
    }
2106
    my $reguser = $register{$uuid}->{'user'};
2107
    my $dbstatus = $register{$uuid}->{'status'};
2108
    my $image = $register{$uuid}->{'image'};
2109
    my $image2 = $register{$uuid}->{'image2'};
2110
    my $image3 = $register{$uuid}->{'image3'};
2111
    my $image4 = $register{$uuid}->{'image4'};
2112
    my $name = $register{$uuid}->{'name'};
2113
    $image2 = '' if ($image2 eq '--');
2114
    $image3 = '' if ($image3 eq '--');
2115
    $image4 = '' if ($image4 eq '--');
2116
2117
    if ($reguser ne $user) {
2118
        $postreply .= "Status=ERROR You cannot delete a vm you don't own\n";
2119
    } elsif ($dbstatus eq 'inactive' || $dbstatus eq 'shutdown' || $dbstatus eq 'shutoff') {
2120
2121
        # Delete software packages and monitors from register
2122
        $postmsg .= deletePackages($uuid);
2123
        my $sname = $register{$uuid}->{'name'};
2124
        utf8::decode($sname);
2125 48fcda6b Origo
        $postmsg .= deleteMonitors($uuid)?" deleted monitors for $sname ":'';
2126 95b003ff Origo
2127
        delete $register{$uuid};
2128
        delete $xmlreg{$uuid};
2129
2130
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2131
        $imagereg{$image}->{'status'} = "unused" if ($imagereg{$image});
2132
        $imagereg{$image2}->{'status'} = "unused" if ($image2 && $imagereg{$image2});
2133
        $imagereg{$image3}->{'status'} = "unused" if ($image3 && $imagereg{$image3});
2134
        $imagereg{$image4}->{'status'} = "unused" if ($image4 && $imagereg{$image4});
2135
        untie %imagereg;
2136
2137
        # Delete metrics
2138
        my $metricsdir = "/var/lib/graphite/whisper/domains/$uuid";
2139
        `rm -r $metricsdir` if (-e $metricsdir);
2140
        my $rrdfile = "/var/cache/rrdtool/".$uuid."_highres.rrd";
2141
        `rm $rrdfile` if (-e $rrdfile);
2142
2143
        $main::syslogit->($user, "info", "Deleted domain $uuid from db");
2144
        utf8::decode($name);
2145 48fcda6b Origo
        $postmsg .= " deleted server $name";
2146 95b003ff Origo
        $postreply = "[]";
2147
        sleep 1;
2148
    } else {
2149
        $postreply .= "Status=ERROR Cannot delete a $dbstatus server\n";
2150
    }
2151
    return $postreply;
2152
}
2153
2154
# Delete all monitors belonging to a server
2155
sub deleteMonitors {
2156
    my ($serveruuid) = @_;
2157
    my $match;
2158
    if ($serveruuid) {
2159
        if ($register{$serveruuid}->{'user'} eq $user || $isadmin) {
2160
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf");
2161
            # undef $/; # This makes <> read in the entire file in one go
2162
            my $uuidmatch;
2163
            while (<>) {
2164
                if (/^watch (\S+)/) {
2165
                    if ($1 eq $serveruuid) {$uuidmatch = $serveruuid}
2166
                    else {$uuidmatch = ''};
2167
                };
2168
                if ($uuidmatch) {
2169
                    $match = 1;
2170
                } else {
2171
                    #chomp;
2172
                    print unless (/^hostgroup $serveruuid/);
2173
                }
2174
                close ARGV if eof;
2175
            }
2176
            #$/ = "\n";
2177
        }
2178
        unlink glob "/var/log/stabile/*:$serveruuid:*";
2179
    }
2180
    `/usr/bin/moncmd reset keepstate` if ($match);
2181
    return $match;
2182
}
2183
2184
sub deletePackages {
2185
    my ($uuid, $issystem, %packreg) = @_;
2186
    unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2187
2188
    my @domains;
2189
    if ($issystem) {
2190
        foreach my $valref (values %register) {
2191
            if (($valref->{'system'} eq $uuid || $uuid eq '*')
2192
                    && ($valref->{'user'} eq $user || $fulllist)) {
2193
                push(@domains, $valref->{'uuid'});
2194
            }
2195
        }
2196
    } else { # Allow if domain no longer exists or belongs to user
2197
        push(@domains, $uuid) if (!$register{$uuid} || $register{$uuid}->{'user'} eq $user || $fulllist);
2198
    }
2199
2200
    foreach my $domuuid (@domains) {
2201
        foreach my $packref (values %packreg) {
2202
            my $id = $packref->{'id'};
2203
            if (substr($id, 0,36) eq $domuuid || ($uuid eq '*' && $packref->{'user'} eq $user)) {
2204
                delete $packreg{$id};
2205
            }
2206
        }
2207
    }
2208
    tied(%packreg)->commit;# if (%packreg);
2209
    if ($issystem) {
2210
        my $sname = $register{$uuid}->{'name'};
2211
        utf8::decode($sname);
2212
        return "Status=OK Cleared packages for $sname\n";
2213
    } elsif ($register{$uuid}) {
2214
        my $sname = $register{$uuid}->{'name'};
2215
        utf8::decode($sname);
2216
        return "Status=OK Cleared packages for $sname\n";
2217
    } else {
2218
        return "Status=OK Cleared packages. System not registered\n";
2219
    }
2220
}
2221
2222
sub Save {
2223
    my ($uuid, $action, $obj) = @_;
2224
    if ($help) {
2225
        return <<END
2226 04c16f26 hq
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:
2227 95b003ff Origo
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.
2228
Depending on your privileges not all changes are permitted. If you save without specifying a uuid, a new server is created.
2229
If you pass [user] parameter it is assumed you want to move server to this user's account.
2230
Supported parameters:
2231
2232
uuid: UUID
2233
name: string
2234
user: string
2235 48fcda6b Origo
system: UUID of stack this server belongs to
2236 95b003ff Origo
autostart: true|false
2237
locktonode: true|false
2238
mac: MAC address of target node
2239
2240
memory: int bytes
2241
vcpu: int
2242
boot: hd|cdrom|network
2243 04c16f26 hq
loader: bios|uefi
2244 95b003ff Origo
diskbus: virtio|ide|scsi
2245
nicmodel1: virtio|rtl8139|ne2k_pci|e1000|i82551|i82557b|i82559er|pcnet
2246
vgpu: int
2247
2248
cdrom: string path
2249
image: string path
2250
image2: string path
2251
image3: string path
2252
image4: string path
2253
2254
networkuuid1: UUID of network connection
2255
networkuuid2: UUID of network connection
2256
networkuuid3: UUID of network connection
2257
2258
END
2259
    }
2260
2261
# notes, opemail, opfullname, opphone, email, fullname, phone, services, recovery, alertemail
2262
# notes: string
2263
# opemail: string
2264
# opfullname: string
2265
# opphone: string
2266
# email: string
2267
# fullname: string
2268
# phone: string
2269
# services: string
2270
# recovery: string
2271
# alertemail: string
2272
2273
    my $system = $obj->{system};
2274
    my $newsystem = $obj->{newsystem};
2275
    my $buildsystem = $obj->{buildsystem};
2276
    my $nicmac1 = $obj->{nicmac1};
2277
    $console = $console || $obj->{console};
2278
2279
    $postmsg = '' if ($buildsystem);
2280
    if (!$uuid && $nicmac1) {
2281
        $uuid = nicmac1ToUuid($nicmac1); # If no uuid try to locate based on mac
2282
    }
2283
    if (!$uuid && $uripath =~ /servers(\.cgi)?\/(.+)/) { # Try to parse uuid out of URI
2284
        my $huuid = $2;
2285
        if ($ug->to_string($ug->from_string($huuid)) eq $huuid) { # Check for valid uuid
2286
            $uuid = $huuid;
2287
        }
2288
    }
2289
    my $regserv = $register{$uuid};
2290
    my $status = $regserv->{'status'} || 'new';
2291
    if ((!$uuid) && $status eq 'new') {
2292
        my $ug = new Data::UUID;
2293
        $uuid = $ug->create_str();
2294
    };
2295
    unless ($uuid && length $uuid == 36){
2296 48fcda6b Origo
        $postmsg = "Status=Error No valid uuid ($uuid), $obj->{image}";
2297 95b003ff Origo
        return $postmsg;
2298
    }
2299
    $nicmac1 = $nicmac1 || $regserv->{'nicmac1'};
2300
    my $name = $obj->{name} || $regserv->{'name'};
2301
    my $memory = $obj->{memory} || $regserv->{'memory'};
2302
    my $vcpu = $obj->{vcpu} || $regserv->{'vcpu'};
2303
    my $image = $obj->{image} || $regserv->{'image'};
2304
    my $imagename = $obj->{imagename} || $regserv->{'imagename'};
2305
    my $image2 = $obj->{image2} || $regserv->{'image2'};
2306
    my $image2name = $obj->{image2name} || $regserv->{'image2name'};
2307
    my $image3 = $obj->{image3} || $regserv->{'image3'};
2308
    my $image3name = $obj->{image3name} || $regserv->{'image3name'};
2309
    my $image4 = $obj->{image4} || $regserv->{'image4'};
2310
    my $image4name = $obj->{image4name} || $regserv->{'image4name'};
2311
    my $diskbus = $obj->{diskbus} || $regserv->{'diskbus'};
2312
    my $cdrom = $obj->{cdrom} || $regserv->{'cdrom'};
2313
    my $boot = $obj->{boot} || $regserv->{'boot'};
2314 04c16f26 hq
    my $loader = $obj->{loader} || $regserv->{'loader'};
2315 95b003ff Origo
    my $networkuuid1 = ($obj->{networkuuid1} || $obj->{networkuuid1} eq '0')?$obj->{networkuuid1}:$regserv->{'networkuuid1'};
2316
    my $networkid1 = $obj->{networkid1} || $regserv->{'networkid1'};
2317
    my $networkname1 = $obj->{networkname1} || $regserv->{'networkname1'};
2318
    my $nicmodel1 = $obj->{nicmodel1} || $regserv->{'nicmodel1'};
2319
    my $networkuuid2 = ($obj->{networkuuid2} || $obj->{networkuuid2} eq '0')?$obj->{networkuuid2}:$regserv->{'networkuuid2'};
2320
    my $networkid2 = $obj->{networkid2} || $regserv->{'networkid2'};
2321
    my $networkname2 = $obj->{networkname2} || $regserv->{'networkname2'};
2322
    my $nicmac2 = $obj->{nicmac2} || $regserv->{'nicmac2'};
2323
    my $networkuuid3 = ($obj->{networkuuid3} || $obj->{networkuuid3} eq '0')?$obj->{networkuuid3}:$regserv->{'networkuuid3'};
2324
    my $networkid3 = $obj->{networkid3} || $regserv->{'networkid3'};
2325
    my $networkname3 = $obj->{networkname3} || $regserv->{'networkname3'};
2326
    my $nicmac3 = $obj->{nicmac3} || $regserv->{'nicmac3'};
2327
    my $notes = $obj->{notes} || $regserv->{'notes'};
2328
    my $autostart = $obj->{autostart} || $regserv->{'autostart'};
2329
    my $locktonode = $obj->{locktonode} || $regserv->{'locktonode'};
2330
    my $mac = $obj->{mac} || $regserv->{'mac'};
2331
    my $created = $regserv->{'created'} || time;
2332
    # Sanity checks
2333
    my $tenderpaths = $Stabile::config->get('STORAGE_POOLS_LOCAL_PATHS') || "/mnt/stabile/images";
2334
    my @tenderpathslist = split(/,\s*/, $tenderpaths);
2335
2336
    $networkid1 = $networkreg{$networkuuid1}->{'id'};
2337
    my $networktype1 = $networkreg{$networkuuid1}->{'type'};
2338
    my $networktype2;
2339
    if (!$nicmac1 || $nicmac1 eq "--") {$nicmac1 = randomMac();}
2340
    if ($networkuuid2 && $networkuuid2 ne "--") {
2341
        $networkid2 = $networkreg{$networkuuid2}->{'id'};
2342
        $nicmac2 = randomMac() if (!$nicmac2 || $nicmac2 eq "--");
2343
        $networktype2 = $networkreg{$networkuuid2}->{'type'};
2344
    }
2345
    if ($networkuuid3 && $networkuuid3 ne "--") {
2346
        $networkid3 = $networkreg{$networkuuid3}->{'id'};
2347
        $networkname3 = $networkreg{$networkuuid3}->{'name'};
2348
        $nicmac3 = randomMac() if (!$nicmac3 || $nicmac3 eq "--");
2349
        $networktype3 = $networkreg{$networkuuid3}->{'type'};
2350
    }
2351
2352
    my $imgdup;
2353
    my $netdup;
2354
    my $json_text; # returned if all goes well
2355
2356
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2357
2358
    if ($networkid1 > 1 && $networkid2 > 1 && $networktype1 ne 'gateway' && $networktype2 ne 'gateway'
2359
        && $networkuuid1 eq $networkuuid2) {
2360
        $netdup = 1;
2361
    }
2362
    if ($networkid1 > 1 && $networkid3 > 1 && $networktype1 ne 'gateway' && $networktype3 ne 'gateway'
2363
        && $networkuuid1 eq $networkuuid3) {
2364
        $netdup = 11;
2365
    }
2366
    if ($image eq $image2
2367
        || $image eq $image3
2368
        || $image eq $image4
2369
        || $image2 && $image2 ne '--' && $image2 eq $image3
2370
        || $image2 && $image2 ne '--' && $image2 eq $image4
2371
        || $image3 && $image3 ne '--' && $image3 eq $image4
2372
    ) {
2373
        $imgdup = 1;
2374
    } elsif ($image =~ m/\.master\.qcow2/
2375
        || $image2 =~ m/\.master\.qcow2/
2376
        || $image3 =~ m/\.master\.qcow2/
2377
        || $image4 =~ m/\.master\.qcow2/
2378
    ) {
2379
        $imgdup = 2;
2380
    } else {
2381
        # Check if another server is using image
2382
        my @regkeys = (tied %register)->select_where("user = '$user' OR user = 'common'");
2383
        foreach my $k (@regkeys) {
2384
            my $val = $register{$k};
2385 a2e0bc7e hq
            if ($val->{'uuid'} ne $uuid) {
2386 95b003ff Origo
                if (
2387 a2e0bc7e hq
                    $image eq $val->{'image'} || $image eq $val->{'image2'}|| $image eq $val->{'image3'}|| $image eq $val->{'image4'}
2388 95b003ff Origo
                ) {
2389
                    $imgdup = 51;
2390
                } elsif ($image2 && $image2 ne "--" &&
2391 a2e0bc7e hq
                    ($image2 eq $val->{'image'} || $image2 eq $val->{'image2'} || $image2 eq $val->{'image3'} || $image2 eq $val->{'image4'})
2392 95b003ff Origo
                ) {
2393
                    $imgdup = 52;
2394
                } elsif ($image3 && $image3 ne "--" &&
2395 a2e0bc7e hq
                    ($image3 eq $val->{'image'} || $image3 eq $val->{'image2'} || $image3 eq $val->{'image3'} || $image3 eq $val->{'image4'})
2396 95b003ff Origo
                ) {
2397
                    $imgdup = 53;
2398
                } elsif ($image4 && $image4 ne "--" &&
2399 a2e0bc7e hq
                    ($image4 eq $val->{'image'} || $image4 eq $val->{'image2'} || $image4 eq $val->{'image3'} || $image4 eq $val->{'image4'})
2400 95b003ff Origo
                ) {
2401
                    $imgdup = 54;
2402
                }
2403
2404
                if ($networkid1>1) {
2405
                    if ($networktype1 ne 'gateway' &&
2406 a2e0bc7e hq
                        ($networkuuid1 eq $val->{'networkuuid1'} || $networkuuid1 eq $val->{'networkuuid2'})
2407 95b003ff Origo
                    ) {
2408
                        $netdup = 51;
2409
                    }
2410
                }
2411
                if ($networkid2>1) {
2412
                    if ($networktype2 ne 'gateway' && $networkuuid2 && $networkuuid2 ne "--" &&
2413 a2e0bc7e hq
                        ($networkuuid2 eq $val->{'networkuuid1'} || $networkuuid2 eq $val->{'networkuuid2'})
2414 95b003ff Origo
                    ) {
2415
                        $netdup = 52;
2416
                    }
2417
                }
2418
            }
2419
        }
2420
        my $legalpath;
2421
        if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2422
            $legalpath = 1;
2423
        } else {
2424
            foreach my $path (@tenderpathslist) {
2425
                if ($image =~ m/$path\/$user/) {
2426
                    $legalpath = 1;
2427
                    last;
2428
                }
2429
            }
2430
        }
2431
        $imgdup = 6 unless $legalpath;
2432
        if ($image2 && $image2 ne "--") { # TODO: We should probably check for conflicting nodes for image3 and image 4 too
2433
            if ($image2 =~ m/\/mnt\/stabile\/node\/$user/) {
2434
                if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2435
                    if ($imagereg{$image}->{'mac'} eq $imagereg{$image2}->{'mac'}) {
2436
                        $legalpath = 1;
2437
                    } else {
2438
                        $legalpath = 0; # Images are on two different nodes
2439
                    }
2440
                } else {
2441
                    $legalpath = 1;
2442
                }
2443
            } else {
2444
                $legalpath = 0;
2445
                foreach my $path (@tenderpathslist) {
2446
                    if ($image2 =~ m/$path\/$user/) {
2447
                        $legalpath = 1;
2448
                        last;
2449
                    }
2450
                }
2451
            }
2452
            $imgdup = 7 unless $legalpath;
2453
        }
2454
    }
2455
2456
    if (!$imgdup && !$netdup) {
2457
        if ($status eq "new") {
2458
            $status = "shutoff";
2459
            $name = $name || 'New Server';
2460
            $memory = $memory || 1024;
2461
            $vcpu = $vcpu || 1;
2462
            $imagename = $imagename || '--';
2463
            $image2 = $image2 || '--';
2464
            $image2name = $image2name || '--';
2465
            $image3 = $image3 || '--';
2466
            $image3name = $image3name || '--';
2467
            $image4 = $image4 || '--';
2468
            $image4name = $image4name || '--';
2469
            $diskbus = $diskbus || 'ide';
2470
            $cdrom = $cdrom || '--';
2471
            $boot = $boot || 'hd';
2472 04c16f26 hq
            $loader = $loader || 'bios';
2473 95b003ff Origo
            $networkuuid1 = $networkuuid1 || 1;
2474
            $networkid1 = $networkid1 || 1;
2475
            $networkname1 = $networkname1 || '--';
2476
            $nicmodel1 = $nicmodel1 || 'rtl8139';
2477
            $nicmac1 = $nicmac1 || randomMac();
2478
            $networkuuid2 = $networkuuid2 || '--';
2479
            $networkid2 = $networkid2 || '--';
2480
            $networkname2 = $networkname2 || '--';
2481
            $nicmac2 = $nicmac2 || randomMac();
2482
            $networkuuid3 = $networkuuid3 || '--';
2483
            $networkid3 = $networkid3 || '--';
2484
            $networkname3 = $networkname3 || '--';
2485
            $nicmac3 = $nicmac3 || randomMac();
2486
            #    $uiuuid = $uuid; # No need to update ui for new server with jsonreststore
2487 8d7785ff Origo
            $postmsg .= "OK Created new server: $name";
2488 3657de20 Origo
            $postmsg .= ", uuid: $uuid " if ($console);
2489 95b003ff Origo
        }
2490
        # Update status of images
2491
        my @imgs = ($image, $image2, $image3, $image4);
2492
        my @imgkeys = ('image', 'image2', 'image3', 'image4');
2493
        for (my $i=0; $i<4; $i++) {
2494
            my $img = $imgs[$i];
2495
            my $k = $imgkeys[$i];
2496
            my $regimg = $imagereg{$img};
2497
            # if ($img && $img ne '--' && ($status eq 'new' || $img ne $regserv->{$k})) { # Servers image changed - update image status
2498
            if ($img && $img ne '--') { # Always update image status
2499
                $regimg->{'status'} = 'used' if (
2500
                    $regimg->{'status'} eq 'unused'
2501
                        # Image cannot be active if server is shutoff
2502
                        || ($regimg->{'status'} eq 'active' && $status eq 'shutoff')
2503
                );
2504
                $regimg->{'domains'} = $uuid;
2505
                $regimg->{'domainnames'} = $name;
2506
            }
2507
            # If image has changed, release the old image
2508
            if ($status ne 'new' && $img ne $regserv->{$k} && $imagereg{$regserv->{$k}}) {
2509
                $imagereg{$regserv->{$k}}->{'status'} = 'unused';
2510
                delete $imagereg{$regserv->{$k}}->{'domains'};
2511
                delete $imagereg{$regserv->{$k}}->{'domainnames'};
2512
            }
2513
        }
2514
2515
        my $valref = {
2516
            uuid=>$uuid,
2517
            user=>$user,
2518
            name=>$name,
2519
            memory=>$memory,
2520
            vcpu=>$vcpu,
2521
            image=>$image,
2522
            imagename=>$imagename,
2523
            image2=>$image2,
2524
            image2name=>$image2name,
2525
            image3=>$image3,
2526
            image3name=>$image3name,
2527
            image4=>$image4,
2528
            image4name=>$image4name,
2529
            diskbus=>$diskbus,
2530
            cdrom=>$cdrom,
2531
            boot=>$boot,
2532 04c16f26 hq
            loader=>$loader,
2533 95b003ff Origo
            networkuuid1=>$networkuuid1,
2534
            networkid1=>$networkid1,
2535
            networkname1=>$networkname1,
2536
            nicmodel1=>$nicmodel1,
2537
            nicmac1=>$nicmac1,
2538
            networkuuid2=>$networkuuid2,
2539
            networkid2=>$networkid2,
2540
            networkname2=>$networkname2,
2541
            nicmac2=>$nicmac2,
2542
            networkuuid3=>$networkuuid3,
2543
            networkid3=>$networkid3,
2544
            networkname3=>$networkname3,
2545
            nicmac3=>$nicmac3,
2546
            status=>$status,
2547
            notes=>$notes,
2548
            autostart=>$autostart,
2549
            locktonode=>$locktonode,
2550
            action=>"",
2551
            created=>$created
2552
        };
2553
        $valref->{'system'} = $system if ($system);
2554
        if ($mac && $locktonode eq 'true') {
2555
            $valref->{'mac'} = $mac;
2556
            $valref->{'macip'} = $nodereg{$mac}->{'ip'};
2557
            $valref->{'macname'} = $nodereg{$mac}->{'name'};
2558
        }
2559
        if ($newsystem) {
2560
            my $ug = new Data::UUID;
2561
            $sysuuid = $ug->create_str();
2562
            $valref->{'system'} = $sysuuid;
2563 3657de20 Origo
            $postmsg .= "OK sysuuid: $sysuuid " if ($console);
2564 95b003ff Origo
        }
2565
2566
        # Remove domain uuid from old networks. Leave gateways alone - they get updated on next listing
2567
        my $oldnetworkuuid1 = $regserv->{'networkuuid1'};
2568
        if ($oldnetworkuuid1 ne $networkuuid1 && $networkreg{$oldnetworkuuid1}) {
2569
            $networkreg{$oldnetworkuuid1}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2570
        }
2571
        $register{$uuid} = validateItem($valref);
2572
2573
        if ($networkreg{$networkuuid1}->{'type'} eq 'gateway') {
2574 04c16f26 hq
            # We now remove before adding to support API calls that dont necessarily list afterwards
2575
            $networkreg{$networkuuid1}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2576 95b003ff Origo
            my $domains = $networkreg{$networkuuid1}->{'domains'};
2577
            $networkreg{$networkuuid1}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2578 04c16f26 hq
2579
            $networkreg{$networkuuid1}->{'domainnames'} =~ s/($name)(,?)( ?)//;
2580 95b003ff Origo
            my $domainnames = $networkreg{$networkuuid1}->{'domainnames'};
2581
            $networkreg{$networkuuid1}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2582
        } else {
2583
            $networkreg{$networkuuid1}->{'domains'}  = $uuid;
2584
            $networkreg{$networkuuid1}->{'domainnames'}  = $name;
2585
        }
2586
2587
        if ($networkuuid2 && $networkuuid2 ne '--') {
2588
            if ($networkreg{$networkuuid2}->{'type'} eq 'gateway') {
2589 04c16f26 hq
                $networkreg{$networkuuid2}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2590 95b003ff Origo
                my $domains = $networkreg{$networkuuid2}->{'domains'};
2591
                $networkreg{$networkuuid2}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2592 04c16f26 hq
2593
                $networkreg{$networkuuid2}->{'domainnames'} =~ s/($name)(,?)( ?)//;
2594 95b003ff Origo
                my $domainnames = $networkreg{$networkuuid2}->{'domainnames'};
2595
                $networkreg{$networkuuid2}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2596
            } else {
2597
                $networkreg{$networkuuid2}->{'domains'}  = $uuid;
2598
                $networkreg{$networkuuid2}->{'domainnames'}  = $name;
2599
            }
2600
        }
2601
2602
        if ($networkuuid3 && $networkuuid3 ne '--') {
2603
            if ($networkreg{$networkuuid3}->{'type'} eq 'gateway') {
2604
                my $domains = $networkreg{$networkuuid3}->{'domains'};
2605
                $networkreg{$networkuuid3}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2606
                my $domainnames = $networkreg{$networkuuid3}->{'domainnames'};
2607
                $networkreg{$networkuuid3}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2608
            } else {
2609
                $networkreg{$networkuuid3}->{'domains'}  = $uuid;
2610
                $networkreg{$networkuuid3}->{'domainnames'}  = $name;
2611
            }
2612
        }
2613
        my %jitem = %{$register{$uuid}};
2614
        $json_text = to_json(\%jitem, {pretty=>1});
2615
        $json_text =~ s/null/"--"/g;
2616
        $uiuuid = $uuid;
2617
        $uiname = $name;
2618
2619
        tied(%register)->commit;
2620
        tied(%networkreg)->commit;
2621 a2e0bc7e hq
        tied(%imagereg)->commit;
2622 95b003ff Origo
2623
    } else {
2624 48fcda6b Origo
        $postmsg .= "ERROR This image ($image) cannot be used ($imgdup) " if ($imgdup);
2625
        $postmsg .= "ERROR This network ($networkname1) cannot be used ($netdup)" if ($netdup);
2626 95b003ff Origo
    }
2627
2628
    my $domuser = $obj->{'user'};
2629
    # We were asked to move server to another account
2630
    if ($domuser && $domuser ne '--' && $domuser ne $user) {
2631
        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")};
2632
        if ($status eq 'shutoff' || $status eq 'inactive') {
2633
            unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {$posterror =  "Unable to access user register"; return 0;};
2634
            my @accounts = split(/,\s*/, $userreg{$tktuser}->{'accounts'});
2635
            my @accountsprivs = split(/,\s*/, $userreg{$tktuser}->{'accountsprivileges'});
2636
            %ahash = ($tktuser, $userreg{$tktuser}->{'privileges'}); # Include tktuser in accounts hash
2637
            for my $i (0 .. scalar @accounts)
2638
            {
2639
                next unless $accounts[$i];
2640
                $ahash{$accounts[$i]} = $accountsprivs[$i] || 'r';
2641
            }
2642
            untie %userreg;
2643
2644
            if (!$isreadonly && $ahash{$domuser} && !($ahash{$domuser} =~ /r/)) { # Check if user is allow to access account
2645
                my $imgdone;
2646
                my $netdone;
2647
                # First move main image
2648
                $Stabile::Images::user = $user;
2649
                require "$Stabile::basedir/cgi/images.cgi";
2650
                $Stabile::Images::console = 1;
2651
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving image $imagename to account: $domuser"});
2652
                my $nimage = Stabile::Images::Move($image, $domuser);
2653 48fcda6b Origo
                chomp $nimage;
2654 95b003ff Origo
                if ($nimage) {
2655
                    $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2656
                    $register{$uuid}->{'image'} = $nimage;
2657
                    $imgdone = 1;
2658
                } else {
2659
                    $main::syslogit->($user, "info", "Unable to move image $imagename to account: $domuser");
2660
                }
2661
                # Move other attached images
2662
                my @images = ($image2, $image3, $image4);
2663
                my @imagenames = ($image2name, $image3name, $image4name);
2664
                my @imagekeys = ('image2', 'image3', 'image4');
2665
                for (my $i=0; $i<3; $i++) {
2666
                    my $img = $images[$i];
2667
                    my $imgname = $imagenames[$i];
2668
                    my $imgkey = $imagekeys[$i];
2669
                    if ($img && $img ne '--') {
2670
                        $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving $imgkey $imgname to account: $domuser"});
2671
                        $nimage = Stabile::Images::Move($img, $domuser);
2672 48fcda6b Origo
                        chomp $nimage;
2673 95b003ff Origo
                        if ($nimage) {
2674
                            $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2675
                            $register{$uuid}->{$imgkey} = $nimage;
2676
                        } else {
2677
                            $main::syslogit->($user, "info", "Unable to move $imagekeys[$i] $img to account: $domuser");
2678
                        }
2679
                    }
2680
                }
2681 6fdc8676 hq
                # Then move network(s)
2682 95b003ff Origo
                if ($imgdone) {
2683
                    $Stabile::Networks::user = $user;
2684
                    require "$Stabile::basedir/cgi/networks.cgi";
2685
                    $Stabile::Networks::console = 1;
2686
                    my @networks = ($networkuuid1, $networkuuid2, $networkuuid3);
2687
                    my @netkeys = ('networkuuid1', 'networkuuid2', 'networkuuid3');
2688
                    my @netnamekeys = ('networkname1', 'networkname2', 'networkname3');
2689
                    for (my $i=0; $i<scalar @networks; $i++) {
2690
                        my $net = $networks[$i];
2691
                        my $netkey = $netkeys[$i];
2692
                        my $netnamekey = $netnamekeys[$i];
2693 48fcda6b Origo
                        my $regnet = $networkreg{$net};
2694
                        my $oldid = $regnet->{'id'};
2695 95b003ff Origo
                        next if ($net eq '' || $net eq '--');
2696 48fcda6b Origo
                        if ($regnet->{'type'} eq 'gateway') {
2697 95b003ff Origo
                            if ($oldid > 1) { # Private gateway
2698
                                foreach my $networkvalref (values %networkreg) { # use gateway with same id if it exists
2699
                                    if ($networkvalref->{'user'} eq $domuser
2700
                                        && $networkvalref->{'type'} eq 'gateway'
2701
                                        && $networkvalref->{'id'} == $oldid) {
2702
                                        # We found an existing gateway with same id - use it
2703
                                        $register{$uuid}->{$netkey} = $networkvalref->{'uuid'};
2704
                                        $register{$uuid}->{$netnamekey} = $networkvalref->{'name'};
2705
                                        $netdone = 1;
2706
                                        $main::updateUI->({tab=>"networks", user=>$user, message=>"Using network $networkvalref->{'name'} from account: $domuser"});
2707
                                        last;
2708
                                    }
2709
                                }
2710
                                if (!($netdone)) {
2711
                                    # Make a new gateway
2712
                                    my $ug = new Data::UUID;
2713
                                    my $newuuid = $ug->create_str();
2714 48fcda6b Origo
                                    Stabile::Networks::save($oldid, $newuuid, $regnet->{'name'}, 'new', 'gateway', '', '', $regnet->{'ports'}, 0, $domuser);
2715 95b003ff Origo
                                    $register{$uuid}->{$netkey} = $newuuid;
2716 48fcda6b Origo
                                    $register{$uuid}->{$netnamekey} = $regnet->{'name'};
2717 95b003ff Origo
                                    $netdone = 1;
2718 48fcda6b Origo
                                    $main::updateUI->({tab=>"networks", user=>$user, message=>"Created gateway $regnet->{'name'} for account: $domuser"});
2719
                                    $main::syslogit->($user, "info", "Created gateway $regnet->{'name'} for account: $domuser");
2720 95b003ff Origo
                                }
2721
                            } elsif ($oldid==0 || $oldid==1) {
2722
                                $netdone = 1; # Use common gateway
2723 48fcda6b Origo
                                $main::updateUI->({tab=>"networks", user=>$user, message=>"Reused network $regnet->{'name'} for account: $domuser"});
2724 95b003ff Origo
                            }
2725
                        } else {
2726
                            my $newid = Stabile::Networks::getNextId('', $domuser);
2727
                            $networkreg{$net}->{'id'} = $newid;
2728
                            $networkreg{$net}->{'user'} = $domuser;
2729 6fdc8676 hq
                        #    if ($regnet->{'type'} eq 'internalip' || $regnet->{'type'} eq 'ipmapping') {
2730 95b003ff Origo
                                # Deactivate network and assign new internal ip
2731 48fcda6b Origo
                                Stabile::Networks::Deactivate($regnet->{'uuid'});
2732 95b003ff Origo
                                $networkreg{$net}->{'internalip'} =
2733 48fcda6b Origo
                                    Stabile::Networks::getNextInternalIP('',$regnet->{'uuid'}, $newid, $domuser);
2734 6fdc8676 hq
                        #    }
2735 95b003ff Origo
                            $netdone = 1;
2736 48fcda6b Origo
                            $main::updateUI->({tab=>"networks", user=>$user, message=>"Moved network $regnet->{'name'} to account: $domuser"});
2737
                            $main::syslogit->($user, "info", "Moved network $regnet->{'name'} to account: $domuser");
2738 95b003ff Origo
                        }
2739
                    }
2740
                    if ($netdone) {
2741
                        # Finally move the server
2742
                        $register{$uuid}->{'user'} = $domuser;
2743 48fcda6b Origo
                        $postmsg .= "OK Moved server $name to account: $domuser";
2744 95b003ff Origo
                        $main::syslogit->($user, "info", "Moved server $name ($uuid) to account: $domuser");
2745 48fcda6b Origo
                        $main::updateUI->({tab=>"servers", user=>$user, type=>"update"});
2746 51e32e00 hq
                        # Remove the server's IP from pressurecontrol's cache
2747
                        # Repeat 8 times because pressurecontrol runs 8 http daemons
2748
                        for (my $i = 0; $i < 8; $i++) {
2749
                            my $nuuid = $register{$uuid}->{networkuuid1};
2750
                            `curl "http://localhost:8082//http://$nuuid/networkreload"`;
2751
                        }
2752 95b003ff Origo
                    } else {
2753 48fcda6b Origo
                        $postmsg .= "ERROR Unable to move network to account: $domuser";
2754 95b003ff Origo
                        $main::updateUI->({tab=>"image", user=>$user, message=>"Unable to move network to account: $domuser"});
2755
                    }
2756
                } else {
2757
                    $main::updateUI->({tab=>"image", user=>$user, message=>"Could not move image to account: $domuser"});
2758
                }
2759
            } else {
2760 48fcda6b Origo
                $postmsg .= "ERROR No access to move server";
2761 95b003ff Origo
            }
2762
        } else {
2763 48fcda6b Origo
            $postmsg .= "Error Unable to move $status server";
2764 95b003ff Origo
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Please shut down before moving server"});
2765
        }
2766
        untie %userreg;
2767
    }
2768
2769
    if ($console) {
2770
        $postreply = $postmsg;
2771
    } else {
2772
        $postreply = $json_text || $postmsg;
2773
    }
2774
    return $postreply;
2775
    untie %imagereg;
2776
}
2777
2778
2779
sub Shutdown {
2780
    my ($uuid, $action, $obj) = @_;
2781
    if ($help) {
2782
        return <<END
2783
GET:uuid:
2784
Marks a server for shutdown, i.e. send and ACPI shutdown event to the server. If OS supports ACPI, it begins a shutdown.
2785
END
2786
    }
2787
    $uistatus = "shuttingdown";
2788
    my $dbstatus = $obj->{status};
2789
    my $mac = $obj->{mac};
2790
    my $macname = $obj->{macname};
2791
    my $name = $obj->{name};
2792
    if ($dbstatus eq 'running') {
2793
        my $tasks;
2794
        $tasks = $nodereg{$mac}->{'tasks'} if ($nodereg{$mac});
2795
        $nodereg{$mac}->{'tasks'} = $tasks . "SHUTDOWN $uuid $user\n";
2796
        tied(%nodereg)->commit;
2797
        $register{$uuid}->{'status'} = $uistatus;
2798
        $register{$uuid}->{'statustime'} = $current_time;
2799
        $uiuuid = $uuid;
2800
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2801
        $postreply .= "Status=$uistatus OK $uistatus $name\n";
2802
    } else {
2803
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2804
        $postreply .= "Status=ERROR problem $uistatus $name...\n";
2805
    }
2806
    return $postreply;
2807
}
2808
2809
sub Suspend {
2810
    my ($uuid, $action, $obj) = @_;
2811
    if ($help) {
2812
        return <<END
2813
GET:uuid:
2814
Marks a server for suspend, i.e. pauses the server. Server must be running
2815
END
2816
    }
2817
    $uistatus = "suspending";
2818
    my $dbstatus = $obj->{status};
2819
    my $mac = $obj->{mac};
2820
    my $macname = $obj->{macname};
2821
    my $name = $obj->{name};
2822 a2e0bc7e hq
    my $areply = '';
2823 95b003ff Origo
    if ($dbstatus eq 'running') {
2824
        my $tasks = $nodereg{$mac}->{'tasks'};
2825
        $nodereg{$mac}->{'tasks'} = $tasks . "SUSPEND $uuid $user\n";
2826
        tied(%nodereg)->commit;
2827
        $register{$uuid}->{'status'} = $uistatus;
2828
        $register{$uuid}->{'statustime'} = $current_time;
2829
        $uiuuid = $uuid;
2830
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2831 a2e0bc7e hq
        $areply .= "Status=$uistatus OK $uistatus $name.\n";
2832 95b003ff Origo
    } else {
2833
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2834 a2e0bc7e hq
        $areply .= "Status=ERROR problem $uistatus $name.\n";
2835 95b003ff Origo
    }
2836 a2e0bc7e hq
    return $areply;
2837 95b003ff Origo
}
2838
2839
sub Resume {
2840
    my ($uuid, $action, $obj) = @_;
2841
    if ($help) {
2842
        return <<END
2843
GET:uuid:
2844
Marks a server for resume running. Server must be paused.
2845
END
2846
    }
2847
    my $dbstatus = $obj->{status};
2848
    my $mac = $obj->{mac};
2849
    my $macname = $obj->{macname};
2850
    my $name = $obj->{name};
2851
    my $image = $obj->{image};
2852
    my $image2 = $obj->{image2};
2853
    my $image3 = $obj->{image3};
2854
    my $image4 = $obj->{image4};
2855
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$posterror = "Unable to access image register"; return;};
2856
    if ($imagereg{$image}->{'status'} ne "paused"
2857
        || ($image2 && $image2 ne '--' && $imagereg{$image}->{'status'} ne "paused")
2858
        || ($image3 && $image3 ne '--' && $imagereg{$image3}->{'status'} ne "paused")
2859
        || ($image4 && $image4 ne '--' && $imagereg{$image4}->{'status'} ne "paused")
2860
    ) {
2861
        $postreply .= "Status=ERROR Image $uuid busy ($imagereg{$image}->{'status'}), please wait 30 sec.\n";
2862
        untie %imagereg;
2863
        return $postreply   ;
2864
    } else {
2865
        untie %imagereg;
2866
    }
2867
    $uistatus = "resuming";
2868
    if ($dbstatus eq 'paused') {
2869
        my $tasks = $nodereg{$mac}->{'tasks'};
2870
        $nodereg{$mac}->{'tasks'} = $tasks . "RESUME $uuid $user\n";
2871
        tied(%nodereg)->commit;
2872
        $register{$uuid}->{'status'} = $uistatus;
2873
        $register{$uuid}->{'statustime'} = $current_time;
2874
        $uiuuid = $uuid;
2875
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2876
        $postreply .= "Status=$uistatus OK $uistatus ". $register{$uuid}->{'name'} . "\n";
2877
    } else {
2878
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2879
        $postreply .= "Status=ERROR problem $uistatus ". $register{$uuid}->{'name'} . "\n";
2880
    }
2881
    return $postreply;
2882
}
2883
2884 d3805c61 hq
sub Abort {
2885
    my ($uuid, $action, $obj) = @_;
2886
    if ($help) {
2887
        return <<END
2888
GET:uuid,mac:
2889
Aborts an ongoing server move between nodes initiated with move or stormove.
2890
END
2891
    }
2892
    my $dbstatus = $obj->{status};
2893
    my $dmac = $obj->{mac};
2894
    my $name = $obj->{name};
2895
    if ($isadmin || $register{$uuid}->{user} eq $user) {
2896
        my $tasks = $nodereg{$dmac}->{'tasks'};
2897
        $tasks .= "ABORT $uuid $user\n";
2898
        $nodereg{$dmac}->{'tasks'} = $tasks;
2899
        tied(%nodereg)->commit;
2900
        $postreply = "Status=aborting Aborting move of server $name ($dbstatus) on node $dmac\n";
2901
    } else {
2902
        $postreply = "Status=OK Insufficient privileges\n";
2903
    }
2904
}
2905
2906 95b003ff Origo
sub Move {
2907
    my ($uuid, $action, $obj) = @_;
2908
    if ($help) {
2909
        return <<END
2910
GET:uuid,mac:
2911 d3805c61 hq
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.
2912 95b003ff Origo
END
2913
    }
2914
    my $dbstatus = $obj->{status};
2915
    my $dmac = $obj->{mac};
2916
    my $name = $obj->{name};
2917
    my $mem = $obj->{memory};
2918
    my $vcpu = $obj->{vcpu};
2919
    my $image = $obj->{image};
2920
    my $image2 = $obj->{image2};
2921
    my $image3 = $obj->{image3};
2922
    my $image4 = $obj->{image4};
2923 d3805c61 hq
2924 95b003ff Origo
    $uistatus = "moving";
2925
    if ($dbstatus eq 'running' && $isadmin) {
2926
        my $hypervisor = getHypervisor($image);
2927
        my $mac = $register{$uuid}->{'mac'};
2928
        $dmac = "" if ($dmac eq "--");
2929
        $mac = "" if ($mac eq "--");
2930
2931 d3805c61 hq
        if (( $image =~ /\/mnt\/stabile\/node\//
2932 95b003ff Origo
            || $image2 =~ /\/mnt\/stabile\/node\//
2933
            || $image3 =~ /\/mnt\/stabile\/node\//
2934 d3805c61 hq
            || $image4 =~ /\/mnt\/stabile\/node\// ) && $action ne 'stormove'
2935 95b003ff Origo
        ) {
2936 d3805c61 hq
            $postreply = qq|{"error": 1, "message": "Servers with local storage must be moved with stormove"}|;
2937
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Servers with local storage must be moved with stormove"});
2938 95b003ff Origo
        } else {
2939
            my ($targetmac, $targetname, $targetip, $port) =
2940 d3805c61 hq
                locateTargetNode($uuid, $dmac, $mem, $vcpu, $image, $image2, $image3, $image4, $hypervisor, $mac, 1);
2941 95b003ff Origo
            if ($targetmac) {
2942
                my $tasks = $nodereg{$targetmac}->{'tasks'};
2943 d3805c61 hq
                if ($action eq 'stormove') {
2944
                    $tasks = $tasks . "RECEIVESTOR $uuid $user\n";
2945
                } else {
2946
                    $tasks = $tasks . "RECEIVE $uuid $user\n";
2947
                }
2948 95b003ff Origo
                # Also update allowed port forwards
2949
                $nodereg{$targetmac}->{'tasks'} = $tasks . "PERMITOPEN $user\n";
2950
                $register{$uuid}->{'status'} = "moving";
2951
                $register{$uuid}->{'statustime'} = $current_time;
2952
                $uiuuid = $uuid;
2953
                $uidisplayip = $targetip;
2954
                $uidisplayport = $port;
2955
                $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus to $targetname ($targetmac)");
2956
                $postreply .= "Status=OK $uistatus ". $register{$uuid}->{'name'} . "\n";
2957
2958 d3805c61 hq
                # Precreate images on destination node
2959
                if ($action eq 'stormove') {
2960
                    my $preimages = '';
2961
                    $Stabile::Images::user = $user;
2962
                    require "$Stabile::basedir/cgi/images.cgi";
2963
                    $Stabile::Images::console = 1;
2964
                    if ($targetip eq '10.0.0.1') { # Moving from node
2965
                        if ($image =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2966
                            my $res = Stabile::Images::Move($image, $user, '0', '', 0, 1);
2967
                            $preimages .= " $register{$uuid}->{imagename}";
2968
                        }
2969
                        if ($image2 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2970
                            my $res = Stabile::Images::Move($image2, $user, '0', '', 0, 1);
2971
                            $preimages .= " $register{$uuid}->{image2name}";
2972
                        }
2973
                        if ($image3 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2974
                            my $res = Stabile::Images::Move($image3, $user, '0', '', 0, 1);
2975
                            $preimages .= " $register{$uuid}->{image3name}";
2976
                        }
2977
                        if ($image4 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2978
                            my $res = Stabile::Images::Move($image4, $user, '0', '', 0, 1);
2979
                            $preimages .= " $register{$uuid}->{image4name}";
2980
                        }
2981
                    } else { # Moving to node or between nodes - always move primary image, also if on shared storage
2982
                        my $res = Stabile::Images::Move($image, $user, '-1', $targetmac, 0, 1);
2983
                        $preimages .= " $register{$uuid}->{imagename}";
2984
                        if ($image2 && $image2 ne '--') {
2985
                            # We don't migrate data disks away from shared storage
2986
                            unless ($image2 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
2987
                                my $res = Stabile::Images::Move($image2, $user, '-1', $targetmac, 0, 1);
2988
                                $preimages .= " $register{$uuid}->{image2name}";
2989
                            }
2990
                        }
2991
                        if ($image3 && $image3 ne '--') {
2992
                            unless ($image3 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
2993
                                my $res = Stabile::Images::Move($image3, $user, '-1', $targetmac, 0, 1);
2994
                                $preimages .= " $register{$uuid}->{image3name}";
2995
                            }
2996
                        }
2997
                        if ($image4 && $image4 ne '--') {
2998
                            unless ($image4 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
2999
                                my $res = Stabile::Images::Move($image4, $user, '-1', $targetmac, 0, 1);
3000
                                $preimages .= " $register{$uuid}->{image4name}";
3001
                            }
3002
                        }
3003
                    }
3004
                    if ($preimages) {
3005
                        $main::syslogit->($user, "info", "Precreating images $preimages on node $targetmac");
3006
                        $main::updateUI->({tab=>"servers", user=>$user, message=>"Precreating images $preimages on node $targetmac"});
3007
                    }
3008
                }
3009 95b003ff Origo
                if ($params{'PUTDATA'}) {
3010
                    my %jitem = %{$register{$uuid}};
3011
                    my $json_text = to_json(\%jitem);
3012
                    $json_text =~ s/null/"--"/g;
3013
                    $postreply = $json_text;
3014
                }
3015 d3805c61 hq
#                $main::updateUI->({tab=>"servers", user=>$user, status=>'moving', uuid=>$uuid, type=>'update', message=>"Moving $register{$uuid}->{name} to $targetmac"});
3016 95b003ff Origo
            } else {
3017
                $main::syslogit->($user, "info", "Could not find $hypervisor target for $uistatus $uuid ($image)");
3018 d3805c61 hq
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Could not find target for $uistatus $register{$uuid}->{'name'}"});
3019 95b003ff Origo
                $postreply = qq|{"error": 1, "message": "Could not find target for $uistatus $register{$uuid}->{'name'}"}|;
3020
            }
3021
        }
3022
    } else {
3023
        $main::syslogit->($user, "info", "Problem moving a $dbstatus domain: $uuid");
3024 d3805c61 hq
        my $serv = $register{$uuid};
3025
        $postreply .= qq|{"error": 1, "message": "ERROR problem moving $serv->{'name'} ($dbstatus)"}|;
3026 95b003ff Origo
    }
3027
    return $postreply;
3028
}
3029
3030 c899e439 Origo
sub Changepassword {
3031
    my ($uuid, $action, $obj) = @_;
3032
    if ($help) {
3033
        return <<END
3034
POST:uuid,username,password:
3035
Attempts to set password for [username] to [password] using guestfish. If no username is specified, user 'stabile' is assumed.
3036
END
3037
    }
3038
    my $img = $register{$uuid}->{'image'};
3039
    my $username = $obj->{'username'} || 'stabile';
3040
    my $password = $obj->{'password'};
3041
    return "Status=Error Please supply a password\n" unless ($password);
3042
    return "Status=Error Please shut down the server before changing password\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
3043
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
3044
3045
    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;};
3046
    my $cmd = qq/guestfish --rw -a $img -i command "bash -c 'echo $username:$password | chpasswd'" 2>\&1/;
3047
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
3048
        my $mac = $imagereg{$img}->{'mac'};
3049
        my $macip = $nodereg{$mac}->{'ip'};
3050
        $cmd = "$sshcmd $macip $cmd";
3051
    }
3052
    my $res = `$cmd`;
3053
    $res = $1 if ($res =~ /guestfish: (.*)/);
3054
    chomp $res;
3055
    return "Status=OK Ran chpasswd for user $username in server $register{$uuid}->{'name'}: $res\n";
3056
}
3057
3058
sub Sshaccess {
3059
    my ($uuid, $action, $obj) = @_;
3060
    if ($help) {
3061
        return <<END
3062
POST:uuid,address:
3063
Attempts to change the ip addresses you can access the server over SSH (port 22) from, by adding [address] to /etc/hosts.allow.
3064
[address] should either be an IP address or a range in CIDR notation. Please note that no validation of [address] is performed.
3065
END
3066
    }
3067
    my $img = $register{$uuid}->{'image'};
3068
    my $address = $obj->{'address'};
3069
    return "Status=Error Please supply an aaddress\n" unless ($address);
3070
    return "Status=Error Please shut down the server before changing SSH access\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
3071
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
3072
3073
    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;};
3074
3075
    my $isshcmd = '';
3076
    my $cmd = qq[guestfish --rw -a $img -i command "sed -i -re 's|(sshd: .*)#stabile|\\1 $address #stabile|' /etc/hosts.allow"];
3077
#    my $cmd = qq[guestfish --rw -a $img -i command "bash -c 'echo sshd: $address >> /etc/hosts.allow'"];
3078
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
3079
        my $mac = $imagereg{$img}->{'mac'};
3080
        my $macip = $nodereg{$mac}->{'ip'};
3081
        $isshcmd = "$sshcmd $macip ";
3082
    }
3083
    my $res = `$isshcmd$cmd`;
3084
    chomp $res;
3085
    #$cmd = qq[guestfish --rw -a $img -i command "bash -c 'cat /etc/hosts.allow'"];
3086
    #$res .= `$isshcmd$cmd`;
3087
    #chomp $res;
3088
    return "Status=OK Tried to add sshd: $address to /etc/hosts.allow in server $register{$uuid}->{'name'}\n";
3089
}
3090
3091 95b003ff Origo
sub Mountcd {
3092
    my ($uuid, $action, $obj) = @_;
3093
    if ($help) {
3094
        return <<END
3095
GET:uuid,cdrom:
3096
Mounts a cdrom on a server. Server must be running. Mounting the special cdrom named '--' unomunts any currently mounted cdrom.
3097
END
3098
    }
3099
    my $dbstatus = $obj->{status};
3100
    my $mac = $obj->{mac};
3101
    my $cdrom = $obj->{cdrom};
3102
    unless ($cdrom && $dbstatus eq 'running') {
3103
        $main::updateUI->({tab=>"servers", user=>$user, uuid=>$uuid, type=>'update', message=>"Unable to mount cdrom"});
3104
        $postreply = qq|{"Error": 1, "message": "Problem mounting cdrom on $obj->{name}"}|;
3105
        return;
3106
    }
3107
    my $tasks = $nodereg{$mac}->{'tasks'};
3108
    # $user is in the middle here, because $cdrom may contain spaces...
3109
    $nodereg{$mac}->{'tasks'} = $tasks . "MOUNT $uuid $user \"$cdrom\"\n";
3110
    tied(%nodereg)->commit;
3111
    if ($cdrom eq "--") {
3112
        $postreply = qq|{"OK": 1, "message": "OK unmounting cdrom from $obj->{name}"}|;
3113
    } else {
3114
        $postreply = qq|{"OK": 1, "message": "OK mounting cdrom $cdrom on $obj->{name}"}|;
3115
    }
3116
    $register{$uuid}->{'cdrom'} = $cdrom unless ($cdrom eq 'virtio');
3117
    return $postreply;
3118
}