Project

General

Profile

Download (126 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
#        if ($vgpu && $vgpu ne "--") {
1181
#            $xml .= <<ENDXML2
1182
#  <qemu:commandline>
1183
#    <qemu:arg value='-device'/>
1184
#    <qemu:arg value='vfio-pci,host=01:00.0,x-vga=on'/>
1185
#    <qemu:arg value='-device'/>
1186
#    <qemu:arg value='vfio-pci,host=02:00.0,x-vga=on'/>
1187
#  </qemu:commandline>
1188
#ENDXML2
1189
#            ;
1190
#        }
1191
1192
#    <qemu:arg value='-set'/>
1193
#    <qemu:arg value='device.hostdev1.x-vga=on'/>
1194
#    <qemu:arg value='-cpu'/>
1195
#	<qemu:arg value='host,kvm=off'/>
1196
#    <qemu:arg value='-device'/>
1197
#	<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'/>
1198
1199
#  <cpu mode='host-model'>
1200
#    <vendor>Intel</vendor>
1201
#    <model>core2duo</model>
1202
#  </cpu>
1203
1204
#    <loader readonly='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE.fd</loader>
1205
#    <nvram template='/usr/share/OVMF/OVMF_VARS.fd'/>
1206 04c16f26 hq
        my $loader_xml = <<ENDXML
1207
    <bootmenu enable='yes' timeout='200'/>
1208
    <smbios mode='sysinfo'/>
1209
ENDXML
1210
        ;
1211 d3805c61 hq
        if ($loader eq 'uefi') {
1212
            $loader_xml = <<ENDXML
1213 04c16f26 hq
  <loader readonly='yes' secure='no' type='pflash'>/usr/share/ovmf/OVMF.fd</loader>
1214
  <nvram template='/usr/share/OVMF/OVMF_VARS.fd'>/tmp/guest_VARS.fd</nvram>
1215
ENDXML
1216
    ;
1217 d3805c61 hq
        }
1218
        my $iotune_xml = <<ENDXML
1219
      <iotune>
1220
        <read_bytes_sec>$vm_readlimit</read_bytes_sec>
1221
        <write_bytes_sec>$vm_writelimit</write_bytes_sec>
1222
        <read_iops_sec>$vm_iopsreadlimit</read_iops_sec>
1223
        <write_iops_sec>$vm_iopswritelimit</write_iops_sec>
1224
      </iotune>
1225
ENDXML
1226
;
1227
        $iotune_xml = '' unless ($enforceiolimits);
1228 95b003ff Origo
1229 705b5366 hq
        if ($vgpu && $vgpu ne "--") {
1230
            $xml .= <<ENDXML
1231 95b003ff Origo
  <cpu mode='host-passthrough'>
1232
    <feature policy='disable' name='hypervisor'/>
1233
  </cpu>
1234
ENDXML
1235
;
1236 705b5366 hq
        } else {
1237
            $xml .= <<ENDXML
1238
  <cpu mode='host-model'>
1239
  </cpu>
1240
ENDXML
1241
            ;
1242 95b003ff Origo
        }
1243
        $xml .=  <<ENDXML
1244
  <name>$uname</name>
1245
  <uuid>$uuid</uuid>
1246
  <memory>$mem</memory>
1247
  <vcpu>$vcpu</vcpu>
1248
  <os>
1249
    <type arch='x86_64' machine='pc'>hvm</type>
1250
    <boot dev='$boot'/>
1251 04c16f26 hq
$loader_xml
1252 95b003ff Origo
  </os>
1253
  <sysinfo type='smbios'>
1254
    <bios>
1255
      <entry name='vendor'>Origo</entry>
1256
    </bios>
1257
    <system>
1258
      <entry name='manufacturer'>Origo</entry>
1259
      <entry name='sku'>$networkid1ip</entry>
1260
    </system>
1261
  </sysinfo>
1262
  <features>
1263
ENDXML
1264
;
1265
        if ($vgpu && $vgpu ne "--") { $xml .= <<ENDXML
1266
    <kvm>
1267
      <hidden state='on'/>
1268
    </kvm>
1269
ENDXML
1270
;
1271
        }
1272
        $xml .= <<ENDXML
1273
    <pae/>
1274
    <acpi/>
1275
    <apic/>
1276
  </features>
1277
  <clock offset='localtime'>
1278
    <timer name='rtc' tickpolicy='catchup' track='guest'/>
1279
    <timer name='pit' tickpolicy='delay'/>
1280
    <timer name='hpet' present='no'/>
1281
  </clock>
1282
  <on_poweroff>destroy</on_poweroff>
1283 04c16f26 hq
  <on_reboot>restart</on_reboot>½
1284 95b003ff Origo
  <on_crash>restart</on_crash>
1285
  <devices>
1286 e837d785 hq
  <sound model='ich6'/>
1287 95b003ff Origo
ENDXML
1288
;
1289
#        if ($vgpu && $vgpu ne "--") {
1290
#            $xml .= <<ENDXML2
1291
#  <hostdev mode='subsystem' type='pci' managed='yes'>
1292
#    <source>
1293
#      <address domain='0x0000' bus='0x01' slot='0x00' function='0x0' multifunction='on'/>
1294
#    </source>
1295
#  </hostdev>
1296
#  <hostdev mode='subsystem' type='pci' managed='yes'>
1297
#    <source>
1298
#      <address domain='0x0000' bus='0x02' slot='0x00' function='0x0' multifunction='on'/>
1299
#    </source>
1300
#  </hostdev>
1301
#ENDXML2
1302
#;
1303
#        }
1304
        if ($image && $image ne "" && $image ne "--") {
1305
						$xml .= <<ENDXML2
1306
    <disk type='file' device='disk'>
1307
      <source file='$image'/>$driver1
1308
      <target dev='$diskdev' bus='$diskbus'/>
1309 d3805c61 hq
$iotune_xml
1310 95b003ff Origo
    </disk>
1311
ENDXML2
1312
;
1313
        };
1314
1315
        if ($image2 && $image2 ne "" && $image2 ne "--") {
1316
						$xml .= <<ENDXML2
1317
    <disk type='file' device='disk'>$driver2
1318
      <source file='$image2'/>
1319
      <target dev='$diskdev2' bus='$diskbus'/>
1320 d3805c61 hq
$iotune_xml
1321 95b003ff Origo
    </disk>
1322
ENDXML2
1323
;
1324
        };
1325
        if ($image3 && $image3 ne "" && $image3 ne "--") {
1326
						$xml .= <<ENDXML2
1327
    <disk type='file' device='disk'>$driver3
1328
      <source file='$image3'/>
1329
      <target dev='$diskdev3' bus='$diskbus'/>
1330 d3805c61 hq
$iotune_xml
1331 95b003ff Origo
    </disk>
1332
ENDXML2
1333
;
1334
        };
1335
        if ($image4 && $image4 ne "" && $image4 ne "--") {
1336
						$xml .= <<ENDXML2
1337
    <disk type='file' device='disk'>$driver4
1338
      <source file='$image4'/>
1339
      <target dev='$diskdev4' bus='$diskbus'/>
1340 d3805c61 hq
$iotune_xml
1341 95b003ff Origo
    </disk>
1342
ENDXML2
1343
;
1344
        };
1345
1346
        unless ($image4 && $image4 ne '--' && $diskbus eq 'ide') {
1347
            if ($cdrom && $cdrom ne "" && $cdrom ne "--") {
1348
						$xml .= <<ENDXML3
1349
    <disk type='file' device='cdrom'>
1350
      <source file='$cdrom'/>
1351
      <target dev='hdd' bus='ide'/>
1352
      <readonly/>
1353
    </disk>
1354
ENDXML3
1355
;
1356
            } elsif ($hypervisor ne "vbox") {
1357
						$xml .= <<ENDXML3
1358
    <disk type='file' device='cdrom'>
1359
      <target dev='hdd' bus='ide'/>
1360
      <readonly/>
1361
    </disk>
1362
ENDXML3
1363
;
1364
            }
1365
        }
1366
1367
        $xml .= <<ENDXML4
1368
    <interface type='$networktype1'>
1369
      <source $networktype1='$networksource1'/>
1370
      <forward mode='$networkforward1'/>
1371
      <port isolated='$networkisolated1'/>
1372
      <model type='$nicmodel1'/>
1373
      <mac address='$nicmac1'/>
1374
    </interface>
1375
ENDXML4
1376
;
1377
1378
        if (($networkuuid2 && $networkuuid2 ne '--') || $networkuuid2 eq '0') {
1379
            $xml .= <<ENDXML5
1380
    <interface type='$networktype2'>
1381
      <source $networktype2='$networksource2'/>
1382
      <forward mode='$networkforward2'/>
1383
      <port isolated='$networkisolated2'/>
1384
      <model type='$nicmodel1'/>
1385
      <mac address='$nicmac2'/>
1386
    </interface>
1387
ENDXML5
1388
;
1389
        }
1390
        if (($networkuuid3 && $networkuuid3 ne '--') || $networkuuid3 eq '0') {
1391
            $xml .= <<ENDXML5
1392
    <interface type='$networktype3'>
1393
      <source $networktype3='$networksource3'/>
1394
      <forward mode='$networkforward3'/>
1395
      <port isolated='$networkisolated3'/>
1396
      <model type='$nicmodel1'/>
1397
      <mac address='$nicmac3'/>
1398
    </interface>
1399
ENDXML5
1400
;
1401
        }
1402
        $xml .= <<ENDXML6
1403
     <serial type='pty'>
1404
       <source path='/dev/pts/0'/>
1405
       <target port='0'/>
1406
     </serial>
1407
    <input type='tablet' bus='usb'/>
1408
    <graphics type='$graphics' port='$port'/>
1409
  </devices>
1410
</domain>
1411
ENDXML6
1412
;
1413
1414
1415
#    <graphics type='$graphics' port='$port' keymap='en-us'/>
1416
#     <console type='pty' tty='/dev/pts/0'>
1417
#       <source path='/dev/pts/0'/>
1418
#       <target port='0'/>
1419
#     </console>
1420
#     <graphics type='$graphics' port='-1' autoport='yes'/>
1421
1422
        $xmlreg{$uuid} = {
1423
            xml=>URI::Escape::uri_escape($xml)
1424
        };
1425
1426
        # Actually ask node to start domain
1427
        if ($targetmac) {
1428
            $register{$uuid}->{'mac'} = $targetmac;
1429
            $register{$uuid}->{'macname'} = $targetname;
1430
            $register{$uuid}->{'macip'} = $targetip;
1431
1432
            my $tasks = $nodereg{$targetmac}->{'tasks'};
1433
            $tasks .= "START $uuid $user\n";
1434
            $nodereg{$targetmac}->{'tasks'} = $tasks;
1435
            tied(%nodereg)->commit;
1436
            $uiuuid = $uuid;
1437
            $uidisplayip = $targetip;
1438
            $uidisplayport = $port;
1439
            $register{$uuid}->{'status'} = $uistatus;
1440
            $register{$uuid}->{'statustime'} = $current_time;
1441
            tied(%register)->commit;
1442
1443
            # Activate networks
1444
            require "$Stabile::basedir/cgi/networks.cgi";
1445
            Stabile::Networks::Activate($networkuuid1, 'activate');
1446
            Stabile::Networks::Activate($networkuuid2, 'activate') if ($networkuuid2 && $networkuuid2 ne '--');
1447
            Stabile::Networks::Activate($networkuuid3, 'activate') if ($networkuuid3 && $networkuuid3 ne '--');
1448
1449
            $main::syslogit->($user, "info", "Marked $name ($uuid) for ". $serv->{'status'} . " on $targetname ($targetmac)");
1450
            $postreply .= "Status=starting OK $uistatus ". $serv->{'name'} . "\n";
1451
        } else {
1452
            $main::syslogit->($user, "info", "Could not find $hypervisor target for creating $uuid ($image)");
1453
            $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . " (unable to locate target node)\n";
1454
        };
1455
    } else {
1456
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
1457
        $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . "\n";
1458
    }
1459
    #return ($uiuuid, $uidisplayip, $uidisplayport, $postreply, $targetmac);
1460
    return $postreply;
1461
}
1462
1463
sub do_attach {
1464
    my ($uuid, $action, $obj) = @_;
1465
    if ($help) {
1466
        return <<END
1467
GET:uuid,image:
1468
Attaches an image to a server as a disk device. Image must not be in use.
1469
END
1470
    }
1471
    my $dev = '';
1472
    my $imagenum = 0;
1473
    my $serv = $register{$uuid};
1474
1475
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1476
        return "Status=Error Server must exist and be running\n";
1477
    }
1478
    my $macip = $serv->{macip};
1479
    my $image = $obj->{image} || $obj->{path};
1480
    if ($image && !($image =~ /^\//)) { # We have a uuid
1481
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Status=Error Unable to access images register\n"};
1482
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1483
        untie %imagereg2;
1484
    }
1485
    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;};
1486
    unless ($macip && $imagereg{$image} && $imagereg{$image}->{'user'} eq $user && $serv->{'user'} eq $user)  {$postreply .= "Status=Error Invalid image or server\n"; return $postreply;};
1487
    if ($imagereg{$image}->{'status'} ne 'unused') {return "Status=Error Image $image is already in use ($imagereg{$image}->{'status'})\n"};
1488
1489
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1490
    my $res = `$cmd`;
1491
    unless ($res =~ /vdb\s+.+/) {$dev = 'vdb'; $imagenum = 2};
1492
    unless ($dev || $res =~ /vdc\s+.+/)  {$dev = 'vdc'; $imagenum = 3};
1493
    unless ($dev || $res =~ /vdd\s+.+/)  {$dev = 'vdd'; $imagenum = 4};
1494
    if (!$dev) {
1495
        $postreply = "Status=Error No more images can be attached\n";
1496
    } else {
1497
        my $xml = <<END
1498
<disk type='file' device='disk'>
1499
  <driver type='qcow2' name='qemu' cache='default'/>
1500
  <source file='$image'/>
1501
  <target dev='$dev' bus='virtio'/>
1502
</disk>
1503
END
1504
;
1505
        $cmd = qq|$sshcmd $macip "echo \\"$xml\\" > /tmp/attach-device-$uuid.xml"|;
1506
        $res = `$cmd`;
1507
        $res .= `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh attach-device $uuid /tmp/attach-device-$uuid.xml`;
1508
        chomp $res;
1509
        if ($res =~ /successfully/) {
1510
            $postreply .= "Status=OK Attaching $image to $dev\n";
1511
            $imagereg{$image}->{'status'} = 'active';
1512
            $imagereg{$image}->{'domains'} = $uuid;
1513
            $imagereg{$image}->{'domainnames'} = $serv->{'name'};
1514
            $serv->{"image$imagenum"} = $image;
1515
            $serv->{"image$imagenum"."name"} = $imagereg{$image}->{'name'};
1516
            $serv->{"image$imagenum"."type"} = 'qcow2';
1517
        } else {
1518
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1519
        }
1520
    }
1521
    untie %imagereg;
1522
    return $postreply;
1523
}
1524
1525
sub do_detach {
1526
    my ($uuid, $action, $obj) = @_;
1527
    if ($help) {
1528
        return <<END
1529
GET:uuid,image:
1530
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.
1531
END
1532
    }
1533
    my $dev = '';
1534
    my $serv = $register{$uuid};
1535
1536
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1537
        return "Status=Error Server must exist and be running\n";
1538
    }
1539
    my $macip = $serv->{macip};
1540
1541
    my $image = $obj->{image} || $obj->{path} || $serv->{'image2'};
1542
    if ($image && !($image =~ /^\//)) { # We have a uuid
1543
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1544
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1545
        untie %imagereg2;
1546
    }
1547
    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;};
1548
    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;};
1549
1550
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1551
    my $res = `$cmd`;
1552
    $dev = $1 if ($res =~ /(vd.)\s+.+$image/);
1553
    if (!$dev) {
1554
        $postreply =  qq|Status=Error Image $image, $cmd, is not currently attached\n|;
1555
    } elsif ($dev eq 'vda') {
1556
        $postreply = "Status=Error You cannot detach the primary image\n";
1557
    } else {
1558
        $res = `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh detach-disk $uuid $dev`;
1559
        chomp $res;
1560
        if ($res =~ /successfully/) {
1561
            $postreply .= "Status=OK Detaching image $image, $imagereg{$image}->{'uuid'} from $dev\n";
1562
            my $imagenum;
1563
            $imagenum = 2 if ($serv->{'image2'} eq $image);
1564
            $imagenum = 3 if ($serv->{'image3'} eq $image);
1565
            $imagenum = 4 if ($serv->{'image4'} eq $image);
1566
            $imagereg{$image}->{'status'} = 'unused';
1567
            $imagereg{$image}->{'domains'} = '';
1568
            $imagereg{$image}->{'domainnames'} = '';
1569
            if ($imagenum) {
1570
                $serv->{"image$imagenum"} = '';
1571
                $serv->{"image$imagenum"."name"} = '';
1572
                $serv->{"image$imagenum"."type"} = '';
1573
            }
1574
        } else {
1575
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1576
        }
1577
    }
1578
    untie %imagereg;
1579
    return $postreply;
1580
}
1581
1582
sub Destroy {
1583
    my ($uuid, $action, $obj) = @_;
1584
    if ($help) {
1585
        return <<END
1586
GET:uuid,wait:
1587
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.
1588
END
1589
    }
1590
    my $uistatus = 'destroying';
1591
    my $name = $register{$uuid}->{'name'};
1592
    my $mac = $register{$uuid}->{'mac'};
1593
    my $macname = $register{$uuid}->{'macname'};
1594
    my $dbstatus = $register{$uuid}->{'status'};
1595
    my $wait = $obj->{'wait'};
1596
    if ($dbstatus eq 'running' or $dbstatus eq 'paused'
1597
        or $dbstatus eq 'shuttingdown' or $dbstatus eq 'starting'
1598
        or $dbstatus eq 'destroying' or $dbstatus eq 'upgrading'
1599
        or $dbstatus eq 'suspending' or $dbstatus eq 'resuming') {
1600
        if ($wait) {
1601 6372a66e hq
            my $username = $register{$uuid}->{'user'} || $user;
1602
            $username = $user unless ($isadmin);
1603
            $postreply = destroyUserServers($username, 1, $uuid);
1604 95b003ff Origo
        } else {
1605 6372a66e hq
            my $node = $nodereg{$mac};
1606
            my $tasks = $node->{'tasks'};
1607
            $node->{'tasks'} = $tasks . "DESTROY $uuid $user\n";
1608 95b003ff Origo
            tied(%nodereg)->commit;
1609
            $register{$uuid}->{'status'} = $uistatus;
1610
            $register{$uuid}->{'statustime'} = $current_time;
1611
            $uiuuid = $uuid;
1612
            $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus on $macname ($mac)");
1613
            $postreply .= "Status=destroying $uistatus ". $register{$uuid}->{'name'} . "\n";
1614
        }
1615
    } else {
1616
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $name ($uuid)");
1617
        $postreply .= "Status=ERROR problem $uistatus $name\n";
1618
    }
1619
    return $postreply;
1620
}
1621
1622
sub getHypervisor {
1623
	my $image = shift;
1624
	# Produce a mapping of image file suffixes to hypervisors
1625
	my %idreg;
1626
    unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access nodeidentities register"};
1627
    my @idvalues = values %idreg;
1628
	my %formats;
1629
	foreach my $val (@idvalues) {
1630
		my %h = %$val;
1631
		foreach (split(/,/,$h{'formats'})) {
1632
			$formats{lc $_} = $h{'hypervisor'}
1633
		}
1634
	}
1635
	untie %idreg;
1636
1637
	# and then determine the hypervisor in question
1638
	my $hypervisor = "vbox";
1639
	my ($pathname, $path, $suffix) = fileparse($image, '\.[^\.]*');
1640
	$suffix = substr $suffix, 1;
1641
	my $hypervisor = $formats{lc $suffix};
1642
	return $hypervisor;
1643
}
1644
1645
sub nicmac1ToUuid {
1646
    my $nicmac1 = shift;
1647
    my $uuid;
1648
    return $uuid unless $nicmac1;
1649
    my @regkeys = (tied %register)->select_where("user = '$user' AND nicmac1 = '$nicmac1");
1650
	foreach my $k (@regkeys) {
1651
	    my $val = $register{$k};
1652
		my %h = %$val;
1653
		if (lc $h{'nicmac1'} eq lc $nicmac1 && $user eq $h{'user'}) {
1654
    		$uuid =  $h{'uuid'};
1655
    		last;
1656
		}
1657
	}
1658
	return $uuid;
1659
}
1660
1661
sub randomMac {
1662
	my ( %vendor, $lladdr, $i );
1663
#	$lladdr = '00';
1664
	$lladdr = '52:54:00';# KVM vendor string
1665
	while ( ++$i )
1666
#	{ last if $i > 10;
1667
	{ last if $i > 6;
1668
		$lladdr .= ':' if $i % 2;
1669
		$lladdr .= sprintf "%" . ( qw (X x) [int ( rand ( 2 ) ) ] ), int ( rand ( 16 ) );
1670
	}
1671
	return $lladdr;
1672
}
1673
1674
sub overQuotas {
1675
    my $meminc = shift;
1676
    my $vcpuinc = shift;
1677
	my $usedmemory = 0;
1678
	my $usedvcpus = 0;
1679
	my $overquota = 0;
1680
    return $overquota if ($isadmin || $Stabile::userprivileges =~ /a/); # Don't enforce quotas for admins
1681
1682 a2e0bc7e hq
	my $memoryquota = $Stabile::usermemoryquota;
1683
	my $vcpuquota = $Stabile::uservcpuquota;
1684 95b003ff Origo
1685
	if (!$memoryquota || !$vcpuquota) { # 0 or empty quota means use defaults
1686
        $memoryquota = $memoryquota || $Stabile::config->get('MEMORY_QUOTA');
1687
        $vcpuquota = $vcpuquota || $Stabile::config->get('VCPU_QUOTA');
1688
    }
1689
1690
    my @regkeys = (tied %register)->select_where("user = '$user'");
1691
	foreach my $k (@regkeys) {
1692
	    my $val = $register{$k};
1693
		if ($val->{'user'} eq $user && $val->{'status'} ne "shutoff" &&
1694
		    $val->{'status'} ne "inactive" && $val->{'status'} ne "shutdown" ) {
1695
1696
		    $usedmemory += $val->{'memory'};
1697
		    $usedvcpus += $val->{'vcpu'};
1698
		}
1699
	}
1700
	$overquota = $usedmemory+$meminc if ($memoryquota!=-1 && $usedmemory+$meminc > $memoryquota); # -1 means no quota
1701
	$overquota = $usedvcpus+$vcpuinc if ($vcpuquota!=-1 && $usedvcpus+$vcpuinc > $vcpuquota);
1702
	return $overquota;
1703
}
1704
1705
sub validateItem {
1706 a2e0bc7e hq
    unless (%imagereg) {
1707
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1708
    }
1709 95b003ff Origo
    my $valref = shift;
1710
    my $img = $imagereg{$valref->{'image'}};
1711
    my $imagename = $img->{'name'};
1712
    $valref->{'imagename'} = $imagename if ($imagename);
1713
    my $imagetype = $img->{'type'};
1714
    $valref->{'imagetype'} = $imagetype if ($imagetype);
1715
1716
    # imagex may be registered by uuid instead of path - find the path
1717
    # We now support up to 4 images
1718
    for (my $i=2; $i<=4; $i++) {
1719
        if ($valref->{"image$i"} && $valref->{"image$i"} ne '--' && !($valref->{"image$i"} =~ /^\//)) {
1720
            unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1721
            $valref->{"image$i"} = $imagereg2{$valref->{"image$i"}}->{'path'};
1722
            untie %imagereg2;
1723
        }
1724
1725
        my $imgi = $imagereg{$valref->{"image$i"}};
1726
        $valref->{"image$i" . 'name'} = $imgi->{'name'} || $valref->{"image$i" . 'name'};
1727
        $valref->{"image$i" . 'type'} = $imgi->{'type'} || $valref->{"image$i" . 'type'};
1728
    }
1729
1730
    my $net1 = $networkreg{$valref->{'networkuuid1'}};
1731
    my $networkname1 = $net1->{'name'};
1732
    $valref->{'networkname1'} = $networkname1 if ($networkname1);
1733
    my $net2 = $networkreg{$valref->{'networkuuid2'}};
1734
    my $networkname2 = $net2->{'name'};
1735
    $valref->{'networkname2'} = $networkname2 if ($networkname2);
1736
    my $name = $valref->{'name'};
1737
    $valref->{'name'} = $imagename unless $name;
1738
1739 a2e0bc7e hq
    # Make sure we start shutoff servers on the node their image is on
1740 95b003ff Origo
    if ($valref->{'status'} eq "shutoff" || $valref->{'status'} eq "inactive") {
1741
        my $node = $nodereg{$valref->{'mac'}};
1742
        if ($valref->{'image'} =~ /\/mnt\/stabile\/node\//) {
1743
            $valref->{'mac'} = $img->{'mac'};
1744
            $valref->{'macname'} = $node->{'name'};
1745
            $valref->{'macip'} = $node->{'ip'};
1746
        } elsif ($valref->{'image2'} =~ /\/mnt\/stabile\/node\//) {
1747
            $valref->{'mac'} = $imagereg{$valref->{'image2'}}->{'mac'};
1748
            $valref->{'macname'} = $node->{'name'};
1749
            $valref->{'macip'} = $node->{'ip'};
1750
        } elsif ($valref->{'image3'} =~ /\/mnt\/stabile\/node\//) {
1751
            $valref->{'mac'} = $imagereg{$valref->{'image3'}}->{'mac'};
1752
            $valref->{'macname'} = $node->{'name'};
1753
            $valref->{'macip'} = $node->{'ip'};
1754
        } elsif ($valref->{'image4'} =~ /\/mnt\/stabile\/node\//) {
1755
            $valref->{'mac'} = $imagereg{$valref->{'image4'}}->{'mac'};
1756
            $valref->{'macname'} = $node->{'name'};
1757
            $valref->{'macip'} = $node->{'ip'};
1758
        }
1759
    }
1760
# Mark domains we have heard from in the last 20 secs as inactive
1761
    my $dbtimestamp = 0;
1762
    $dbtimestamp = $register{$valref->{'uuid'}}->{'timestamp'} if ($register{$valref->{'uuid'}});
1763
    my $timediff = $current_time - $dbtimestamp;
1764
    if ($timediff >= 20) {
1765
        if  (! ($valref->{'status'} eq "shutoff"
1766
                || $valref->{'status'} eq "starting"
1767
            #    || $valref->{'status'} eq "shuttingdown"
1768
            #    || $valref->{'status'} eq "destroying"
1769 d3805c61 hq
                || ($valref->{'status'} =~ /moving/ && $timediff<40)
1770 95b003ff Origo
            )) { # Move has probably failed
1771
            $valref->{'status'} = "inactive";
1772
            $imagereg{$valref->{'image'}}->{'status'} = "used" if ($valref->{'image'} && $imagereg{$valref->{'image'}});
1773 a2e0bc7e hq
            $imagereg{$valref->{'image2'}}->{'status'} = "used" if ($valref->{'image2'} && $imagereg{$valref->{'image2'}});
1774 95b003ff Origo
            $imagereg{$valref->{'image3'}}->{'status'} = "used" if ($valref->{'image3'} && $imagereg{$valref->{'image3'}});
1775
            $imagereg{$valref->{'image4'}}->{'status'} = "used" if ($valref->{'image4'} && $imagereg{$valref->{'image4'}});
1776
        }
1777
    };
1778 a2e0bc7e hq
#    untie %imagereg;
1779 95b003ff Origo
    return $valref;
1780
}
1781
1782
# Run through all domains and mark domains we have heard from in the last 20 secs as inactive
1783
sub updateRegister {
1784
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access user register"};
1785
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1786
1787
    my @regkeys = (tied %register)->select_where("user = '$user'");
1788
1789
    foreach my $k (@regkeys) {
1790
        my $valref = $register{$k};
1791
        next unless ($userreg{$valref->{'user'}});
1792
        my $dbtimestamp = $valref->{'timestamp'};
1793
        my $dbstatus = $valref->{'status'};
1794
        my $timediff = $current_time - $dbtimestamp;
1795
        my $imgstatus;
1796
        my $domstatus;
1797
        if ($timediff >= 20) {
1798
            if  ( $valref->{'status'} eq "shutoff" ) {
1799
                $imgstatus = 'used';
1800
            } elsif ((  $valref->{'status'} eq "starting"
1801
                            || $valref->{'status'} eq "shuttingdown"
1802
                        ) && $timediff>50) {
1803
                $imgstatus = 'used';
1804
                $domstatus = 'inactive';
1805
            } elsif ($valref->{'status'} eq "destroying" || $valref->{'status'} eq "moving") {
1806
                ;
1807
            } else {
1808
                $domstatus = 'inactive';
1809
                $imgstatus = 'used';
1810
            }
1811
            $valref->{'status'} = $domstatus if ($domstatus);
1812
            my $image = $valref->{'image'};
1813
            my $image2 = $valref->{'image2'};
1814
            my $image3 = $valref->{'image3'};
1815
            my $image4 = $valref->{'image4'};
1816
            $imagereg{$image}->{'status'} = $imgstatus if ($imgstatus);
1817
            $imagereg{$image2}->{'status'} = $imgstatus if ($image2 && $imgstatus);
1818
            $imagereg{$image3}->{'status'} = $imgstatus if ($image3 && $imgstatus);
1819
            $imagereg{$image4}->{'status'} = $imgstatus if ($image4 && $imgstatus);
1820
            if ($domstatus eq 'inactive ' && $dbstatus ne 'inactive') {
1821
                $main::updateUI->({ tab=>'servers',
1822
                                    user=>$valref->{'user'},
1823
                                    uuid=>$valref->{'uuid'},
1824
                                    sender=>'updateRegister',
1825
                                    status=>'inactive'})
1826
            }
1827
        };
1828
1829
    }
1830
    untie %userreg;
1831
    untie %imagereg;
1832
}
1833
1834
1835
sub locateTargetNode {
1836 d3805c61 hq
    my ($uuid, $dmac, $mem, $vcpu, $image, $image2, $image3, $image4, $hypervisor, $smac, $stormove)= @_;
1837 95b003ff Origo
    my $targetname;
1838
    my $targetip;
1839
    my $port;
1840
    my $targetnode;
1841
    my $targetindex; # Availability index of located target node
1842
    my %avhash;
1843
1844 d3805c61 hq
    $dmac = '' unless ($isadmin); # Only allow admins to select specific node
1845 95b003ff Origo
    my $mnode = $register{$uuid};
1846 d3805c61 hq
    if (!$dmac
1847 95b003ff Origo
            && $mnode->{'locktonode'} eq 'true'
1848
            && $mnode->{'mac'}
1849
            && $mnode->{'mac'} ne '--'
1850 d3805c61 hq
            ) {
1851
        $dmac = $mnode->{'mac'}; # Server is locked to specific node
1852
    }
1853 95b003ff Origo
    if ($dmac && !$nodereg{$dmac}) {
1854
        $main::syslogit->($user, "info", "The target node $dmac no longer exists, starting $uuid on another node if possible");
1855
        $dmac = '';
1856
    }
1857 d3805c61 hq
    my $imageonnode = ((!$stormove) && ($image =~ /\/mnt\/stabile\/node\//
1858 95b003ff Origo
                                          || $image2 =~ /\/mnt\/stabile\/node\//
1859
                                          || $image3 =~ /\/mnt\/stabile\/node\//
1860
                                          || $image4 =~ /\/mnt\/stabile\/node\//
1861 d3805c61 hq
                                          ));
1862 95b003ff Origo
1863
    foreach $node (values %nodereg) {
1864
        my $nstatus = $node->{'status'};
1865
        my $maintenance = $node->{'maintenance'};
1866
        my $nmac = $node->{'mac'};
1867
1868
        if (($nstatus eq 'running' || $nstatus eq 'asleep' || $nstatus eq 'maintenance' || $nstatus eq 'waking')
1869
         && $smac ne $nmac
1870
         && (( ($node->{'memfree'} > $mem+512*1024)
1871
         && (($node->{'vmvcpus'} + $vcpu) <= ($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'})) ) || $action eq 'listnodeavailability')
1872
        ) {
1873
        # Determine how available this node is
1874
        # Available memory
1875
            my $memweight = 0.2; # memory weighing factor
1876
            my $memindex = $avhash{$nmac}->{'memindex'} = int(100* $memweight* $node->{'memfree'} / (1024*1024) )/100;
1877
        # Free cores
1878
            my $cpuindex = $avhash{$nmac}->{'cpuindex'} = int(100*($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'} - $node->{'vmvcpus'} - $node->{'reservedvcpus'}))/100;
1879
        # Asleep - not asleep gives a +3
1880
            my $sleepindex = $avhash{$nmac}->{'sleepindex'} = ($node->{'status'} eq 'asleep' || $node->{'status'} eq 'waking')?'0':'3';
1881
            $avhash{$nmac}->{'vmvcpus'} = $node->{'vmvcpus'};
1882
#            $avhash{$nmac}->{'cpucommision'} = $cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'};
1883
#            $avhash{$nmac}->{'cpureservation'} = $node->{'vmvcpus'} + $node->{'reservedvcpus'};
1884
            $avhash{$nmac}->{'name'} = $node->{'name'};
1885
            $avhash{$nmac}->{'mac'} = $node->{'mac'};
1886
1887
            my $aindex = $memindex + $cpuindex + $sleepindex;
1888
        # Don't use nodes that are out of memory of cores
1889
            $aindex = 0 if ($memindex <= 0 || $cpuindex <= 0);
1890
            $avhash{$nmac}->{'index'} = $aindex;
1891
            $avhash{$nmac}->{'storfree'} = $node->{'storfree'};
1892 c899e439 Origo
            $avhash{$nmac}->{'memfree'} = $node->{'memfree'};
1893 95b003ff Origo
            $avhash{$nmac}->{'ip'} = $node->{'ip'};
1894
            $avhash{$nmac}->{'identity'} = $node->{'identity'};
1895
            $avhash{$nmac}->{'status'} = $node->{'status'};
1896
            $avhash{$nmac}->{'maintenance'} = $maintenance;
1897
            $avhash{$nmac}->{'reservedvcpus'} = $node->{'reservedvcpus'};
1898
            my $nodeidentity = $node->{'identity'};
1899
            $nodeidentity = 'kvm' if ($nodeidentity eq 'local_kvm');
1900
            if ($hypervisor eq $nodeidentity) {
1901
                # If image is on node, we must start on same node - registered when moving image
1902
                if ($imageonnode) {
1903
                    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1904
                    $dmac = $imagereg{$image}->{'mac'};
1905
                    $dmac = $imagereg{$image2}->{'mac'} unless ($dmac);
1906
                    $dmac = $imagereg{$image3}->{'mac'} unless ($dmac);
1907
                    $dmac = $imagereg{$image4}->{'mac'} unless ($dmac);
1908
                    untie %imagereg;
1909
                    if (!$dmac) {
1910
                        $postreply .= "Status=ERROR Image node not found\n";
1911
                        last;
1912
                    }
1913
                }
1914
                $dmac = "" if ($dmac eq "--");
1915 a439a9c4 hq
            # If a specific node is asked for, match mac addresses
1916 95b003ff Origo
                if ($dmac eq $nmac) {
1917
                    $targetnode = $node;
1918
                    last;
1919
                } elsif (!$dmac && $nstatus ne "maintenance" && !$maintenance) {
1920
            # pack or disperse
1921
                    if (!$targetindex) {
1922
                        $targetindex = $aindex;
1923
                        $targetnode = $node;
1924
                    } elsif ($dpolicy eq 'pack') {
1925
                        if ($aindex < $targetindex) {
1926
                            $targetnode = $node;
1927
                            $targetindex = $aindex;
1928
                        }
1929
                    } else {
1930
                        if ($aindex > $targetindex) {
1931
                            $targetnode = $node;
1932
                            $targetindex = $aindex;
1933
                        }
1934
                    }
1935
                }
1936
            }
1937
        }
1938
    }
1939
    if ($targetnode && $uuid) {
1940
        if ($targetnode->{'status'} eq 'asleep') {
1941
            my $nmac = $targetnode->{'mac'};
1942
            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);
1943
            my $nlogmsg = "Node $nmac marked for wake ";
1944
            if ($brutalsleep && (
1945
                    ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--')
1946
                || ($targetnode->{'ipmiip'} && $targetnode->{'ipmiip'} ne '--')
1947
                )) {
1948
                my $wakecmd;
1949
                if ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--') {
1950
                    $wakecmd = "echo 'y' | AMT_PASSWORD='$amtpasswd' /usr/bin/amttool $targetnode->{'amtip'} powerup pxe";
1951
                } else {
1952
                    $wakecmd = "ipmitool -I lanplus -H $targetnode->{'ipmiip'} -U ADMIN -P ADMIN power on";
1953
                }
1954
                $nlogmsg .= `$wakecmd`;
1955
            } else {
1956
                my $broadcastip = $targetnode->{'ip'};
1957
                $broadcastip =~ s/\.\d{1,3}$/.255/;
1958
                $nlogmsg .= 'on lan ' . `/usr/bin/wakeonlan -i $broadcastip $realmac`;
1959
            }
1960
            $targetnode->{'status'} = "waking";
1961
            $nlogmsg =~ s/\n/ /g;
1962
            $main::syslogit->($user, "info", $nlogmsg);
1963
            $postreply .= "Status=OK waking $targetnode->{'name'}\n";
1964
        }
1965
        $targetname = $targetnode->{'name'};
1966
        $targetmac = $targetnode->{'mac'};
1967
        $targetip = $targetnode->{'ip'};
1968
        $targetip = $targetnode->{'ip'};
1969
        my $porttaken = 1;
1970
        while ($porttaken) {
1971
            $porttaken = 0;
1972
            $port = $targetnode->{'vms'} + (($hypervisor eq "vbox")?3389:5900);
1973
            $port += int(rand(200));
1974
            my @regkeys = (tied %register)->select_where("port = '$port' AND macip = '$targetip'");
1975
            foreach my $k (@regkeys) {
1976
                $r = $register{$k};
1977
                if ($r->{'port'} eq $port && $r->{'macip'} eq $targetip) {
1978
                    $porttaken = 1;
1979
                }
1980
            }
1981
        }
1982
        $targetnode->{'vms'}++;
1983
        $targetnode->{'vmvcpus'} += $vcpu;
1984
        $register{$uuid}->{'port'} = $port;
1985
#        $register{$uuid}->{'mac'} = $targetmac;
1986
#        $register{$uuid}->{'macname'} = $targetname;
1987
#        $register{$uuid}->{'macip'} = $targetip;
1988
        $register{$uuid}->{'display'} = (($hypervisor eq "vbox")?'rdp':'vnc');
1989
    } else {
1990
        my $macstatus;
1991
        $macstatus = $nodereg{$dmac}->{status} if ($nodereg{$dmac});
1992 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);
1993 95b003ff Origo
    }
1994
    return ($targetmac, $targetname, $targetip, $port, \%avhash);
1995
}
1996
1997
sub destroyUserServers {
1998
    my $username = shift;
1999
    my $wait = shift; # Should we wait for servers do die
2000
    my $duuid = shift;
2001 6372a66e hq
    return unless ($username && ($isadmin || $user eq $username));
2002 95b003ff Origo
    my @updateList;
2003
2004
    my @regkeys = (tied %register)->select_where("user = '$username'");
2005
    foreach my $uuid (@regkeys) {
2006
        if ($register{$uuid}->{'user'} eq $username
2007
            && $register{$uuid}->{'status'} ne 'shutoff'
2008
            && (!$duuid || $duuid eq $uuid)
2009
        ) {
2010
            $postreply .= "Destroying $username server $register{$uuid}->{'name'}, $uuid\n";
2011
            Destroy($uuid);
2012
            push (@updateList,{ tab=>'servers',
2013
                                user=>$user,
2014
                                uuid=>$duuid,
2015
                                status=>'destroying'});
2016
        }
2017
    }
2018
    $main::updateUI->(@updateList) if (@updateList);
2019
    if ($wait) {
2020
        my @regkeys = (tied %register)->select_where("user = '$username'");
2021
        my $activeservers = 1;
2022
        my $i = 0;
2023 6372a66e hq
        while ($activeservers && $i<30) {
2024 95b003ff Origo
            $activeservers = 0;
2025
            foreach my $k (@regkeys) {
2026
                my $valref = $register{$k};
2027
                if ($username eq $valref->{'user'}
2028
                    && ($valref->{'status'} ne 'shutoff'
2029
                    && $valref->{'status'} ne 'inactive')
2030
                    && (!$duuid || $duuid eq $valref->{'uuid'})
2031
                ) {
2032
                    $activeservers = $valref->{'uuid'};
2033
                }
2034
            }
2035
            $i++;
2036
            if ($activeservers) {
2037
                my $res .= "Status=OK Waiting $i for server $register{$activeservers}->{'name'}, $register{$activeservers}->{'status'} to die...\n";
2038 9de5a3f1 hq
            #    print $res if ($console);
2039 95b003ff Origo
                $postreply .= $res;
2040
                sleep 2;
2041
            }
2042
        }
2043
        $postreply .= "Status=OK Servers halted for $username\n" unless ($activeservers);
2044
    }
2045
    return $postreply;
2046
}
2047
2048
sub removeUserServers {
2049
    my $username = shift;
2050
    my $uuid = shift;
2051
    my $destroy = shift; # Should running servers be destroyed before removing
2052
    return unless (($isadmin || $user eq $username) && !$isreadonly);
2053
    $user = $username;
2054
    my @regkeys = (tied %register)->select_where("user = '$username'");
2055
    foreach my $ruuid (@regkeys) {
2056
        next if ($uuid && $ruuid ne $uuid);
2057
        if ($destroy && $register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} ne 'shutoff' && $register{$ruuid}->{'status'} ne 'inactive')) {
2058
            destroyUserServers($username, 1, $ruuid);
2059
        }
2060
2061
        if ($register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} eq 'shutoff' || $register{$ruuid}->{'status'} eq 'inactive')) {
2062
            $postreply .= "Removing $username server $register{$ruuid}->{'name'}, $ruuid" . ($console?'':'<br>') . "\n";
2063
            Remove($ruuid);
2064
        }
2065
    }
2066
}
2067
2068
sub Remove {
2069
    my ($uuid, $action) = @_;
2070
    if ($help) {
2071
        return <<END
2072
DELETE:uuid:
2073
Removes a server. Server must be shutoff. Does not remove associated images or networks.
2074
END
2075
    }
2076
    my $reguser = $register{$uuid}->{'user'};
2077
    my $dbstatus = $register{$uuid}->{'status'};
2078
    my $image = $register{$uuid}->{'image'};
2079
    my $image2 = $register{$uuid}->{'image2'};
2080
    my $image3 = $register{$uuid}->{'image3'};
2081
    my $image4 = $register{$uuid}->{'image4'};
2082
    my $name = $register{$uuid}->{'name'};
2083
    $image2 = '' if ($image2 eq '--');
2084
    $image3 = '' if ($image3 eq '--');
2085
    $image4 = '' if ($image4 eq '--');
2086
2087
    if ($reguser ne $user) {
2088
        $postreply .= "Status=ERROR You cannot delete a vm you don't own\n";
2089
    } elsif ($dbstatus eq 'inactive' || $dbstatus eq 'shutdown' || $dbstatus eq 'shutoff') {
2090
2091
        # Delete software packages and monitors from register
2092
        $postmsg .= deletePackages($uuid);
2093
        my $sname = $register{$uuid}->{'name'};
2094
        utf8::decode($sname);
2095 48fcda6b Origo
        $postmsg .= deleteMonitors($uuid)?" deleted monitors for $sname ":'';
2096 95b003ff Origo
2097
        delete $register{$uuid};
2098
        delete $xmlreg{$uuid};
2099
2100
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2101
        $imagereg{$image}->{'status'} = "unused" if ($imagereg{$image});
2102
        $imagereg{$image2}->{'status'} = "unused" if ($image2 && $imagereg{$image2});
2103
        $imagereg{$image3}->{'status'} = "unused" if ($image3 && $imagereg{$image3});
2104
        $imagereg{$image4}->{'status'} = "unused" if ($image4 && $imagereg{$image4});
2105
        untie %imagereg;
2106
2107
        # Delete metrics
2108
        my $metricsdir = "/var/lib/graphite/whisper/domains/$uuid";
2109
        `rm -r $metricsdir` if (-e $metricsdir);
2110
        my $rrdfile = "/var/cache/rrdtool/".$uuid."_highres.rrd";
2111
        `rm $rrdfile` if (-e $rrdfile);
2112
2113
        $main::syslogit->($user, "info", "Deleted domain $uuid from db");
2114
        utf8::decode($name);
2115 48fcda6b Origo
        $postmsg .= " deleted server $name";
2116 95b003ff Origo
        $postreply = "[]";
2117
        sleep 1;
2118
    } else {
2119
        $postreply .= "Status=ERROR Cannot delete a $dbstatus server\n";
2120
    }
2121
    return $postreply;
2122
}
2123
2124
# Delete all monitors belonging to a server
2125
sub deleteMonitors {
2126
    my ($serveruuid) = @_;
2127
    my $match;
2128
    if ($serveruuid) {
2129
        if ($register{$serveruuid}->{'user'} eq $user || $isadmin) {
2130
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf");
2131
            # undef $/; # This makes <> read in the entire file in one go
2132
            my $uuidmatch;
2133
            while (<>) {
2134
                if (/^watch (\S+)/) {
2135
                    if ($1 eq $serveruuid) {$uuidmatch = $serveruuid}
2136
                    else {$uuidmatch = ''};
2137
                };
2138
                if ($uuidmatch) {
2139
                    $match = 1;
2140
                } else {
2141
                    #chomp;
2142
                    print unless (/^hostgroup $serveruuid/);
2143
                }
2144
                close ARGV if eof;
2145
            }
2146
            #$/ = "\n";
2147
        }
2148
        unlink glob "/var/log/stabile/*:$serveruuid:*";
2149
    }
2150
    `/usr/bin/moncmd reset keepstate` if ($match);
2151
    return $match;
2152
}
2153
2154
sub deletePackages {
2155
    my ($uuid, $issystem, %packreg) = @_;
2156
    unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2157
2158
    my @domains;
2159
    if ($issystem) {
2160
        foreach my $valref (values %register) {
2161
            if (($valref->{'system'} eq $uuid || $uuid eq '*')
2162
                    && ($valref->{'user'} eq $user || $fulllist)) {
2163
                push(@domains, $valref->{'uuid'});
2164
            }
2165
        }
2166
    } else { # Allow if domain no longer exists or belongs to user
2167
        push(@domains, $uuid) if (!$register{$uuid} || $register{$uuid}->{'user'} eq $user || $fulllist);
2168
    }
2169
2170
    foreach my $domuuid (@domains) {
2171
        foreach my $packref (values %packreg) {
2172
            my $id = $packref->{'id'};
2173
            if (substr($id, 0,36) eq $domuuid || ($uuid eq '*' && $packref->{'user'} eq $user)) {
2174
                delete $packreg{$id};
2175
            }
2176
        }
2177
    }
2178
    tied(%packreg)->commit;# if (%packreg);
2179
    if ($issystem) {
2180
        my $sname = $register{$uuid}->{'name'};
2181
        utf8::decode($sname);
2182
        return "Status=OK Cleared packages for $sname\n";
2183
    } elsif ($register{$uuid}) {
2184
        my $sname = $register{$uuid}->{'name'};
2185
        utf8::decode($sname);
2186
        return "Status=OK Cleared packages for $sname\n";
2187
    } else {
2188
        return "Status=OK Cleared packages. System not registered\n";
2189
    }
2190
}
2191
2192
sub Save {
2193
    my ($uuid, $action, $obj) = @_;
2194
    if ($help) {
2195
        return <<END
2196 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:
2197 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.
2198
Depending on your privileges not all changes are permitted. If you save without specifying a uuid, a new server is created.
2199
If you pass [user] parameter it is assumed you want to move server to this user's account.
2200
Supported parameters:
2201
2202
uuid: UUID
2203
name: string
2204
user: string
2205 48fcda6b Origo
system: UUID of stack this server belongs to
2206 95b003ff Origo
autostart: true|false
2207
locktonode: true|false
2208
mac: MAC address of target node
2209
2210
memory: int bytes
2211
vcpu: int
2212
boot: hd|cdrom|network
2213 04c16f26 hq
loader: bios|uefi
2214 95b003ff Origo
diskbus: virtio|ide|scsi
2215
nicmodel1: virtio|rtl8139|ne2k_pci|e1000|i82551|i82557b|i82559er|pcnet
2216
vgpu: int
2217
2218
cdrom: string path
2219
image: string path
2220
image2: string path
2221
image3: string path
2222
image4: string path
2223
2224
networkuuid1: UUID of network connection
2225
networkuuid2: UUID of network connection
2226
networkuuid3: UUID of network connection
2227
2228
END
2229
    }
2230
2231
# notes, opemail, opfullname, opphone, email, fullname, phone, services, recovery, alertemail
2232
# notes: string
2233
# opemail: string
2234
# opfullname: string
2235
# opphone: string
2236
# email: string
2237
# fullname: string
2238
# phone: string
2239
# services: string
2240
# recovery: string
2241
# alertemail: string
2242
2243
    my $system = $obj->{system};
2244
    my $newsystem = $obj->{newsystem};
2245
    my $buildsystem = $obj->{buildsystem};
2246
    my $nicmac1 = $obj->{nicmac1};
2247
    $console = $console || $obj->{console};
2248
2249
    $postmsg = '' if ($buildsystem);
2250
    if (!$uuid && $nicmac1) {
2251
        $uuid = nicmac1ToUuid($nicmac1); # If no uuid try to locate based on mac
2252
    }
2253
    if (!$uuid && $uripath =~ /servers(\.cgi)?\/(.+)/) { # Try to parse uuid out of URI
2254
        my $huuid = $2;
2255
        if ($ug->to_string($ug->from_string($huuid)) eq $huuid) { # Check for valid uuid
2256
            $uuid = $huuid;
2257
        }
2258
    }
2259
    my $regserv = $register{$uuid};
2260
    my $status = $regserv->{'status'} || 'new';
2261
    if ((!$uuid) && $status eq 'new') {
2262
        my $ug = new Data::UUID;
2263
        $uuid = $ug->create_str();
2264
    };
2265
    unless ($uuid && length $uuid == 36){
2266 48fcda6b Origo
        $postmsg = "Status=Error No valid uuid ($uuid), $obj->{image}";
2267 95b003ff Origo
        return $postmsg;
2268
    }
2269
    $nicmac1 = $nicmac1 || $regserv->{'nicmac1'};
2270
    my $name = $obj->{name} || $regserv->{'name'};
2271
    my $memory = $obj->{memory} || $regserv->{'memory'};
2272
    my $vcpu = $obj->{vcpu} || $regserv->{'vcpu'};
2273
    my $image = $obj->{image} || $regserv->{'image'};
2274
    my $imagename = $obj->{imagename} || $regserv->{'imagename'};
2275
    my $image2 = $obj->{image2} || $regserv->{'image2'};
2276
    my $image2name = $obj->{image2name} || $regserv->{'image2name'};
2277
    my $image3 = $obj->{image3} || $regserv->{'image3'};
2278
    my $image3name = $obj->{image3name} || $regserv->{'image3name'};
2279
    my $image4 = $obj->{image4} || $regserv->{'image4'};
2280
    my $image4name = $obj->{image4name} || $regserv->{'image4name'};
2281
    my $diskbus = $obj->{diskbus} || $regserv->{'diskbus'};
2282
    my $cdrom = $obj->{cdrom} || $regserv->{'cdrom'};
2283
    my $boot = $obj->{boot} || $regserv->{'boot'};
2284 04c16f26 hq
    my $loader = $obj->{loader} || $regserv->{'loader'};
2285 95b003ff Origo
    my $networkuuid1 = ($obj->{networkuuid1} || $obj->{networkuuid1} eq '0')?$obj->{networkuuid1}:$regserv->{'networkuuid1'};
2286
    my $networkid1 = $obj->{networkid1} || $regserv->{'networkid1'};
2287
    my $networkname1 = $obj->{networkname1} || $regserv->{'networkname1'};
2288
    my $nicmodel1 = $obj->{nicmodel1} || $regserv->{'nicmodel1'};
2289
    my $networkuuid2 = ($obj->{networkuuid2} || $obj->{networkuuid2} eq '0')?$obj->{networkuuid2}:$regserv->{'networkuuid2'};
2290
    my $networkid2 = $obj->{networkid2} || $regserv->{'networkid2'};
2291
    my $networkname2 = $obj->{networkname2} || $regserv->{'networkname2'};
2292
    my $nicmac2 = $obj->{nicmac2} || $regserv->{'nicmac2'};
2293
    my $networkuuid3 = ($obj->{networkuuid3} || $obj->{networkuuid3} eq '0')?$obj->{networkuuid3}:$regserv->{'networkuuid3'};
2294
    my $networkid3 = $obj->{networkid3} || $regserv->{'networkid3'};
2295
    my $networkname3 = $obj->{networkname3} || $regserv->{'networkname3'};
2296
    my $nicmac3 = $obj->{nicmac3} || $regserv->{'nicmac3'};
2297
    my $notes = $obj->{notes} || $regserv->{'notes'};
2298
    my $autostart = $obj->{autostart} || $regserv->{'autostart'};
2299
    my $locktonode = $obj->{locktonode} || $regserv->{'locktonode'};
2300
    my $mac = $obj->{mac} || $regserv->{'mac'};
2301
    my $created = $regserv->{'created'} || time;
2302
    # Sanity checks
2303
    my $tenderpaths = $Stabile::config->get('STORAGE_POOLS_LOCAL_PATHS') || "/mnt/stabile/images";
2304
    my @tenderpathslist = split(/,\s*/, $tenderpaths);
2305
2306
    $networkid1 = $networkreg{$networkuuid1}->{'id'};
2307
    my $networktype1 = $networkreg{$networkuuid1}->{'type'};
2308
    my $networktype2;
2309
    if (!$nicmac1 || $nicmac1 eq "--") {$nicmac1 = randomMac();}
2310
    if ($networkuuid2 && $networkuuid2 ne "--") {
2311
        $networkid2 = $networkreg{$networkuuid2}->{'id'};
2312
        $nicmac2 = randomMac() if (!$nicmac2 || $nicmac2 eq "--");
2313
        $networktype2 = $networkreg{$networkuuid2}->{'type'};
2314
    }
2315
    if ($networkuuid3 && $networkuuid3 ne "--") {
2316
        $networkid3 = $networkreg{$networkuuid3}->{'id'};
2317
        $networkname3 = $networkreg{$networkuuid3}->{'name'};
2318
        $nicmac3 = randomMac() if (!$nicmac3 || $nicmac3 eq "--");
2319
        $networktype3 = $networkreg{$networkuuid3}->{'type'};
2320
    }
2321
2322
    my $imgdup;
2323
    my $netdup;
2324
    my $json_text; # returned if all goes well
2325
2326
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2327
2328
    if ($networkid1 > 1 && $networkid2 > 1 && $networktype1 ne 'gateway' && $networktype2 ne 'gateway'
2329
        && $networkuuid1 eq $networkuuid2) {
2330
        $netdup = 1;
2331
    }
2332
    if ($networkid1 > 1 && $networkid3 > 1 && $networktype1 ne 'gateway' && $networktype3 ne 'gateway'
2333
        && $networkuuid1 eq $networkuuid3) {
2334
        $netdup = 11;
2335
    }
2336
    if ($image eq $image2
2337
        || $image eq $image3
2338
        || $image eq $image4
2339
        || $image2 && $image2 ne '--' && $image2 eq $image3
2340
        || $image2 && $image2 ne '--' && $image2 eq $image4
2341
        || $image3 && $image3 ne '--' && $image3 eq $image4
2342
    ) {
2343
        $imgdup = 1;
2344
    } elsif ($image =~ m/\.master\.qcow2/
2345
        || $image2 =~ m/\.master\.qcow2/
2346
        || $image3 =~ m/\.master\.qcow2/
2347
        || $image4 =~ m/\.master\.qcow2/
2348
    ) {
2349
        $imgdup = 2;
2350
    } else {
2351
        # Check if another server is using image
2352
        my @regkeys = (tied %register)->select_where("user = '$user' OR user = 'common'");
2353
        foreach my $k (@regkeys) {
2354
            my $val = $register{$k};
2355 a2e0bc7e hq
            if ($val->{'uuid'} ne $uuid) {
2356 95b003ff Origo
                if (
2357 a2e0bc7e hq
                    $image eq $val->{'image'} || $image eq $val->{'image2'}|| $image eq $val->{'image3'}|| $image eq $val->{'image4'}
2358 95b003ff Origo
                ) {
2359
                    $imgdup = 51;
2360
                } elsif ($image2 && $image2 ne "--" &&
2361 a2e0bc7e hq
                    ($image2 eq $val->{'image'} || $image2 eq $val->{'image2'} || $image2 eq $val->{'image3'} || $image2 eq $val->{'image4'})
2362 95b003ff Origo
                ) {
2363
                    $imgdup = 52;
2364
                } elsif ($image3 && $image3 ne "--" &&
2365 a2e0bc7e hq
                    ($image3 eq $val->{'image'} || $image3 eq $val->{'image2'} || $image3 eq $val->{'image3'} || $image3 eq $val->{'image4'})
2366 95b003ff Origo
                ) {
2367
                    $imgdup = 53;
2368
                } elsif ($image4 && $image4 ne "--" &&
2369 a2e0bc7e hq
                    ($image4 eq $val->{'image'} || $image4 eq $val->{'image2'} || $image4 eq $val->{'image3'} || $image4 eq $val->{'image4'})
2370 95b003ff Origo
                ) {
2371
                    $imgdup = 54;
2372
                }
2373
2374
                if ($networkid1>1) {
2375
                    if ($networktype1 ne 'gateway' &&
2376 a2e0bc7e hq
                        ($networkuuid1 eq $val->{'networkuuid1'} || $networkuuid1 eq $val->{'networkuuid2'})
2377 95b003ff Origo
                    ) {
2378
                        $netdup = 51;
2379
                    }
2380
                }
2381
                if ($networkid2>1) {
2382
                    if ($networktype2 ne 'gateway' && $networkuuid2 && $networkuuid2 ne "--" &&
2383 a2e0bc7e hq
                        ($networkuuid2 eq $val->{'networkuuid1'} || $networkuuid2 eq $val->{'networkuuid2'})
2384 95b003ff Origo
                    ) {
2385
                        $netdup = 52;
2386
                    }
2387
                }
2388
            }
2389
        }
2390
        my $legalpath;
2391
        if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2392
            $legalpath = 1;
2393
        } else {
2394
            foreach my $path (@tenderpathslist) {
2395
                if ($image =~ m/$path\/$user/) {
2396
                    $legalpath = 1;
2397
                    last;
2398
                }
2399
            }
2400
        }
2401
        $imgdup = 6 unless $legalpath;
2402
        if ($image2 && $image2 ne "--") { # TODO: We should probably check for conflicting nodes for image3 and image 4 too
2403
            if ($image2 =~ m/\/mnt\/stabile\/node\/$user/) {
2404
                if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2405
                    if ($imagereg{$image}->{'mac'} eq $imagereg{$image2}->{'mac'}) {
2406
                        $legalpath = 1;
2407
                    } else {
2408
                        $legalpath = 0; # Images are on two different nodes
2409
                    }
2410
                } else {
2411
                    $legalpath = 1;
2412
                }
2413
            } else {
2414
                $legalpath = 0;
2415
                foreach my $path (@tenderpathslist) {
2416
                    if ($image2 =~ m/$path\/$user/) {
2417
                        $legalpath = 1;
2418
                        last;
2419
                    }
2420
                }
2421
            }
2422
            $imgdup = 7 unless $legalpath;
2423
        }
2424
    }
2425
2426
    if (!$imgdup && !$netdup) {
2427
        if ($status eq "new") {
2428
            $status = "shutoff";
2429
            $name = $name || 'New Server';
2430
            $memory = $memory || 1024;
2431
            $vcpu = $vcpu || 1;
2432
            $imagename = $imagename || '--';
2433
            $image2 = $image2 || '--';
2434
            $image2name = $image2name || '--';
2435
            $image3 = $image3 || '--';
2436
            $image3name = $image3name || '--';
2437
            $image4 = $image4 || '--';
2438
            $image4name = $image4name || '--';
2439
            $diskbus = $diskbus || 'ide';
2440
            $cdrom = $cdrom || '--';
2441
            $boot = $boot || 'hd';
2442 04c16f26 hq
            $loader = $loader || 'bios';
2443 95b003ff Origo
            $networkuuid1 = $networkuuid1 || 1;
2444
            $networkid1 = $networkid1 || 1;
2445
            $networkname1 = $networkname1 || '--';
2446
            $nicmodel1 = $nicmodel1 || 'rtl8139';
2447
            $nicmac1 = $nicmac1 || randomMac();
2448
            $networkuuid2 = $networkuuid2 || '--';
2449
            $networkid2 = $networkid2 || '--';
2450
            $networkname2 = $networkname2 || '--';
2451
            $nicmac2 = $nicmac2 || randomMac();
2452
            $networkuuid3 = $networkuuid3 || '--';
2453
            $networkid3 = $networkid3 || '--';
2454
            $networkname3 = $networkname3 || '--';
2455
            $nicmac3 = $nicmac3 || randomMac();
2456
            #    $uiuuid = $uuid; # No need to update ui for new server with jsonreststore
2457 8d7785ff Origo
            $postmsg .= "OK Created new server: $name";
2458 3657de20 Origo
            $postmsg .= ", uuid: $uuid " if ($console);
2459 95b003ff Origo
        }
2460
        # Update status of images
2461
        my @imgs = ($image, $image2, $image3, $image4);
2462
        my @imgkeys = ('image', 'image2', 'image3', 'image4');
2463
        for (my $i=0; $i<4; $i++) {
2464
            my $img = $imgs[$i];
2465
            my $k = $imgkeys[$i];
2466
            my $regimg = $imagereg{$img};
2467
            # if ($img && $img ne '--' && ($status eq 'new' || $img ne $regserv->{$k})) { # Servers image changed - update image status
2468
            if ($img && $img ne '--') { # Always update image status
2469
                $regimg->{'status'} = 'used' if (
2470
                    $regimg->{'status'} eq 'unused'
2471
                        # Image cannot be active if server is shutoff
2472
                        || ($regimg->{'status'} eq 'active' && $status eq 'shutoff')
2473
                );
2474
                $regimg->{'domains'} = $uuid;
2475
                $regimg->{'domainnames'} = $name;
2476
            }
2477
            # If image has changed, release the old image
2478
            if ($status ne 'new' && $img ne $regserv->{$k} && $imagereg{$regserv->{$k}}) {
2479
                $imagereg{$regserv->{$k}}->{'status'} = 'unused';
2480
                delete $imagereg{$regserv->{$k}}->{'domains'};
2481
                delete $imagereg{$regserv->{$k}}->{'domainnames'};
2482
            }
2483
        }
2484
2485
        my $valref = {
2486
            uuid=>$uuid,
2487
            user=>$user,
2488
            name=>$name,
2489
            memory=>$memory,
2490
            vcpu=>$vcpu,
2491
            image=>$image,
2492
            imagename=>$imagename,
2493
            image2=>$image2,
2494
            image2name=>$image2name,
2495
            image3=>$image3,
2496
            image3name=>$image3name,
2497
            image4=>$image4,
2498
            image4name=>$image4name,
2499
            diskbus=>$diskbus,
2500
            cdrom=>$cdrom,
2501
            boot=>$boot,
2502 04c16f26 hq
            loader=>$loader,
2503 95b003ff Origo
            networkuuid1=>$networkuuid1,
2504
            networkid1=>$networkid1,
2505
            networkname1=>$networkname1,
2506
            nicmodel1=>$nicmodel1,
2507
            nicmac1=>$nicmac1,
2508
            networkuuid2=>$networkuuid2,
2509
            networkid2=>$networkid2,
2510
            networkname2=>$networkname2,
2511
            nicmac2=>$nicmac2,
2512
            networkuuid3=>$networkuuid3,
2513
            networkid3=>$networkid3,
2514
            networkname3=>$networkname3,
2515
            nicmac3=>$nicmac3,
2516
            status=>$status,
2517
            notes=>$notes,
2518
            autostart=>$autostart,
2519
            locktonode=>$locktonode,
2520
            action=>"",
2521
            created=>$created
2522
        };
2523
        $valref->{'system'} = $system if ($system);
2524
        if ($mac && $locktonode eq 'true') {
2525
            $valref->{'mac'} = $mac;
2526
            $valref->{'macip'} = $nodereg{$mac}->{'ip'};
2527
            $valref->{'macname'} = $nodereg{$mac}->{'name'};
2528
        }
2529
        if ($newsystem) {
2530
            my $ug = new Data::UUID;
2531
            $sysuuid = $ug->create_str();
2532
            $valref->{'system'} = $sysuuid;
2533 3657de20 Origo
            $postmsg .= "OK sysuuid: $sysuuid " if ($console);
2534 95b003ff Origo
        }
2535
2536
        # Remove domain uuid from old networks. Leave gateways alone - they get updated on next listing
2537
        my $oldnetworkuuid1 = $regserv->{'networkuuid1'};
2538
        if ($oldnetworkuuid1 ne $networkuuid1 && $networkreg{$oldnetworkuuid1}) {
2539
            $networkreg{$oldnetworkuuid1}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2540
        }
2541
        $register{$uuid} = validateItem($valref);
2542
2543
        if ($networkreg{$networkuuid1}->{'type'} eq 'gateway') {
2544 04c16f26 hq
            # We now remove before adding to support API calls that dont necessarily list afterwards
2545
            $networkreg{$networkuuid1}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2546 95b003ff Origo
            my $domains = $networkreg{$networkuuid1}->{'domains'};
2547
            $networkreg{$networkuuid1}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2548 04c16f26 hq
2549
            $networkreg{$networkuuid1}->{'domainnames'} =~ s/($name)(,?)( ?)//;
2550 95b003ff Origo
            my $domainnames = $networkreg{$networkuuid1}->{'domainnames'};
2551
            $networkreg{$networkuuid1}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2552
        } else {
2553
            $networkreg{$networkuuid1}->{'domains'}  = $uuid;
2554
            $networkreg{$networkuuid1}->{'domainnames'}  = $name;
2555
        }
2556
2557
        if ($networkuuid2 && $networkuuid2 ne '--') {
2558
            if ($networkreg{$networkuuid2}->{'type'} eq 'gateway') {
2559 04c16f26 hq
                $networkreg{$networkuuid2}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2560 95b003ff Origo
                my $domains = $networkreg{$networkuuid2}->{'domains'};
2561
                $networkreg{$networkuuid2}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2562 04c16f26 hq
2563
                $networkreg{$networkuuid2}->{'domainnames'} =~ s/($name)(,?)( ?)//;
2564 95b003ff Origo
                my $domainnames = $networkreg{$networkuuid2}->{'domainnames'};
2565
                $networkreg{$networkuuid2}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2566
            } else {
2567
                $networkreg{$networkuuid2}->{'domains'}  = $uuid;
2568
                $networkreg{$networkuuid2}->{'domainnames'}  = $name;
2569
            }
2570
        }
2571
2572
        if ($networkuuid3 && $networkuuid3 ne '--') {
2573
            if ($networkreg{$networkuuid3}->{'type'} eq 'gateway') {
2574
                my $domains = $networkreg{$networkuuid3}->{'domains'};
2575
                $networkreg{$networkuuid3}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2576
                my $domainnames = $networkreg{$networkuuid3}->{'domainnames'};
2577
                $networkreg{$networkuuid3}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2578
            } else {
2579
                $networkreg{$networkuuid3}->{'domains'}  = $uuid;
2580
                $networkreg{$networkuuid3}->{'domainnames'}  = $name;
2581
            }
2582
        }
2583
        my %jitem = %{$register{$uuid}};
2584
        $json_text = to_json(\%jitem, {pretty=>1});
2585
        $json_text =~ s/null/"--"/g;
2586
        $uiuuid = $uuid;
2587
        $uiname = $name;
2588
2589
        tied(%register)->commit;
2590
        tied(%networkreg)->commit;
2591 a2e0bc7e hq
        tied(%imagereg)->commit;
2592 95b003ff Origo
2593
    } else {
2594 48fcda6b Origo
        $postmsg .= "ERROR This image ($image) cannot be used ($imgdup) " if ($imgdup);
2595
        $postmsg .= "ERROR This network ($networkname1) cannot be used ($netdup)" if ($netdup);
2596 95b003ff Origo
    }
2597
2598
    my $domuser = $obj->{'user'};
2599
    # We were asked to move server to another account
2600
    if ($domuser && $domuser ne '--' && $domuser ne $user) {
2601
        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")};
2602
        if ($status eq 'shutoff' || $status eq 'inactive') {
2603
            unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {$posterror =  "Unable to access user register"; return 0;};
2604
            my @accounts = split(/,\s*/, $userreg{$tktuser}->{'accounts'});
2605
            my @accountsprivs = split(/,\s*/, $userreg{$tktuser}->{'accountsprivileges'});
2606
            %ahash = ($tktuser, $userreg{$tktuser}->{'privileges'}); # Include tktuser in accounts hash
2607
            for my $i (0 .. scalar @accounts)
2608
            {
2609
                next unless $accounts[$i];
2610
                $ahash{$accounts[$i]} = $accountsprivs[$i] || 'r';
2611
            }
2612
            untie %userreg;
2613
2614
            if (!$isreadonly && $ahash{$domuser} && !($ahash{$domuser} =~ /r/)) { # Check if user is allow to access account
2615
                my $imgdone;
2616
                my $netdone;
2617
                # First move main image
2618
                $Stabile::Images::user = $user;
2619
                require "$Stabile::basedir/cgi/images.cgi";
2620
                $Stabile::Images::console = 1;
2621
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving image $imagename to account: $domuser"});
2622
                my $nimage = Stabile::Images::Move($image, $domuser);
2623 48fcda6b Origo
                chomp $nimage;
2624 95b003ff Origo
                if ($nimage) {
2625
                    $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2626
                    $register{$uuid}->{'image'} = $nimage;
2627
                    $imgdone = 1;
2628
                } else {
2629
                    $main::syslogit->($user, "info", "Unable to move image $imagename to account: $domuser");
2630
                }
2631
                # Move other attached images
2632
                my @images = ($image2, $image3, $image4);
2633
                my @imagenames = ($image2name, $image3name, $image4name);
2634
                my @imagekeys = ('image2', 'image3', 'image4');
2635
                for (my $i=0; $i<3; $i++) {
2636
                    my $img = $images[$i];
2637
                    my $imgname = $imagenames[$i];
2638
                    my $imgkey = $imagekeys[$i];
2639
                    if ($img && $img ne '--') {
2640
                        $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving $imgkey $imgname to account: $domuser"});
2641
                        $nimage = Stabile::Images::Move($img, $domuser);
2642 48fcda6b Origo
                        chomp $nimage;
2643 95b003ff Origo
                        if ($nimage) {
2644
                            $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2645
                            $register{$uuid}->{$imgkey} = $nimage;
2646
                        } else {
2647
                            $main::syslogit->($user, "info", "Unable to move $imagekeys[$i] $img to account: $domuser");
2648
                        }
2649
                    }
2650
                }
2651 6fdc8676 hq
                # Then move network(s)
2652 95b003ff Origo
                if ($imgdone) {
2653
                    $Stabile::Networks::user = $user;
2654
                    require "$Stabile::basedir/cgi/networks.cgi";
2655
                    $Stabile::Networks::console = 1;
2656
                    my @networks = ($networkuuid1, $networkuuid2, $networkuuid3);
2657
                    my @netkeys = ('networkuuid1', 'networkuuid2', 'networkuuid3');
2658
                    my @netnamekeys = ('networkname1', 'networkname2', 'networkname3');
2659
                    for (my $i=0; $i<scalar @networks; $i++) {
2660
                        my $net = $networks[$i];
2661
                        my $netkey = $netkeys[$i];
2662
                        my $netnamekey = $netnamekeys[$i];
2663 48fcda6b Origo
                        my $regnet = $networkreg{$net};
2664
                        my $oldid = $regnet->{'id'};
2665 95b003ff Origo
                        next if ($net eq '' || $net eq '--');
2666 48fcda6b Origo
                        if ($regnet->{'type'} eq 'gateway') {
2667 95b003ff Origo
                            if ($oldid > 1) { # Private gateway
2668
                                foreach my $networkvalref (values %networkreg) { # use gateway with same id if it exists
2669
                                    if ($networkvalref->{'user'} eq $domuser
2670
                                        && $networkvalref->{'type'} eq 'gateway'
2671
                                        && $networkvalref->{'id'} == $oldid) {
2672
                                        # We found an existing gateway with same id - use it
2673
                                        $register{$uuid}->{$netkey} = $networkvalref->{'uuid'};
2674
                                        $register{$uuid}->{$netnamekey} = $networkvalref->{'name'};
2675
                                        $netdone = 1;
2676
                                        $main::updateUI->({tab=>"networks", user=>$user, message=>"Using network $networkvalref->{'name'} from account: $domuser"});
2677
                                        last;
2678
                                    }
2679
                                }
2680
                                if (!($netdone)) {
2681
                                    # Make a new gateway
2682
                                    my $ug = new Data::UUID;
2683
                                    my $newuuid = $ug->create_str();
2684 48fcda6b Origo
                                    Stabile::Networks::save($oldid, $newuuid, $regnet->{'name'}, 'new', 'gateway', '', '', $regnet->{'ports'}, 0, $domuser);
2685 95b003ff Origo
                                    $register{$uuid}->{$netkey} = $newuuid;
2686 48fcda6b Origo
                                    $register{$uuid}->{$netnamekey} = $regnet->{'name'};
2687 95b003ff Origo
                                    $netdone = 1;
2688 48fcda6b Origo
                                    $main::updateUI->({tab=>"networks", user=>$user, message=>"Created gateway $regnet->{'name'} for account: $domuser"});
2689
                                    $main::syslogit->($user, "info", "Created gateway $regnet->{'name'} for account: $domuser");
2690 95b003ff Origo
                                }
2691
                            } elsif ($oldid==0 || $oldid==1) {
2692
                                $netdone = 1; # Use common gateway
2693 48fcda6b Origo
                                $main::updateUI->({tab=>"networks", user=>$user, message=>"Reused network $regnet->{'name'} for account: $domuser"});
2694 95b003ff Origo
                            }
2695
                        } else {
2696
                            my $newid = Stabile::Networks::getNextId('', $domuser);
2697
                            $networkreg{$net}->{'id'} = $newid;
2698
                            $networkreg{$net}->{'user'} = $domuser;
2699 6fdc8676 hq
                        #    if ($regnet->{'type'} eq 'internalip' || $regnet->{'type'} eq 'ipmapping') {
2700 95b003ff Origo
                                # Deactivate network and assign new internal ip
2701 48fcda6b Origo
                                Stabile::Networks::Deactivate($regnet->{'uuid'});
2702 95b003ff Origo
                                $networkreg{$net}->{'internalip'} =
2703 48fcda6b Origo
                                    Stabile::Networks::getNextInternalIP('',$regnet->{'uuid'}, $newid, $domuser);
2704 6fdc8676 hq
                        #    }
2705 95b003ff Origo
                            $netdone = 1;
2706 48fcda6b Origo
                            $main::updateUI->({tab=>"networks", user=>$user, message=>"Moved network $regnet->{'name'} to account: $domuser"});
2707
                            $main::syslogit->($user, "info", "Moved network $regnet->{'name'} to account: $domuser");
2708 95b003ff Origo
                        }
2709
                    }
2710
                    if ($netdone) {
2711
                        # Finally move the server
2712
                        $register{$uuid}->{'user'} = $domuser;
2713 48fcda6b Origo
                        $postmsg .= "OK Moved server $name to account: $domuser";
2714 95b003ff Origo
                        $main::syslogit->($user, "info", "Moved server $name ($uuid) to account: $domuser");
2715 48fcda6b Origo
                        $main::updateUI->({tab=>"servers", user=>$user, type=>"update"});
2716 95b003ff Origo
                    } else {
2717 48fcda6b Origo
                        $postmsg .= "ERROR Unable to move network to account: $domuser";
2718 95b003ff Origo
                        $main::updateUI->({tab=>"image", user=>$user, message=>"Unable to move network to account: $domuser"});
2719
                    }
2720
                } else {
2721
                    $main::updateUI->({tab=>"image", user=>$user, message=>"Could not move image to account: $domuser"});
2722
                }
2723
            } else {
2724 48fcda6b Origo
                $postmsg .= "ERROR No access to move server";
2725 95b003ff Origo
            }
2726
        } else {
2727 48fcda6b Origo
            $postmsg .= "Error Unable to move $status server";
2728 95b003ff Origo
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Please shut down before moving server"});
2729
        }
2730
        untie %userreg;
2731
    }
2732
2733
    if ($console) {
2734
        $postreply = $postmsg;
2735
    } else {
2736
        $postreply = $json_text || $postmsg;
2737
    }
2738
    return $postreply;
2739
    untie %imagereg;
2740
}
2741
2742
2743
sub Shutdown {
2744
    my ($uuid, $action, $obj) = @_;
2745
    if ($help) {
2746
        return <<END
2747
GET:uuid:
2748
Marks a server for shutdown, i.e. send and ACPI shutdown event to the server. If OS supports ACPI, it begins a shutdown.
2749
END
2750
    }
2751
    $uistatus = "shuttingdown";
2752
    my $dbstatus = $obj->{status};
2753
    my $mac = $obj->{mac};
2754
    my $macname = $obj->{macname};
2755
    my $name = $obj->{name};
2756
    if ($dbstatus eq 'running') {
2757
        my $tasks;
2758
        $tasks = $nodereg{$mac}->{'tasks'} if ($nodereg{$mac});
2759
        $nodereg{$mac}->{'tasks'} = $tasks . "SHUTDOWN $uuid $user\n";
2760
        tied(%nodereg)->commit;
2761
        $register{$uuid}->{'status'} = $uistatus;
2762
        $register{$uuid}->{'statustime'} = $current_time;
2763
        $uiuuid = $uuid;
2764
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2765
        $postreply .= "Status=$uistatus OK $uistatus $name\n";
2766
    } else {
2767
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2768
        $postreply .= "Status=ERROR problem $uistatus $name...\n";
2769
    }
2770
    return $postreply;
2771
}
2772
2773
sub Suspend {
2774
    my ($uuid, $action, $obj) = @_;
2775
    if ($help) {
2776
        return <<END
2777
GET:uuid:
2778
Marks a server for suspend, i.e. pauses the server. Server must be running
2779
END
2780
    }
2781
    $uistatus = "suspending";
2782
    my $dbstatus = $obj->{status};
2783
    my $mac = $obj->{mac};
2784
    my $macname = $obj->{macname};
2785
    my $name = $obj->{name};
2786 a2e0bc7e hq
    my $areply = '';
2787 95b003ff Origo
    if ($dbstatus eq 'running') {
2788
        my $tasks = $nodereg{$mac}->{'tasks'};
2789
        $nodereg{$mac}->{'tasks'} = $tasks . "SUSPEND $uuid $user\n";
2790
        tied(%nodereg)->commit;
2791
        $register{$uuid}->{'status'} = $uistatus;
2792
        $register{$uuid}->{'statustime'} = $current_time;
2793
        $uiuuid = $uuid;
2794
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2795 a2e0bc7e hq
        $areply .= "Status=$uistatus OK $uistatus $name.\n";
2796 95b003ff Origo
    } else {
2797
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2798 a2e0bc7e hq
        $areply .= "Status=ERROR problem $uistatus $name.\n";
2799 95b003ff Origo
    }
2800 a2e0bc7e hq
    return $areply;
2801 95b003ff Origo
}
2802
2803
sub Resume {
2804
    my ($uuid, $action, $obj) = @_;
2805
    if ($help) {
2806
        return <<END
2807
GET:uuid:
2808
Marks a server for resume running. Server must be paused.
2809
END
2810
    }
2811
    my $dbstatus = $obj->{status};
2812
    my $mac = $obj->{mac};
2813
    my $macname = $obj->{macname};
2814
    my $name = $obj->{name};
2815
    my $image = $obj->{image};
2816
    my $image2 = $obj->{image2};
2817
    my $image3 = $obj->{image3};
2818
    my $image4 = $obj->{image4};
2819
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$posterror = "Unable to access image register"; return;};
2820
    if ($imagereg{$image}->{'status'} ne "paused"
2821
        || ($image2 && $image2 ne '--' && $imagereg{$image}->{'status'} ne "paused")
2822
        || ($image3 && $image3 ne '--' && $imagereg{$image3}->{'status'} ne "paused")
2823
        || ($image4 && $image4 ne '--' && $imagereg{$image4}->{'status'} ne "paused")
2824
    ) {
2825
        $postreply .= "Status=ERROR Image $uuid busy ($imagereg{$image}->{'status'}), please wait 30 sec.\n";
2826
        untie %imagereg;
2827
        return $postreply   ;
2828
    } else {
2829
        untie %imagereg;
2830
    }
2831
    $uistatus = "resuming";
2832
    if ($dbstatus eq 'paused') {
2833
        my $tasks = $nodereg{$mac}->{'tasks'};
2834
        $nodereg{$mac}->{'tasks'} = $tasks . "RESUME $uuid $user\n";
2835
        tied(%nodereg)->commit;
2836
        $register{$uuid}->{'status'} = $uistatus;
2837
        $register{$uuid}->{'statustime'} = $current_time;
2838
        $uiuuid = $uuid;
2839
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2840
        $postreply .= "Status=$uistatus OK $uistatus ". $register{$uuid}->{'name'} . "\n";
2841
    } else {
2842
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2843
        $postreply .= "Status=ERROR problem $uistatus ". $register{$uuid}->{'name'} . "\n";
2844
    }
2845
    return $postreply;
2846
}
2847
2848 d3805c61 hq
sub Abort {
2849
    my ($uuid, $action, $obj) = @_;
2850
    if ($help) {
2851
        return <<END
2852
GET:uuid,mac:
2853
Aborts an ongoing server move between nodes initiated with move or stormove.
2854
END
2855
    }
2856
    my $dbstatus = $obj->{status};
2857
    my $dmac = $obj->{mac};
2858
    my $name = $obj->{name};
2859
    if ($isadmin || $register{$uuid}->{user} eq $user) {
2860
        my $tasks = $nodereg{$dmac}->{'tasks'};
2861
        $tasks .= "ABORT $uuid $user\n";
2862
        $nodereg{$dmac}->{'tasks'} = $tasks;
2863
        tied(%nodereg)->commit;
2864
        $postreply = "Status=aborting Aborting move of server $name ($dbstatus) on node $dmac\n";
2865
    } else {
2866
        $postreply = "Status=OK Insufficient privileges\n";
2867
    }
2868
}
2869
2870 95b003ff Origo
sub Move {
2871
    my ($uuid, $action, $obj) = @_;
2872
    if ($help) {
2873
        return <<END
2874
GET:uuid,mac:
2875 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.
2876 95b003ff Origo
END
2877
    }
2878
    my $dbstatus = $obj->{status};
2879
    my $dmac = $obj->{mac};
2880
    my $name = $obj->{name};
2881
    my $mem = $obj->{memory};
2882
    my $vcpu = $obj->{vcpu};
2883
    my $image = $obj->{image};
2884
    my $image2 = $obj->{image2};
2885
    my $image3 = $obj->{image3};
2886
    my $image4 = $obj->{image4};
2887 d3805c61 hq
2888 95b003ff Origo
    $uistatus = "moving";
2889
    if ($dbstatus eq 'running' && $isadmin) {
2890
        my $hypervisor = getHypervisor($image);
2891
        my $mac = $register{$uuid}->{'mac'};
2892
        $dmac = "" if ($dmac eq "--");
2893
        $mac = "" if ($mac eq "--");
2894
2895 d3805c61 hq
        if (( $image =~ /\/mnt\/stabile\/node\//
2896 95b003ff Origo
            || $image2 =~ /\/mnt\/stabile\/node\//
2897
            || $image3 =~ /\/mnt\/stabile\/node\//
2898 d3805c61 hq
            || $image4 =~ /\/mnt\/stabile\/node\// ) && $action ne 'stormove'
2899 95b003ff Origo
        ) {
2900 d3805c61 hq
            $postreply = qq|{"error": 1, "message": "Servers with local storage must be moved with stormove"}|;
2901
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Servers with local storage must be moved with stormove"});
2902 95b003ff Origo
        } else {
2903
            my ($targetmac, $targetname, $targetip, $port) =
2904 d3805c61 hq
                locateTargetNode($uuid, $dmac, $mem, $vcpu, $image, $image2, $image3, $image4, $hypervisor, $mac, 1);
2905 95b003ff Origo
            if ($targetmac) {
2906
                my $tasks = $nodereg{$targetmac}->{'tasks'};
2907 d3805c61 hq
                if ($action eq 'stormove') {
2908
                    $tasks = $tasks . "RECEIVESTOR $uuid $user\n";
2909
                } else {
2910
                    $tasks = $tasks . "RECEIVE $uuid $user\n";
2911
                }
2912 95b003ff Origo
                # Also update allowed port forwards
2913
                $nodereg{$targetmac}->{'tasks'} = $tasks . "PERMITOPEN $user\n";
2914
                $register{$uuid}->{'status'} = "moving";
2915
                $register{$uuid}->{'statustime'} = $current_time;
2916
                $uiuuid = $uuid;
2917
                $uidisplayip = $targetip;
2918
                $uidisplayport = $port;
2919
                $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus to $targetname ($targetmac)");
2920
                $postreply .= "Status=OK $uistatus ". $register{$uuid}->{'name'} . "\n";
2921
2922 d3805c61 hq
                # Precreate images on destination node
2923
                if ($action eq 'stormove') {
2924
                    my $preimages = '';
2925
                    $Stabile::Images::user = $user;
2926
                    require "$Stabile::basedir/cgi/images.cgi";
2927
                    $Stabile::Images::console = 1;
2928
                    if ($targetip eq '10.0.0.1') { # Moving from node
2929
                        if ($image =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2930
                            my $res = Stabile::Images::Move($image, $user, '0', '', 0, 1);
2931
                            $preimages .= " $register{$uuid}->{imagename}";
2932
                        }
2933
                        if ($image2 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2934
                            my $res = Stabile::Images::Move($image2, $user, '0', '', 0, 1);
2935
                            $preimages .= " $register{$uuid}->{image2name}";
2936
                        }
2937
                        if ($image3 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2938
                            my $res = Stabile::Images::Move($image3, $user, '0', '', 0, 1);
2939
                            $preimages .= " $register{$uuid}->{image3name}";
2940
                        }
2941
                        if ($image4 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
2942
                            my $res = Stabile::Images::Move($image4, $user, '0', '', 0, 1);
2943
                            $preimages .= " $register{$uuid}->{image4name}";
2944
                        }
2945
                    } else { # Moving to node or between nodes - always move primary image, also if on shared storage
2946
                        my $res = Stabile::Images::Move($image, $user, '-1', $targetmac, 0, 1);
2947
                        $preimages .= " $register{$uuid}->{imagename}";
2948
                        if ($image2 && $image2 ne '--') {
2949
                            # We don't migrate data disks away from shared storage
2950
                            unless ($image2 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
2951
                                my $res = Stabile::Images::Move($image2, $user, '-1', $targetmac, 0, 1);
2952
                                $preimages .= " $register{$uuid}->{image2name}";
2953
                            }
2954
                        }
2955
                        if ($image3 && $image3 ne '--') {
2956
                            unless ($image3 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
2957
                                my $res = Stabile::Images::Move($image3, $user, '-1', $targetmac, 0, 1);
2958
                                $preimages .= " $register{$uuid}->{image3name}";
2959
                            }
2960
                        }
2961
                        if ($image4 && $image4 ne '--') {
2962
                            unless ($image4 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
2963
                                my $res = Stabile::Images::Move($image4, $user, '-1', $targetmac, 0, 1);
2964
                                $preimages .= " $register{$uuid}->{image4name}";
2965
                            }
2966
                        }
2967
                    }
2968
                    if ($preimages) {
2969
                        $main::syslogit->($user, "info", "Precreating images $preimages on node $targetmac");
2970
                        $main::updateUI->({tab=>"servers", user=>$user, message=>"Precreating images $preimages on node $targetmac"});
2971
                    }
2972
                }
2973 95b003ff Origo
                if ($params{'PUTDATA'}) {
2974
                    my %jitem = %{$register{$uuid}};
2975
                    my $json_text = to_json(\%jitem);
2976
                    $json_text =~ s/null/"--"/g;
2977
                    $postreply = $json_text;
2978
                }
2979 d3805c61 hq
#                $main::updateUI->({tab=>"servers", user=>$user, status=>'moving', uuid=>$uuid, type=>'update', message=>"Moving $register{$uuid}->{name} to $targetmac"});
2980 95b003ff Origo
            } else {
2981
                $main::syslogit->($user, "info", "Could not find $hypervisor target for $uistatus $uuid ($image)");
2982 d3805c61 hq
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Could not find target for $uistatus $register{$uuid}->{'name'}"});
2983 95b003ff Origo
                $postreply = qq|{"error": 1, "message": "Could not find target for $uistatus $register{$uuid}->{'name'}"}|;
2984
            }
2985
        }
2986
    } else {
2987
        $main::syslogit->($user, "info", "Problem moving a $dbstatus domain: $uuid");
2988 d3805c61 hq
        my $serv = $register{$uuid};
2989
        $postreply .= qq|{"error": 1, "message": "ERROR problem moving $serv->{'name'} ($dbstatus)"}|;
2990 95b003ff Origo
    }
2991
    return $postreply;
2992
}
2993
2994 c899e439 Origo
sub Changepassword {
2995
    my ($uuid, $action, $obj) = @_;
2996
    if ($help) {
2997
        return <<END
2998
POST:uuid,username,password:
2999
Attempts to set password for [username] to [password] using guestfish. If no username is specified, user 'stabile' is assumed.
3000
END
3001
    }
3002
    my $img = $register{$uuid}->{'image'};
3003
    my $username = $obj->{'username'} || 'stabile';
3004
    my $password = $obj->{'password'};
3005
    return "Status=Error Please supply a password\n" unless ($password);
3006
    return "Status=Error Please shut down the server before changing password\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
3007
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
3008
3009
    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;};
3010
    my $cmd = qq/guestfish --rw -a $img -i command "bash -c 'echo $username:$password | chpasswd'" 2>\&1/;
3011
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
3012
        my $mac = $imagereg{$img}->{'mac'};
3013
        my $macip = $nodereg{$mac}->{'ip'};
3014
        $cmd = "$sshcmd $macip $cmd";
3015
    }
3016
    my $res = `$cmd`;
3017
    $res = $1 if ($res =~ /guestfish: (.*)/);
3018
    chomp $res;
3019
    return "Status=OK Ran chpasswd for user $username in server $register{$uuid}->{'name'}: $res\n";
3020
}
3021
3022
sub Sshaccess {
3023
    my ($uuid, $action, $obj) = @_;
3024
    if ($help) {
3025
        return <<END
3026
POST:uuid,address:
3027
Attempts to change the ip addresses you can access the server over SSH (port 22) from, by adding [address] to /etc/hosts.allow.
3028
[address] should either be an IP address or a range in CIDR notation. Please note that no validation of [address] is performed.
3029
END
3030
    }
3031
    my $img = $register{$uuid}->{'image'};
3032
    my $address = $obj->{'address'};
3033
    return "Status=Error Please supply an aaddress\n" unless ($address);
3034
    return "Status=Error Please shut down the server before changing SSH access\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
3035
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
3036
3037
    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;};
3038
3039
    my $isshcmd = '';
3040
    my $cmd = qq[guestfish --rw -a $img -i command "sed -i -re 's|(sshd: .*)#stabile|\\1 $address #stabile|' /etc/hosts.allow"];
3041
#    my $cmd = qq[guestfish --rw -a $img -i command "bash -c 'echo sshd: $address >> /etc/hosts.allow'"];
3042
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
3043
        my $mac = $imagereg{$img}->{'mac'};
3044
        my $macip = $nodereg{$mac}->{'ip'};
3045
        $isshcmd = "$sshcmd $macip ";
3046
    }
3047
    my $res = `$isshcmd$cmd`;
3048
    chomp $res;
3049
    #$cmd = qq[guestfish --rw -a $img -i command "bash -c 'cat /etc/hosts.allow'"];
3050
    #$res .= `$isshcmd$cmd`;
3051
    #chomp $res;
3052
    return "Status=OK Tried to add sshd: $address to /etc/hosts.allow in server $register{$uuid}->{'name'}\n";
3053
}
3054
3055 95b003ff Origo
sub Mountcd {
3056
    my ($uuid, $action, $obj) = @_;
3057
    if ($help) {
3058
        return <<END
3059
GET:uuid,cdrom:
3060
Mounts a cdrom on a server. Server must be running. Mounting the special cdrom named '--' unomunts any currently mounted cdrom.
3061
END
3062
    }
3063
    my $dbstatus = $obj->{status};
3064
    my $mac = $obj->{mac};
3065
    my $cdrom = $obj->{cdrom};
3066
    unless ($cdrom && $dbstatus eq 'running') {
3067
        $main::updateUI->({tab=>"servers", user=>$user, uuid=>$uuid, type=>'update', message=>"Unable to mount cdrom"});
3068
        $postreply = qq|{"Error": 1, "message": "Problem mounting cdrom on $obj->{name}"}|;
3069
        return;
3070
    }
3071
    my $tasks = $nodereg{$mac}->{'tasks'};
3072
    # $user is in the middle here, because $cdrom may contain spaces...
3073
    $nodereg{$mac}->{'tasks'} = $tasks . "MOUNT $uuid $user \"$cdrom\"\n";
3074
    tied(%nodereg)->commit;
3075
    if ($cdrom eq "--") {
3076
        $postreply = qq|{"OK": 1, "message": "OK unmounting cdrom from $obj->{name}"}|;
3077
    } else {
3078
        $postreply = qq|{"OK": 1, "message": "OK mounting cdrom $cdrom on $obj->{name}"}|;
3079
    }
3080
    $register{$uuid}->{'cdrom'} = $cdrom unless ($cdrom eq 'virtio');
3081
    return $postreply;
3082
}