Project

General

Profile

Download (132 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 a93267ad hq
        untie %register;
40
        untie %networkreg;
41 95b003ff Origo
        untie %nodereg;
42
        untie %xmlreg;
43
    }
44
45
} catch Error with {
46 a93267ad hq
    my $ex = shift;
47 95b003ff Origo
    print $Stabile::q->header('text/html', '500 Internal Server Error') unless ($console);
48 a93267ad hq
    if ($ex->{-text}) {
49 95b003ff Origo
        print "Got error: ", $ex->{-text}, " on line ", $ex->{-line}, "\n";
50 a93267ad hq
    } else {
51
        print "Status=ERROR\n";
52
    }
53 95b003ff Origo
} 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 a93267ad hq
    if ($h{'action'} eq 'destroy' || $action eq 'destroy' || $action eq 'destroyuserservers' || $action eq 'start' || $action eq 'attach' || $action eq 'detach' || $action =~ /changepassword|sshaccess/) {
68
        $h{action} = $action unless ($h{action});
69 95b003ff Origo
        $obj = \%h;
70
        return $obj;
71
    }
72
73
    # Allow specifying nicmac1 instead of uuid if known
74
    if (!$uuid) {
75
        $uuid = nicmac1ToUuid($h{"nicmac1"});
76
    }
77
    my $status = 'new';
78
    $status = $register{$uuid}->{'status'} if ($register{$uuid});
79
80
    my $objaction = lc $h{"action"};
81
    $objaction = "" if ($status eq "new");
82
83
    if ((!$uuid) && $status eq 'new') {
84
        my $ug = new Data::UUID;
85
        $uuid = $ug->create_str();
86
        if ($uripath =~ /servers(\.cgi)?\/(.+)/) {
87
            my $huuid = $2;
88
            if ($ug->to_string($ug->from_string($huuid)) eq $huuid) { # Check for valid uuid
89
                $uuid = $huuid;
90
            }
91
        }
92
    };
93
    unless ($uuid && length $uuid == 36) {
94
        $posterror .= "Status=Error Invalid uuid.\n";
95
        return;
96
    }
97
98
    my $dbobj = $register{$uuid} || {};
99
100
    my $name = $h{"name"} || $dbobj->{'name'};
101
    utf8::decode($name);
102
    my $memory = $h{"memory"} || $dbobj->{'memory'};
103
    my $vcpu = $h{"vcpu"} || $dbobj->{'vcpu'};
104 a93267ad hq
    my $vgpu = $dbobj->{'vgpu'};
105
    $vgpu = $h{"vgpu"} if (defined $h{"vgpu"});
106
    my $vmemory = dbobj->{'vmemory'};
107
    $vmemory = $h{"vmemory"} if (defined $h{"vmemory"}),
108 95b003ff Origo
    my $boot = $h{"boot"} || $dbobj->{'boot'};
109 04c16f26 hq
    my $loader = $h{"loader"} || $dbobj->{'loader'};
110 95b003ff Origo
    my $image = $h{"image"} || $dbobj->{'image'};
111
    my $imagename = $h{"imagename"} || $dbobj->{'imagename'};
112
    if ($image && $image ne '--' && !($image =~ /^\//)) { # Image is registered by uuid - we find the path
113
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {$posterror = "Unable to access image uuid register"; return;};
114
        $image = $imagereg2{$image}->{'path'};
115
        $imagename = $imagereg2{$image}->{'name'};
116
        untie %imagereg2;
117
        return unless ($image);
118
    }
119
    my $image2 = $h{"image2"} || $dbobj->{'image2'};
120
    my $image3 = $h{"image3"} || $dbobj->{'image3'};
121
    my $image4 = $h{"image4"} || $dbobj->{'image4'};
122
    my $image2name = $h{"image2name"} || $dbobj->{'image2name'};
123
    my $image3name = $h{"image3name"} || $dbobj->{'image3name'};
124
    my $image4name = $h{"image4name"} || $dbobj->{'image4name'};
125
    if ($image2 && $image2 ne '--' && !($image2 =~ /^\//)) { # Image2 is registered by uuid - we find the path
126
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {$postreply = "Unable to access image uuid register"; return $postreply;};
127
        $image2 = $imagereg2{$image2}->{'path'};
128
        $image2name = $imagereg2{$image2}->{'name'};
129
        untie %imagereg2;
130
    }
131
    my $diskbus = $h{"diskbus"} || $dbobj->{'diskbus'};
132
    my $diskdev = "vda";
133
    my $diskdev2 = "vdb";
134
    my $diskdev3 = "vdc";
135
    my $diskdev4 = "vdd";
136
    if ($diskbus eq "ide") {$diskdev = "hda"; $diskdev2 = "hdb"; $diskdev3 = "hdc"; $diskdev4 = "hdd"};
137
    my $cdrom = $h{"cdrom"} || $dbobj->{'cdrom'};
138 04c16f26 hq
    if ($cdrom && $cdrom ne '--' && !($cdrom =~ /^\//) && $cdrom ne 'virtio') {
139 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;};
140
        $cdrom = $imagereg2{$cdrom}->{'path'};
141
        untie %imagereg2;
142
    }
143
144
    my $networkuuid1 = $h{"networkuuid1"} || $dbobj->{'networkuuid1'};
145
    if ($h{"networkuuid1"} eq "0") {$networkuuid1 = "0"}; #Stupid perl... :-)
146
    my $networkid1 = $h{"networkid1"} || $dbobj->{'networkid1'};
147
    my $networkname1 = $h{"networkname1"} || $dbobj->{'networkname1'};
148
    my $nicmodel1 = $h{"nicmodel1"} || $dbobj->{'nicmodel1'};
149
    my $nicmac1 = $h{"nicmac1"} || $dbobj->{'nicmac1'};
150
    if (!$nicmac1 || $nicmac1 eq "--") {$nicmac1 = randomMac();}
151
152
    my $networkuuid2 = $h{"networkuuid2"} || $dbobj->{'networkuuid2'};
153
    if ($h{"networkuuid2"} eq "0") {$networkuuid2 = "0"};
154
    my $networkid2 = $h{"networkid2"} || $dbobj->{'networkid2'};
155
    my $networkname2 = $h{"networkname2"} || $dbobj->{'networkname2'};
156
    my $nicmac2 = $h{"nicmac2"} || $dbobj->{'nicmac2'};
157
    if (!$nicmac2 || $nicmac2 eq "--") {$nicmac2 = randomMac();}
158
159
    my $networkuuid3 = $h{"networkuuid3"} || $dbobj->{'networkuuid3'};
160
    if ($h{"networkuuid3"} eq "0") {$networkuuid3 = "0"};
161
    my $networkid3 = $h{"networkid3"} || $dbobj->{'networkid3'};
162
    my $networkname3 = $h{"networkname3"} || $dbobj->{'networkname3'};
163
    my $nicmac3 = $h{"nicmac3"} || $dbobj->{'nicmac3'};
164
    if (!$nicmac3 || $nicmac3 eq "--") {$nicmac3 = randomMac();}
165
166
    my $action = $h{"action"};
167
    my $notes = $h{"notes"};
168
    $notes = $dbobj->{'notes'} if (!$notes || $notes eq '--');
169
    my $reguser = $dbobj->{'user'};
170
    my $autostart = ($h{"autostart"} ."") || $dbobj->{'autostart'};
171
    if ($autostart && $autostart ne "false") {$autostart = "true";}
172
    my $locktonode = ($h{"locktonode"} ."") || $dbobj->{'locktonode'};
173
    if ($locktonode && $locktonode ne "false") {$locktonode = "true";}
174
    my $mac;
175 d3805c61 hq
    $mac = $dbobj->{'mac'} unless ($objaction eq 'start' || $objaction eq 'move' || $objaction eq 'stormove');
176 95b003ff Origo
    $mac = $h{"mac"} if ($isadmin && $h{"mac"});
177
    my $domuser = $h{"user"} || $user; # Set if user is trying to move server to another account
178
179
    # Sanity checks
180
    if (
181
        ($name && length $name > 255)
182
            || ($networkuuid1<0)
183
            || ($networkuuid2<0)
184
            || ($networkuuid3<0)
185
            || ($networkuuid1>1 && length $networkuuid1 != 36)
186
            || ($networkuuid2>1 && length $networkuuid2 != 36)
187
            || ($networkuuid3>1 && length $networkuuid3 != 36)
188
            || ($image && length $image > 255)
189
            || ($imagename && length $imagename > 255)
190
            || ($image2 && length $image2 > 255)
191
            || ($image3 && length $image3 > 255)
192
            || ($image4 && length $image4 > 255)
193
            || ($image2name && length $image2name > 255)
194
            || ($image3name && length $image3name > 255)
195
            || ($image4name && length $image4name > 255)
196
            || ($cdrom && length $cdrom > 255)
197 a439a9c4 hq
            || ($memory && ($memory<64 || $memory >1024*64))
198 95b003ff Origo
    ) {
199 a439a9c4 hq
        $postreply .= "Status=ERROR Invalid server data: $name\n";
200 95b003ff Origo
        return 0;
201
    }
202
203
    # Security check
204 2a63870a Christian Orellana
    if ($status eq 'new' && (($action && $action ne '--' && $action ne 'save') || !$image || $image eq '--')) {
205
        $postreply .= "Status=ERROR Bad server data: $name\n";
206
        $postmsg = "Bad server data";
207 95b003ff Origo
        return 0;
208
    }
209
    if (!$reguser && $status ne 'new'
210
        && !($name && $memory && $vcpu && $boot && $image && $diskbus && $networkuuid1 && $nicmodel1)) {
211
        $posterror .= "Status=ERROR Insufficient data: $name\n";
212
        return 0;
213
    }
214
    if (!$isadmin) {
215
        if (($networkuuid1>1 && $networkreg{$networkuuid1}->{'user'} ne $user)
216
            || ($networkuuid2>1 && $networkreg{$networkuuid2}->{'user'} ne $user)
217
            || ($networkuuid3>1 && $networkreg{$networkuuid3}->{'user'} ne $user)
218
        )
219
        {
220
            $postreply .= "Status=ERROR No privileges: $networkname1 $networkname2\n";
221
            return 0;
222
        }
223 91a21c75 hq
        if ( ($reguser && ($user ne $reguser) && $action ) || ($reguser && $status eq "new"))
224 95b003ff Origo
        {
225
            $postreply .= "Status=ERROR No privileges: $name\n";
226
            return 0;
227
        }
228
        if (!($image =~ /\/$user\//)
229
            || ($image2 && $image2 ne "--" && !($image2 =~ /\/$user\//))
230
            || ($image3 && $image3 ne "--" && !($image3 =~ /\/$user\//))
231
            || ($image4 && $image4 ne "--" && !($image4 =~ /\/$user\//))
232
        )
233
        {
234
            $postreply .= "Status=ERROR No image privileges: $name\n";
235
            return 0;
236
        }
237
    }
238
239
    # No action - regular save of domain properties
240 04c16f26 hq
    $cdrom = '--' if ($cdrom eq 'virtio' && $action ne 'mountcd');
241 95b003ff Origo
    $obj = {
242
        uuid => $uuid,
243
        status => $status,
244
        name => $name,
245
        memory => $memory,
246
        vcpu => $vcpu,
247 a93267ad hq
        vmemory => $vmemory,
248
        vgpu => $vgpu,
249 95b003ff Origo
        image => $image,
250
        imagename => $imagename,
251
        image2 => $image2,
252
        image2name => $image2name,
253
        image3 => $image3,
254
        image3name => $image3name,
255
        image4 => $image4,
256
        image4name => $image4name,
257
        diskbus => $diskbus,
258
        cdrom => $cdrom,
259
        boot => $boot,
260 04c16f26 hq
        loader=> $loader,
261 95b003ff Origo
        networkuuid1 => $networkuuid1,
262
        networkid1 => $networkid1,
263
        networkname1 => $networkname1,
264
        nicmodel1 => $nicmodel1,
265
        nicmac1 => $nicmac1,
266
        networkuuid2 => $networkuuid2,
267
        networkid2 => $networkid2,
268
        networkname2 => $networkname2,
269
        nicmac2 => $nicmac2,
270
        networkuuid3 => $networkuuid3,
271
        networkid3 => $networkid3,
272
        networkname3 => $networkname3,
273
        nicmac3 => $nicmac3,
274
        notes => $notes,
275
        autostart => $autostart,
276
        locktonode => $locktonode,
277
        mac => $mac,
278
        user => $domuser
279
    };
280
    return $obj;
281
}
282
283
sub Init {
284
    # Tie database tables to hashes
285
    unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access image register"};
286
    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access network register"};
287
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) ) {return "Unable to access nodes register"};
288
    unless ( tie(%xmlreg,'Tie::DBI', Hash::Merge::merge({table=>'domainxml'}, $Stabile::dbopts)) ) {return "Unable to access domainxml register"};
289
290
    # simplify globals initialized in Stabile.pm
291
    $tktuser = $tktuser || $Stabile::tktuser;
292
    $user = $user || $Stabile::user;
293
    $isadmin = $isadmin || $Stabile::isadmin;
294
    $privileges = $privileges || $Stabile::privileges;
295
296
    # Create aliases of functions
297
    *header = \&CGI::header;
298
    *to_json = \&JSON::to_json;
299
300
    *Showautostart = \&Autostartall;
301 d3805c61 hq
    *Stormove = \&Move;
302 95b003ff Origo
303
    *do_save = \&Save;
304
    *do_tablelist = \&do_list;
305
    *do_jsonlist = \&do_list;
306
    *do_showautostart = \&action;
307
    *do_autostartall = \&privileged_action;
308
    *do_help = \&action;
309
310
    *do_start = \&privileged_action;
311
    *do_destroy = \&action;
312
    *do_shutdown = \&action;
313
    *do_suspend = \&action;
314
    *do_resume = \&action;
315
    *do_remove = \&privileged_action;
316
    *do_move = \&action;
317 d3805c61 hq
    *do_abort = \&action;
318
    *do_stormove = \&action;
319 95b003ff Origo
    *do_mountcd = \&action;
320 c899e439 Origo
    *do_changepassword = \&privileged_action;
321
    *do_sshaccess = \&privileged_action;
322 95b003ff Origo
323
    *do_gear_start = \&do_gear_action;
324
    *do_gear_autostart = \&do_gear_action;
325
    *do_gear_showautostart = \&do_gear_action;
326
    *do_gear_autostartall = \&do_gear_action;
327
    *do_gear_remove = \&do_gear_action;
328 c899e439 Origo
    *do_gear_changepassword = \&do_gear_action;
329
    *do_gear_sshaccess = \&do_gear_action;
330 95b003ff Origo
331
}
332
333
sub do_list {
334
    my ($uuid, $action) = @_;
335
    if ($help) {
336
        return <<END
337
GET:uuid:
338
List servers current user has access to.
339
END
340
    }
341
342
    my $res;
343
    my $filter;
344
    my $statusfilter;
345
    my $uuidfilter;
346
    my $curserv = $register{$curuuid};
347
    if ($curuuid && ($isadmin || $curserv->{'user'} eq $user) && $uripath =~ /servers(\.cgi)?\/(\?|)(this)/) {
348
        $uuidfilter = $curuuid;
349
    } elsif ($uripath =~ /servers(\.cgi)?\/(\?|)(name|status)/) {
350
        $filter = $3 if ($uripath =~ /servers(\.cgi)?\/\??name(:|=)(.+)/);
351
        $filter = $1 if ($filter =~ /(.*)\*$/);
352
        $statusfilter = $4 if ($uripath =~ /servers(\.cgi)?\/\??(.+ AND )?status(:|=)(\w+)/);
353
    } elsif ($uripath =~ /servers(\.cgi)?\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})/) {
354
        $uuidfilter = $2;
355
    }
356
    $filter = $1 if ($filter =~ /(.*)\*/);
357
358
    my $sysuuid;
359
    if ($params{'system'}) {
360
        $sysuuid = $params{'system'};
361
        $sysuuid = $cursysuuid || $curuuid if ($params{'system'} eq 'this');
362
    }
363
    my @curregvalues;
364
    my @regkeys;
365
    if ($fulllist && $isadmin) {
366
        @regkeys = keys %register;
367
    } elsif ($uuidfilter && $isadmin) {
368
        @regkeys = (tied %register)->select_where("uuid = '$uuidfilter'");
369
    } elsif ($sysuuid) {
370
        @regkeys = (tied %register)->select_where("system = '$sysuuid' OR uuid = '$sysuuid'");
371
    } else {
372
        @regkeys = (tied %register)->select_where("user = '$user'");
373
    }
374
375
    unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
376
    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;};
377
378
    foreach my $k (@regkeys) {
379
        $valref = $register{$k};
380
        # Only include VM's belonging to current user (or all users if specified and user is admin)
381
        if ($user eq $valref->{'user'} || $fulllist || ($uuidfilter && $isadmin)) {
382
            next unless (!$sysuuid || $valref->{'system'} eq $sysuuid || $valref->{'uuid'} eq $sysuuid);
383
384
            my $validatedref = validateItem($valref);
385
            my %val = %{$validatedref}; # Deference and assign to new ass array, effectively cloning object
386
            $val{'memory'} += 0;
387
            $val{'vcpu'} += 0;
388 a93267ad hq
            $val{'vgpu'} += 0;
389
            $val{'vmemory'} += 0;
390 95b003ff Origo
            $val{'nodetype'} = 'parent';
391
            $val{'internalip'} = $networkreg{$val{'networkuuid1'}}->{'internalip'};
392
            $val{'self'} = 1 if ($curuuid && $curuuid eq $val{'uuid'});
393
            if ($action eq 'treelist') {
394
                if ($val{'system'} && $val{'system'} ne '') {
395
                    my $sysuuid = $val{'system'};
396
                    my $sysname = $sysreg{$sysuuid}->{'name'};
397
                    if (!$sysname) {
398
                        $sysname = $1 if ($sysname =~ /(.+)\..*/);
399
                        $sysname = $val{'name'};
400
                        $sysname =~ s/server/System/i;
401
                    }
402
                    $sysreg{$sysuuid} = {
403
                        uuid => $sysuuid,
404
                        name => $sysname,
405
                        user => 'irigo'
406
                    };
407
408
                    my %pval = %{$sysreg{$sysuuid}};
409
                    $pval{'nodetype'} = 'parent';
410
                    $pval{'status'} = '--';
411
                    $val{'nodetype'} = 'child';
412
413
                    my @children;
414
                    push @children,\%val;
415
                    $pval{'children'} = \@children;
416
                    push @curregvalues,\%pval;
417
                } else {
418
                    push @curregvalues,\%val;
419
                }
420
            } elsif ($filter || $statusfilter || $uuidfilter) { # List filtered servers
421
                my $fmatch;
422
                my $smatch;
423
                my $umatch;
424
                $fmatch = 1 if (!$filter || $val{'name'}=~/$filter/i);
425
                $smatch = 1 if (!$statusfilter || $statusfilter eq 'all'
426
                    || $statusfilter eq $val{'status'}
427
                );
428
                $umatch = 1 if ($val{'uuid'} eq $uuidfilter);
429
                if ($fmatch && $smatch && !$uuidfilter) {
430
                    push @curregvalues,\%val;
431
                } elsif ($umatch) {
432
                    push @curregvalues,\%val;
433
                    last;
434
                }
435
            } else {
436
                push @curregvalues,\%val;
437
            }
438
        }
439
    }
440
    tied(%sysreg)->commit;
441
    untie(%sysreg);
442
    untie %imagereg;
443
    @curregvalues = (sort {$a->{'status'} cmp $b->{'status'}} @curregvalues); # Sort by status
444
445
    # Sort @curregvalues
446 2a63870a Christian Orellana
    @curregvalues = (sort {$b->{'name'} <=> $a->{'name'}} @curregvalues); # Always sort by name first
447 95b003ff Origo
    my $sort = 'status';
448
    $sort = $2 if ($uripath =~ /sort\((\+|\-)(\S+)\)/);
449
    my $reverse;
450
    $reverse = 1 if ($1 eq '-');
451
    if ($reverse) { # sort reverse
452 a93267ad hq
        if ($sort =~ /memory|vcpu|vmemory|vgpu/) {
453 95b003ff Origo
            @curregvalues = (sort {$b->{$sort} <=> $a->{$sort}} @curregvalues); # Sort as number
454
        } else {
455
            @curregvalues = (sort {$b->{$sort} cmp $a->{$sort}} @curregvalues); # Sort as string
456
        }
457
    } else {
458 a93267ad hq
        if ($sort =~ /memory|vcpu|vmemory|vgpu/) {
459 95b003ff Origo
            @curregvalues = (sort {$a->{$sort} <=> $b->{$sort}} @curregvalues); # Sort as number
460
        } else {
461
            @curregvalues = (sort {$a->{$sort} cmp $b->{$sort}} @curregvalues); # Sort as string
462
        }
463
    }
464
465
    if ($action eq 'tablelist') {
466
        my $t2;
467
468
        if ($isadmin) {
469
            $t2 = Text::SimpleTable->new(36,20,20,10,10,12,7);
470
            $t2->row('uuid', 'name', 'imagename', 'memory', 'user', 'mac', 'status');
471
        } else {
472
            $t2 = Text::SimpleTable->new(36,20,20,10,10,7);
473
            $t2->row('uuid', 'name', 'imagename', 'memory', 'user', 'status');
474
        }
475
        $t2->hr;
476
        my $pattern = $options{m};
477
        foreach $rowref (@curregvalues){
478
            if ($pattern) {
479
                my $rowtext = $rowref->{'uuid'} . " " . $rowref->{'name'} . " " . $rowref->{'imagename'} . " " . $rowref->{'memory'}
480
                    . " " .  $rowref->{'user'} . " " . $rowref->{'status'};
481
                $rowtext .= " " . $rowref->{'mac'} if ($isadmin);
482
                next unless ($rowtext =~ /$pattern/i);
483
            }
484
            if ($isadmin) {
485
                $t2->row($rowref->{'uuid'}, $rowref->{'name'}, $rowref->{'imagename'}, $rowref->{'memory'},
486
                    $rowref->{'user'}, $rowref->{'mac'}, $rowref->{'status'});
487
            } else {
488
                $t2->row($rowref->{'uuid'}, $rowref->{'name'}, $rowref->{'imagename'}, $rowref->{'memory'},
489
                    $rowref->{'user'}, $rowref->{'status'});
490
            }
491
        }
492
        $res .= $t2->draw;
493
    } elsif ($console) {
494
        $res .= Dumper(\@curregvalues);
495
    } else {
496
        my $json_text;
497
        if ($uuidfilter && @curregvalues) {
498
            $json_text = to_json($curregvalues[0], {pretty => 1});
499
        } else {
500
            $json_text = to_json(\@curregvalues, {pretty => 1});
501
        }
502
503
        $json_text =~ s/\x/ /g;
504
        $json_text =~ s/\"\"/"--"/g;
505 c899e439 Origo
        $json_text =~ s/null/"--"/g;
506 04c16f26 hq
        $json_text =~ s/"autostart"\s?:\s?"true"/"autostart": true/g;
507
        $json_text =~ s/"autostart"\s?:\s?"--"/"autostart": false/g;
508
        $json_text =~ s/"locktonode"\s?:\s?"true"/"locktonode": true/g;
509
        $json_text =~ s/"locktonode"\s?:\s?"--"/"locktonode": false/g;
510
        $json_text =~ s/"loader"\s?:\s?"--"/"loader": "bios"/g;
511 95b003ff Origo
        if ($action eq 'jsonlist' || $action eq 'list' || !$action) {
512
            $res .= $json_text;
513
        } else {
514
            $res .= qq|{"action": "$action", "identifier": "uuid", "label": "uuid", "items" : $json_text}|;
515
        }
516
    }
517
    return $res;
518
}
519
520
sub do_uuidshow {
521
    my ($uuid, $action) = @_;
522
    if ($help) {
523
        return <<END
524
GET:uuid:
525
Simple action for showing a single server.
526
END
527
    }
528
    my $res;
529
    $res .= $Stabile::q->header('text/plain') unless $console;
530
    my $u = $uuid || $options{u};
531
    if ($u || $u eq '0') {
532
        foreach my $uuid (keys %register) {
533
            if (($register{$uuid}->{'user'} eq $user || $register{$uuid}->{'user'} eq 'common' || $isadmin)
534
                && $uuid =~ /^$u/) {
535
                my %hash = %{$register{$uuid}};
536
                delete $hash{'action'};
537
                my $dump = Dumper(\%hash);
538
                $dump =~ s/undef/"--"/g;
539
                $res .= $dump;
540
                last;
541
            }
542
        }
543
    }
544
    return $res;
545
}
546
547
sub do_uuidlookup {
548
    if ($help) {
549
        return <<END
550
GET:uuid:
551
Simple action for looking up a uuid or part of a uuid and returning the complete uuid.
552
END
553
    }
554
    my $res;
555
    $res .= header('text/plain') unless $console;
556
    my $u = $options{u};
557
    $u = $curuuid unless ($u || $u eq '0');
558
    my $ruuid;
559
    if ($u || $u eq '0') {
560
        my $match;
561
        foreach my $uuid (keys %register) {
562
            if ($uuid =~ /^$u/) {
563
                $ruuid = $uuid if ($register{$uuid}->{'user'} eq $user || index($privileges,"a")!=-1);
564
                $match = 1;
565
                last;
566
            }
567
        }
568
        if (!$match && $isadmin) { # If no match and user is admin, do comprehensive lookup
569
            foreach my $uuid (keys %register) {
570
                if ($uuid =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/) {
571
                    $ruuid = $uuid;
572
                    last;
573
                }
574
            }
575
        }
576
    }
577
    $res .= "$ruuid\n" if ($ruuid);
578
    return $res;
579
}
580
581
sub do_destroyuserservers {
582 6372a66e hq
    my ($uuid, $action, $obj) = @_;
583 95b003ff Origo
    if ($help) {
584
        return <<END
585 6372a66e hq
GET:username:
586 95b003ff Origo
Simple action for destroying all servers belonging to a user
587
END
588
    }
589 6372a66e hq
    $username = $obj->{username};
590 95b003ff Origo
    my $res;
591
    $res .= $Stabile::q->header('text/plain') unless $console;
592 6372a66e hq
593
    destroyUserServers($username);
594 95b003ff Origo
    $res .= $postreply;
595
    return $res;
596
}
597
598
sub do_removeuserservers {
599
    if ($help) {
600
        return <<END
601
GET::
602
Simple action for removing all servers belonging to a user
603
END
604
    }
605
    my $res;
606
    $res .= $Stabile::q->header('text/plain') unless $console;
607
    removeUserServers($user);
608
    $res .= $postreply;
609
    return $res;
610
}
611
612
sub do_getappid {
613
    my ($uuid, $action) = @_;
614
    if ($help) {
615
        return <<END
616
GET:uuid:
617
Simple action for getting the app id
618
END
619
    }
620
    my $res;
621
    $res .= $Stabile::q->header('text/plain') unless $console;
622
    $uuid = $uuid || $options{u};
623
    $uuid = $curuuid unless ($uuid);
624
    if ($uuid && $register{$uuid}) {
625
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access image register"};
626
        $res .= "appid: ". $imagereg{$register{$uuid}->{image}}->{appid}, "\n";
627
        untie %imagereg;
628
    }
629
    return $res;
630
}
631
632
sub do_setrunning {
633
    my ($uuid, $action) = @_;
634
    if ($help) {
635
        return <<END
636
GET:uuid:
637
Simple action for setting status back to running after e.g. an upgrade
638
END
639
    }
640
    my $res;
641
    $res .= $Stabile::q->header('text/plain') unless $console;
642
    $uuid = $uuid || $options{u};
643
    $uuid = $curuuid unless ($uuid);
644
    if ($uuid && $register{$uuid}) {
645
        $register{$uuid}->{'status'} = 'running';
646
        $main::updateUI->({ tab => 'servers',
647
            user                => $user,
648
            uuid                => $uuid,
649
            status              => 'running' })
650
651
    };
652
    $res .= "Status=OK Set status of $register{$uuid}->{'name'} to running\n";
653
    return $res;
654
}
655
656
sub do_getappinfo {
657
    my ($uuid, $action) = @_;
658
    if ($help) {
659
        return <<END
660
GET:uuid:
661
Simple action for getting the apps basic info
662
END
663
    }
664
    my $res;
665
    $res .= $Stabile::q->header('application/json') unless $console;
666
    $uuid = $uuid || $options{u};
667
    $uuid = $curuuid unless ($uuid);
668
    my %appinfo;
669
    if ($uuid && $register{$uuid}) {
670
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access image register"};
671
        $appinfo{'appid'} = $imagereg{$register{$uuid}->{image}}->{appid} || '';
672
        $appinfo{'managementlink'} = $imagereg{$register{$uuid}->{image}}->{managementlink} || '';
673
        $appinfo{'managementlink'} =~ s/{uuid}/$register{$uuid}->{networkuuid1}/;
674
675
        my $termlink = $imagereg{$register{$uuid}->{image}}->{terminallink} || '';
676
        $termlink =~ s/{uuid}/$register{$uuid}->{networkuuid1}/;
677
        my $burl = $baseurl;
678
        $burl = $1 if ($termlink =~ /\/stabile/ && $baseurl =~ /(.+)\/stabile/); # Unpretty, but works for now
679 6fdc8676 hq
        # $termlink = $1 if ($termlink =~ /\/(.+)/);
680
        # $termlink = "$burl/$termlink" unless ($termlink =~ /^http/ || !$termlink); # || $termlink =~ /^\//
681 95b003ff Origo
        $appinfo{'terminallink'} = $termlink;
682
683
        $appinfo{'upgradelink'} = $imagereg{$register{$uuid}->{image}}->{upgradelink} || '';
684
        $appinfo{'upgradelink'} =~ s/{uuid}/$register{$uuid}->{networkuuid1}/;
685
        $appinfo{'version'} = $imagereg{$register{$uuid}->{image}}->{version} || '';
686
        $appinfo{'status'} = $register{$uuid}->{status} || '';
687
        $appinfo{'name'} = $register{$uuid}->{name} || '';
688 d3d1a2d4 Origo
        $appinfo{'system'} = $register{$uuid}->{system} || '';
689
690
        if ($appinfo{'system'}) {
691
            unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
692
            $appinfo{'systemname'} = $sysreg{$appinfo{'system'}}->{name} || '';
693
            untie(%sysreg);
694
        } else {
695
            $appinfo{'systemname'} = $appinfo{'name'};
696
        }
697
698 95b003ff Origo
699
        if ($appinfo{'appid'}) {
700
            my @regkeys = (tied %imagereg)->select_where("appid = '$appinfo{appid}'");
701
            foreach my $k (@regkeys) {
702
                my $imgref = $imagereg{$k};
703
                if ($imgref->{'path'} =~ /\.master\.qcow2$/ && $imgref->{'appid'} eq $appinfo{'appid'}
704 a93267ad hq
                    && $imgref->{'installable'} && $imgref->{'installable'} ne 'false'
705 95b003ff Origo
                ) {
706
                    if ($imgref->{'version'} > $appinfo{'currentversion'}) {
707
                        $appinfo{'currentversion'} = $imgref->{'version'};
708
                        $appinfo{'appname'} = $imgref->{'name'};
709
                    }
710
                }
711
            }
712
        }
713
714
        untie %imagereg;
715
    }
716
    $appinfo{'appstoreurl'} = $appstoreurl;
717
    $appinfo{'dnsdomain'} = ($enginelinked)?$dnsdomain:'';
718 6fdc8676 hq
    $appinfo{'dnssubdomain'} = ($enginelinked)?substr($engineid, 0, 8):'';
719 95b003ff Origo
    $appinfo{'uuid'} = $uuid;
720
    $appinfo{'user'} = $user;
721
    $appinfo{'remoteip'} = $remoteip;
722
    $res .= to_json(\%appinfo, { pretty => 1 });
723
    return $res;
724
}
725
726
sub do_removeserver {
727
    if ($help) {
728
        return <<END
729
GET:uuid:
730
Simple action for destroying and removing a single server
731
END
732
    }
733
    my $res;
734
    $res .= $Stabile::q->header('text/plain') unless $console;
735
    if ($curuuid) {
736
        removeUserServers($user, $curuuid, 1);
737
    }
738
    else {
739
        $postreply .= "Status=Error Unable to uninstall\n";
740
    }
741
    $res .= $postreply;
742
    return $res;
743
}
744
745
sub do_updateregister {
746
    if ($help) {
747
        return <<END
748
GET::
749
Update server register
750
END
751
    }
752
    my $res;
753
    $res .= $Stabile::q->header('text/plain') unless $console;
754
    return unless $isadmin;
755
    updateRegister();
756
    $res .= "Status=OK Updated server registry for all users\n";
757
    return $res;
758
}
759
760
sub Autostartall {
761
    my ($uuid, $action) = @_;
762
    if ($help) {
763
        return <<END
764
GET::
765
Start all servers marked for autostart. When called as showautostart only shows which would be started.
766
END
767
    }
768
    my $res;
769
    $res .= $Stabile::q->header('text/plain') unless $console;
770
    my $mes;
771
    return $res if ($isreadonly);
772
773
    # Wait for all pistons to be online
774
    my $nodedown;
775
    my $nodecount;
776 f222b89c hq
    for (my $i = 0; $i < 20; $i++) {
777 95b003ff Origo
        $nodedown = 0;
778
        foreach my $node (values %nodereg) {
779
            if ($node->{'status'} ne 'running' && $node->{'status'} ne 'maintenance') {
780
                $nodedown = 1;
781
            }
782
            else {
783
                $nodecount++ unless ($node->{'status'} eq 'maintenance');
784
            }
785
        }
786
        if ($nodedown) {
787
            # Wait and see if nodes come online
788
            $mes = "Waiting for nodes...(" . (10 - $i) . ")\n";
789
            print $mes if ($console);
790
            $res .= $mes;
791 f222b89c hq
            sleep 10;
792 95b003ff Origo
        }
793
        else {
794
            last;
795
        }
796
    }
797
798 a2e0bc7e hq
    $mes = "$nodecount nodes ready - autostarting servers...\n";
799 f222b89c hq
    $main::syslogit->("irigo", "info", "$nodecount nodes ready - autostarting servers...");
800
801 a2e0bc7e hq
    print $mes if ($console);
802
    $res .= $mes;
803 95b003ff Origo
    if (!%nodereg || $nodedown || !$nodecount) {
804 a2e0bc7e hq
        $mes = "Only autostarting servers on local node - not all nodes ready!\n";
805 95b003ff Origo
        print $mes if ($console);
806
        $res .= $mes;
807
    }
808 a2e0bc7e hq
    if ($action eq "showautostart") {
809
        $mes = "Only showing which servers would be starting!\n";
810 95b003ff Origo
        print $mes if ($console);
811
        $res .= $mes;
812 a2e0bc7e hq
    }
813 95b003ff Origo
814 a2e0bc7e hq
    $Stabile::Networks::user = $user;
815
    require "$Stabile::basedir/cgi/networks.cgi";
816
    $Stabile::Networks::console = 1;
817
818
    foreach my $dom (values %register) {
819
        if ($nodedown) { # Only start local servers
820
            unless ($dom->{mac} && $nodereg{$dom->{mac}}->{identity} eq 'local_kvm') {
821
                $mes = "Skipping non-local domain $dom->{name}, $dom->{status}\n";
822
                print $mes if ($console);
823
                $res .= $mes;
824
                next;
825
            }
826
        }
827
        if ($dom->{'autostart'} eq '1' || $dom->{'autostart'} eq 'true') {
828
            $res .= "Checking if $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'}) should be started\n";
829
            my $networkstatus1 = $networkreg{$dom->{'networkuuid1'}}->{status};
830
            my $networkstatus2 = ($networkreg{$dom->{'networkuuid2'}})?$networkreg{$dom->{'networkuuid2'}}->{status}:'';
831
            my $networkstatus3 = ($networkreg{$dom->{'networkuuid3'}})?$networkreg{$dom->{'networkuuid3'}}->{status}:'';
832
            my @dnets;
833
            push @dnets, $dom->{'networkuuid1'} if ($dom->{'networkuuid1'} && $dom->{'networkuuid1'} ne '--' && $networkstatus1 ne 'up');
834
            push @dnets, $dom->{'networkuuid2'} if ($dom->{'networkuuid2'} && $dom->{'networkuuid2'} ne '--' && $networkstatus2 ne 'up');
835
            push @dnets, $dom->{'networkuuid3'} if ($dom->{'networkuuid3'} && $dom->{'networkuuid3'} ne '--' && $networkstatus3 ne 'up');
836
            my $i;
837
            for ($i=0; $i<5; $i++) { # wait for status newer than 10 secs
838
                validateItem($dom);
839
                last if (time() - $dom->{timestamp} < 10);
840
                $mes = "Waiting for newer timestamp, current is " . (time() - $dom->{timestamp}) . " old\n";
841
                print $mes if ($console);
842
                $res .= $mes;
843
                sleep 2;
844
            }
845
            if (
846
                $dom->{'status'} eq 'shutoff' || $dom->{'status'} eq 'inactive'
847
            ) {
848
                if ($action eq "showautostart") { # Dry run
849
                    $mes = "Starting $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
850 95b003ff Origo
                    print $mes if ($console);
851
                    $res .= $mes;
852
                }
853 a2e0bc7e hq
                else {
854
                    $mes = "Starting $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
855
                    print $mes if ($console);
856
                    $res .= $mes;
857
                    $postreply = Start($dom->{'uuid'});
858
                    print $postreply if ($console);
859
                    $res .= $postreply;
860 a93267ad hq
                    #                        $mes = `REMOTE_USER=$dom->{'user'} $base/cgi/servers.cgi -a start -u $dom->{'uuid'}`;
861 a2e0bc7e hq
                    print $mes if ($console);
862
                    $res .= $mes;
863
                    sleep 1;
864
                }
865
            }
866
            elsif (@dnets) {
867
                if ($action eq "showautostart") { # Dry run
868
                    foreach my $networkuuid (@dnets) {
869
                        $mes = "Would bring network $networkreg{$networkuuid}->{name} up for $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
870 95b003ff Origo
                        print $mes if ($console);
871
                        $res .= $mes;
872
                    }
873 a2e0bc7e hq
                }
874
                else {
875
                    foreach my $networkuuid (@dnets) {
876
                        $mes = "Bringing network $networkreg{$networkuuid}->{name} up for $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
877 95b003ff Origo
                        print $mes if ($console);
878
                        $res .= $mes;
879 a2e0bc7e hq
                        $mes = Stabile::Networks::Activate($networkuuid, 'activate');
880 48fcda6b Origo
                        print $mes if ($console);
881
                        $res .= $mes;
882 95b003ff Origo
                        sleep 1;
883
                    }
884
                }
885
            }
886 a2e0bc7e hq
        } else {
887
            $res .= "Not marked for autostart ($dom->{'autostart'}): $dom->{'name'} ($dom->{'user'}, $dom->{'uuid'})\n";
888
            validateItem($dom);
889 95b003ff Origo
        }
890
    }
891
    return $res;
892
}
893
894
sub do_listnodeavailability {
895
    if ($help) {
896
        return <<END
897
GET::
898
Utility call - only informational. Shows availability of nodes for starting servers.
899
END
900
    }
901
    my $res;
902
    $res .= $Stabile::q->header('application/json') unless ($console);
903 a93267ad hq
    my ($temp1, $temp2, $temp3, $temp4, $ahashref, $targeterror) = locateTargetNode();
904 95b003ff Origo
    my @avalues = values %$ahashref;
905
    my @sorted_values = (sort {$b->{'index'} <=> $a->{'index'}} @avalues);
906
    $res .= to_json(\@sorted_values, { pretty => 1 });
907
    return $res;
908
}
909
910
sub do_listbillingdata {
911
    if ($help) {
912
        return <<END
913
GET::
914
List current billing data.
915
END
916
    }
917
    my $res;
918
    $res .= $Stabile::q->header('application/json') unless ($console);
919
    my $buser = URI::Escape::uri_unescape($params{'user'}) || $user;
920
    my %b;
921
    my @bmonths;
922
    if ($isadmin || $buser eq $user) {
923
        my $bmonth = URI::Escape::uri_unescape($params{'month'}) || $month;
924
        my $byear = URI::Escape::uri_unescape($params{'year'}) || $year;
925
        if ($bmonth eq "all") {
926
            @bmonths = ("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12");
927
        }
928
        else {
929
            @bmonths = ($bmonth);
930
        }
931
932
        unless ( tie(%billingreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_domains', key=>'usernodetime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
933
934
        my @nkeys = keys %nodereg;
935
        foreach my $bm (@bmonths) {
936
            my $vcpuavg = 0;
937
            my $memoryavg = 0;
938
            foreach my $nmac (@nkeys) {
939
                $vcpuavg += $billingreg{"$buser-$nmac-$byear-$bm"}->{'vcpuavg'};
940
                $memoryavg += $billingreg{"$buser-$nmac-$byear-$bm"}->{'memoryavg'};
941
            }
942
            $b{"$buser-$byear-$bm"} = {
943
                id        => "$buser-$byear-$bm",
944
                vcpuavg   => $vcpuavg,
945
                memoryavg => $memoryavg,
946
                month     => $bm + 0,
947
                year      => $byear + 0
948
            }
949
        }
950
        untie %billingreg;
951
    }
952
    my @bvalues = values %b;
953
    $res .= "{\"identifier\": \"id\", \"label\": \"id\", \"items\":" . to_json(\@bvalues) . "}";
954
    return $res;
955
}
956
957
# Print list of available actions on objects
958
sub do_plainhelp {
959
    my $res;
960
    $res .= $Stabile::q->header('text/plain') unless $console;
961
    $res .= <<END
962
new [name="name"]
963
* start: Starts a server
964
* destroy: Destroys a server, i.e. terminates the VM, equivalent of turning the power off a physical computer
965
* shutdown: Asks the operating system of a server to shut down via ACPI
966
* suspend: Suspends the VM, effectively putting the server to sleep
967
* resume: Resumes a suspended VM, effectively waking the server from sleep
968
* move [mac="mac"]: Moves a server to specified node. If no node is specified, moves to other node with highest availability
969
index
970
* delete: Deletes a server. Image and network are not deleted, only information about the server. Server cannot be
971
runing
972
* mountcd [cdrom="path"]: Mounts a cd rom
973
END
974
    ;
975
    return $res;
976
}
977
978
# Helper function
979
sub recurse($) {
980 a93267ad hq
    my($path) = @_;
981
    my @files;
982
    ## append a trailing / if it's not there
983
    $path .= '/' if($path !~ /\/$/);
984
    ## loop through the files contained in the directory
985
    for my $eachFile (glob($path.'*')) {
986
        ## if the file is a directory
987
        if( -d $eachFile) {
988
            ## pass the directory to the routine ( recursion )
989
            push(@files,recurse($eachFile));
990
        } else {
991
            push(@files,$eachFile);
992
        }
993
    }
994
    return @files;
995 95b003ff Origo
}
996
997
sub Start {
998
    my ($uuid, $action, $obj) = @_;
999
    $dmac = $obj->{mac};
1000
    $buildsystem = $obj->{buildsystem};
1001
    $uistatus = $obj->{uistatus};
1002
    if ($help) {
1003
        return <<END
1004
GET:uuid,mac:
1005
Start a server. Supply mac for starting on specific node.
1006
END
1007
    }
1008
    $dmac = $dmac || $params{'mac'};
1009
    return "Status=ERROR No uuid\n" unless ($register{$uuid});
1010
    my $serv = $register{$uuid};
1011
    $postreply = '' if ($buildsystem);
1012
1013
    my $name = $serv->{'name'};
1014
    utf8::decode($name);
1015
    my $image = $serv->{'image'};
1016
    my $image2 = $serv->{'image2'};
1017
    my $image3 = $serv->{'image3'};
1018
    my $image4 = $serv->{'image4'};
1019
    my $memory = $serv->{'memory'};
1020 a93267ad hq
    my $vmemory = $serv->{'vmemory'};
1021 95b003ff Origo
    my $vcpu = $serv->{'vcpu'};
1022
    my $vgpu = $serv->{'vgpu'};
1023
    my $dbstatus = $serv->{'status'};
1024
    my $mac = $serv->{'mac'};
1025
    my $macname = $serv->{'macname'};
1026
    my $networkuuid1 = $serv->{'networkuuid1'};
1027
    my $networkuuid2 = $serv->{'networkuuid2'};
1028
    my $networkuuid3 = $serv->{'networkuuid3'};
1029
    my $nicmodel1 = $serv->{'nicmodel1'};
1030
    my $nicmac1 = $serv->{'nicmac1'};
1031
    my $nicmac2 = $serv->{'nicmac2'};
1032
    my $nicmac3 = $serv->{'nicmac3'};
1033
    my $boot = $serv->{'boot'};
1034 04c16f26 hq
    my $loader = $serv->{'loader'};
1035 95b003ff Origo
    my $diskbus = $serv->{'diskbus'};
1036
    my $cdrom = $serv->{'cdrom'};
1037
    my $diskdev = "vda";
1038
    my $diskdev2 = "vdb";
1039
    my $diskdev3 = "vdc";
1040
    my $diskdev4 = "vdd";
1041
    if ($diskbus eq "ide") {$diskdev = "hda"; $diskdev2 = "hdb"; $diskdev3 = "hdc"; $diskdev4 = "hdd"};
1042
1043
    my $mem = $memory * 1024;
1044
1045
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access image register"};
1046
1047
    my $img = $imagereg{$image};
1048
    my $imagename = $img->{'name'};
1049
    my $imagestatus = $img->{'status'};
1050
    my $img2 = $imagereg{$image2};
1051
    my $image2status = $img2->{'status'};
1052
    my $img3 = $imagereg{$image3};
1053
    my $image3status = $img3->{'status'};
1054
    my $img4 = $imagereg{$image4};
1055
    my $image4status = $img4->{'status'};
1056
1057
    if (!$imagereg{$image}) {
1058
        $postreply .= "Status=Error Image $image not found - please select a new image for your server, not starting $name\n";
1059
        untie %imagereg;
1060
        return $postreply;
1061
    }
1062
    untie %imagereg;
1063
1064
    if ($imagestatus ne "used" && $imagestatus ne "cloning") {
1065
        $postreply .= "Status=ERROR Image $imagename $image is $imagestatus, not starting $name\n";
1066
    } elsif ($image2 && $image2 ne '--' && $image2status ne "used" && $image2status ne "cloning") {
1067
        $postreply .= "Status=ERROR Image2 is $image2status, not starting $name\n";
1068
    } elsif ($image3 && $image3 ne '--' && $image3status ne "used" && $image3status ne "cloning") {
1069
        $postreply .= "Status=ERROR Image3 is $image3status, not starting $name\n";
1070
    } elsif ($image4 && $image4 ne '--' && $image4status ne "used" && $image4status ne "cloning") {
1071
        $postreply .= "Status=ERROR Image4 is $image4status, not starting $name\n";
1072 a2e0bc7e hq
    } elsif (Stabile::Servers::overQuotas($memory,$vcpu)) {
1073
        $main::syslogit->($user, "info", "Over quota ($memory, $vcpu, " . Stabile::Servers::overQuotas($memory,$vcpu) .  ") starting a $dbstatus domain: $uuid");
1074 95b003ff Origo
        $postreply .= "Status=ERROR Over quota - not starting $name\n";
1075 a93267ad hq
        # Status inactive is typically caused by a movepiston having problems. We should not start inactive servers since
1076
        # they could possibly be running even if movepiston is down. Movepiston on the node should be brought up to update
1077
        # the status, or the node should be removed from the stabile.
1078
        # We now allow to force start of inactive server when dmac is specified
1079 95b003ff Origo
    } elsif ((!$dmac || $dmac eq $mac) && $dbstatus eq 'inactive' && $nodereg{$mac} && ($nodereg{$mac}->{'status'} eq 'inactive' || $nodereg{$mac}->{'status'} eq 'shutdown')) {
1080
        $main::syslogit->($user, "info", "Not starting inactive domain: $uuid (last seen on $mac)");
1081
        $postreply .= "Status=ERROR Not starting $name - Please bring up node $macname\n";
1082
    } elsif ($dbstatus eq 'inactive' || $dbstatus eq 'shutdown' || $dbstatus eq 'shutoff' || $dbstatus eq 'new') {
1083
        unless ($dmac && $isadmin) {
1084
            $dmac = $mac if ($dbstatus eq 'inactive'); # If movepiston crashed while shutting down, allow server to start on same node
1085
        }
1086
        $uistatus = "starting" unless ($uistatus);
1087
        my $hypervisor = getHypervisor($image);
1088 a93267ad hq
        my ($targetmac, $targetname, $targetip, $port, $avhash, $targeterror) = locateTargetNode($uuid, $dmac, $mem, $vcpu, $vgpu, $vmemory, $image, $image2 ,$image3, $image4, $hypervisor);
1089 95b003ff Origo
1090 a2e0bc7e hq
        # Read limits from nodeconfig
1091
        my $vm_readlimit = '';
1092
        my $vm_writelimit = '';
1093
        my $vm_iopsreadlimit = ''; # e.g. 1000 IOPS
1094
        my $vm_iopswritelimit = '';
1095
        if  (-e "/etc/stabile/nodeconfig.cfg") {
1096
            my $nodecfg = new Config::Simple("/etc/stabile/nodeconfig.cfg");
1097
            $vm_readlimit = $nodecfg->param('VM_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
1098
            $vm_writelimit = $nodecfg->param('VM_WRITE_LIMIT');
1099
            $vm_iopsreadlimit = $nodecfg->param('VM_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
1100
            $vm_iopswritelimit = $nodecfg->param('VM_IOPS_WRITE_LIMIT');
1101
        }
1102
1103 95b003ff Origo
        # Build XML for starting domain
1104
        my $graphics = "vnc";
1105
        $graphics = "rdp" if ($hypervisor eq "vbox");
1106
        my $net1 = $networkreg{$networkuuid1};
1107
        my $networkid1 = $net1->{'id'}; # Get the current vlan id of the network
1108
        my $net2 = $networkreg{$networkuuid2};
1109
        my $networkid2 = $net2->{'id'}; # Get the current vlan id of the network
1110
        my $net3 = $networkreg{$networkuuid2};
1111
        my $networkid3 = $net3->{'id'}; # Get the current vlan id of the network
1112
        my $networkid1ip = $net1->{'internalip'};
1113
        $networkid1ip = $net1->{'externalip'} if ($net1->{'type'} eq 'externalip');
1114
1115
        my $uname = $name . substr($uuid,0,8); # We don't enforce unique names, so we make them
1116
        $uname =~ s/[^[:ascii:]]/_/g; # Get rid of funny chars - they mess up Guacamole
1117
        $uname =~ s/\W/_/g;
1118
1119
        my $driver1;
1120
        my $driver2;
1121
        if ($hypervisor eq 'kvm') {
1122
            my $fmt1 = ($image =~ /\.qcow2$/)?'qcow2':'raw';
1123
            my $fmt2 = ($image2 =~ /\.qcow2$/)?'qcow2':'raw';
1124
            my $fmt3 = ($image3 =~ /\.qcow2$/)?'qcow2':'raw';
1125
            my $fmt4 = ($image4 =~ /\.qcow2$/)?'qcow2':'raw';
1126 2a63870a Christian Orellana
            my $cache1 = ($image =~ /\/node\//)?'default':'writeback';
1127
            my $cache2 = ($image2 =~ /\/node\//)?'default':'writeback';
1128
            my $cache3 = ($image3 =~ /\/node\//)?'default':'writeback';
1129
            my $cache4 = ($image4 =~ /\/node\//)?'default':'writeback';
1130
            $driver1 = "\n      <driver name='qemu' type='$fmt1' cache='$cache1'/>";
1131
            $driver2 = "\n      <driver name='qemu' type='$fmt2' cache='$cache2'/>";
1132
            $driver3 = "\n      <driver name='qemu' type='$fmt3' cache='$cache3'/>";
1133
            $driver4 = "\n      <driver name='qemu' type='$fmt4' cache='$cache4'/>";
1134 95b003ff Origo
        }
1135
1136
        my $networktype1 = "user";
1137
        my $networksource1 = "default";
1138
        my $networkforward1 = "bridge";
1139
        my $networkisolated1 = "no";
1140
        $networksource1 = "vboxnet0" if ($hypervisor eq "vbox");
1141
        if ($networkid1 eq '0') {
1142
            $networktype1 = "user";
1143
            $networkforward1 = "nat";
1144 f222b89c hq
            $networkisolated1 = "no"
1145 95b003ff Origo
        } elsif ($networkid1 == 1) {
1146
            $networktype1 = "network" ;
1147
            $networkforward1 = "nat";
1148
            $networkisolated1 = "yes"
1149
        } elsif ($networkid1 > 1) {
1150
            $networktype1 = "bridge";
1151
            $networksource1 = "br$networkid1";
1152
        }
1153
        my $networktype2 = "user";
1154
        my $networksource2 = "default";
1155
        my $networkforward2 = "bridge";
1156
        my $networkisolated2 = "no";
1157
        $networksource2 = "vboxnet0" if ($hypervisor eq "vbox");
1158
        if ($networkid2 eq '0') {
1159
            $networktype2 = "user";
1160
            $networkforward2 = "nat";
1161
            $networkisolated2 = "yes"
1162
        } elsif ($networkid2 == 1) {
1163
            $networktype2 = "network" ;
1164
            $networkforward2 = "nat";
1165
            $networkisolated2 = "yes"
1166
        } elsif ($networkid2 > 1) {
1167
            $networktype2 = "bridge";
1168
            $networksource2 = "br$networkid2";
1169
        }
1170
        my $networktype3 = "user";
1171
        my $networksource3 = "default";
1172
        my $networkforward3 = "bridge";
1173
        my $networkisolated3 = "no";
1174
        $networksource3 = "vboxnet0" if ($hypervisor eq "vbox");
1175
        if ($networkid3 eq '0') {
1176
            $networktype3 = "user";
1177
            $networkforward3 = "nat";
1178
            $networkisolated3 = "yes"
1179
        } elsif ($networkid3 == 1) {
1180
            $networktype3 = "network" ;
1181
            $networkforward3 = "nat";
1182
            $networkisolated3 = "yes"
1183
        } elsif ($networkid3 > 1) {
1184
            $networktype3 = "bridge";
1185
            $networksource3 = "br$networkid3";
1186
        }
1187
1188
        my $xml = "<domain type='$hypervisor' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>\n";
1189 51e32e00 hq
        my $vgpuxml = '';
1190 a93267ad hq
        if ($Stabile::gpupassthroughenabled && $vgpu && $vgpu ne "--") {
1191 51e32e00 hq
            $Stabile::Nodes::user = $user;
1192 a93267ad hq
            require "$Stabile::basedir/cgi/nodes.cgi";
1193 51e32e00 hq
            $Stabile::Nodes::console = 1;
1194 a93267ad hq
            my @gpus = Stabile::Nodes::getNextGpus($vgpu, $targetmac);
1195 51e32e00 hq
            if (@gpus) {
1196
                foreach my $gpu (@gpus) {
1197
                    $vgpuxml .= <<ENDXML2
1198
 <hostdev mode='subsystem' type='pci' managed='yes'>
1199
   <source>
1200 a93267ad hq
     <address domain='0x0000' bus='0x$gpu->{bus}' slot='0x$gpu->{device}' function='0x$gpu->{function}' multifunction='on'/>
1201 51e32e00 hq
   </source>
1202
 </hostdev>
1203
ENDXML2
1204
                    ;
1205
                }
1206
            }
1207
        }
1208
1209 a93267ad hq
        #    <loader readonly='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE.fd</loader>
1210
        #    <nvram template='/usr/share/OVMF/OVMF_VARS.fd'/>
1211 04c16f26 hq
        my $loader_xml = <<ENDXML
1212
    <bootmenu enable='yes' timeout='200'/>
1213
    <smbios mode='sysinfo'/>
1214
ENDXML
1215
        ;
1216 d3805c61 hq
        if ($loader eq 'uefi') {
1217
            $loader_xml = <<ENDXML
1218 04c16f26 hq
  <loader readonly='yes' secure='no' type='pflash'>/usr/share/ovmf/OVMF.fd</loader>
1219
  <nvram template='/usr/share/OVMF/OVMF_VARS.fd'>/tmp/guest_VARS.fd</nvram>
1220
ENDXML
1221 a93267ad hq
            ;
1222 d3805c61 hq
        }
1223
        my $iotune_xml = <<ENDXML
1224
      <iotune>
1225
        <read_bytes_sec>$vm_readlimit</read_bytes_sec>
1226
        <write_bytes_sec>$vm_writelimit</write_bytes_sec>
1227
        <read_iops_sec>$vm_iopsreadlimit</read_iops_sec>
1228
        <write_iops_sec>$vm_iopswritelimit</write_iops_sec>
1229
      </iotune>
1230
ENDXML
1231 a93267ad hq
        ;
1232 d3805c61 hq
        $iotune_xml = '' unless ($enforceiolimits);
1233 95b003ff Origo
1234 a93267ad hq
        if ($vgpuxml) {
1235 705b5366 hq
            $xml .= <<ENDXML
1236 95b003ff Origo
  <cpu mode='host-passthrough'>
1237
    <feature policy='disable' name='hypervisor'/>
1238
  </cpu>
1239
ENDXML
1240 a93267ad hq
            ;
1241
#  <qemu:commandline>
1242
#    <qemu:arg value='-cpu'/>
1243
#    <qemu:arg value='host,hv_time,kvm=off,hv_vendor_id=null'/>
1244
#  </qemu:commandline>
1245
1246
#   <cpu mode='host-model'>
1247
#   </cpu>
1248
1249
       } else {
1250 705b5366 hq
            $xml .= <<ENDXML
1251 a93267ad hq
  <cpu match='exact'>
1252
    <model fallback='allow'>core2duo</model>
1253 705b5366 hq
  </cpu>
1254
ENDXML
1255
            ;
1256 95b003ff Origo
        }
1257
        $xml .=  <<ENDXML
1258
  <name>$uname</name>
1259
  <uuid>$uuid</uuid>
1260
  <memory>$mem</memory>
1261
  <vcpu>$vcpu</vcpu>
1262
  <os>
1263
    <type arch='x86_64' machine='pc'>hvm</type>
1264
    <boot dev='$boot'/>
1265 04c16f26 hq
$loader_xml
1266 95b003ff Origo
  </os>
1267
  <sysinfo type='smbios'>
1268
    <bios>
1269
      <entry name='vendor'>Origo</entry>
1270
    </bios>
1271
    <system>
1272
      <entry name='manufacturer'>Origo</entry>
1273
      <entry name='sku'>$networkid1ip</entry>
1274
    </system>
1275
  </sysinfo>
1276
  <features>
1277
ENDXML
1278 a93267ad hq
        ;
1279
        if ($vgpuxml) {
1280
            $xml .= <<ENDXML
1281 95b003ff Origo
    <kvm>
1282
      <hidden state='on'/>
1283
    </kvm>
1284
ENDXML
1285 a93267ad hq
        ;
1286 95b003ff Origo
        }
1287
        $xml .= <<ENDXML
1288
    <pae/>
1289
    <acpi/>
1290
    <apic/>
1291
  </features>
1292
  <clock offset='localtime'>
1293
    <timer name='rtc' tickpolicy='catchup' track='guest'/>
1294
    <timer name='pit' tickpolicy='delay'/>
1295
  </clock>
1296
  <on_poweroff>destroy</on_poweroff>
1297 04c16f26 hq
  <on_reboot>restart</on_reboot>½
1298 95b003ff Origo
  <on_crash>restart</on_crash>
1299
  <devices>
1300 e837d785 hq
  <sound model='ich6'/>
1301 95b003ff Origo
ENDXML
1302 a93267ad hq
        ;
1303
        if ($vgpuxml) {
1304
            $xml .= $vgpuxml;
1305
        }
1306 95b003ff Origo
        if ($image && $image ne "" && $image ne "--") {
1307 a93267ad hq
            $xml .= <<ENDXML2
1308 95b003ff Origo
    <disk type='file' device='disk'>
1309
      <source file='$image'/>$driver1
1310
      <target dev='$diskdev' bus='$diskbus'/>
1311 d3805c61 hq
$iotune_xml
1312 95b003ff Origo
    </disk>
1313
ENDXML2
1314 a93267ad hq
            ;
1315 95b003ff Origo
        };
1316
1317
        if ($image2 && $image2 ne "" && $image2 ne "--") {
1318 a93267ad hq
            $xml .= <<ENDXML2
1319 95b003ff Origo
    <disk type='file' device='disk'>$driver2
1320
      <source file='$image2'/>
1321
      <target dev='$diskdev2' bus='$diskbus'/>
1322 d3805c61 hq
$iotune_xml
1323 95b003ff Origo
    </disk>
1324
ENDXML2
1325 a93267ad hq
            ;
1326 95b003ff Origo
        };
1327
        if ($image3 && $image3 ne "" && $image3 ne "--") {
1328 a93267ad hq
            $xml .= <<ENDXML2
1329 95b003ff Origo
    <disk type='file' device='disk'>$driver3
1330
      <source file='$image3'/>
1331
      <target dev='$diskdev3' bus='$diskbus'/>
1332 d3805c61 hq
$iotune_xml
1333 95b003ff Origo
    </disk>
1334
ENDXML2
1335 a93267ad hq
            ;
1336 95b003ff Origo
        };
1337
        if ($image4 && $image4 ne "" && $image4 ne "--") {
1338 a93267ad hq
            $xml .= <<ENDXML2
1339 95b003ff Origo
    <disk type='file' device='disk'>$driver4
1340
      <source file='$image4'/>
1341
      <target dev='$diskdev4' bus='$diskbus'/>
1342 d3805c61 hq
$iotune_xml
1343 95b003ff Origo
    </disk>
1344
ENDXML2
1345 a93267ad hq
            ;
1346 95b003ff Origo
        };
1347
1348
        unless ($image4 && $image4 ne '--' && $diskbus eq 'ide') {
1349
            if ($cdrom && $cdrom ne "" && $cdrom ne "--") {
1350 a93267ad hq
                $xml .= <<ENDXML3
1351 95b003ff Origo
    <disk type='file' device='cdrom'>
1352
      <source file='$cdrom'/>
1353
      <target dev='hdd' bus='ide'/>
1354
      <readonly/>
1355
    </disk>
1356
ENDXML3
1357 a93267ad hq
                ;
1358 95b003ff Origo
            } elsif ($hypervisor ne "vbox") {
1359 a93267ad hq
                $xml .= <<ENDXML3
1360 95b003ff Origo
    <disk type='file' device='cdrom'>
1361
      <target dev='hdd' bus='ide'/>
1362
      <readonly/>
1363
    </disk>
1364
ENDXML3
1365 a93267ad hq
                ;
1366 95b003ff Origo
            }
1367
        }
1368
1369
        $xml .= <<ENDXML4
1370
    <interface type='$networktype1'>
1371
      <source $networktype1='$networksource1'/>
1372
      <forward mode='$networkforward1'/>
1373
      <port isolated='$networkisolated1'/>
1374
      <model type='$nicmodel1'/>
1375
      <mac address='$nicmac1'/>
1376
    </interface>
1377
ENDXML4
1378 a93267ad hq
        ;
1379 95b003ff Origo
1380
        if (($networkuuid2 && $networkuuid2 ne '--') || $networkuuid2 eq '0') {
1381
            $xml .= <<ENDXML5
1382
    <interface type='$networktype2'>
1383
      <source $networktype2='$networksource2'/>
1384
      <forward mode='$networkforward2'/>
1385
      <port isolated='$networkisolated2'/>
1386
      <model type='$nicmodel1'/>
1387
      <mac address='$nicmac2'/>
1388
    </interface>
1389
ENDXML5
1390 a93267ad hq
            ;
1391 95b003ff Origo
        }
1392
        if (($networkuuid3 && $networkuuid3 ne '--') || $networkuuid3 eq '0') {
1393
            $xml .= <<ENDXML5
1394
    <interface type='$networktype3'>
1395
      <source $networktype3='$networksource3'/>
1396
      <forward mode='$networkforward3'/>
1397
      <port isolated='$networkisolated3'/>
1398
      <model type='$nicmodel1'/>
1399
      <mac address='$nicmac3'/>
1400
    </interface>
1401
ENDXML5
1402 a93267ad hq
            ;
1403 95b003ff Origo
        }
1404
        $xml .= <<ENDXML6
1405
     <serial type='pty'>
1406
       <source path='/dev/pts/0'/>
1407
       <target port='0'/>
1408
     </serial>
1409
    <input type='tablet' bus='usb'/>
1410
    <graphics type='$graphics' port='$port'/>
1411
  </devices>
1412
</domain>
1413
ENDXML6
1414 a93267ad hq
        ;
1415 95b003ff Origo
1416
1417 a93267ad hq
        #    <graphics type='$graphics' port='$port' keymap='en-us'/>
1418
        #     <console type='pty' tty='/dev/pts/0'>
1419
        #       <source path='/dev/pts/0'/>
1420
        #       <target port='0'/>
1421
        #     </console>
1422
        #     <graphics type='$graphics' port='-1' autoport='yes'/>
1423 95b003ff Origo
1424
        $xmlreg{$uuid} = {
1425
            xml=>URI::Escape::uri_escape($xml)
1426
        };
1427
1428
        # Actually ask node to start domain
1429
        if ($targetmac) {
1430
            $register{$uuid}->{'mac'} = $targetmac;
1431
            $register{$uuid}->{'macname'} = $targetname;
1432
            $register{$uuid}->{'macip'} = $targetip;
1433
1434
            my $tasks = $nodereg{$targetmac}->{'tasks'};
1435
            $tasks .= "START $uuid $user\n";
1436
            $nodereg{$targetmac}->{'tasks'} = $tasks;
1437 a93267ad hq
            if ($vgpuxml) {
1438
                $nodereg{$targetmac}->{'gpusfree'} = $nodereg{$targetmac}->{'gpusfree'} - $vgpu;
1439
            }
1440 95b003ff Origo
            tied(%nodereg)->commit;
1441
            $uiuuid = $uuid;
1442
            $uidisplayip = $targetip;
1443
            $uidisplayport = $port;
1444
            $register{$uuid}->{'status'} = $uistatus;
1445
            $register{$uuid}->{'statustime'} = $current_time;
1446
            tied(%register)->commit;
1447
1448
            # Activate networks
1449
            require "$Stabile::basedir/cgi/networks.cgi";
1450
            Stabile::Networks::Activate($networkuuid1, 'activate');
1451
            Stabile::Networks::Activate($networkuuid2, 'activate') if ($networkuuid2 && $networkuuid2 ne '--');
1452
            Stabile::Networks::Activate($networkuuid3, 'activate') if ($networkuuid3 && $networkuuid3 ne '--');
1453
1454
            $main::syslogit->($user, "info", "Marked $name ($uuid) for ". $serv->{'status'} . " on $targetname ($targetmac)");
1455
            $postreply .= "Status=starting OK $uistatus ". $serv->{'name'} . "\n";
1456
        } else {
1457
            $main::syslogit->($user, "info", "Could not find $hypervisor target for creating $uuid ($image)");
1458
            $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . " (unable to locate target node)\n";
1459 a93267ad hq
            $postreply .= $targeterror if ($targeterror);
1460 95b003ff Origo
        };
1461
    } else {
1462
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
1463
        $postreply .= "Status=ERROR problem $uistatus ". $serv->{'name'} . "\n";
1464
    }
1465
    #return ($uiuuid, $uidisplayip, $uidisplayport, $postreply, $targetmac);
1466
    return $postreply;
1467
}
1468
1469
sub do_attach {
1470
    my ($uuid, $action, $obj) = @_;
1471
    if ($help) {
1472
        return <<END
1473
GET:uuid,image:
1474
Attaches an image to a server as a disk device. Image must not be in use.
1475
END
1476
    }
1477
    my $dev = '';
1478
    my $imagenum = 0;
1479
    my $serv = $register{$uuid};
1480
1481
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1482
        return "Status=Error Server must exist and be running\n";
1483
    }
1484
    my $macip = $serv->{macip};
1485
    my $image = $obj->{image} || $obj->{path};
1486
    if ($image && !($image =~ /^\//)) { # We have a uuid
1487
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Status=Error Unable to access images register\n"};
1488
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1489
        untie %imagereg2;
1490
    }
1491
    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;};
1492
    unless ($macip && $imagereg{$image} && $imagereg{$image}->{'user'} eq $user && $serv->{'user'} eq $user)  {$postreply .= "Status=Error Invalid image or server\n"; return $postreply;};
1493
    if ($imagereg{$image}->{'status'} ne 'unused') {return "Status=Error Image $image is already in use ($imagereg{$image}->{'status'})\n"};
1494
1495
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1496
    my $res = `$cmd`;
1497
    unless ($res =~ /vdb\s+.+/) {$dev = 'vdb'; $imagenum = 2};
1498
    unless ($dev || $res =~ /vdc\s+.+/)  {$dev = 'vdc'; $imagenum = 3};
1499
    unless ($dev || $res =~ /vdd\s+.+/)  {$dev = 'vdd'; $imagenum = 4};
1500
    if (!$dev) {
1501
        $postreply = "Status=Error No more images can be attached\n";
1502
    } else {
1503
        my $xml = <<END
1504
<disk type='file' device='disk'>
1505
  <driver type='qcow2' name='qemu' cache='default'/>
1506
  <source file='$image'/>
1507
  <target dev='$dev' bus='virtio'/>
1508
</disk>
1509
END
1510 a93267ad hq
        ;
1511 95b003ff Origo
        $cmd = qq|$sshcmd $macip "echo \\"$xml\\" > /tmp/attach-device-$uuid.xml"|;
1512
        $res = `$cmd`;
1513
        $res .= `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh attach-device $uuid /tmp/attach-device-$uuid.xml`;
1514
        chomp $res;
1515
        if ($res =~ /successfully/) {
1516
            $postreply .= "Status=OK Attaching $image to $dev\n";
1517
            $imagereg{$image}->{'status'} = 'active';
1518
            $imagereg{$image}->{'domains'} = $uuid;
1519
            $imagereg{$image}->{'domainnames'} = $serv->{'name'};
1520
            $serv->{"image$imagenum"} = $image;
1521
            $serv->{"image$imagenum"."name"} = $imagereg{$image}->{'name'};
1522
            $serv->{"image$imagenum"."type"} = 'qcow2';
1523
        } else {
1524
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1525
        }
1526
    }
1527
    untie %imagereg;
1528
    return $postreply;
1529
}
1530
1531
sub do_detach {
1532
    my ($uuid, $action, $obj) = @_;
1533
    if ($help) {
1534
        return <<END
1535
GET:uuid,image:
1536
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.
1537
END
1538
    }
1539
    my $dev = '';
1540
    my $serv = $register{$uuid};
1541
1542
    if (!$serv->{'uuid'} || ($serv->{'status'} ne 'running' && $serv->{'status'} ne 'paused')) {
1543
        return "Status=Error Server must exist and be running\n";
1544
    }
1545
    my $macip = $serv->{macip};
1546
1547
    my $image = $obj->{image} || $obj->{path} || $serv->{'image2'};
1548
    if ($image && !($image =~ /^\//)) { # We have a uuid
1549
        unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1550
        $image = $imagereg2{$image}->{'path'} if ($imagereg2{$image});
1551
        untie %imagereg2;
1552
    }
1553
    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;};
1554
    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;};
1555
1556
    my $cmd = qq|$sshcmd $macip "LIBVIRT_DEFAULT_URI=qemu:///system virsh domblklist $uuid"|;
1557
    my $res = `$cmd`;
1558
    $dev = $1 if ($res =~ /(vd.)\s+.+$image/);
1559
    if (!$dev) {
1560
        $postreply =  qq|Status=Error Image $image, $cmd, is not currently attached\n|;
1561
    } elsif ($dev eq 'vda') {
1562
        $postreply = "Status=Error You cannot detach the primary image\n";
1563
    } else {
1564
        $res = `$sshcmd $macip LIBVIRT_DEFAULT_URI=qemu:///system virsh detach-disk $uuid $dev`;
1565
        chomp $res;
1566
        if ($res =~ /successfully/) {
1567
            $postreply .= "Status=OK Detaching image $image, $imagereg{$image}->{'uuid'} from $dev\n";
1568
            my $imagenum;
1569
            $imagenum = 2 if ($serv->{'image2'} eq $image);
1570
            $imagenum = 3 if ($serv->{'image3'} eq $image);
1571
            $imagenum = 4 if ($serv->{'image4'} eq $image);
1572
            $imagereg{$image}->{'status'} = 'unused';
1573
            $imagereg{$image}->{'domains'} = '';
1574
            $imagereg{$image}->{'domainnames'} = '';
1575
            if ($imagenum) {
1576
                $serv->{"image$imagenum"} = '';
1577
                $serv->{"image$imagenum"."name"} = '';
1578
                $serv->{"image$imagenum"."type"} = '';
1579
            }
1580
        } else {
1581
            $postreply .= "Status=Error Unable to attach image $image to $dev ($res)\n";
1582
        }
1583
    }
1584
    untie %imagereg;
1585
    return $postreply;
1586
}
1587
1588
sub Destroy {
1589
    my ($uuid, $action, $obj) = @_;
1590
    if ($help) {
1591
        return <<END
1592
GET:uuid,wait:
1593
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.
1594
END
1595
    }
1596
    my $uistatus = 'destroying';
1597
    my $name = $register{$uuid}->{'name'};
1598
    my $mac = $register{$uuid}->{'mac'};
1599
    my $macname = $register{$uuid}->{'macname'};
1600
    my $dbstatus = $register{$uuid}->{'status'};
1601
    my $wait = $obj->{'wait'};
1602
    if ($dbstatus eq 'running' or $dbstatus eq 'paused'
1603
        or $dbstatus eq 'shuttingdown' or $dbstatus eq 'starting'
1604
        or $dbstatus eq 'destroying' or $dbstatus eq 'upgrading'
1605
        or $dbstatus eq 'suspending' or $dbstatus eq 'resuming') {
1606
        if ($wait) {
1607 6372a66e hq
            my $username = $register{$uuid}->{'user'} || $user;
1608
            $username = $user unless ($isadmin);
1609
            $postreply = destroyUserServers($username, 1, $uuid);
1610 95b003ff Origo
        } else {
1611 6372a66e hq
            my $node = $nodereg{$mac};
1612
            my $tasks = $node->{'tasks'};
1613
            $node->{'tasks'} = $tasks . "DESTROY $uuid $user\n";
1614 95b003ff Origo
            tied(%nodereg)->commit;
1615
            $register{$uuid}->{'status'} = $uistatus;
1616
            $register{$uuid}->{'statustime'} = $current_time;
1617
            $uiuuid = $uuid;
1618
            $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus on $macname ($mac)");
1619
            $postreply .= "Status=destroying $uistatus ". $register{$uuid}->{'name'} . "\n";
1620
        }
1621
    } else {
1622
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $name ($uuid)");
1623
        $postreply .= "Status=ERROR problem $uistatus $name\n";
1624
    }
1625
    return $postreply;
1626
}
1627
1628
sub getHypervisor {
1629 a93267ad hq
    my $image = shift;
1630
    # Produce a mapping of image file suffixes to hypervisors
1631
    my %idreg;
1632 95b003ff Origo
    unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access nodeidentities register"};
1633
    my @idvalues = values %idreg;
1634 a93267ad hq
    my %formats;
1635
    foreach my $val (@idvalues) {
1636
        my %h = %$val;
1637
        foreach (split(/,/,$h{'formats'})) {
1638
            $formats{lc $_} = $h{'hypervisor'}
1639
        }
1640
    }
1641
    untie %idreg;
1642
1643
    # and then determine the hypervisor in question
1644
    my $hypervisor = "vbox";
1645
    my ($pathname, $path, $suffix) = fileparse($image, '\.[^\.]*');
1646
    $suffix = substr $suffix, 1;
1647
    my $hypervisor = $formats{lc $suffix};
1648
    return $hypervisor;
1649 95b003ff Origo
}
1650
1651
sub nicmac1ToUuid {
1652
    my $nicmac1 = shift;
1653
    my $uuid;
1654
    return $uuid unless $nicmac1;
1655
    my @regkeys = (tied %register)->select_where("user = '$user' AND nicmac1 = '$nicmac1");
1656 a93267ad hq
    foreach my $k (@regkeys) {
1657
        my $val = $register{$k};
1658
        my %h = %$val;
1659
        if (lc $h{'nicmac1'} eq lc $nicmac1 && $user eq $h{'user'}) {
1660
            $uuid =  $h{'uuid'};
1661
            last;
1662
        }
1663
    }
1664
    return $uuid;
1665 95b003ff Origo
}
1666
1667
sub randomMac {
1668 a93267ad hq
    my ( %vendor, $lladdr, $i );
1669
    #	$lladdr = '00';
1670
    $lladdr = '52:54:00';# KVM vendor string
1671
    while ( ++$i )
1672
    #	{ last if $i > 10;
1673
    { last if $i > 6;
1674
        $lladdr .= ':' if $i % 2;
1675
        $lladdr .= sprintf "%" . ( qw (X x) [int ( rand ( 2 ) ) ] ), int ( rand ( 16 ) );
1676
    }
1677
    return $lladdr;
1678 95b003ff Origo
}
1679
1680
sub overQuotas {
1681
    my $meminc = shift;
1682
    my $vcpuinc = shift;
1683 a93267ad hq
    my $usedmemory = 0;
1684
    my $usedvcpus = 0;
1685
    my $overquota = 0;
1686 95b003ff Origo
    return $overquota if ($isadmin || $Stabile::userprivileges =~ /a/); # Don't enforce quotas for admins
1687
1688 a93267ad hq
    my $memoryquota = $Stabile::usermemoryquota;
1689
    my $vcpuquota = $Stabile::uservcpuquota;
1690 95b003ff Origo
1691 a93267ad hq
    if (!$memoryquota || !$vcpuquota) { # 0 or empty quota means use defaults
1692 95b003ff Origo
        $memoryquota = $memoryquota || $Stabile::config->get('MEMORY_QUOTA');
1693
        $vcpuquota = $vcpuquota || $Stabile::config->get('VCPU_QUOTA');
1694
    }
1695
1696
    my @regkeys = (tied %register)->select_where("user = '$user'");
1697 a93267ad hq
    foreach my $k (@regkeys) {
1698
        my $val = $register{$k};
1699
        if ($val->{'user'} eq $user && $val->{'status'} ne "shutoff" &&
1700
            $val->{'status'} ne "inactive" && $val->{'status'} ne "shutdown" ) {
1701
1702
            $usedmemory += $val->{'memory'};
1703
            $usedvcpus += $val->{'vcpu'};
1704
        }
1705
    }
1706
    $overquota = $usedmemory+$meminc if ($memoryquota!=-1 && $usedmemory+$meminc > $memoryquota); # -1 means no quota
1707
    $overquota = $usedvcpus+$vcpuinc if ($vcpuquota!=-1 && $usedvcpus+$vcpuinc > $vcpuquota);
1708
    return $overquota;
1709 95b003ff Origo
}
1710
1711
sub validateItem {
1712 a2e0bc7e hq
    unless (%imagereg) {
1713
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1714
    }
1715 95b003ff Origo
    my $valref = shift;
1716
    my $img = $imagereg{$valref->{'image'}};
1717
    my $imagename = $img->{'name'};
1718
    $valref->{'imagename'} = $imagename if ($imagename);
1719
    my $imagetype = $img->{'type'};
1720
    $valref->{'imagetype'} = $imagetype if ($imagetype);
1721
1722
    # imagex may be registered by uuid instead of path - find the path
1723
    # We now support up to 4 images
1724
    for (my $i=2; $i<=4; $i++) {
1725
        if ($valref->{"image$i"} && $valref->{"image$i"} ne '--' && !($valref->{"image$i"} =~ /^\//)) {
1726
            unless ( tie(%imagereg2,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1727
            $valref->{"image$i"} = $imagereg2{$valref->{"image$i"}}->{'path'};
1728
            untie %imagereg2;
1729
        }
1730
1731
        my $imgi = $imagereg{$valref->{"image$i"}};
1732
        $valref->{"image$i" . 'name'} = $imgi->{'name'} || $valref->{"image$i" . 'name'};
1733
        $valref->{"image$i" . 'type'} = $imgi->{'type'} || $valref->{"image$i" . 'type'};
1734
    }
1735
1736
    my $net1 = $networkreg{$valref->{'networkuuid1'}};
1737
    my $networkname1 = $net1->{'name'};
1738
    $valref->{'networkname1'} = $networkname1 if ($networkname1);
1739
    my $net2 = $networkreg{$valref->{'networkuuid2'}};
1740
    my $networkname2 = $net2->{'name'};
1741
    $valref->{'networkname2'} = $networkname2 if ($networkname2);
1742
    my $name = $valref->{'name'};
1743
    $valref->{'name'} = $imagename unless $name;
1744
1745 a2e0bc7e hq
    # Make sure we start shutoff servers on the node their image is on
1746 95b003ff Origo
    if ($valref->{'status'} eq "shutoff" || $valref->{'status'} eq "inactive") {
1747
        my $node = $nodereg{$valref->{'mac'}};
1748
        if ($valref->{'image'} =~ /\/mnt\/stabile\/node\//) {
1749
            $valref->{'mac'} = $img->{'mac'};
1750
            $valref->{'macname'} = $node->{'name'};
1751
            $valref->{'macip'} = $node->{'ip'};
1752
        } elsif ($valref->{'image2'} =~ /\/mnt\/stabile\/node\//) {
1753
            $valref->{'mac'} = $imagereg{$valref->{'image2'}}->{'mac'};
1754
            $valref->{'macname'} = $node->{'name'};
1755
            $valref->{'macip'} = $node->{'ip'};
1756
        } elsif ($valref->{'image3'} =~ /\/mnt\/stabile\/node\//) {
1757
            $valref->{'mac'} = $imagereg{$valref->{'image3'}}->{'mac'};
1758
            $valref->{'macname'} = $node->{'name'};
1759
            $valref->{'macip'} = $node->{'ip'};
1760
        } elsif ($valref->{'image4'} =~ /\/mnt\/stabile\/node\//) {
1761
            $valref->{'mac'} = $imagereg{$valref->{'image4'}}->{'mac'};
1762
            $valref->{'macname'} = $node->{'name'};
1763
            $valref->{'macip'} = $node->{'ip'};
1764
        }
1765
    }
1766 a93267ad hq
    # Mark domains we have heard from in the last 20 secs as inactive
1767 95b003ff Origo
    my $dbtimestamp = 0;
1768
    $dbtimestamp = $register{$valref->{'uuid'}}->{'timestamp'} if ($register{$valref->{'uuid'}});
1769
    my $timediff = $current_time - $dbtimestamp;
1770
    if ($timediff >= 20) {
1771
        if  (! ($valref->{'status'} eq "shutoff"
1772 a93267ad hq
            || $valref->{'status'} eq "starting"
1773 95b003ff Origo
            #    || $valref->{'status'} eq "shuttingdown"
1774
            #    || $valref->{'status'} eq "destroying"
1775 a93267ad hq
            || ($valref->{'status'} =~ /moving/ && $timediff<40)
1776
        )) { # Move has probably failed
1777 95b003ff Origo
            $valref->{'status'} = "inactive";
1778
            $imagereg{$valref->{'image'}}->{'status'} = "used" if ($valref->{'image'} && $imagereg{$valref->{'image'}});
1779 a2e0bc7e hq
            $imagereg{$valref->{'image2'}}->{'status'} = "used" if ($valref->{'image2'} && $imagereg{$valref->{'image2'}});
1780 95b003ff Origo
            $imagereg{$valref->{'image3'}}->{'status'} = "used" if ($valref->{'image3'} && $imagereg{$valref->{'image3'}});
1781
            $imagereg{$valref->{'image4'}}->{'status'} = "used" if ($valref->{'image4'} && $imagereg{$valref->{'image4'}});
1782
        }
1783
    };
1784 a93267ad hq
    #    untie %imagereg;
1785 95b003ff Origo
    return $valref;
1786
}
1787
1788
# Run through all domains and mark domains we have heard from in the last 20 secs as inactive
1789
sub updateRegister {
1790
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access user register"};
1791
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1792
1793
    my @regkeys = (tied %register)->select_where("user = '$user'");
1794
1795
    foreach my $k (@regkeys) {
1796
        my $valref = $register{$k};
1797
        next unless ($userreg{$valref->{'user'}});
1798
        my $dbtimestamp = $valref->{'timestamp'};
1799
        my $dbstatus = $valref->{'status'};
1800
        my $timediff = $current_time - $dbtimestamp;
1801
        my $imgstatus;
1802
        my $domstatus;
1803
        if ($timediff >= 20) {
1804
            if  ( $valref->{'status'} eq "shutoff" ) {
1805
                $imgstatus = 'used';
1806
            } elsif ((  $valref->{'status'} eq "starting"
1807 a93267ad hq
                || $valref->{'status'} eq "shuttingdown"
1808
            ) && $timediff>50) {
1809 95b003ff Origo
                $imgstatus = 'used';
1810
                $domstatus = 'inactive';
1811
            } elsif ($valref->{'status'} eq "destroying" || $valref->{'status'} eq "moving") {
1812
                ;
1813
            } else {
1814
                $domstatus = 'inactive';
1815
                $imgstatus = 'used';
1816
            }
1817
            $valref->{'status'} = $domstatus if ($domstatus);
1818
            my $image = $valref->{'image'};
1819
            my $image2 = $valref->{'image2'};
1820
            my $image3 = $valref->{'image3'};
1821
            my $image4 = $valref->{'image4'};
1822
            $imagereg{$image}->{'status'} = $imgstatus if ($imgstatus);
1823
            $imagereg{$image2}->{'status'} = $imgstatus if ($image2 && $imgstatus);
1824
            $imagereg{$image3}->{'status'} = $imgstatus if ($image3 && $imgstatus);
1825
            $imagereg{$image4}->{'status'} = $imgstatus if ($image4 && $imgstatus);
1826
            if ($domstatus eq 'inactive ' && $dbstatus ne 'inactive') {
1827
                $main::updateUI->({ tab=>'servers',
1828 a93267ad hq
                    user=>$valref->{'user'},
1829
                    uuid=>$valref->{'uuid'},
1830
                    sender=>'updateRegister',
1831
                    status=>'inactive'})
1832 95b003ff Origo
            }
1833
        };
1834
1835
    }
1836
    untie %userreg;
1837
    untie %imagereg;
1838
}
1839
1840
sub locateTargetNode {
1841 a93267ad hq
    my ($uuid, $dmac, $mem, $vcpu, $vgpu, $vmem, $image, $image2, $image3, $image4, $hypervisor, $smac, $stormove)= @_;
1842 95b003ff Origo
    my $targetname;
1843
    my $targetip;
1844
    my $port;
1845
    my $targetnode;
1846
    my $targetindex; # Availability index of located target node
1847 a93267ad hq
    my $targeterror = '';
1848 95b003ff Origo
    my %avhash;
1849
1850 d3805c61 hq
    $dmac = '' unless ($isadmin); # Only allow admins to select specific node
1851 a93267ad hq
    my $serv = $register{$uuid};
1852 d3805c61 hq
    if (!$dmac
1853 a93267ad hq
        && $serv->{'locktonode'} eq 'true'
1854
        && $serv->{'mac'}
1855
        && $serv->{'mac'} ne '--'
1856
    ) {
1857
        $dmac = $serv->{'mac'}; # Server is locked to specific node
1858
        $dmac = '' if ($nodereg{$dmac}->{maintenance});
1859 d3805c61 hq
    }
1860 95b003ff Origo
    if ($dmac && !$nodereg{$dmac}) {
1861
        $main::syslogit->($user, "info", "The target node $dmac no longer exists, starting $uuid on another node if possible");
1862
        $dmac = '';
1863
    }
1864 d3805c61 hq
    my $imageonnode = ((!$stormove) && ($image =~ /\/mnt\/stabile\/node\//
1865 a93267ad hq
        || $image2 =~ /\/mnt\/stabile\/node\//
1866
        || $image3 =~ /\/mnt\/stabile\/node\//
1867
        || $image4 =~ /\/mnt\/stabile\/node\//
1868
    ));
1869
    my $memok;
1870
    my $vcpuok;
1871
    my $vgpuok;
1872
    my $vmemok;
1873
    foreach my $node (values %nodereg) {
1874 95b003ff Origo
        my $nstatus = $node->{'status'};
1875
        my $maintenance = $node->{'maintenance'};
1876
        my $nmac = $node->{'mac'};
1877 a93267ad hq
        if (
1878
            ($nstatus eq 'running' || $nstatus eq 'asleep'
1879
            || ($nstatus eq 'maintenance' && $nmac eq $dmac && $isadmin)
1880
            || $nstatus eq 'waking')
1881
            && $smac ne $nmac
1882 95b003ff Origo
        ) {
1883 a93267ad hq
            unless ($action eq 'listnodeavailability') { # We dont do ressource checks when listing node availability
1884
                if ($vgpu && !$Stabile::gpupassthroughenabled) {
1885
                    $targeterror = "GPU ressources was asked for, but GPU support is not enabled";
1886
                    $postreply .= "Status=ERROR GPU ressources was asked for, but GPU support is not enabled\n";
1887
                    last;
1888
                }
1889
                if (!$dmac && $serv->{'locktonode'} eq 'true') {
1890
                    $targeterror = "Server is locked to node, but node not available";
1891
                    $postreply .= "Status=ERROR server is locked to node, but node not available\n";
1892
                    last;
1893
                }
1894
                if ($node->{'memfree'} > $mem+512*1024) { $memok = 1; } else { next; }
1895
                if ((($node->{'vmvcpus'} + $vcpu) <= ($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'}))) { $vcpuok = 1; } else { next; }
1896
                if (($node->{'vmvgpus'} + $vgpu) <= $node->{'gpusfree'}) { $vgpuok = 1; } else { next; }
1897
                if (
1898
                    (!$vmem || $vmem <= $node->{vmem}) # If minimum vmem is specified, enforce
1899
                ) { $vmemok = 1; } else { next; }
1900
            }
1901
            # Determine how available this node is
1902
            # Available memory
1903 95b003ff Origo
            my $memweight = 0.2; # memory weighing factor
1904
            my $memindex = $avhash{$nmac}->{'memindex'} = int(100* $memweight* $node->{'memfree'} / (1024*1024) )/100;
1905 a93267ad hq
            # Free cores
1906 95b003ff Origo
            my $cpuindex = $avhash{$nmac}->{'cpuindex'} = int(100*($cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'} - $node->{'vmvcpus'} - $node->{'reservedvcpus'}))/100;
1907 a93267ad hq
            # Free GPUs
1908
            my $gpuindex = $avhash{$nmac}->{'gpuindex'} = int(100*($node->{'gpusfree'} - $node->{'vmvgpus'}))/100;
1909
1910
            # Asleep - not asleep gives a +3
1911 95b003ff Origo
            my $sleepindex = $avhash{$nmac}->{'sleepindex'} = ($node->{'status'} eq 'asleep' || $node->{'status'} eq 'waking')?'0':'3';
1912
            $avhash{$nmac}->{'vmvcpus'} = $node->{'vmvcpus'};
1913 a93267ad hq
            #            $avhash{$nmac}->{'cpucommision'} = $cpuovercommision * $node->{'cpucores'} * $node->{'cpucount'};
1914
            #            $avhash{$nmac}->{'cpureservation'} = $node->{'vmvcpus'} + $node->{'reservedvcpus'};
1915 95b003ff Origo
            $avhash{$nmac}->{'name'} = $node->{'name'};
1916
            $avhash{$nmac}->{'mac'} = $node->{'mac'};
1917
1918
            my $aindex = $memindex + $cpuindex + $sleepindex;
1919 a93267ad hq
            # Don't use nodes that are out of memory or cores
1920 95b003ff Origo
            $aindex = 0 if ($memindex <= 0 || $cpuindex <= 0);
1921
            $avhash{$nmac}->{'index'} = $aindex;
1922
            $avhash{$nmac}->{'storfree'} = $node->{'storfree'};
1923 c899e439 Origo
            $avhash{$nmac}->{'memfree'} = $node->{'memfree'};
1924 95b003ff Origo
            $avhash{$nmac}->{'ip'} = $node->{'ip'};
1925
            $avhash{$nmac}->{'identity'} = $node->{'identity'};
1926
            $avhash{$nmac}->{'status'} = $node->{'status'};
1927
            $avhash{$nmac}->{'maintenance'} = $maintenance;
1928
            $avhash{$nmac}->{'reservedvcpus'} = $node->{'reservedvcpus'};
1929 a93267ad hq
            $avhash{$nmac}->{'gpuindex'} = $gpuindex;
1930 95b003ff Origo
            my $nodeidentity = $node->{'identity'};
1931
            $nodeidentity = 'kvm' if ($nodeidentity eq 'local_kvm');
1932
            if ($hypervisor eq $nodeidentity) {
1933
                # If image is on node, we must start on same node - registered when moving image
1934
                if ($imageonnode) {
1935
                    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1936
                    $dmac = $imagereg{$image}->{'mac'};
1937
                    $dmac = $imagereg{$image2}->{'mac'} unless ($dmac);
1938
                    $dmac = $imagereg{$image3}->{'mac'} unless ($dmac);
1939
                    $dmac = $imagereg{$image4}->{'mac'} unless ($dmac);
1940
                    untie %imagereg;
1941
                    if (!$dmac) {
1942 a93267ad hq
                        $targeterror = "Image node not found";
1943 95b003ff Origo
                        $postreply .= "Status=ERROR Image node not found\n";
1944
                        last;
1945
                    }
1946
                }
1947
                $dmac = "" if ($dmac eq "--");
1948 a93267ad hq
                # If a specific node is asked for, match mac addresses
1949 95b003ff Origo
                if ($dmac eq $nmac) {
1950 a93267ad hq
                    if ($vgpu && $vmem) { # a GPU was asked for
1951
                        unless (($nodereg{$dmac}->{vmvgpus} + $vgpu) <= $nodereg{$dmac}->{gpusfree}) {
1952
                            $targeterror = "Image node does not have the requested available GPUs";
1953
                            $postreply .= "Status=ERROR Image node does not have the requested available GPUs\n";
1954
                        }
1955
                        unless ($vmem <= $nodereg{$dmac}->{vmem}) {
1956
                            $targeterror = "Image node GPUs do not have the requested amount of VRAM";
1957
                            $postreply .= "Status=ERROR Image node GPUs do not have the requeste amount of VRAM\n";
1958
                        }
1959
                    } else {
1960
                        $targetnode = $node;
1961
                    }
1962 95b003ff Origo
                    last;
1963
                } elsif (!$dmac && $nstatus ne "maintenance" && !$maintenance) {
1964 a93267ad hq
                    # pack or disperse
1965 95b003ff Origo
                    if (!$targetindex) {
1966
                        $targetindex = $aindex;
1967
                        $targetnode = $node;
1968
                    } elsif ($dpolicy eq 'pack') {
1969
                        if ($aindex < $targetindex) {
1970
                            $targetnode = $node;
1971
                            $targetindex = $aindex;
1972
                        }
1973
                    } else {
1974
                        if ($aindex > $targetindex) {
1975
                            $targetnode = $node;
1976
                            $targetindex = $aindex;
1977
                        }
1978
                    }
1979
                }
1980
            }
1981
        }
1982
    }
1983
    if ($targetnode && $uuid) {
1984
        if ($targetnode->{'status'} eq 'asleep') {
1985
            my $nmac = $targetnode->{'mac'};
1986
            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);
1987
            my $nlogmsg = "Node $nmac marked for wake ";
1988
            if ($brutalsleep && (
1989 a93267ad hq
                ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--')
1990
                    || ($targetnode->{'ipmiip'} && $targetnode->{'ipmiip'} ne '--')
1991
            )) {
1992 95b003ff Origo
                my $wakecmd;
1993
                if ($targetnode->{'amtip'} && $targetnode->{'amtip'} ne '--') {
1994
                    $wakecmd = "echo 'y' | AMT_PASSWORD='$amtpasswd' /usr/bin/amttool $targetnode->{'amtip'} powerup pxe";
1995
                } else {
1996
                    $wakecmd = "ipmitool -I lanplus -H $targetnode->{'ipmiip'} -U ADMIN -P ADMIN power on";
1997
                }
1998
                $nlogmsg .= `$wakecmd`;
1999
            } else {
2000
                my $broadcastip = $targetnode->{'ip'};
2001
                $broadcastip =~ s/\.\d{1,3}$/.255/;
2002
                $nlogmsg .= 'on lan ' . `/usr/bin/wakeonlan -i $broadcastip $realmac`;
2003
            }
2004
            $targetnode->{'status'} = "waking";
2005
            $nlogmsg =~ s/\n/ /g;
2006
            $main::syslogit->($user, "info", $nlogmsg);
2007
            $postreply .= "Status=OK waking $targetnode->{'name'}\n";
2008
        }
2009
        $targetname = $targetnode->{'name'};
2010
        $targetmac = $targetnode->{'mac'};
2011
        $targetip = $targetnode->{'ip'};
2012
        $targetip = $targetnode->{'ip'};
2013
        my $porttaken = 1;
2014
        while ($porttaken) {
2015
            $porttaken = 0;
2016
            $port = $targetnode->{'vms'} + (($hypervisor eq "vbox")?3389:5900);
2017
            $port += int(rand(200));
2018
            my @regkeys = (tied %register)->select_where("port = '$port' AND macip = '$targetip'");
2019
            foreach my $k (@regkeys) {
2020
                $r = $register{$k};
2021
                if ($r->{'port'} eq $port && $r->{'macip'} eq $targetip) {
2022
                    $porttaken = 1;
2023
                }
2024
            }
2025
        }
2026
        $targetnode->{'vms'}++;
2027
        $targetnode->{'vmvcpus'} += $vcpu;
2028
        $register{$uuid}->{'port'} = $port;
2029 a93267ad hq
        #        $register{$uuid}->{'mac'} = $targetmac;
2030
        #        $register{$uuid}->{'macname'} = $targetname;
2031
        #        $register{$uuid}->{'macip'} = $targetip;
2032 95b003ff Origo
        $register{$uuid}->{'display'} = (($hypervisor eq "vbox")?'rdp':'vnc');
2033
    } else {
2034 a93267ad hq
        $targeterror = "Status=Error The requested amount of memory ($mem) is not available" unless ($memok);
2035
        $targeterror = "Status=Error The requested amount of vCPUs ($vcpu) is not available" unless ($vcpuok || $targeterror);
2036
        $targeterror = "Status=Error The requested amount of NVRAM ($vmem) is not available" if ($vmem && !$vmemok && !$targeterror);
2037
        $targeterror = "Status=Error The requested amount of GPUs ($vgpu) is not available" if ($vgpu && !$vgpuok && !$targeterror);
2038 95b003ff Origo
        my $macstatus;
2039
        $macstatus = $nodereg{$dmac}->{status} if ($nodereg{$dmac});
2040 a93267ad 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);
2041 95b003ff Origo
    }
2042 a93267ad hq
    return ($targetmac, $targetname, $targetip, $port, \%avhash, $targeterror);
2043 95b003ff Origo
}
2044
2045
sub destroyUserServers {
2046
    my $username = shift;
2047
    my $wait = shift; # Should we wait for servers do die
2048
    my $duuid = shift;
2049 6372a66e hq
    return unless ($username && ($isadmin || $user eq $username));
2050 95b003ff Origo
    my @updateList;
2051
2052
    my @regkeys = (tied %register)->select_where("user = '$username'");
2053
    foreach my $uuid (@regkeys) {
2054
        if ($register{$uuid}->{'user'} eq $username
2055
            && $register{$uuid}->{'status'} ne 'shutoff'
2056
            && (!$duuid || $duuid eq $uuid)
2057
        ) {
2058
            $postreply .= "Destroying $username server $register{$uuid}->{'name'}, $uuid\n";
2059
            Destroy($uuid);
2060
            push (@updateList,{ tab=>'servers',
2061 a93267ad hq
                user=>$user,
2062
                uuid=>$duuid,
2063
                status=>'destroying'});
2064 95b003ff Origo
        }
2065
    }
2066
    $main::updateUI->(@updateList) if (@updateList);
2067
    if ($wait) {
2068
        my @regkeys = (tied %register)->select_where("user = '$username'");
2069
        my $activeservers = 1;
2070
        my $i = 0;
2071 6372a66e hq
        while ($activeservers && $i<30) {
2072 95b003ff Origo
            $activeservers = 0;
2073
            foreach my $k (@regkeys) {
2074
                my $valref = $register{$k};
2075
                if ($username eq $valref->{'user'}
2076
                    && ($valref->{'status'} ne 'shutoff'
2077
                    && $valref->{'status'} ne 'inactive')
2078
                    && (!$duuid || $duuid eq $valref->{'uuid'})
2079
                ) {
2080
                    $activeservers = $valref->{'uuid'};
2081
                }
2082
            }
2083
            $i++;
2084
            if ($activeservers) {
2085
                my $res .= "Status=OK Waiting $i for server $register{$activeservers}->{'name'}, $register{$activeservers}->{'status'} to die...\n";
2086 a93267ad hq
                #    print $res if ($console);
2087 95b003ff Origo
                $postreply .= $res;
2088
                sleep 2;
2089
            }
2090
        }
2091
        $postreply .= "Status=OK Servers halted for $username\n" unless ($activeservers);
2092
    }
2093
    return $postreply;
2094
}
2095
2096
sub removeUserServers {
2097
    my $username = shift;
2098
    my $uuid = shift;
2099
    my $destroy = shift; # Should running servers be destroyed before removing
2100
    return unless (($isadmin || $user eq $username) && !$isreadonly);
2101
    $user = $username;
2102
    my @regkeys = (tied %register)->select_where("user = '$username'");
2103
    foreach my $ruuid (@regkeys) {
2104
        next if ($uuid && $ruuid ne $uuid);
2105
        if ($destroy && $register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} ne 'shutoff' && $register{$ruuid}->{'status'} ne 'inactive')) {
2106
            destroyUserServers($username, 1, $ruuid);
2107
        }
2108
2109
        if ($register{$ruuid}->{'user'} eq $username && ($register{$ruuid}->{'status'} eq 'shutoff' || $register{$ruuid}->{'status'} eq 'inactive')) {
2110
            $postreply .= "Removing $username server $register{$ruuid}->{'name'}, $ruuid" . ($console?'':'<br>') . "\n";
2111
            Remove($ruuid);
2112
        }
2113
    }
2114
}
2115
2116
sub Remove {
2117
    my ($uuid, $action) = @_;
2118
    if ($help) {
2119
        return <<END
2120
DELETE:uuid:
2121
Removes a server. Server must be shutoff. Does not remove associated images or networks.
2122
END
2123
    }
2124
    my $reguser = $register{$uuid}->{'user'};
2125
    my $dbstatus = $register{$uuid}->{'status'};
2126
    my $image = $register{$uuid}->{'image'};
2127
    my $image2 = $register{$uuid}->{'image2'};
2128
    my $image3 = $register{$uuid}->{'image3'};
2129
    my $image4 = $register{$uuid}->{'image4'};
2130
    my $name = $register{$uuid}->{'name'};
2131
    $image2 = '' if ($image2 eq '--');
2132
    $image3 = '' if ($image3 eq '--');
2133
    $image4 = '' if ($image4 eq '--');
2134
2135
    if ($reguser ne $user) {
2136
        $postreply .= "Status=ERROR You cannot delete a vm you don't own\n";
2137
    } elsif ($dbstatus eq 'inactive' || $dbstatus eq 'shutdown' || $dbstatus eq 'shutoff') {
2138
2139
        # Delete software packages and monitors from register
2140
        $postmsg .= deletePackages($uuid);
2141
        my $sname = $register{$uuid}->{'name'};
2142
        utf8::decode($sname);
2143 48fcda6b Origo
        $postmsg .= deleteMonitors($uuid)?" deleted monitors for $sname ":'';
2144 95b003ff Origo
2145
        delete $register{$uuid};
2146
        delete $xmlreg{$uuid};
2147
2148
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2149
        $imagereg{$image}->{'status'} = "unused" if ($imagereg{$image});
2150
        $imagereg{$image2}->{'status'} = "unused" if ($image2 && $imagereg{$image2});
2151
        $imagereg{$image3}->{'status'} = "unused" if ($image3 && $imagereg{$image3});
2152
        $imagereg{$image4}->{'status'} = "unused" if ($image4 && $imagereg{$image4});
2153
        untie %imagereg;
2154
2155
        # Delete metrics
2156
        my $metricsdir = "/var/lib/graphite/whisper/domains/$uuid";
2157
        `rm -r $metricsdir` if (-e $metricsdir);
2158
        my $rrdfile = "/var/cache/rrdtool/".$uuid."_highres.rrd";
2159
        `rm $rrdfile` if (-e $rrdfile);
2160
2161
        $main::syslogit->($user, "info", "Deleted domain $uuid from db");
2162
        utf8::decode($name);
2163 48fcda6b Origo
        $postmsg .= " deleted server $name";
2164 95b003ff Origo
        $postreply = "[]";
2165
        sleep 1;
2166
    } else {
2167
        $postreply .= "Status=ERROR Cannot delete a $dbstatus server\n";
2168
    }
2169
    return $postreply;
2170
}
2171
2172
# Delete all monitors belonging to a server
2173
sub deleteMonitors {
2174
    my ($serveruuid) = @_;
2175
    my $match;
2176
    if ($serveruuid) {
2177
        if ($register{$serveruuid}->{'user'} eq $user || $isadmin) {
2178
            local($^I, @ARGV) = ('.bak', "/etc/mon/mon.cf");
2179
            # undef $/; # This makes <> read in the entire file in one go
2180
            my $uuidmatch;
2181
            while (<>) {
2182
                if (/^watch (\S+)/) {
2183
                    if ($1 eq $serveruuid) {$uuidmatch = $serveruuid}
2184
                    else {$uuidmatch = ''};
2185
                };
2186
                if ($uuidmatch) {
2187
                    $match = 1;
2188
                } else {
2189
                    #chomp;
2190
                    print unless (/^hostgroup $serveruuid/);
2191
                }
2192
                close ARGV if eof;
2193
            }
2194
            #$/ = "\n";
2195
        }
2196
        unlink glob "/var/log/stabile/*:$serveruuid:*";
2197
    }
2198
    `/usr/bin/moncmd reset keepstate` if ($match);
2199
    return $match;
2200
}
2201
2202
sub deletePackages {
2203
    my ($uuid, $issystem, %packreg) = @_;
2204
    unless ( tie(%packreg,'Tie::DBI', Hash::Merge::merge({table=>'packages', key=>'id'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2205
2206
    my @domains;
2207
    if ($issystem) {
2208
        foreach my $valref (values %register) {
2209
            if (($valref->{'system'} eq $uuid || $uuid eq '*')
2210 a93267ad hq
                && ($valref->{'user'} eq $user || $fulllist)) {
2211 95b003ff Origo
                push(@domains, $valref->{'uuid'});
2212
            }
2213
        }
2214
    } else { # Allow if domain no longer exists or belongs to user
2215
        push(@domains, $uuid) if (!$register{$uuid} || $register{$uuid}->{'user'} eq $user || $fulllist);
2216
    }
2217
2218
    foreach my $domuuid (@domains) {
2219
        foreach my $packref (values %packreg) {
2220
            my $id = $packref->{'id'};
2221
            if (substr($id, 0,36) eq $domuuid || ($uuid eq '*' && $packref->{'user'} eq $user)) {
2222
                delete $packreg{$id};
2223
            }
2224
        }
2225
    }
2226
    tied(%packreg)->commit;# if (%packreg);
2227
    if ($issystem) {
2228
        my $sname = $register{$uuid}->{'name'};
2229
        utf8::decode($sname);
2230
        return "Status=OK Cleared packages for $sname\n";
2231
    } elsif ($register{$uuid}) {
2232
        my $sname = $register{$uuid}->{'name'};
2233
        utf8::decode($sname);
2234
        return "Status=OK Cleared packages for $sname\n";
2235
    } else {
2236
        return "Status=OK Cleared packages. System not registered\n";
2237
    }
2238
}
2239
2240
sub Save {
2241
    my ($uuid, $action, $obj) = @_;
2242
    if ($help) {
2243
        return <<END
2244 a93267ad hq
POST:uuid, name, user, system, autostart, locktonode, mac, memory, vcpu, boot, loader, diskbus, nicmodel1, vgpu, vmemory, cdrom, image, image2, image3, image4, networkuuid2, networkuuid3, networkuuid1, nicmac1, nicmac2, nicmac3:
2245 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.
2246
Depending on your privileges not all changes are permitted. If you save without specifying a uuid, a new server is created.
2247
If you pass [user] parameter it is assumed you want to move server to this user's account.
2248
Supported parameters:
2249
2250
uuid: UUID
2251
name: string
2252
user: string
2253 48fcda6b Origo
system: UUID of stack this server belongs to
2254 95b003ff Origo
autostart: true|false
2255
locktonode: true|false
2256
mac: MAC address of target node
2257
2258
memory: int bytes
2259 a93267ad hq
vmemory: int bytes
2260 95b003ff Origo
vcpu: int
2261
boot: hd|cdrom|network
2262 04c16f26 hq
loader: bios|uefi
2263 95b003ff Origo
diskbus: virtio|ide|scsi
2264
nicmodel1: virtio|rtl8139|ne2k_pci|e1000|i82551|i82557b|i82559er|pcnet
2265
vgpu: int
2266 a93267ad hq
vmemory: int bytes
2267 95b003ff Origo
2268
cdrom: string path
2269
image: string path
2270
image2: string path
2271
image3: string path
2272
image4: string path
2273
2274
networkuuid1: UUID of network connection
2275
networkuuid2: UUID of network connection
2276
networkuuid3: UUID of network connection
2277
2278
END
2279
    }
2280
2281 a93267ad hq
    # notes, opemail, opfullname, opphone, email, fullname, phone, services, recovery, alertemail
2282
    # notes: string
2283
    # opemail: string
2284
    # opfullname: string
2285
    # opphone: string
2286
    # email: string
2287
    # fullname: string
2288
    # phone: string
2289
    # services: string
2290
    # recovery: string
2291
    # alertemail: string
2292 95b003ff Origo
2293
    my $system = $obj->{system};
2294
    my $newsystem = $obj->{newsystem};
2295
    my $buildsystem = $obj->{buildsystem};
2296
    my $nicmac1 = $obj->{nicmac1};
2297
    $console = $console || $obj->{console};
2298
2299
    $postmsg = '' if ($buildsystem);
2300
    if (!$uuid && $nicmac1) {
2301
        $uuid = nicmac1ToUuid($nicmac1); # If no uuid try to locate based on mac
2302
    }
2303
    if (!$uuid && $uripath =~ /servers(\.cgi)?\/(.+)/) { # Try to parse uuid out of URI
2304
        my $huuid = $2;
2305
        if ($ug->to_string($ug->from_string($huuid)) eq $huuid) { # Check for valid uuid
2306
            $uuid = $huuid;
2307
        }
2308
    }
2309
    my $regserv = $register{$uuid};
2310
    my $status = $regserv->{'status'} || 'new';
2311
    if ((!$uuid) && $status eq 'new') {
2312
        my $ug = new Data::UUID;
2313
        $uuid = $ug->create_str();
2314
    };
2315
    unless ($uuid && length $uuid == 36){
2316 48fcda6b Origo
        $postmsg = "Status=Error No valid uuid ($uuid), $obj->{image}";
2317 95b003ff Origo
        return $postmsg;
2318
    }
2319
    $nicmac1 = $nicmac1 || $regserv->{'nicmac1'};
2320
    my $name = $obj->{name} || $regserv->{'name'};
2321
    my $memory = $obj->{memory} || $regserv->{'memory'};
2322
    my $vcpu = $obj->{vcpu} || $regserv->{'vcpu'};
2323 a93267ad hq
    my $vgpu = $regserv->{'vgpu'};
2324
    if (defined $obj->{vgpu}) {
2325
        $vgpu = $obj->{vgpu};
2326
    }
2327
    $vgpu = 0 if ($vgpu eq '--');
2328
    my $vmemory = $regserv->{'vmemory'};
2329
    if (defined $obj->{vmemory}) {
2330
        $vmemory = $obj->{vmemory};
2331
    }
2332
    $vmemory = 0 if ($vmemory eq '--');
2333
    $vmemory = 0 unless ($vgpu);
2334 95b003ff Origo
    my $image = $obj->{image} || $regserv->{'image'};
2335
    my $imagename = $obj->{imagename} || $regserv->{'imagename'};
2336
    my $image2 = $obj->{image2} || $regserv->{'image2'};
2337
    my $image2name = $obj->{image2name} || $regserv->{'image2name'};
2338
    my $image3 = $obj->{image3} || $regserv->{'image3'};
2339
    my $image3name = $obj->{image3name} || $regserv->{'image3name'};
2340
    my $image4 = $obj->{image4} || $regserv->{'image4'};
2341
    my $image4name = $obj->{image4name} || $regserv->{'image4name'};
2342
    my $diskbus = $obj->{diskbus} || $regserv->{'diskbus'};
2343
    my $cdrom = $obj->{cdrom} || $regserv->{'cdrom'};
2344
    my $boot = $obj->{boot} || $regserv->{'boot'};
2345 04c16f26 hq
    my $loader = $obj->{loader} || $regserv->{'loader'};
2346 95b003ff Origo
    my $networkuuid1 = ($obj->{networkuuid1} || $obj->{networkuuid1} eq '0')?$obj->{networkuuid1}:$regserv->{'networkuuid1'};
2347
    my $networkid1 = $obj->{networkid1} || $regserv->{'networkid1'};
2348
    my $networkname1 = $obj->{networkname1} || $regserv->{'networkname1'};
2349
    my $nicmodel1 = $obj->{nicmodel1} || $regserv->{'nicmodel1'};
2350
    my $networkuuid2 = ($obj->{networkuuid2} || $obj->{networkuuid2} eq '0')?$obj->{networkuuid2}:$regserv->{'networkuuid2'};
2351
    my $networkid2 = $obj->{networkid2} || $regserv->{'networkid2'};
2352
    my $networkname2 = $obj->{networkname2} || $regserv->{'networkname2'};
2353
    my $nicmac2 = $obj->{nicmac2} || $regserv->{'nicmac2'};
2354
    my $networkuuid3 = ($obj->{networkuuid3} || $obj->{networkuuid3} eq '0')?$obj->{networkuuid3}:$regserv->{'networkuuid3'};
2355
    my $networkid3 = $obj->{networkid3} || $regserv->{'networkid3'};
2356
    my $networkname3 = $obj->{networkname3} || $regserv->{'networkname3'};
2357
    my $nicmac3 = $obj->{nicmac3} || $regserv->{'nicmac3'};
2358
    my $notes = $obj->{notes} || $regserv->{'notes'};
2359
    my $autostart = $obj->{autostart} || $regserv->{'autostart'};
2360
    my $locktonode = $obj->{locktonode} || $regserv->{'locktonode'};
2361
    my $mac = $obj->{mac} || $regserv->{'mac'};
2362
    my $created = $regserv->{'created'} || time;
2363
    # Sanity checks
2364
    my $tenderpaths = $Stabile::config->get('STORAGE_POOLS_LOCAL_PATHS') || "/mnt/stabile/images";
2365
    my @tenderpathslist = split(/,\s*/, $tenderpaths);
2366
2367
    $networkid1 = $networkreg{$networkuuid1}->{'id'};
2368
    my $networktype1 = $networkreg{$networkuuid1}->{'type'};
2369
    my $networktype2;
2370
    if (!$nicmac1 || $nicmac1 eq "--") {$nicmac1 = randomMac();}
2371
    if ($networkuuid2 && $networkuuid2 ne "--") {
2372
        $networkid2 = $networkreg{$networkuuid2}->{'id'};
2373
        $nicmac2 = randomMac() if (!$nicmac2 || $nicmac2 eq "--");
2374
        $networktype2 = $networkreg{$networkuuid2}->{'type'};
2375
    }
2376
    if ($networkuuid3 && $networkuuid3 ne "--") {
2377
        $networkid3 = $networkreg{$networkuuid3}->{'id'};
2378
        $networkname3 = $networkreg{$networkuuid3}->{'name'};
2379
        $nicmac3 = randomMac() if (!$nicmac3 || $nicmac3 eq "--");
2380
        $networktype3 = $networkreg{$networkuuid3}->{'type'};
2381
    }
2382
2383
    my $imgdup;
2384
    my $netdup;
2385
    my $json_text; # returned if all goes well
2386
2387
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
2388
2389
    if ($networkid1 > 1 && $networkid2 > 1 && $networktype1 ne 'gateway' && $networktype2 ne 'gateway'
2390
        && $networkuuid1 eq $networkuuid2) {
2391
        $netdup = 1;
2392
    }
2393
    if ($networkid1 > 1 && $networkid3 > 1 && $networktype1 ne 'gateway' && $networktype3 ne 'gateway'
2394
        && $networkuuid1 eq $networkuuid3) {
2395
        $netdup = 11;
2396
    }
2397
    if ($image eq $image2
2398
        || $image eq $image3
2399
        || $image eq $image4
2400
        || $image2 && $image2 ne '--' && $image2 eq $image3
2401
        || $image2 && $image2 ne '--' && $image2 eq $image4
2402
        || $image3 && $image3 ne '--' && $image3 eq $image4
2403
    ) {
2404
        $imgdup = 1;
2405
    } elsif ($image =~ m/\.master\.qcow2/
2406
        || $image2 =~ m/\.master\.qcow2/
2407
        || $image3 =~ m/\.master\.qcow2/
2408
        || $image4 =~ m/\.master\.qcow2/
2409
    ) {
2410
        $imgdup = 2;
2411
    } else {
2412
        # Check if another server is using image
2413
        my @regkeys = (tied %register)->select_where("user = '$user' OR user = 'common'");
2414
        foreach my $k (@regkeys) {
2415
            my $val = $register{$k};
2416 a2e0bc7e hq
            if ($val->{'uuid'} ne $uuid) {
2417 95b003ff Origo
                if (
2418 a2e0bc7e hq
                    $image eq $val->{'image'} || $image eq $val->{'image2'}|| $image eq $val->{'image3'}|| $image eq $val->{'image4'}
2419 95b003ff Origo
                ) {
2420
                    $imgdup = 51;
2421
                } elsif ($image2 && $image2 ne "--" &&
2422 a2e0bc7e hq
                    ($image2 eq $val->{'image'} || $image2 eq $val->{'image2'} || $image2 eq $val->{'image3'} || $image2 eq $val->{'image4'})
2423 95b003ff Origo
                ) {
2424
                    $imgdup = 52;
2425
                } elsif ($image3 && $image3 ne "--" &&
2426 a2e0bc7e hq
                    ($image3 eq $val->{'image'} || $image3 eq $val->{'image2'} || $image3 eq $val->{'image3'} || $image3 eq $val->{'image4'})
2427 95b003ff Origo
                ) {
2428
                    $imgdup = 53;
2429
                } elsif ($image4 && $image4 ne "--" &&
2430 a2e0bc7e hq
                    ($image4 eq $val->{'image'} || $image4 eq $val->{'image2'} || $image4 eq $val->{'image3'} || $image4 eq $val->{'image4'})
2431 95b003ff Origo
                ) {
2432
                    $imgdup = 54;
2433
                }
2434
2435
                if ($networkid1>1) {
2436
                    if ($networktype1 ne 'gateway' &&
2437 a2e0bc7e hq
                        ($networkuuid1 eq $val->{'networkuuid1'} || $networkuuid1 eq $val->{'networkuuid2'})
2438 95b003ff Origo
                    ) {
2439
                        $netdup = 51;
2440
                    }
2441
                }
2442
                if ($networkid2>1) {
2443
                    if ($networktype2 ne 'gateway' && $networkuuid2 && $networkuuid2 ne "--" &&
2444 a2e0bc7e hq
                        ($networkuuid2 eq $val->{'networkuuid1'} || $networkuuid2 eq $val->{'networkuuid2'})
2445 95b003ff Origo
                    ) {
2446
                        $netdup = 52;
2447
                    }
2448
                }
2449
            }
2450
        }
2451
        my $legalpath;
2452
        if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2453
            $legalpath = 1;
2454
        } else {
2455
            foreach my $path (@tenderpathslist) {
2456
                if ($image =~ m/$path\/$user/) {
2457
                    $legalpath = 1;
2458
                    last;
2459
                }
2460
            }
2461
        }
2462
        $imgdup = 6 unless $legalpath;
2463
        if ($image2 && $image2 ne "--") { # TODO: We should probably check for conflicting nodes for image3 and image 4 too
2464
            if ($image2 =~ m/\/mnt\/stabile\/node\/$user/) {
2465
                if ($image =~ m/\/mnt\/stabile\/node\/$user/) {
2466
                    if ($imagereg{$image}->{'mac'} eq $imagereg{$image2}->{'mac'}) {
2467
                        $legalpath = 1;
2468
                    } else {
2469
                        $legalpath = 0; # Images are on two different nodes
2470
                    }
2471
                } else {
2472
                    $legalpath = 1;
2473
                }
2474
            } else {
2475
                $legalpath = 0;
2476
                foreach my $path (@tenderpathslist) {
2477
                    if ($image2 =~ m/$path\/$user/) {
2478
                        $legalpath = 1;
2479
                        last;
2480
                    }
2481
                }
2482
            }
2483
            $imgdup = 7 unless $legalpath;
2484
        }
2485
    }
2486
2487
    if (!$imgdup && !$netdup) {
2488
        if ($status eq "new") {
2489
            $status = "shutoff";
2490
            $name = $name || 'New Server';
2491
            $memory = $memory || 1024;
2492
            $vcpu = $vcpu || 1;
2493
            $imagename = $imagename || '--';
2494
            $image2 = $image2 || '--';
2495
            $image2name = $image2name || '--';
2496
            $image3 = $image3 || '--';
2497
            $image3name = $image3name || '--';
2498
            $image4 = $image4 || '--';
2499
            $image4name = $image4name || '--';
2500
            $diskbus = $diskbus || 'ide';
2501
            $cdrom = $cdrom || '--';
2502
            $boot = $boot || 'hd';
2503 04c16f26 hq
            $loader = $loader || 'bios';
2504 95b003ff Origo
            $networkuuid1 = $networkuuid1 || 1;
2505
            $networkid1 = $networkid1 || 1;
2506
            $networkname1 = $networkname1 || '--';
2507
            $nicmodel1 = $nicmodel1 || 'rtl8139';
2508
            $nicmac1 = $nicmac1 || randomMac();
2509
            $networkuuid2 = $networkuuid2 || '--';
2510
            $networkid2 = $networkid2 || '--';
2511
            $networkname2 = $networkname2 || '--';
2512
            $nicmac2 = $nicmac2 || randomMac();
2513
            $networkuuid3 = $networkuuid3 || '--';
2514
            $networkid3 = $networkid3 || '--';
2515
            $networkname3 = $networkname3 || '--';
2516
            $nicmac3 = $nicmac3 || randomMac();
2517
            #    $uiuuid = $uuid; # No need to update ui for new server with jsonreststore
2518 8d7785ff Origo
            $postmsg .= "OK Created new server: $name";
2519 3657de20 Origo
            $postmsg .= ", uuid: $uuid " if ($console);
2520 95b003ff Origo
        }
2521
        # Update status of images
2522
        my @imgs = ($image, $image2, $image3, $image4);
2523
        my @imgkeys = ('image', 'image2', 'image3', 'image4');
2524
        for (my $i=0; $i<4; $i++) {
2525
            my $img = $imgs[$i];
2526
            my $k = $imgkeys[$i];
2527
            my $regimg = $imagereg{$img};
2528
            # if ($img && $img ne '--' && ($status eq 'new' || $img ne $regserv->{$k})) { # Servers image changed - update image status
2529
            if ($img && $img ne '--') { # Always update image status
2530
                $regimg->{'status'} = 'used' if (
2531
                    $regimg->{'status'} eq 'unused'
2532
                        # Image cannot be active if server is shutoff
2533
                        || ($regimg->{'status'} eq 'active' && $status eq 'shutoff')
2534
                );
2535
                $regimg->{'domains'} = $uuid;
2536
                $regimg->{'domainnames'} = $name;
2537
            }
2538
            # If image has changed, release the old image
2539
            if ($status ne 'new' && $img ne $regserv->{$k} && $imagereg{$regserv->{$k}}) {
2540
                $imagereg{$regserv->{$k}}->{'status'} = 'unused';
2541
                delete $imagereg{$regserv->{$k}}->{'domains'};
2542
                delete $imagereg{$regserv->{$k}}->{'domainnames'};
2543
            }
2544
        }
2545
2546
        my $valref = {
2547
            uuid=>$uuid,
2548
            user=>$user,
2549
            name=>$name,
2550
            memory=>$memory,
2551
            vcpu=>$vcpu,
2552 a93267ad hq
            vmemory=>$vmemory,
2553
            vgpu=>$vgpu,
2554 95b003ff Origo
            image=>$image,
2555
            imagename=>$imagename,
2556
            image2=>$image2,
2557
            image2name=>$image2name,
2558
            image3=>$image3,
2559
            image3name=>$image3name,
2560
            image4=>$image4,
2561
            image4name=>$image4name,
2562
            diskbus=>$diskbus,
2563
            cdrom=>$cdrom,
2564
            boot=>$boot,
2565 04c16f26 hq
            loader=>$loader,
2566 95b003ff Origo
            networkuuid1=>$networkuuid1,
2567
            networkid1=>$networkid1,
2568
            networkname1=>$networkname1,
2569
            nicmodel1=>$nicmodel1,
2570
            nicmac1=>$nicmac1,
2571
            networkuuid2=>$networkuuid2,
2572
            networkid2=>$networkid2,
2573
            networkname2=>$networkname2,
2574
            nicmac2=>$nicmac2,
2575
            networkuuid3=>$networkuuid3,
2576
            networkid3=>$networkid3,
2577
            networkname3=>$networkname3,
2578
            nicmac3=>$nicmac3,
2579
            status=>$status,
2580
            notes=>$notes,
2581
            autostart=>$autostart,
2582
            locktonode=>$locktonode,
2583
            action=>"",
2584
            created=>$created
2585
        };
2586
        $valref->{'system'} = $system if ($system);
2587
        if ($mac && $locktonode eq 'true') {
2588
            $valref->{'mac'} = $mac;
2589
            $valref->{'macip'} = $nodereg{$mac}->{'ip'};
2590
            $valref->{'macname'} = $nodereg{$mac}->{'name'};
2591
        }
2592
        if ($newsystem) {
2593
            my $ug = new Data::UUID;
2594
            $sysuuid = $ug->create_str();
2595
            $valref->{'system'} = $sysuuid;
2596 3657de20 Origo
            $postmsg .= "OK sysuuid: $sysuuid " if ($console);
2597 95b003ff Origo
        }
2598
2599
        # Remove domain uuid from old networks. Leave gateways alone - they get updated on next listing
2600
        my $oldnetworkuuid1 = $regserv->{'networkuuid1'};
2601
        if ($oldnetworkuuid1 ne $networkuuid1 && $networkreg{$oldnetworkuuid1}) {
2602
            $networkreg{$oldnetworkuuid1}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2603
        }
2604 a93267ad hq
        $register{$uuid} = Stabile::Servers::validateItem($valref);
2605
        if (!$register{$uuid}) {
2606
            use Data::Dumper;
2607
            return "Status=ERROR Unable to save domain. " . Dumper($valref);
2608
        }
2609 95b003ff Origo
2610
        if ($networkreg{$networkuuid1}->{'type'} eq 'gateway') {
2611 04c16f26 hq
            # We now remove before adding to support API calls that dont necessarily list afterwards
2612
            $networkreg{$networkuuid1}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2613 95b003ff Origo
            my $domains = $networkreg{$networkuuid1}->{'domains'};
2614
            $networkreg{$networkuuid1}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2615 04c16f26 hq
2616
            $networkreg{$networkuuid1}->{'domainnames'} =~ s/($name)(,?)( ?)//;
2617 95b003ff Origo
            my $domainnames = $networkreg{$networkuuid1}->{'domainnames'};
2618
            $networkreg{$networkuuid1}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2619
        } else {
2620
            $networkreg{$networkuuid1}->{'domains'}  = $uuid;
2621
            $networkreg{$networkuuid1}->{'domainnames'}  = $name;
2622
        }
2623
2624
        if ($networkuuid2 && $networkuuid2 ne '--') {
2625
            if ($networkreg{$networkuuid2}->{'type'} eq 'gateway') {
2626 04c16f26 hq
                $networkreg{$networkuuid2}->{'domains'} =~ s/($uuid)(,?)( ?)//;
2627 95b003ff Origo
                my $domains = $networkreg{$networkuuid2}->{'domains'};
2628
                $networkreg{$networkuuid2}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2629 04c16f26 hq
2630
                $networkreg{$networkuuid2}->{'domainnames'} =~ s/($name)(,?)( ?)//;
2631 95b003ff Origo
                my $domainnames = $networkreg{$networkuuid2}->{'domainnames'};
2632
                $networkreg{$networkuuid2}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2633
            } else {
2634
                $networkreg{$networkuuid2}->{'domains'}  = $uuid;
2635
                $networkreg{$networkuuid2}->{'domainnames'}  = $name;
2636
            }
2637
        }
2638
2639
        if ($networkuuid3 && $networkuuid3 ne '--') {
2640
            if ($networkreg{$networkuuid3}->{'type'} eq 'gateway') {
2641
                my $domains = $networkreg{$networkuuid3}->{'domains'};
2642
                $networkreg{$networkuuid3}->{'domains'} = ($domains?"$domains, ":"") . $uuid;
2643
                my $domainnames = $networkreg{$networkuuid3}->{'domainnames'};
2644
                $networkreg{$networkuuid3}->{'domainnames'} = ($domainnames?"$domainnames, ":"") . $name;
2645
            } else {
2646
                $networkreg{$networkuuid3}->{'domains'}  = $uuid;
2647
                $networkreg{$networkuuid3}->{'domainnames'}  = $name;
2648
            }
2649
        }
2650
        my %jitem = %{$register{$uuid}};
2651
        $json_text = to_json(\%jitem, {pretty=>1});
2652
        $json_text =~ s/null/"--"/g;
2653
        $uiuuid = $uuid;
2654
        $uiname = $name;
2655
2656
        tied(%register)->commit;
2657
        tied(%networkreg)->commit;
2658 a2e0bc7e hq
        tied(%imagereg)->commit;
2659 95b003ff Origo
2660
    } else {
2661 48fcda6b Origo
        $postmsg .= "ERROR This image ($image) cannot be used ($imgdup) " if ($imgdup);
2662
        $postmsg .= "ERROR This network ($networkname1) cannot be used ($netdup)" if ($netdup);
2663 95b003ff Origo
    }
2664
2665
    my $domuser = $obj->{'user'};
2666
    # We were asked to move server to another account
2667
    if ($domuser && $domuser ne '--' && $domuser ne $user) {
2668
        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")};
2669
        if ($status eq 'shutoff' || $status eq 'inactive') {
2670
            unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {$posterror =  "Unable to access user register"; return 0;};
2671
            my @accounts = split(/,\s*/, $userreg{$tktuser}->{'accounts'});
2672
            my @accountsprivs = split(/,\s*/, $userreg{$tktuser}->{'accountsprivileges'});
2673
            %ahash = ($tktuser, $userreg{$tktuser}->{'privileges'}); # Include tktuser in accounts hash
2674
            for my $i (0 .. scalar @accounts)
2675
            {
2676
                next unless $accounts[$i];
2677
                $ahash{$accounts[$i]} = $accountsprivs[$i] || 'r';
2678
            }
2679
            untie %userreg;
2680
2681
            if (!$isreadonly && $ahash{$domuser} && !($ahash{$domuser} =~ /r/)) { # Check if user is allow to access account
2682
                my $imgdone;
2683
                my $netdone;
2684
                # First move main image
2685
                $Stabile::Images::user = $user;
2686
                require "$Stabile::basedir/cgi/images.cgi";
2687
                $Stabile::Images::console = 1;
2688
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving image $imagename to account: $domuser"});
2689
                my $nimage = Stabile::Images::Move($image, $domuser);
2690 48fcda6b Origo
                chomp $nimage;
2691 95b003ff Origo
                if ($nimage) {
2692
                    $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2693
                    $register{$uuid}->{'image'} = $nimage;
2694
                    $imgdone = 1;
2695
                } else {
2696
                    $main::syslogit->($user, "info", "Unable to move image $imagename to account: $domuser");
2697
                }
2698
                # Move other attached images
2699
                my @images = ($image2, $image3, $image4);
2700
                my @imagenames = ($image2name, $image3name, $image4name);
2701
                my @imagekeys = ('image2', 'image3', 'image4');
2702
                for (my $i=0; $i<3; $i++) {
2703
                    my $img = $images[$i];
2704
                    my $imgname = $imagenames[$i];
2705
                    my $imgkey = $imagekeys[$i];
2706
                    if ($img && $img ne '--') {
2707
                        $main::updateUI->({tab=>"servers", user=>$user, message=>"Moving $imgkey $imgname to account: $domuser"});
2708
                        $nimage = Stabile::Images::Move($img, $domuser);
2709 48fcda6b Origo
                        chomp $nimage;
2710 95b003ff Origo
                        if ($nimage) {
2711
                            $main::syslogit->($user, "info", "Moving $nimage to account: $domuser");
2712
                            $register{$uuid}->{$imgkey} = $nimage;
2713
                        } else {
2714
                            $main::syslogit->($user, "info", "Unable to move $imagekeys[$i] $img to account: $domuser");
2715
                        }
2716
                    }
2717
                }
2718 6fdc8676 hq
                # Then move network(s)
2719 95b003ff Origo
                if ($imgdone) {
2720
                    $Stabile::Networks::user = $user;
2721
                    require "$Stabile::basedir/cgi/networks.cgi";
2722
                    $Stabile::Networks::console = 1;
2723
                    my @networks = ($networkuuid1, $networkuuid2, $networkuuid3);
2724
                    my @netkeys = ('networkuuid1', 'networkuuid2', 'networkuuid3');
2725
                    my @netnamekeys = ('networkname1', 'networkname2', 'networkname3');
2726
                    for (my $i=0; $i<scalar @networks; $i++) {
2727
                        my $net = $networks[$i];
2728
                        my $netkey = $netkeys[$i];
2729
                        my $netnamekey = $netnamekeys[$i];
2730 48fcda6b Origo
                        my $regnet = $networkreg{$net};
2731
                        my $oldid = $regnet->{'id'};
2732 95b003ff Origo
                        next if ($net eq '' || $net eq '--');
2733 48fcda6b Origo
                        if ($regnet->{'type'} eq 'gateway') {
2734 95b003ff Origo
                            if ($oldid > 1) { # Private gateway
2735
                                foreach my $networkvalref (values %networkreg) { # use gateway with same id if it exists
2736
                                    if ($networkvalref->{'user'} eq $domuser
2737
                                        && $networkvalref->{'type'} eq 'gateway'
2738
                                        && $networkvalref->{'id'} == $oldid) {
2739
                                        # We found an existing gateway with same id - use it
2740
                                        $register{$uuid}->{$netkey} = $networkvalref->{'uuid'};
2741
                                        $register{$uuid}->{$netnamekey} = $networkvalref->{'name'};
2742
                                        $netdone = 1;
2743
                                        $main::updateUI->({tab=>"networks", user=>$user, message=>"Using network $networkvalref->{'name'} from account: $domuser"});
2744
                                        last;
2745
                                    }
2746
                                }
2747
                                if (!($netdone)) {
2748
                                    # Make a new gateway
2749
                                    my $ug = new Data::UUID;
2750
                                    my $newuuid = $ug->create_str();
2751 48fcda6b Origo
                                    Stabile::Networks::save($oldid, $newuuid, $regnet->{'name'}, 'new', 'gateway', '', '', $regnet->{'ports'}, 0, $domuser);
2752 95b003ff Origo
                                    $register{$uuid}->{$netkey} = $newuuid;
2753 48fcda6b Origo
                                    $register{$uuid}->{$netnamekey} = $regnet->{'name'};
2754 95b003ff Origo
                                    $netdone = 1;
2755 48fcda6b Origo
                                    $main::updateUI->({tab=>"networks", user=>$user, message=>"Created gateway $regnet->{'name'} for account: $domuser"});
2756
                                    $main::syslogit->($user, "info", "Created gateway $regnet->{'name'} for account: $domuser");
2757 95b003ff Origo
                                }
2758
                            } elsif ($oldid==0 || $oldid==1) {
2759
                                $netdone = 1; # Use common gateway
2760 48fcda6b Origo
                                $main::updateUI->({tab=>"networks", user=>$user, message=>"Reused network $regnet->{'name'} for account: $domuser"});
2761 95b003ff Origo
                            }
2762
                        } else {
2763
                            my $newid = Stabile::Networks::getNextId('', $domuser);
2764
                            $networkreg{$net}->{'id'} = $newid;
2765
                            $networkreg{$net}->{'user'} = $domuser;
2766 a93267ad hq
                            #    if ($regnet->{'type'} eq 'internalip' || $regnet->{'type'} eq 'ipmapping') {
2767
                            # Deactivate network and assign new internal ip
2768
                            Stabile::Networks::Deactivate($regnet->{'uuid'});
2769
                            $networkreg{$net}->{'internalip'} =
2770
                                Stabile::Networks::getNextInternalIP('',$regnet->{'uuid'}, $newid, $domuser);
2771
                            #    }
2772 95b003ff Origo
                            $netdone = 1;
2773 48fcda6b Origo
                            $main::updateUI->({tab=>"networks", user=>$user, message=>"Moved network $regnet->{'name'} to account: $domuser"});
2774
                            $main::syslogit->($user, "info", "Moved network $regnet->{'name'} to account: $domuser");
2775 95b003ff Origo
                        }
2776
                    }
2777
                    if ($netdone) {
2778
                        # Finally move the server
2779
                        $register{$uuid}->{'user'} = $domuser;
2780 48fcda6b Origo
                        $postmsg .= "OK Moved server $name to account: $domuser";
2781 95b003ff Origo
                        $main::syslogit->($user, "info", "Moved server $name ($uuid) to account: $domuser");
2782 48fcda6b Origo
                        $main::updateUI->({tab=>"servers", user=>$user, type=>"update"});
2783 51e32e00 hq
                        # Remove the server's IP from pressurecontrol's cache
2784
                        # Repeat 8 times because pressurecontrol runs 8 http daemons
2785
                        for (my $i = 0; $i < 8; $i++) {
2786
                            my $nuuid = $register{$uuid}->{networkuuid1};
2787
                            `curl "http://localhost:8082//http://$nuuid/networkreload"`;
2788
                        }
2789 95b003ff Origo
                    } else {
2790 48fcda6b Origo
                        $postmsg .= "ERROR Unable to move network to account: $domuser";
2791 95b003ff Origo
                        $main::updateUI->({tab=>"image", user=>$user, message=>"Unable to move network to account: $domuser"});
2792
                    }
2793
                } else {
2794
                    $main::updateUI->({tab=>"image", user=>$user, message=>"Could not move image to account: $domuser"});
2795
                }
2796
            } else {
2797 48fcda6b Origo
                $postmsg .= "ERROR No access to move server";
2798 95b003ff Origo
            }
2799
        } else {
2800 48fcda6b Origo
            $postmsg .= "Error Unable to move $status server";
2801 95b003ff Origo
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Please shut down before moving server"});
2802
        }
2803
        untie %userreg;
2804
    }
2805
2806
    if ($console) {
2807
        $postreply = $postmsg;
2808
    } else {
2809
        $postreply = $json_text || $postmsg;
2810
    }
2811
    return $postreply;
2812
}
2813
2814
2815
sub Shutdown {
2816
    my ($uuid, $action, $obj) = @_;
2817
    if ($help) {
2818
        return <<END
2819
GET:uuid:
2820
Marks a server for shutdown, i.e. send and ACPI shutdown event to the server. If OS supports ACPI, it begins a shutdown.
2821
END
2822
    }
2823
    $uistatus = "shuttingdown";
2824
    my $dbstatus = $obj->{status};
2825
    my $mac = $obj->{mac};
2826
    my $macname = $obj->{macname};
2827
    my $name = $obj->{name};
2828
    if ($dbstatus eq 'running') {
2829
        my $tasks;
2830
        $tasks = $nodereg{$mac}->{'tasks'} if ($nodereg{$mac});
2831
        $nodereg{$mac}->{'tasks'} = $tasks . "SHUTDOWN $uuid $user\n";
2832
        tied(%nodereg)->commit;
2833
        $register{$uuid}->{'status'} = $uistatus;
2834
        $register{$uuid}->{'statustime'} = $current_time;
2835
        $uiuuid = $uuid;
2836
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2837
        $postreply .= "Status=$uistatus OK $uistatus $name\n";
2838
    } else {
2839
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2840
        $postreply .= "Status=ERROR problem $uistatus $name...\n";
2841
    }
2842
    return $postreply;
2843
}
2844
2845
sub Suspend {
2846
    my ($uuid, $action, $obj) = @_;
2847
    if ($help) {
2848
        return <<END
2849
GET:uuid:
2850
Marks a server for suspend, i.e. pauses the server. Server must be running
2851
END
2852
    }
2853
    $uistatus = "suspending";
2854
    my $dbstatus = $obj->{status};
2855
    my $mac = $obj->{mac};
2856
    my $macname = $obj->{macname};
2857
    my $name = $obj->{name};
2858 a2e0bc7e hq
    my $areply = '';
2859 95b003ff Origo
    if ($dbstatus eq 'running') {
2860
        my $tasks = $nodereg{$mac}->{'tasks'};
2861
        $nodereg{$mac}->{'tasks'} = $tasks . "SUSPEND $uuid $user\n";
2862
        tied(%nodereg)->commit;
2863
        $register{$uuid}->{'status'} = $uistatus;
2864
        $register{$uuid}->{'statustime'} = $current_time;
2865
        $uiuuid = $uuid;
2866
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2867 a2e0bc7e hq
        $areply .= "Status=$uistatus OK $uistatus $name.\n";
2868 95b003ff Origo
    } else {
2869
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2870 a2e0bc7e hq
        $areply .= "Status=ERROR problem $uistatus $name.\n";
2871 95b003ff Origo
    }
2872 a2e0bc7e hq
    return $areply;
2873 95b003ff Origo
}
2874
2875
sub Resume {
2876
    my ($uuid, $action, $obj) = @_;
2877
    if ($help) {
2878
        return <<END
2879
GET:uuid:
2880
Marks a server for resume running. Server must be paused.
2881
END
2882
    }
2883
    my $dbstatus = $obj->{status};
2884
    my $mac = $obj->{mac};
2885
    my $macname = $obj->{macname};
2886
    my $name = $obj->{name};
2887
    my $image = $obj->{image};
2888
    my $image2 = $obj->{image2};
2889
    my $image3 = $obj->{image3};
2890
    my $image4 = $obj->{image4};
2891
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {$posterror = "Unable to access image register"; return;};
2892
    if ($imagereg{$image}->{'status'} ne "paused"
2893
        || ($image2 && $image2 ne '--' && $imagereg{$image}->{'status'} ne "paused")
2894
        || ($image3 && $image3 ne '--' && $imagereg{$image3}->{'status'} ne "paused")
2895
        || ($image4 && $image4 ne '--' && $imagereg{$image4}->{'status'} ne "paused")
2896
    ) {
2897
        $postreply .= "Status=ERROR Image $uuid busy ($imagereg{$image}->{'status'}), please wait 30 sec.\n";
2898
        untie %imagereg;
2899
        return $postreply   ;
2900
    } else {
2901
        untie %imagereg;
2902
    }
2903
    $uistatus = "resuming";
2904
    if ($dbstatus eq 'paused') {
2905
        my $tasks = $nodereg{$mac}->{'tasks'};
2906
        $nodereg{$mac}->{'tasks'} = $tasks . "RESUME $uuid $user\n";
2907
        tied(%nodereg)->commit;
2908
        $register{$uuid}->{'status'} = $uistatus;
2909
        $register{$uuid}->{'statustime'} = $current_time;
2910
        $uiuuid = $uuid;
2911
        $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus by $macname ($mac)");
2912
        $postreply .= "Status=$uistatus OK $uistatus ". $register{$uuid}->{'name'} . "\n";
2913
    } else {
2914
        $main::syslogit->($user, "info", "Problem $uistatus a $dbstatus domain: $uuid");
2915
        $postreply .= "Status=ERROR problem $uistatus ". $register{$uuid}->{'name'} . "\n";
2916
    }
2917
    return $postreply;
2918
}
2919
2920 d3805c61 hq
sub Abort {
2921
    my ($uuid, $action, $obj) = @_;
2922
    if ($help) {
2923
        return <<END
2924
GET:uuid,mac:
2925
Aborts an ongoing server move between nodes initiated with move or stormove.
2926
END
2927
    }
2928
    my $dbstatus = $obj->{status};
2929
    my $dmac = $obj->{mac};
2930
    my $name = $obj->{name};
2931
    if ($isadmin || $register{$uuid}->{user} eq $user) {
2932
        my $tasks = $nodereg{$dmac}->{'tasks'};
2933
        $tasks .= "ABORT $uuid $user\n";
2934
        $nodereg{$dmac}->{'tasks'} = $tasks;
2935
        tied(%nodereg)->commit;
2936
        $postreply = "Status=aborting Aborting move of server $name ($dbstatus) on node $dmac\n";
2937
    } else {
2938
        $postreply = "Status=OK Insufficient privileges\n";
2939
    }
2940
}
2941
2942 95b003ff Origo
sub Move {
2943
    my ($uuid, $action, $obj) = @_;
2944
    if ($help) {
2945
        return <<END
2946
GET:uuid,mac:
2947 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.
2948 95b003ff Origo
END
2949
    }
2950
    my $dbstatus = $obj->{status};
2951
    my $dmac = $obj->{mac};
2952
    my $name = $obj->{name};
2953
    my $mem = $obj->{memory};
2954 a93267ad hq
    my $vmem = $obj->{vmemory};
2955 95b003ff Origo
    my $vcpu = $obj->{vcpu};
2956 a93267ad hq
    my $vgpu = $obj->{vgpu};
2957 95b003ff Origo
    my $image = $obj->{image};
2958
    my $image2 = $obj->{image2};
2959
    my $image3 = $obj->{image3};
2960
    my $image4 = $obj->{image4};
2961 d3805c61 hq
2962 95b003ff Origo
    $uistatus = "moving";
2963
    if ($dbstatus eq 'running' && $isadmin) {
2964
        my $hypervisor = getHypervisor($image);
2965
        my $mac = $register{$uuid}->{'mac'};
2966
        $dmac = "" if ($dmac eq "--");
2967
        $mac = "" if ($mac eq "--");
2968
2969 d3805c61 hq
        if (( $image =~ /\/mnt\/stabile\/node\//
2970 95b003ff Origo
            || $image2 =~ /\/mnt\/stabile\/node\//
2971
            || $image3 =~ /\/mnt\/stabile\/node\//
2972 d3805c61 hq
            || $image4 =~ /\/mnt\/stabile\/node\// ) && $action ne 'stormove'
2973 95b003ff Origo
        ) {
2974 d3805c61 hq
            $postreply = qq|{"error": 1, "message": "Servers with local storage must be moved with stormove"}|;
2975
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Servers with local storage must be moved with stormove"});
2976 a93267ad hq
        } elsif ($vgpu && $vgpu ne '--') {
2977
            $postreply = qq|{"error": 1, "message": "Servers with GPUs attached cannot be moved"}|;
2978
            $main::updateUI->({tab=>"servers", user=>$user, message=>"Servers with GPUs attached cannot be moved"});
2979 95b003ff Origo
        } else {
2980
            my ($targetmac, $targetname, $targetip, $port) =
2981 a93267ad hq
                locateTargetNode($uuid, $dmac, $mem, $vcpu, $vgpu, $vmem, $image, $image2, $image3, $image4, $hypervisor, $mac, 1);
2982
            if ($vgpu) {
2983
                $main::syslogit->($user, "info", "Live migration of $uistatus $register{$uuid}->{'name'} with attached GPU is not supported");
2984
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Live migration of $uistatus $register{$uuid}->{'name'} with attached GPU is not supported"});
2985
                $postreply = qq|{"error": 1, "message": "Live migration of VMs with attached GPU is not supported"}|;
2986
            } elsif ($targetmac) {
2987 95b003ff Origo
                my $tasks = $nodereg{$targetmac}->{'tasks'};
2988 d3805c61 hq
                if ($action eq 'stormove') {
2989
                    $tasks = $tasks . "RECEIVESTOR $uuid $user\n";
2990
                } else {
2991
                    $tasks = $tasks . "RECEIVE $uuid $user\n";
2992
                }
2993 95b003ff Origo
                # Also update allowed port forwards
2994
                $nodereg{$targetmac}->{'tasks'} = $tasks . "PERMITOPEN $user\n";
2995
                $register{$uuid}->{'status'} = "moving";
2996
                $register{$uuid}->{'statustime'} = $current_time;
2997
                $uiuuid = $uuid;
2998
                $uidisplayip = $targetip;
2999
                $uidisplayport = $port;
3000
                $main::syslogit->($user, "info", "Marked $name ($uuid) for $uistatus to $targetname ($targetmac)");
3001
                $postreply .= "Status=OK $uistatus ". $register{$uuid}->{'name'} . "\n";
3002
3003 d3805c61 hq
                # Precreate images on destination node
3004
                if ($action eq 'stormove') {
3005
                    my $preimages = '';
3006
                    $Stabile::Images::user = $user;
3007
                    require "$Stabile::basedir/cgi/images.cgi";
3008
                    $Stabile::Images::console = 1;
3009
                    if ($targetip eq '10.0.0.1') { # Moving from node
3010
                        if ($image =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
3011
                            my $res = Stabile::Images::Move($image, $user, '0', '', 0, 1);
3012
                            $preimages .= " $register{$uuid}->{imagename}";
3013
                        }
3014
                        if ($image2 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
3015
                            my $res = Stabile::Images::Move($image2, $user, '0', '', 0, 1);
3016
                            $preimages .= " $register{$uuid}->{image2name}";
3017
                        }
3018
                        if ($image3 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
3019
                            my $res = Stabile::Images::Move($image3, $user, '0', '', 0, 1);
3020
                            $preimages .= " $register{$uuid}->{image3name}";
3021
                        }
3022
                        if ($image4 =~ /\/mnt\/stabile\/node\//) { # Only move to shared storage if not already on shared storage
3023
                            my $res = Stabile::Images::Move($image4, $user, '0', '', 0, 1);
3024
                            $preimages .= " $register{$uuid}->{image4name}";
3025
                        }
3026
                    } else { # Moving to node or between nodes - always move primary image, also if on shared storage
3027
                        my $res = Stabile::Images::Move($image, $user, '-1', $targetmac, 0, 1);
3028
                        $preimages .= " $register{$uuid}->{imagename}";
3029
                        if ($image2 && $image2 ne '--') {
3030
                            # We don't migrate data disks away from shared storage
3031
                            unless ($image2 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
3032
                                my $res = Stabile::Images::Move($image2, $user, '-1', $targetmac, 0, 1);
3033
                                $preimages .= " $register{$uuid}->{image2name}";
3034
                            }
3035
                        }
3036
                        if ($image3 && $image3 ne '--') {
3037
                            unless ($image3 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
3038
                                my $res = Stabile::Images::Move($image3, $user, '-1', $targetmac, 0, 1);
3039
                                $preimages .= " $register{$uuid}->{image3name}";
3040
                            }
3041
                        }
3042
                        if ($image4 && $image4 ne '--') {
3043
                            unless ($image4 =~ /\/stabile-images\/images\/.*-data\..*\.qcow2/) {
3044
                                my $res = Stabile::Images::Move($image4, $user, '-1', $targetmac, 0, 1);
3045
                                $preimages .= " $register{$uuid}->{image4name}";
3046
                            }
3047
                        }
3048
                    }
3049
                    if ($preimages) {
3050
                        $main::syslogit->($user, "info", "Precreating images $preimages on node $targetmac");
3051
                        $main::updateUI->({tab=>"servers", user=>$user, message=>"Precreating images $preimages on node $targetmac"});
3052
                    }
3053
                }
3054 95b003ff Origo
                if ($params{'PUTDATA'}) {
3055
                    my %jitem = %{$register{$uuid}};
3056
                    my $json_text = to_json(\%jitem);
3057
                    $json_text =~ s/null/"--"/g;
3058
                    $postreply = $json_text;
3059
                }
3060 a93267ad hq
                #                $main::updateUI->({tab=>"servers", user=>$user, status=>'moving', uuid=>$uuid, type=>'update', message=>"Moving $register{$uuid}->{name} to $targetmac"});
3061 95b003ff Origo
            } else {
3062
                $main::syslogit->($user, "info", "Could not find $hypervisor target for $uistatus $uuid ($image)");
3063 d3805c61 hq
                $main::updateUI->({tab=>"servers", user=>$user, message=>"Could not find target for $uistatus $register{$uuid}->{'name'}"});
3064 95b003ff Origo
                $postreply = qq|{"error": 1, "message": "Could not find target for $uistatus $register{$uuid}->{'name'}"}|;
3065
            }
3066
        }
3067
    } else {
3068
        $main::syslogit->($user, "info", "Problem moving a $dbstatus domain: $uuid");
3069 d3805c61 hq
        my $serv = $register{$uuid};
3070
        $postreply .= qq|{"error": 1, "message": "ERROR problem moving $serv->{'name'} ($dbstatus)"}|;
3071 95b003ff Origo
    }
3072
    return $postreply;
3073
}
3074
3075 c899e439 Origo
sub Changepassword {
3076
    my ($uuid, $action, $obj) = @_;
3077
    if ($help) {
3078
        return <<END
3079
POST:uuid,username,password:
3080
Attempts to set password for [username] to [password] using guestfish. If no username is specified, user 'stabile' is assumed.
3081
END
3082
    }
3083
    my $img = $register{$uuid}->{'image'};
3084
    my $username = $obj->{'username'} || 'stabile';
3085
    my $password = $obj->{'password'};
3086
    return "Status=Error Please supply a password\n" unless ($password);
3087
    return "Status=Error Please shut down the server before changing password\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
3088
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
3089
3090
    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;};
3091
    my $cmd = qq/guestfish --rw -a $img -i command "bash -c 'echo $username:$password | chpasswd'" 2>\&1/;
3092
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
3093
        my $mac = $imagereg{$img}->{'mac'};
3094
        my $macip = $nodereg{$mac}->{'ip'};
3095
        $cmd = "$sshcmd $macip $cmd";
3096
    }
3097
    my $res = `$cmd`;
3098
    $res = $1 if ($res =~ /guestfish: (.*)/);
3099
    chomp $res;
3100
    return "Status=OK Ran chpasswd for user $username in server $register{$uuid}->{'name'}: $res\n";
3101
}
3102
3103
sub Sshaccess {
3104
    my ($uuid, $action, $obj) = @_;
3105
    if ($help) {
3106
        return <<END
3107
POST:uuid,address:
3108
Attempts to change the ip addresses you can access the server over SSH (port 22) from, by adding [address] to /etc/hosts.allow.
3109
[address] should either be an IP address or a range in CIDR notation. Please note that no validation of [address] is performed.
3110
END
3111
    }
3112
    my $img = $register{$uuid}->{'image'};
3113
    my $address = $obj->{'address'};
3114
    return "Status=Error Please supply an aaddress\n" unless ($address);
3115
    return "Status=Error Please shut down the server before changing SSH access\n" unless ($register{$uuid} && $register{$uuid}->{'status'} eq 'shutoff');
3116
    return "Status=Error Not allowed\n" unless ($isadmin || $register{$uuid}->{'user'} eq $user);
3117
3118
    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;};
3119
3120
    my $isshcmd = '';
3121
    my $cmd = qq[guestfish --rw -a $img -i command "sed -i -re 's|(sshd: .*)#stabile|\\1 $address #stabile|' /etc/hosts.allow"];
3122 a93267ad hq
    #    my $cmd = qq[guestfish --rw -a $img -i command "bash -c 'echo sshd: $address >> /etc/hosts.allow'"];
3123 c899e439 Origo
    if ($imagereg{$img} && $imagereg{$img}->{'mac'}) {
3124
        my $mac = $imagereg{$img}->{'mac'};
3125
        my $macip = $nodereg{$mac}->{'ip'};
3126
        $isshcmd = "$sshcmd $macip ";
3127
    }
3128
    my $res = `$isshcmd$cmd`;
3129
    chomp $res;
3130
    #$cmd = qq[guestfish --rw -a $img -i command "bash -c 'cat /etc/hosts.allow'"];
3131
    #$res .= `$isshcmd$cmd`;
3132
    #chomp $res;
3133
    return "Status=OK Tried to add sshd: $address to /etc/hosts.allow in server $register{$uuid}->{'name'}\n";
3134
}
3135
3136 95b003ff Origo
sub Mountcd {
3137
    my ($uuid, $action, $obj) = @_;
3138
    if ($help) {
3139
        return <<END
3140
GET:uuid,cdrom:
3141
Mounts a cdrom on a server. Server must be running. Mounting the special cdrom named '--' unomunts any currently mounted cdrom.
3142
END
3143
    }
3144
    my $dbstatus = $obj->{status};
3145
    my $mac = $obj->{mac};
3146
    my $cdrom = $obj->{cdrom};
3147
    unless ($cdrom && $dbstatus eq 'running') {
3148 a93267ad hq
        $main::updateUI->({ tab => "servers", user => $user, uuid => $uuid, type => 'update', message => "Unable to mount cdrom" });
3149 95b003ff Origo
        $postreply = qq|{"Error": 1, "message": "Problem mounting cdrom on $obj->{name}"}|;
3150
        return;
3151
    }
3152
    my $tasks = $nodereg{$mac}->{'tasks'};
3153
    # $user is in the middle here, because $cdrom may contain spaces...
3154
    $nodereg{$mac}->{'tasks'} = $tasks . "MOUNT $uuid $user \"$cdrom\"\n";
3155
    tied(%nodereg)->commit;
3156
    if ($cdrom eq "--") {
3157
        $postreply = qq|{"OK": 1, "message": "OK unmounting cdrom from $obj->{name}"}|;
3158 a93267ad hq
    }
3159
    else {
3160 95b003ff Origo
        $postreply = qq|{"OK": 1, "message": "OK mounting cdrom $cdrom on $obj->{name}"}|;
3161
    }
3162
    $register{$uuid}->{'cdrom'} = $cdrom unless ($cdrom eq 'virtio');
3163
    return $postreply;
3164 a93267ad hq
}