Project

General

Profile

Download (260 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::Images;
9
10
use Error qw(:try);
11
use File::Basename;
12
use Data::UUID;
13
use Proc::Daemon;
14
use Time::Local;
15
#use Time::HiRes qw( time );
16
use Date::Format;
17
use Date::Parse;
18
use Getopt::Std;
19
#use Encode::Escape;
20 2a63870a Christian Orellana
use String::Escape;
21 95b003ff Origo
use File::Glob qw(bsd_glob);
22
use Sys::Guestfs;
23
use Data::Dumper;
24
use XML::Simple;
25
#use POSIX qw(strftime);
26
use Time::Piece;
27
use Config::Simple;
28
use lib dirname (__FILE__); # Allows us to source libraries from current directory no matter where we are called from
29
use Stabile;
30
31
$\ = ''; # Some of the above seems to set this to \n, resulting in every print appending a line feed
32
33
# Read in some settings from config
34
$backupdir = $Stabile::config->get('STORAGE_BACKUPDIR') || "/mnt/stabile/backups";
35
$backupdir = $1 if ($backupdir =~ /(.+)/); #untaint
36
my $tenders = $Stabile::config->get('STORAGE_POOLS_ADDRESS_PATHS');
37
my @tenderlist = split(/,\s*/, $tenders);
38
my $tenderpaths = $Stabile::config->get('STORAGE_POOLS_LOCAL_PATHS') || "/mnt/stabile/images";
39
my @tenderpathslist = split(/,\s*/, $tenderpaths);
40
my $tendernames = $Stabile::config->get('STORAGE_POOLS_NAMES') || "Standard storage";
41
my @tendernameslist = split(/,\s*/, $tendernames);
42
my $mountabletenders = $Stabile::config->get('STORAGE_POOLS_MOUNTABLE');
43
my @mountabletenderslist = split(/,\s*/, $mountabletenders);
44
my $storagepools = $Stabile::config->get('STORAGE_POOLS_DEFAULTS') || "0";
45
my $spoolsrdiffenabled = $Stabile::config->get('STORAGE_POOLS_RDIFF-BACKUP_ENABLED') || "0";
46
my @rdiffenabledlist = split(/,\s*/, $spoolsrdiffenabled);
47
my $rdiffenabled = $Stabile::config->get('RDIFF-BACKUP_ENABLED') || "0";
48
my $userrdiffenabled = $Stabile::config->get('RDIFF-BACKUP_USERS') || "0";
49
my $nodestorageovercommission = $Stabile::config->get('NODE_STORAGE_OVERCOMMISSION') || "1";
50
my $engineid = $Stabile::config->get('ENGINEID') || "";
51 27512919 Origo
52
my $valve_readlimit = $Stabile::config->get('VALVE_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
53
my $valve_writelimit = $Stabile::config->get('VALVE_WRITE_LIMIT');
54
my $valve_iopsreadlimit = $Stabile::config->get('VALVE_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
55
my $valve_iopswritelimit = $Stabile::config->get('VALVE_IOPS_WRITE_LIMIT');
56
57 95b003ff Origo
my $valve001id = '995e86b7-ae85-4ae0-9800-320c1f59ae33';
58
my $stackspool = '/mnt/stabile/images001';
59
60
our %ahash; # A hash of accounts and associated privileges current user has access to
61
#our %options=();
62
# -a action -h help -f full list -p full update -u uuid -i image -m match pattern -k keywords -g args to gearman task
63
# -v verbose, include HTTP headers -s impersonate subaccount -t target [uuid or image]
64
#Getopt::Std::getopts("a:hfpu:i:g:m:k:vs:t:", \%options);
65
66
try {
67
    Init(); # Perform various initalization tasks
68
    process() if ($package); # Parse and process request. $package is not set if called as a library
69
70
} catch Error with {
71
    my $ex = shift;
72
    print header('text/html', '500 Internal Server Error') unless ($console);
73
    if ($ex->{-text}) {
74
        print "Got error: ", $ex->{-text}, " on line ", $ex->{-line}, "\n";
75
    } else {
76
        print "Status=ERROR\n";
77
    }
78
} finally {
79
};
80
81
1;
82
83
sub Init {
84
85
    # Tie database tables to hashes
86
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access user register"};
87
    unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'images', key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access image register"};
88
    unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access network register"};
89
    unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Unable to access image uuid register"};
90
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
91
92
    # simplify globals initialized in Stabile.pm
93
    $tktuser = $tktuser || $Stabile::tktuser;
94
    $user = $user || $Stabile::user;
95 991e7f1b hq
    $isadmin = $isadmin || $Stabile::isadmin;
96 95b003ff Origo
    $sshcmd = $sshcmd || $Stabile::sshcmd;
97
    $disablesnat = $disablesnat || $Stabile::disablesnat;
98
99
    # Create aliases of functions
100
    *header = \&CGI::header;
101
102
    *Getimagesdevice = \&Liststoragedevices;
103
    *Getbackupdevice = \&Liststoragedevices;
104
    *Listimagesdevices = \&Liststoragedevices;
105
    *Listbackupdevices = \&Liststoragedevices;
106 3657de20 Origo
    *Rebase = \&Unmaster;
107 95b003ff Origo
108
    *do_save = \&privileged_action_async;
109
    *do_sync_save = \&privileged_action;
110 2a63870a Christian Orellana
    *do_sync_backup = \&privileged_action;
111 95b003ff Origo
    *do_sync_clone = \&privileged_action;
112
    *do_updateregister = \&action;
113
    *do_fullupdateregister = \&action;
114
    *do_tablelistall = \&do_list;
115
    *do_tablelist = \&do_list;
116
    *Sync_save = \&Save;
117 2a63870a Christian Orellana
    *Sync_backup = \&Backup;
118 95b003ff Origo
    *Sync_clone = \&Clone;
119
    *do_help = \&action;
120
121
    *do_mount = \&privileged_action;
122
    *do_unmount = \&privileged_action;
123 2a63870a Christian Orellana
    *do_convert = \&privileged_action;
124 95b003ff Origo
    *do_activate = \&privileged_action;
125
    *do_publish = \&privileged_action;
126 2a63870a Christian Orellana
    *do_uploadtoregistry = \&privileged_action;
127 48fcda6b Origo
    *do_release = \&privileged_action;
128 95b003ff Origo
    *do_download = \&privileged_action;
129
    *do_linkmaster = \&privileged_action;
130
    *do_listbackups = \&privileged_action;
131
    *do_listcdroms = \&action;
132
    *do_listfiles = \&privileged_action;
133
    *do_getserverbackups = \&privileged_action;
134
    *do_listserverbackups = \&privileged_action;
135
    *Listserverbackups = \&Getserverbackups;
136
    *do_restorefiles = \&privileged_action;
137
    *do_remove = \&privileged_action;
138
    *do_removeuserimages = \&privileged_action;
139
    *do_updatedownloads = \&privileged_action;
140
    *do_master = \&privileged_action_async;
141
    *do_unmaster = \&privileged_action_async;
142 3657de20 Origo
    *do_rebase = \&privileged_action_async;
143 95b003ff Origo
    *do_clone = \&privileged_action_async;
144
    *do_snapshot = \&privileged_action_async;
145
    *do_unsnap = \&privileged_action_async;
146
    *do_revert = \&privileged_action_async;
147
    *do_inject = \&privileged_action_async;
148
    *do_backup = \&privileged_action_async;
149
    *do_zbackup = \&privileged_action;
150
    *do_restore = \&privileged_action_async;
151
    *do_updatebackingfile = \&privileged_action;
152
    *do_updatebtime = \&privileged_action;
153
    *do_updateallbtimes = \&privileged_action;
154
    *do_initializestorage = \&privileged_action;
155
    *do_liststoragedevices = \&privileged_action;
156
    *do_listimagesdevices = \&privileged_action;
157
    *do_listbackupdevices = \&privileged_action;
158
    *do_getimagesdevice = \&privileged_action;
159
    *do_getbackupdevice = \&privileged_action;
160
    *do_setstoragedevice = \&privileged_action;
161 2a63870a Christian Orellana
    *do_backupfuel = \&privileged_action;
162 95b003ff Origo
163
    *do_gear_save = \&do_gear_action;
164
    *do_gear_sync_save = \&do_gear_action;
165 2a63870a Christian Orellana
    *do_gear_sync_backup = \&do_gear_action;
166 95b003ff Origo
    *do_gear_sync_clone = \&do_gear_action;
167
    *do_gear_mount = \&do_gear_action;
168
    *do_gear_unmount = \&do_gear_action;
169 2a63870a Christian Orellana
    *do_gear_convert = \&do_gear_action;
170 95b003ff Origo
    *do_gear_activate = \&do_gear_action;
171
    *do_gear_publish = \&do_gear_action;
172 2a63870a Christian Orellana
    *do_gear_uploadtoregistry = \&do_gear_action;
173 48fcda6b Origo
    *do_gear_release = \&do_gear_action;
174 95b003ff Origo
    *do_gear_download = \&do_gear_action;
175
    *do_gear_linkmaster = \&do_gear_action;
176
    *do_gear_listbackups = \&do_gear_action;
177
    *do_gear_listserverbackups = \&do_gear_action;
178
    *do_gear_getserverbackups = \&do_gear_action;
179
    *do_gear_listfiles = \&do_gear_action;
180
    *do_gear_restorefiles = \&do_gear_action;
181
    *do_gear_remove = \&do_gear_action;
182
    *do_gear_removeuserimages = \&do_gear_action;
183
    *do_gear_updatedownloads = \&do_gear_action;
184
    *do_gear_master = \&do_gear_action;
185
    *do_gear_unmaster = \&do_gear_action;
186 3657de20 Origo
    *do_gear_rebase = \&do_gear_action;
187 95b003ff Origo
    *do_gear_clone = \&do_gear_action;
188
    *do_gear_snapshot = \&do_gear_action;
189
    *do_gear_unsnap = \&do_gear_action;
190
    *do_gear_revert = \&do_gear_action;
191
    *do_gear_inject = \&do_gear_action;
192
    *do_gear_backup = \&do_gear_action;
193
    *do_gear_zbackup = \&do_gear_action;
194
    *do_gear_restore = \&do_gear_action;
195
    *do_gear_updatebackingfile = \&do_gear_action;
196
    *do_gear_updatebtime = \&do_gear_action;
197
    *do_gear_updateallbtimes = \&do_gear_action;
198
    *do_gear_initializestorage = \&do_gear_action;
199
    *do_gear_liststoragedevices = \&do_gear_action;
200
    *do_gear_listimagesdevices = \&do_gear_action;
201
    *do_gear_listbackupdevices = \&do_gear_action;
202
    *do_gear_getimagesdevice = \&do_gear_action;
203
    *do_gear_getbackupdevice = \&do_gear_action;
204
    *do_gear_setstoragedevice = \&do_gear_action;
205 2a63870a Christian Orellana
    *do_gear_backupfuel = \&do_gear_action;
206 95b003ff Origo
207
    *Fullupdateregister = \&Updateregister;
208
209 48fcda6b Origo
    @users; # global
210 95b003ff Origo
    if ($fulllist) {
211
        @users = keys %userreg;
212 48fcda6b Origo
        push @users, "common";
213 95b003ff Origo
    } else {
214 48fcda6b Origo
        @users = ($user, "common");
215 95b003ff Origo
    }
216
217
    untie %userreg;
218
219
#    my $mounts = decode('ascii-escape', `/bin/cat /proc/mounts`);
220
    my $mounts = `/bin/cat /proc/mounts`;
221
    @spools;
222
223
    # Enumerate and define the storage pools a user has access to
224
    my @spl = split(/,\s*/, $storagepools);
225
    my $reloadnfs;
226
    foreach my $p (@spl) {
227
        if ($tenderlist[$p] && $tenderpathslist[$p] && $tendernameslist[$p]) {
228 8d7785ff Origo
            my $rd = (defined $rdiffenabledlist[$p])?$rdiffenabledlist[$p]:"$rdiffenabledlist[0]";
229 95b003ff Origo
            my %pool = ("hostpath", $tenderlist[$p],
230
                "path", $tenderpathslist[$p],
231
                "name", $tendernameslist[$p],
232 8d7785ff Origo
                "rdiffenabled", $rd,
233 95b003ff Origo
                "mountable", ($tenderlist[$p] eq 'local') || $mountabletenderslist[$p] || '0', # local pools always mountable
234
                "lvm", 0+($tenderlist[$p] eq 'local' && ($mounts =~ m/\/dev\/mapper\/(\S+)-(\S+) $tenderpathslist[$p].+/g) ),
235
                "zfs", (($mounts =~ /(\S+) $tenderpathslist[$p] zfs/)?$1:''),
236
                "id", $p);
237
            $spools[$p] = \%pool;
238
239
            # Directory / mount point must exist
240 e9af6c24 Origo
            unless (-d $tenderpathslist[$p]) {return "Status=Error $tenderpathslist[$p] could not be accessed"};
241 95b003ff Origo
242
            # TODO: This section should be moved to pressurecontrol
243
            if ($tenderlist[$p] eq "local") {
244
                my $lpath = $tenderpathslist[$p];
245
                `mkdir "$lpath"` unless (-e $lpath);
246
                unless (`grep "$lpath 10" /etc/exports`) {
247
                    `echo "$lpath 10.0.0.0/255.255.255.0(sync,no_subtree_check,no_root_squash,rw)" >> /etc/exports`;
248
                    $reloadnfs = 1;
249
                }
250
            } elsif ($mounts =~ m/$tenderpathslist[$p]/i) {
251
                ; # do nothing
252
            } else {
253
                $main::syslogit->($user, 'info', "Mounting $tenderpathslist[$p] from $tenderlist[$p]");
254
                eval {
255
                    system("/bin/mount -o intr,noatime,nfsvers=3 $tenderlist[$p] $tenderpathslist[$p]");
256 c899e439 Origo
                    1;
257
                } or {return "Status=Error $tenderpathslist[$p] could not be mounted"};
258 95b003ff Origo
            }
259
260
            # Create user dir if it does not exist
261
            unless(-d "$tenderpathslist[$p]/$user"){
262
                umask "0000";
263 e9af6c24 Origo
                mkdir "$tenderpathslist[$p]/$user" or {return "Status=Cannot create user dir for $user in  $tenderpathslist[$p]"};
264 95b003ff Origo
            }
265
            unless(-d "$tenderpathslist[$p]/common"){
266
                umask "0000";
267 e9af6c24 Origo
                mkdir "$tenderpathslist[$p]/common" or {return "Status=Cannot create common dir for $user in $tenderpathslist[$p]"};
268 95b003ff Origo
            }
269
        }
270
    }
271
    `/usr/sbin/exportfs -r` if ($reloadnfs); #Reexport nfs shares
272
273
    # Create user's backupdir if it does not exist
274
    unless(-d "$backupdir/$user"){
275
        umask "0000";
276
        mkdir "$backupdir/$user" or {$postreply .= "Status=ERROR No backup dir $backupdir/$user\n"};
277
    }
278
279
}
280
281
sub getObj {
282
    my %h = %{@_[0]};
283
    my $status = $h{"status"};
284
    $console = 1 if $h{"console"};
285
    $api = 1 if $h{"api"};
286
    my $obj;
287
    $action = $action || $h{'action'};
288 9d03439e hq
    if (
289
        $action =~ /^clone|^sync_clone|^removeuserimages|^gear_removeuserimages|^activate|^gear_activate|^publish|uploadtoregistry|^release|^download|^gear_publish/
290 d3805c61 hq
        || $action =~ /^gear_release|zbackup|setimagesdevice|setbackupdevice|initializestorage|setstoragedevice|backupfuel|sync_backup|overquota|^move/
291 9d03439e hq
292
    ) {
293 95b003ff Origo
        $obj = \%h;
294
        return $obj;
295
    }
296
    my $uuid = $h{"uuid"};
297 2a63870a Christian Orellana
    if ($uuid && $uuid =~ /^\// ) { # Ugly clutch
298
        $uuid = $register{$uuid}->{'uuid'};
299
    }
300 95b003ff Origo
    if ($uuid eq 'this' && $curimg
301
        && ($register{$curimg}->{'user'} eq $user || $isadmin )) { # make an ugly exception
302
        $uuid = $register{$curimg}->{'uuid'};
303
    }
304
    my $objaction = lc $h{"action"};
305 04c16f26 hq
    $status = "new" unless ($status || $h{'path'} || $uuid || $action eq 'inject');
306 95b003ff Origo
    if ($status eq "new") {
307
        $objaction = "";
308
    }
309
    if (!$uuid && $register{$h{'path'}} && ( $register{$h{'path'}}->{'user'} eq $user || $isadmin )) {
310
        $uuid = $register{$h{'path'}}->{'uuid'};
311
    }
312
    my $img = $imagereg{$uuid};
313
    $status = $img->{'status'} if ($imagereg{$uuid});
314
    if ($objaction eq 'buildsystem' && !$uuid && $h{'master'}) { # make another exception
315
        my $master = $h{'master'};
316
        foreach my $p (@spools) {
317
            my $dir = $p->{'path'};
318
            if ($master =~ /^$dir\/(common|$user)\/.+/ && $register{$master}) { # valid master image
319
                $uuid = $register{$master}->{'uuid'};
320
                last;
321
            }
322
            elsif ($register{"$dir/common/$master"}) { # valid master image
323
                $uuid = $register{"$dir/$user/$master"}->{'uuid'};
324
                last;
325
            }
326
            elsif ($register{"$dir/$user/$master"}) { # valid master image
327
                $uuid = $register{"$dir/$user/$master"}->{'uuid'};
328
                last;
329
            }
330
        }
331
    }
332
    my $path = '';
333
    $path = $img->{'path'} unless ($status eq "new"); # Only trust path from db /co
334
    my $dbobj = $register{$path} || {};
335
    return 0 unless (($path && $dbobj->{'user'} eq $user) || $isadmin || $status eq "new"); # Security check
336
337
    unless (($uuid && $imagereg{$uuid} && $status ne 'new') || ($status eq 'new' && !$imagereg{$uuid} && (!$uuid || length($uuid) == 36))) {
338
        $postreply .= "Status=ERROR Invalid image " . (($uuid)?" uuid: $uuid":"") . (($path)?" path: $path":"") . "\n";
339
        return 0;
340
    }
341
    if ($isadmin && $h{"status"}) {
342
        $status = $h{"status"} unless ($status eq "new");
343
    } else {
344
        $status = $dbobj->{'status'} unless ($status eq "new"); # Read status from db for existing images
345
    }
346
    my $virtualsize = $h{"virtualsize"} || $dbobj->{'virtualsize'};
347
    # allow shorthand size specifications
348
    $virtualsize = 1024 * $virtualsize if ($virtualsize =~ /k$/i);
349
    $virtualsize = 1024*1024* $virtualsize if ($virtualsize =~ /m$/i);
350
    $virtualsize = 1024*1024*1024* $virtualsize if ($virtualsize =~ /g$/i);
351
    $virtualsize = 10737418240 if ($status eq 'new' && !$virtualsize); # 10 GB
352
353
    $obj = {
354
        path           => $path,
355
        uuid           => $uuid,
356
        status         => $status,
357
        name           => $h{"name"} || $dbobj->{'name'}, # || 'New Image',
358
        size           => $h{"size"} || $dbobj->{'size'},
359
        realsize       => $dbobj->{'realsize'} || 0,
360
        virtualsize    => $virtualsize,
361
        ksize          => int($virtualsize / 1024),
362
        msize          => int($virtualsize / (1024 * 1024)),
363
        type           => $h{"type"} || $dbobj->{'type'} || 'qcow2',
364
        user           => $h{"user"} || $dbobj->{'user'},
365
        reguser        => $dbobj->{'user'},
366
        master         => $dbobj->{'master'},
367
        regstoragepool => $dbobj->{'storagepool'},
368
        storagepool   => (!$h{"storagepool"} && $h{"storagepool"} ne "0") ? $dbobj->{'storagepool'} : $h{"storagepool"},
369
        bschedule      => $h{"bschedule"} || $dbobj->{'bschedule'},
370
        notes          => $h{"notes"},
371
        installable    => ($installable && $installable ne "false") ? "true" : $h{"installable"},
372
        snap1          => $dbobj->{'snap1'},
373
        managementlink => $h{"managementlink"} || $dbobj->{'managementlink'},
374
        upgradelink    => $h{"upgradelink"} || $dbobj->{'upgradelink'},
375
        terminallink   => $h{"terminallink"} || $dbobj->{'terminallink'},
376
        image2         => $h{"image2"} || $dbobj->{'image2'},
377
        mac            => $h{"mac"} || $dbobj->{'mac'},
378
        backup         => $h{"backup"} || '',
379
        domains        => $dbobj->{'domains'} || '--',
380
        domainnames    => $dbobj->{'domainnames'} || '--'
381
    };
382
    # Handle restore of files
383
    $obj->{'restorepath'} = $h{'restorepath'} if ($h{'restorepath'});
384
    $obj->{'files'} = $h{'files'} if ($h{'files'});
385
    $obj->{'sync'} = 1 if ($h{'sync'});
386 2a63870a Christian Orellana
    # For backup
387
    $obj->{'skipzfs'} = 1 if ($h{'skipzfs'});
388 95b003ff Origo
389
    # Sanity checks
390
    if (
391
        ($obj->{name} && length $obj->{name} > 255)
392
            || ($obj->{virtualsize} && ($obj->{virtualsize}<1024 || $obj->{virtualsize} >1024**5))
393
            || ($obj->{master} && length $obj->{master} > 255)
394
            || ($obj->{bschedule} && length $obj->{bschedule} > 255)
395
            || ($path && length $path > 255)
396
            || ($obj->{image2} && length $obj->{image2} > 255)
397
    ) {
398
        $postreply .= "Status=ERROR Bad image data for: $obj->{name}\n";
399
        return 0;
400
    }
401
    # Security check
402
    if (($user ne $obj->{reguser} && $objaction ne 'clone' && $objaction ne 'buildsystem' && !$isadmin && $objaction))
403
    {
404
        $postreply .= "Status=ERROR No privs\n";
405
        return 0;
406
    }
407
    if ($status eq "new" && ($obj->{reguser} || -e $path)) {
408 04c16f26 hq
        $postreply .= "Status=ERROR Image \"$obj->{name}\" does already exist in $path\n";
409 95b003ff Origo
        return 0;
410
    }
411
    if (!$path && $status ne "new") {
412
        $postreply .= "Status=ERROR Image $obj->{name} not found\n";
413
        return 0;
414
    }
415
    return $obj;
416
}
417
418
sub createNodeTask {
419 a2e0bc7e hq
    my ($mac, $newtask, $status, $wake, $path) = @_;
420 95b003ff Origo
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) )
421
        {$postreply .= "Status=Error Node register could not be accessed"};
422
423 54401133 hq
    if ($status eq "active" && $nodereg{$mac}->{'stor'} ne 'lvm') {
424 a2e0bc7e hq
     #   $postreply .= "Status=Error Node $mac is not using LVM, unable to backup active image\n";
425
     #   $main::updateUI->({tab=>"images", user=>$user, type=>"update", path=>$path, status=>$status, message=>"Image (on node) is not on an LVM partition - suspend before backing up"});
426 54401133 hq
        return "node is is not using LVM, unable to backup active image.";
427
    } elsif ($nodereg{$mac}->{'status'} =~ /asleep|inactive/  && !$wake) {
428 a2e0bc7e hq
    #    $postreply .= "Status=Error Node $mac is asleep, not waking\n";
429 54401133 hq
        return "node is asleep, please wake first!";
430 95b003ff Origo
    } else {
431
        my $tasks = $nodereg{$mac}->{'tasks'};
432
        $nodereg{$mac}->{'tasks'} = $tasks . "$newtask\n";
433
        tied(%nodereg)->commit;
434
    }
435
    untie %nodereg;
436
    return 0;
437
}
438
439
sub Recurse {
440
	my($path) = shift; # @_;
441
	my @files;
442
	## append a trailing / if it's not there
443
	$path .= '/' if($path !~ /\/$/);
444
	## loop through the files contained in the directory
445
	for my $eachFile (bsd_glob($path.'*')) {
446
	    next if ($eachFile =~ /\/fuel$/);
447
		## if the file is a directory
448
		if( -d $eachFile) {
449
			## pass the directory to the routine ( recursion )
450
			push(@files,Recurse($eachFile));
451
		} else {
452
			push(@files,$eachFile);
453
		}
454
	}
455
	return @files;
456
}
457
458
# If used with the -f switch ($fulllist) from console, all users images are updated in the db
459
# If used with the -p switch ($fullupdate), also updates status information (ressource intensive - runs through all domains)
460
sub Updateregister {
461
    my ($spath, $action) = @_;
462
    if ($help) {
463
        return <<END
464
GET:image,uuid:
465
If used with the -f switch ($fulllist) from console, all users images are updated in the db.
466
If used with the -p switch ($fullupdate), also updates status information (ressource intensive - runs through all domains)
467 8d7785ff Origo
Only images on shared storage are updated, images on node storage are handled on the node.
468 95b003ff Origo
END
469
    }
470
    return "Status=ERROR You must be an admin to do this!\n" unless ($isadmin);
471
    $fullupdate = 1 if ((!$fullupdate && $params{'fullupdate'}) || $action eq 'fullupdateregister');
472
    my $force = $params{'force'};
473
    my %userregister;
474
    my $res;
475
    # Update size information in db
476
    foreach my $u (@users) {
477
        foreach my $spool (@spools) {
478
            my $pooldir = $spool->{"path"};
479
            my $dir = "$pooldir/$u";
480
            my @thefiles = Recurse($dir);
481
            foreach my $f (@thefiles) {
482
                next if ($spath && $spath ne $f); # Only specific image being updated
483
                if ($f =~ /(.+)(-s\d\d\d\.vmdk$)/) {
484 2a63870a Christian Orellana
                #   `touch "$1.vmdk" 2>/dev/null` unless -e "$1.vmdk";
485 95b003ff Origo
                } elsif ($f =~ /(.+)(-flat\.vmdk$)/) {
486 2a63870a Christian Orellana
                #    `touch "$1.vmdk" 2>/dev/null` unless -e "$1.vmdk";
487 04c16f26 hq
                } elsif(-s $f && $f =~ /(\.vmdk$)|(\.img$)|(\.vhd$)|(\.vhdx$)|(\.qcow$)|(\.qcow2$)|(\.vdi$)|(\.iso$)/i) {
488
                    my($fname, $dirpath, $suffix) = fileparse($f, ("vmdk", "img", "vhd", "vhdx", "qcow", "qcow2", "vdi", "iso"));
489 95b003ff Origo
                    my $uuid;
490
                    my $img = $register{$f};
491
                    $uuid = $img->{'uuid'};
492
            # Create a new uuid if we are dealing with a new file in the file-system
493
                    if (!$uuid) {
494
                        my $ug = new Data::UUID;
495
                        $uuid = $ug->create_str();
496
                    }
497
                    my $storagepool = $spool->{"id"};
498
            # Deal with sizes
499
                    my ($newmtime, $newbackupsize, $newsize, $newrealsize, $newvirtualsize) =
500 8d7785ff Origo
                        getSizes($f, $img->{'mtime'}, $img->{'status'}, $u, $force);
501 95b003ff Origo
                    my $size = $newsize || $img->{'size'};
502
                    my $realsize = $newrealsize || $img->{'realsize'};
503
                    my $virtualsize = $newvirtualsize || $img->{'virtualsize'};
504
                    my $mtime = $newmtime || $img->{'mtime'};
505
                    my $created = $img->{'created'} || $mtime;
506
                    my $name = $img->{'name'} || substr($fname,0,-1);
507
                    $register{$f} = {
508
                        path=>$f,
509
                        user=>$u,
510
                        type=>$suffix,
511
                        size=>$size,
512
                        realsize=>$realsize,
513
                        virtualsize=>$virtualsize,
514
                        backupsize=>$newbackupsize,
515
                        name=>$name,
516
                        uuid=>$uuid,
517
                    #    domains=>$domains,
518
                    #    domainnames=>$domainnames,
519
                        storagepool=>$storagepool,
520
                        backup=>"", # Only set in uservalues at runtime
521
                        created=>$created,
522
                        mtime=>$mtime
523 8d7785ff Origo
                    };
524
                #    $postreply .= "Status=OK $f, $size, $newbackupsize\n" if ($console);
525 95b003ff Origo
                }
526
            }
527
        }
528
    }
529
    # Update status information in db
530
#    my $mounts = decode('ascii-escape', `/bin/cat /proc/mounts`);
531
    my $mounts = `/bin/cat /proc/mounts`;
532
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
533
    foreach my $u (@users) {
534
        my @regkeys = (tied %register)->select_where("user = '$u'");
535
        foreach my $k (@regkeys) {
536
            my $valref = $register{$k};
537
            my $path = $valref->{'path'};
538
# Only update info for images the user has access to.
539
# Remove DB entries for images on removed nodes
540
            if ($valref->{'storagepool'}==-1 && $valref->{'mac'} && $valref->{'mac'} ne '--' && !$nodereg{$valref->{'mac'}}) {
541
                delete $register{$path}; # Clean up database, remove rows which don't have corresponding file
542
                $main::updateUI->({tab=>'images', user=>$u}) unless ($u eq 'common');
543
            } elsif ($valref->{'user'} eq $u && (defined $spools[$valref->{'storagepool'}]->{'id'} || $valref->{'storagepool'}==-1)) {
544
                my $path = $valref->{'path'};
545
                next if ($spath && $spath ne $path); # Only specific image being updated
546
                my $mounted = ($mounts =~ /$path/);
547
                my $domains;
548
                my $domainnames;
549
                my $regstatus = $valref->{'status'};
550
                my $status = $regstatus;
551
                if (!$status || $status eq '--') {
552
                    $status = 'unused';
553
                }
554
                if (-e $path || $valref->{'storagepool'}==-1 || -s "$path.meta") {
555
                # Deal with status
556
                    if ($valref->{'storagepool'}!=-1 && -s "$path.meta") {
557 f222b89c hq
                        if ($regstatus =~ /(downloading|uploading)/ && (-e "$path.meta")) {
558
                            my $adjective = $1;
559
                            my $percentage = `grep -Po '\\d+%' "$path.meta" | tail -n1`;
560
                            chomp $percentage;
561
                            $status = "$adjective $percentage" if ($percentage);
562 95b003ff Origo
                        } else {
563 f222b89c hq
                            my $metastatus;
564
                            $metastatus = `/bin/cat "$path.meta" 2>/dev/null`;
565
                            chomp $metastatus;
566
                            if ($metastatus =~ /status=(.+)&chunk=/) {
567
                                $status = $1;
568
                            } elsif ($metastatus =~ /status=(.+)&path2:(.+)=(.+)/) {
569
                                # A move operation has been completed - update status of both involved
570
                                $status = $1;
571
                                $register{$2}->{'status'} = $3;
572
                                unless ($userregister{$2}) { # If we have not yet parsed image, it is not yet in userregister, so put it there
573
                                    my %mval = %{$register{$2}};
574
                                    $userregister{$2} = \%mval;
575
                                }
576
                                $userregister{$2}->{'status'} = $3;
577
                            } elsif ($metastatus =~ /status=(\w+)/) {
578
                                $status = $1;
579
                            } else {
580
                                #    $status = $metastatus; # Do nothing - this meta file contains no status info
581
                            }
582 95b003ff Origo
                        }
583
                    } elsif (
584
                            $status eq "restoring"
585
                            || $status eq "frestoring"
586
                            || ($status eq "mounted" && $mounted)
587
                            || $status eq "snapshotting"
588
                            || $status eq "unsnapping"
589
                            || $status eq "reverting"
590
                            || $status eq "moving"
591 d3805c61 hq
                            || $status eq "stormoving"
592 95b003ff Origo
                            || $status eq "converting"
593
                            || $status eq "cloning"
594
                            || $status eq "copying"
595
                            || $status eq "rebasing"
596
                            || $status eq "creating"
597
                            || $status eq "resizing"
598
                        ) { # When operation is done, status is updated by piston.cgi
599
                        ; # Do nothing
600
                    } elsif ($status =~ /.(backingup)/) { # When backup is done, status is updated by steamExec
601
                        if ($valref->{'storagepool'}==-1) {
602
                        #    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
603
                            if ($nodereg{$valref->{'mac'}}) {
604
                                my $nodestatus = $nodereg{$valref->{'mac'}}->{status};
605
                                # If node is not available, it cannot be backing up...
606
                                if ($nodestatus eq 'inactive'
607
                                    || $nodestatus eq 'asleep'
608
                                    || $nodestatus eq 'shutoff'
609
                                ) {
610
                                    $valref->{'status'} = 'unused'; # Make sure we don't end here again in endless loop
611
                                    $rstatus = Updateregister(0, $path);
612
                                    $status = $rstatus if ($rstatus);
613
                                    $main::syslogit->($user, 'info', "Updated image status for aborted backup - $user, $path, $rstatus");
614
                                }
615
                            }
616
                            #untie %nodereg;
617
                        }
618
619
                    } elsif ($status eq 'uploading') {
620
                        $status = 'unused' unless (-s "$path.meta");
621
622
                    } elsif (!$status || $status eq 'unused' || $status eq 'active') {
623
                        if ($fullupdate) {
624
                            $status = "unused";
625
                            my @domregkeys;
626
                            if ($fulllist) {@domregkeys = keys %domreg;}
627
                            else {@domregkeys = (tied %domreg)->select_where("user = '$u'");}
628
                            foreach my $domkey (@domregkeys) {
629
                                my $dom = $domreg{$domkey};
630
                                my $img = $dom->{'image'};
631
                                my $img2 = $dom->{'image2'};
632
                                my $img3 = $dom->{'image3'};
633
                                my $img4 = $dom->{'image4'};
634
                                if ($path eq $img || $path eq $img2 || $path eq $img3 || $path eq $img4) {
635
                                    my $domstatus = $dom->{'status'};
636 d3805c61 hq
                                    if ($domstatus =~ /moving/) {;} # do nothing - updated by piston
637
                                    elsif ($domstatus eq "shutoff" || $domstatus eq "inactive") {$status = "used";}
638 95b003ff Origo
                                    elsif ($domstatus eq "paused") {$status = "paused";}
639 d3805c61 hq
                                    else {$status = "active";}
640 95b003ff Origo
                                    $domains = $dom->{'uuid'};
641
                                    $domainnames = $dom->{'name'};
642
                                };
643
                            }
644
                            $valref->{'domains'} = $domains ;
645
                            $valref->{'domainnames'} = $domainnames ;
646
                        } elsif ($valref->{'domains'} && $valref->{'domains'} ne '--'){
647
                            my $dom = $domreg{$valref->{'domains'}};
648
                            if ($dom) {
649
                                my $img = $dom->{'image'};
650
                                my $img2 = $dom->{'image2'};
651
                                my $img3 = $dom->{'image3'};
652
                                my $img4 = $dom->{'image4'};
653
                                if ($path eq $img || $path eq $img2 || $path eq $img3 || $path eq $img4) {
654
                                    my $domstatus = $dom->{'status'};
655 d3805c61 hq
                                    if ($domstatus =~ /moving/) {;} # do nothing - updated by piston
656
                                    elsif ($domstatus eq "shutoff" || $domstatus eq "inactive") {$status = "used";}
657 95b003ff Origo
                                    elsif ($domstatus eq "paused") {$status = "paused";}
658 d3805c61 hq
                                    else {$status = "active";}
659 95b003ff Origo
                                    $valref->{'domainnames'} = $dom->{'name'};
660
                                };
661
                            };
662
                        }
663
                    }
664
                    # Update info in db
665
                    $valref->{'status'} = $status ;
666
                    $res .= $status if ($spath);
667
                } else {
668
                    delete $register{$path}; # Clean up database, remove rows which don't have corresponding file
669
                    $main::updateUI->({tab=>'images', user=>$u}) unless ($u eq 'common');
670
                }
671
            }
672
        }
673
    }
674
    untie %nodereg;
675
    tied(%register)->commit;
676
    $res .= "Status=OK Updated image register for " . join(', ', @users) . "\n";
677 8d7785ff Origo
    $res .= $postreply;
678 95b003ff Origo
    return $res if ($res);
679
}
680
681
sub getVirtualSize {
682
    my $vpath = shift;
683
    my $macip = shift;
684
    my $qinfo;
685 04c16f26 hq
    my($bname, $dirpath, $suffix) = fileparse($vpath, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
686 95b003ff Origo
    if ($suffix eq ".qcow2") {
687
        if ($macip) {
688 3657de20 Origo
            $qinfo = `$sshcmd $macip /usr/bin/qemu-img info --force-share "$vpath"`;
689 95b003ff Origo
        } else {
690 3657de20 Origo
            $qinfo = `/usr/bin/qemu-img info --force-share "$vpath"`;
691 95b003ff Origo
        }
692
        $qinfo =~ /virtual size:.*\((.+) bytes\)/g;
693
        return(int($1)); # report size of new image for billing purposes
694
    } elsif ($status eq ".vdi") {
695
        if ($macip) {
696
            $qinfo = `$sshcmd $macip /usr/bin/VBoxManage showhdinfo "$vpath"`;
697
        } else {
698
            $qinfo = `/usr/bin/VBoxManage showhdinfo "$vpath"`;
699
        }
700
        $qinfo =~ /Logical size:\s*(\d+) MBytes/g;
701
        return(int($1) * 1024 * 1024); # report size of new image for billing purposes
702
    } else {
703
        if ($macip) {
704
            return `$sshcmd $macip perl -e 'my @stat=stat("$vpath"); print $stat[7];'`;
705
        } else {
706
            my @stat = stat($vpath);
707
            return($stat[7]); # report size of new image for billing purposes
708
        }
709
    }
710
}
711
712
sub getSizes {
713 8d7785ff Origo
    my ($f, $lmtime, $status, $buser, $force) = @_;
714 95b003ff Origo
715
    my @stat = stat($f);
716
    my $size = $stat[7];
717
    my $realsize = $stat[12] * 512;
718
    my $virtualsize = $size;
719
    my $backupsize = 0;
720
    my $mtime = $stat[9];
721 04c16f26 hq
    my($fname, $dirpath, $suffix) = fileparse($f, ("vmdk", "img", "vhd", "vhdx", "qcow", "qcow2", "vdi", "iso"));
722 95b003ff Origo
    my $subdir = "";
723 27512919 Origo
    if ($dirpath =~ /.+\/$buser(\/.+)?\//) {
724 95b003ff Origo
        $subdir = $1;
725
    }
726 8d7785ff Origo
    $backupsize = getBackupSize($subdir, "$fname$suffix", $buser);
727 95b003ff Origo
    my $ps = `/bin/ps ax`;
728
729
# Only fire up qemu-img etc. if image has been modified and is not being used
730
    if ((
731
        ($mtime - $lmtime)>300 &&
732
        ($status ne 'active' && $status ne 'downloading') &&
733
        !($ps =~ /$f/)) || $force
734
    ) {
735
736
# Special handling of vmdk's
737
        if ($suffix eq "vmdk") {
738 3657de20 Origo
            my $qinfo = `/usr/bin/qemu-img info --force-share "$f"`;
739 95b003ff Origo
            $qinfo =~ /virtual size:.*\((.+) bytes\)/g;
740
            $virtualsize = int($1);
741
            if ( -s ($dirpath . substr($fname,0,-1) . "-flat." . $suffix)) {
742
                my @fstatus = stat($dirpath . substr($fname,0,-1) . "-flat." . $suffix);
743
                my $fsize = $fstatus[7];
744
                my $frealsize = $fstatus[12] * 512;
745
                $size += $fsize;
746
                $virtualsize += $fsize;
747
                $realsize += $frealsize;
748
            } else {
749
#                $main::syslogit->($user, "info", "VMDK " . $dirpath . substr($fname,0,-1) . "-flat." . $suffix . " does not exist");
750
            }
751
            my $i = 1;
752
            while (@fstatus = stat($dirpath . substr($fname,0,-1) . "-s00$i." . $suffix)) {
753
                my $fsize = $fstatus[7];
754
                my $frealsize = $fstatus[12] * 512;
755
                $size += $fsize;
756
                #$virtualsize += $fsize;
757
                $realsize += $frealsize;
758
759
                my $cmdpath = $dirpath . substr($fname,0,-1) . "-s00$i." . $suffix;
760 3657de20 Origo
                my $qinfo = `/usr/bin/qemu-img info --force-share "$cmdpath"`;
761 95b003ff Origo
                $qinfo =~ /virtual size:.*\((.+) bytes\)/g;
762
                $virtualsize += int($1);
763
764
                $i++;
765
            }
766
# Get virtual size of qcow2 auto-grow volumes
767
        } elsif ($suffix eq "qcow2") {
768 3657de20 Origo
            my $qinfo = `/usr/bin/qemu-img info --force-share "$f"`;
769 95b003ff Origo
            $qinfo =~ /virtual size:.*\((.+) bytes\)/g;
770
            $virtualsize = int($1);
771
# Get virtual size of vdi auto-grow volumes
772
        } elsif ($suffix eq "vdi") {
773
            my $qinfo = `/usr/bin/VBoxManage showhdinfo "$f"`;
774
            $qinfo =~ /Logical size:\s*(\d+) MBytes/g;
775
            $virtualsize = int($1) * 1024 * 1024;
776
        }
777
# Actual used blocks times block size on disk, i.e. $realsize may be bigger than the
778
# logical size of the image file $size and the logical provisioned size of the disk $virtualsize
779
# in order to minimize confusion, we set $realsize to $size if this is the case
780
        $realsize = $size if ($realsize > $size);
781
782
        return ($mtime, $backupsize, $size, $realsize, $virtualsize);
783
    } else {
784
        return (0, $backupsize, $size, $realsize);
785
    }
786
787
}
788
789
sub getHypervisor {
790
	my $image = shift;
791
	# Produce a mapping of image file suffixes to hypervisors
792
	my %idreg;
793
    unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) )
794
        {$postreply .= "Status=Error identity register could not be accessed"};
795
796
	my @idvalues = values %idreg;
797
	my %formats;
798
	foreach my $val (@idvalues) {
799
		my %h = %$val;
800
		foreach (split(/,/,$h{'formats'})) {
801
			$formats{lc $_} = $h{'hypervisor'}
802
		}
803
	}
804
	untie %idreg;
805
806
	# and then determine the hypervisor in question
807
	my $hypervisor = "vbox";
808
	my ($pathname, $path, $suffix) = fileparse($image, '\.[^\.]*');
809
	$suffix = substr $suffix, 1;
810
	my $hypervisor = $formats{lc $suffix};
811
	return $hypervisor;
812
}
813
814
sub Getserverbackups {
815
    my ($domuuid, $action) = @_;
816
    if ($help) {
817
        return <<END
818
GET:uuid:
819
Lists the image backups associated with a server, i.e. the backups of all the images attached to a server.
820
A server UUID should be passed as parameter. A JSON object is returned. May be called as <b>getserverbackups</b>, in
821
which case a JSON object is returned, or as <b>listserverbackups</b>, in which case a string is returned.
822
END
823
    }
824
    my $res;
825
    my @sbackups;
826
    my $backuplist;
827
828
    if ($domreg{$domuuid} && (($domreg{$domuuid}->{'user'} eq $user) || $isadmin)) {
829
        push @sbackups, Listbackups($domreg{$domuuid}->{'image'}, 'getbackups');
830
        push @sbackups, Listbackups($domreg{$domuuid}->{'image2'}, 'getbackups') if ($domreg{$domuuid}->{'image2'} && $domreg{$domuuid}->{'image2'} ne '--');
831
        push @sbackups, Listbackups($domreg{$domuuid}->{'image3'}, 'getbackups') if ($domreg{$domuuid}->{'image3'} && $domreg{$domuuid}->{'image3'} ne '--');
832
        push @sbackups, Listbackups($domreg{$domuuid}->{'image4'}, 'getbackups') if ($domreg{$domuuid}->{'image4'} && $domreg{$domuuid}->{'image4'} ne '--');
833
    }
834
    foreach my $sbackup (@sbackups) {
835
        my @back = @{$sbackup};
836
        my $t = $back[0]->{time};
837
        my $epoch;
838
        my $z;
839
        if ($t eq '--') {
840
            $epoch = $t;
841
        } elsif ($t =~ /(z)/) {
842
#            my $time = Time::Piece->strptime($t, "%Y-%m-%d-%H-%M-%S (z)");
843
            my $time = Time::Piece->strptime($t, "%b %d %T %Y (z)");
844
            $epoch = $time->epoch;
845
            $z = ' (z)';
846
        } else {
847
            $t = $1 if ($t =~ /\* (.*)/);
848
            my $time = Time::Piece->strptime($t, "%b %d %T %Y");
849
            $epoch = $time->epoch;
850
        }
851
        $backuplist .= "$back[-1]->{name}$z/$epoch, " if (@back && $epoch);
852
    }
853
    $backuplist = substr($backuplist,0,-2);
854
855
    if ($action eq 'getserverbackups') {
856
        $res .= to_json(\@sbackups, {pretty=>1});
857
    } else {
858
        $res .= header() unless ($console);
859
        $res .= $backuplist;
860
    }
861
    return $res;
862
863
}
864
865
sub Listbackups {
866
    my ($curimg, $action) = @_;
867
    if ($help) {
868
        return <<END
869
GET:image:
870
List backups on file for the give image, which may be specified as path or uuid.
871
END
872
    }
873
874
    my $res;
875
    my $buser = $user;
876
    $curimg = '' unless ($register{$curimg}); # Image must exist
877
    $buser = $register{$curimg}->{'user'} if ($isadmin && $curimg);
878
    my @backups;
879
    my $subdir = "";
880
    if ($curimg && $curimg ne '--') {
881
        my($bname, $dirpath) = fileparse($curimg);
882 27512919 Origo
        if ($dirpath =~ /.+\/$buser(\/.+)?\//) {
883 95b003ff Origo
            $subdir = $1;
884
        }
885
        my $sbname = "$subdir/$bname";
886 2a63870a Christian Orellana
        $sbname =~ s/ /\\ /g;
887 95b003ff Origo
        $sbname = $1 if ($sbname =~ /(.+)/); # untaint
888
        foreach my $spool (@spools) {
889
            my $imgbasedir = $spool->{"path"};
890
            if (-d "$imgbasedir/.zfs/snapshot") {
891
                my $snaps = `/bin/ls -l --time-style=full-iso $imgbasedir/.zfs/snapshot/*/$buser$sbname 2> /dev/null`;
892
                my @snaplines = split("\n", $snaps);
893
                # -rw-r--r-- 1 root root 216174592 2012-02-19 17:51 /mnt/stabile/images/.zfs/snapshot/SNAPSHOT-20120106002116/cabo/Outlook2007.iso
894
                foreach $line (@snaplines) {
895
                    if ($line =~ /$imgbasedir\/.zfs\/snapshot\/SNAPSHOT-(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\/$buser$subdir\/$bname$/) {
896
                        my $timestamp = timelocal($6,$5,$4,$3,$2-1,$1); #$sec,$min,$hour,$mday,$mon,$year
897
                        my $t = localtime($timestamp)->strftime("%b %e %H:%M:%S %Y");
898
                        # my %incr = ("increment", "SNAPSHOT-$1$2$3$4$5$6", "time", "$1-$2-$3-$4-$5-$6 (z)", "pool", $imgbasedir);
899
                        my %incr = ("increment", "SNAPSHOT-$1$2$3$4$5$6", "time", "$t (z)", "pool", $imgbasedir);
900
                        unshift (@backups, \%incr);
901
                    };
902
                }
903
            }
904
        }
905
        # Also include ZFS snapshots transferred from nodes
906 27512919 Origo
        $imgbasedir = "/stabile-backup";
907 95b003ff Origo
        my $snaps = `/bin/ls -l --time-style=full-iso $imgbasedir/node-*/.zfs/snapshot/*/$buser$sbname 2> /dev/null`;
908
        my @snaplines = split("\n", $snaps);
909
        foreach $line (@snaplines) {
910
            if ($line =~ /($imgbasedir\/node-.+)\/.zfs\/snapshot\/SNAPSHOT-(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\/$buser$subdir\/$bname$/) {
911
                my $timestamp = timelocal($7,$6,$5,$4,$3-1,$2); #$sec,$min,$hour,$mday,$mon,$year
912
                my $t = localtime($timestamp)->strftime("%b %e %H:%M:%S %Y");
913
                # my %incr = ("increment", "SNAPSHOT-$2$3$4$5$6$7", "time", "$2-$3-$4-$5-$6-$7 (z)", "pool", $1);
914
                my %incr = ("increment", "SNAPSHOT-$2$3$4$5$6$7", "time", "$t (z)", "pool", $1);
915
                unshift (@backups, \%incr);
916
            };
917
        }
918
        my $bpath = "$backupdir/$buser$subdir/$bname";
919
        $bpath = $1 if ($bpath =~ /(.+)/); # untaint
920
        if (-d "$bpath") {
921
            my $rdiffs = `/usr/bin/rdiff-backup -l "$bpath"`;
922
            my @mlines = split("\n", $rdiffs);
923
            my $curmirror;
924
            foreach my $line (@mlines) {
925
                if ($line =~ /\s+increments\.(\S+)\.dir\s+\S\S\S (.+)$/) {
926
                    my %incr = ("increment", $1, "time", $2);
927
                    if (-e "$bpath/rdiff-backup-data/increments/$bname.$1.diff.gz"
928
                    ) {
929
                        unshift (@backups, \%incr);
930
                    }
931
                };
932
                if ($line =~ /Current mirror: \S\S\S (.+)$/) {
933
                    $curmirror = $1;
934
                };
935
            }
936
            if ($curmirror) {
937
                my %incr = ("increment", "mirror", "time", "* $curmirror");
938
                unshift @backups, \%incr;
939
            }
940
            my %incr = ("increment", "--", "time", "--", "name", $bname);
941
            push @backups, \%incr;
942
        } else {
943
            my %incr = ("increment", "--", "time", "--", "name", $bname);
944
            push @backups, \%incr;
945
        }
946
    }
947
948
    if ($action eq 'getbackups') {
949
        return \@backups;
950
    } elsif ($console) {
951
        my $t2 = Text::SimpleTable->new(28,28);
952
        $t2->row('increment', 'time');
953
        $t2->hr;
954
        foreach my $fref (@backups) {
955
            $t2->row($fref->{'increment'}, scalar localtime( $fref->{'time'} )) unless ($fref->{'increment'} eq '--');
956
        }
957
        return $t2->draw;
958
    } else {
959
        $res .= header('application/json');
960
        my $json_text = to_json(\@backups, {pretty=>1});
961
        $res .= qq|{"identifier": "increment", "label": "time", "items": $json_text }|;
962
        return $res;
963
    }
964
}
965
966
# Get the timestamp of latest backup of an image
967
sub getBtime {
968
    my $curimg = shift;
969
    my $buser = shift || $user;
970
    return unless ($buser eq $user || $isadmin);
971
    $buser = 'common' if ($register{$curimg}->{user} eq 'common' && $isadmin);
972
    my $subdir = "";
973
    my $lastbtimestamp;
974
    my($bname, $dirpath) = fileparse($curimg);
975 27512919 Origo
    if ($dirpath =~ /.+\/$buser(\/.+)?\//) {
976 95b003ff Origo
        $subdir = $1;
977
    }
978
979
    #require File::Spec;
980
    #my $devnull = File::Spec->devnull();
981
982
    foreach my $spool (@spools) {
983
        my $imgbasedir = $spool->{"path"};
984
        if (-d "$imgbasedir/.zfs/snapshot") {
985
            my $sbname = "$subdir/$bname";
986 2a63870a Christian Orellana
            $sbname =~ s/ /\\ /g;
987 95b003ff Origo
            my $cmd = qq|/bin/ls -l --time-style=full-iso $imgbasedir/.zfs/snapshot/*/$buser$sbname 2>/dev/null|;
988
            my $snaps = `$cmd`;
989
            my @snaplines = split("\n", $snaps);
990
            foreach $line (@snaplines) {
991
                if ($line =~ /$imgbasedir\/.zfs\/snapshot\/SNAPSHOT-(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\/$buser$subdir\/$bname$/) {
992
                    my $timestamp = timelocal($6,$5,$4,$3,$2-1,$1); #$sec,$min,$hour,$mday,$mon,$year
993
                    $lastbtimestamp = $timestamp if ($timestamp > $lastbtimestamp);
994
                };
995
            }
996
        }
997
    }
998
    # Also include ZFS snapshots transferred from nodes
999 27512919 Origo
    $imgbasedir = "/stabile-backup";
1000 95b003ff Origo
    my $snaps = `/bin/ls -l --time-style=full-iso $imgbasedir/node-*/.zfs/snapshot/*/$buser/$bname 2> /dev/null`;
1001
    my @snaplines = split("\n", $snaps);
1002
    foreach $line (@snaplines) {
1003
        if ($line =~ /$imgbasedir\/node-.+\/.zfs\/snapshot\/SNAPSHOT-(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\/$buser$subdir\/$bname$/) {
1004
            my $timestamp = timelocal($6,$5,$4,$3,$2-1,$1); #$sec,$min,$hour,$mday,$mon,$year
1005
            $lastbtimestamp = $timestamp if ($timestamp > $lastbtimestamp);
1006
        };
1007
    }
1008
    my $bpath = "$backupdir/$buser$subdir/$bname";
1009
    $bpath = $1 if ($bpath =~ /(.+)/);
1010
    if (-d "$bpath") {
1011
        my $rdiffs = `/usr/bin/rdiff-backup --parsable-output -l "$bpath"`;
1012
        my @mlines = split("\n", $rdiffs);
1013
        foreach my $line (@mlines) {
1014
            if ($line =~ /(\d+) (\S+)$/) {
1015
                my $timestamp = $1;
1016
                $lastbtimestamp = $timestamp if ($timestamp > $lastbtimestamp);
1017
            };
1018
        }
1019
    }
1020
    return $lastbtimestamp;
1021
}
1022
1023
sub Unmount {
1024
    my $path = shift;
1025
	my $action = shift;
1026
    if ($help) {
1027
        return <<END
1028
GET:image: Unmounts a previously mounted image.
1029
END
1030
    }
1031 04c16f26 hq
    my($bname, $dirpath, $suffix) = fileparse($path, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
1032 95b003ff Origo
    my $mountpath = "$dirpath.$bname$suffix";
1033
#    my $mounts = decode('ascii-escape', `/bin/cat /proc/mounts`);
1034
    my $mounts = `/bin/cat /proc/mounts`;
1035
    my $mounted = ($mounts =~ /$mountpath/);
1036
1037
#    eval {`/bin/umount "$mountpath"` if ($mounted); 1;}
1038
#    eval {`/bin/fusermount -u "$mountpath"` if ($mounted); 1;}
1039
#        or do {$postreply .= "Status=ERROR Problem mounting image $@\n";};
1040
1041
    if ($mounted) {
1042
        $cmd = qq|/bin/fusermount -u "$mountpath" 2>&1|;
1043
        my $mes = qx($cmd);
1044
        my $xc = $? >> 8;
1045
        $main::syslogit->($user, 'info', "Unmounted $curimg $xc");
1046
        if ($xc) {
1047
            $postreply .= "Status=ERROR Problem unmounting image ($mes). ";
1048
            return $postreply;
1049
        }
1050
    }
1051
#    my $mounts2 = decode('ascii-escape', `/bin/cat /proc/mounts`);
1052
    my $mounts2 = `/bin/cat /proc/mounts`;
1053 2a63870a Christian Orellana
    $mounts2 = String::Escape::unbackslash($mounts2);
1054 95b003ff Origo
    my $mounted2 = ($mounts2 =~ /$mountpath/);
1055
    eval {`/bin/rmdir "$mountpath"` if (!$mounted2 && -e $mountpath); 1;}
1056
        or do {$postreply .= "Status=ERROR Problem removing mount point $@\n";};
1057
1058
    if ($mounted) {
1059
        if ($mounted2) {
1060
            $postreply .= "Status=ERROR Unable to unmount $register{$path}->{'name'}\n";
1061
            return $postreply;
1062
        } else {
1063
            $postreply .= "Status=OK Unmounted image $register{$path}->{'name'}\n";
1064
            return $postreply;
1065
        }
1066
    } else {
1067 2a63870a Christian Orellana
        $postreply .= "Status=OK Image $path not mounted\n";
1068 95b003ff Origo
        return $postreply;
1069
    }
1070
}
1071
1072
sub unmountAll {
1073
    my @mounts = split(/\n/, `/bin/cat /proc/mounts`);
1074
    foreach my $mount (@mounts) {
1075
        foreach my $spool (@spools) {
1076
            my $pooldir = $spool->{"path"};
1077
            if ($mount =~ /($pooldir\/$user\/\S+) / || ($mount =~ /($pooldir\/common\/\S+) / && $isadmin)) {
1078
#                $mountpath = decode('ascii-escape', $1);
1079
                $mountpath =  $1;
1080
                $rpath = $mountpath;
1081
                $rpath =~ s/\/\./\//;
1082
                my $processes = `/bin/ps`;
1083
#                if ($register{$rpath} && !($processes =~ /steamExec.+$rpath/)) {
1084
                    $postreply .= "Status=OK Unmounting $rpath\n";
1085
                    Unmount($rpath);
1086
#                }
1087
            }
1088
        }
1089
    }
1090
    return;
1091
}
1092
1093
sub Mount {
1094
    my $path = shift;
1095
	my $action = shift;
1096
    if ($help) {
1097
        return <<END
1098
GET:image:
1099
Tries to mount an image on admin server for listfiles/restorefiles operations.
1100
END
1101
    }
1102 04c16f26 hq
    my($bname, $dirpath, $suffix) = fileparse($path, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
1103 95b003ff Origo
    my $mountpath = "$dirpath.$bname$suffix";
1104
    my $mounts = `/bin/cat /proc/mounts`;
1105 2a63870a Christian Orellana
    $mounts = String::Escape::unbackslash($mounts);
1106 95b003ff Origo
    my $mounted = ($mounts =~ /$mountpath/);
1107
    if ($mounted) {
1108
        unless (`ls "$mountpath"`) { # Check if really mounted
1109 2a63870a Christian Orellana
            Unmount($mountpath);
1110 95b003ff Origo
            $mounted = 0;
1111
        }
1112
    }
1113
1114
    if ($mounted) {
1115
        $postreply .= "Status=OK Image $register{$path}->{'name'} already mounted\n";
1116
        return $postreply;
1117
    } else {
1118
        `/bin/mkdir "$mountpath"` unless (-e "$mountpath");
1119
        `/bin/chown www-data:www-data  "$mountpath"`;
1120
        my $cmd;
1121
1122
        if (lc $suffix eq '.iso') {
1123
            #eval {`/bin/mount -o allow_other,ro,loop "$path" "$mountpath"`; 1;}
1124
            #eval {`/usr/bin/fuseiso -n "$path" "$mountpath" -o user=www-data`; 1;}
1125
            eval {`/usr/bin/fuseiso -n "$path" "$mountpath" -o allow_other`; 1;}
1126
            or do {
1127
                $postreply .= header('text/html', '500 Internal Server Error') unless ($console);
1128
                $postreply .= "Status=ERROR Problem mounting image $@\n";
1129
                return $postreply;
1130
            };
1131
        } else {
1132 705b5366 hq
            # First try to mount using autodiscover -i. If that fails, try to mount /dev/sda1
1133 95b003ff Origo
            $cmd = qq|/usr/bin/guestmount --ro -o allow_other -a "$path" "$mountpath" -i 2>&1|;
1134
            my $mes = qx($cmd);
1135
            my $xc = $? >> 8;
1136 705b5366 hq
            $main::syslogit->($user, 'info', "Trying to mount $curimg $xc");
1137 95b003ff Origo
            if ($xc) {
1138 705b5366 hq
                $cmd = qq|/usr/bin/guestmount --ro -o allow_other -a "$path" "$mountpath"  -m /dev/sda1:/ 2>&1|;
1139
                $mes = qx($cmd);
1140
                $xc = $? >> 8;
1141
                $main::syslogit->($user, 'info', "Trying to mount $curimg $xc");
1142
                if ($xc) {
1143
                    $postreply = header('text/html', '500 Internal Server Error') . $postreply unless ($console);
1144
                    chomp $mes;
1145
                    $postreply .= "Status=Error Problem mounting image ($mes).\n$cmd\n";
1146
                    return $postreply;
1147
                }
1148 95b003ff Origo
            }
1149
        }
1150
1151
        my $mounts2;
1152
        for (my $i=0; $i<5; $i++) {
1153
            $mounts2 = `/bin/cat /proc/mounts`;
1154 2a63870a Christian Orellana
            $mounts2 = String::Escape::unbackslash($mounts2);
1155 95b003ff Origo
            next if ( $mounts2 =~ /$mountpath/);
1156
            sleep 2;
1157
        }
1158
        if ( $mounts2 =~ /$mountpath/) {
1159
            $postreply .= "Status=OK Mounted image $register{$path}->{'name'}\n";
1160
            return $postreply;
1161
        } else {
1162
            $postreply .= header('text/html', '500 Internal Server Error') unless ($console);
1163
            $postreply .= "Status=ERROR Giving up mounting image $register{$path}->{'name'}\n";
1164
            return $postreply;
1165
        }
1166
    }
1167
}
1168
1169
sub Updatebackingfile {
1170
    my ($img, $action) = @_;
1171
    if ($help) {
1172
        return <<END
1173
GET:image:
1174
END
1175
    }
1176
    my $f = $img || $curimg;
1177
    return "Status=Error Image $f not found\n" unless (-e $f);
1178 3657de20 Origo
    my $vinfo = `qemu-img info --force-share "$f"`;
1179 95b003ff Origo
    my $master = $1 if ($vinfo =~ /backing file: (.+)/);
1180
    (my $fname, my $fdir) = fileparse($f);
1181 3657de20 Origo
    if (!$master) {
1182
        $register{$f}->{'master'} = '';
1183
        $postreply .=  "Status=OK Image $f does not use a backing file\n";
1184
    } elsif (-e $master){ # Master OK
1185
        $register{$f}->{'master'} = $master;
1186
        $postreply .=  "Status=OK $master exists, no changes to $f.\n";
1187
    } elsif (-e "$fdir/$master") { # Master OK
1188
        $register{$f}->{'master'} = "$fdir/$master";
1189
        $postreply .=  "Status=OK $master exists in $fdir. No changes to $f.\n"
1190
    } else {
1191
        # Master not immediately found, look for it
1192
        (my $master, my $mdir) = fileparse($master);
1193
        my @busers = @users;
1194
        push (@busers, $billto) if ($billto); # We include images from 'parent' user
1195
        foreach my $u (@busers) {
1196
            foreach my $spool (@spools) {
1197
                my $pooldir = $spool->{"path"};
1198
                my $masterpath = "$pooldir/$u/$master";
1199
                if (-e $masterpath) {
1200 f222b89c hq
                    my $cmd = qq|qemu-img rebase -f qcow2 -u -b "$masterpath" -F qcow2 "$f"|;
1201 3657de20 Origo
                    $register{$f}->{'master'} = $masterpath;
1202
                    $postreply .= "Status=OK found $masterpath, rebasing from $mdir to $pooldir/$u ";
1203
                    $postreply .= `$cmd` . "\n";
1204
                    last;
1205
                }
1206 95b003ff Origo
            }
1207
        }
1208 c05aff24 hq
        if (!$postreply) { # master not found, we rebase to main storage pool, hoping it will appear there
1209
            my $pooldir = $spools[0]->{"path"};
1210
            my $masterpath = "$pooldir/common/$master";
1211
            my $cmd = qq|qemu-img rebase -f qcow2 -u -b "$masterpath" -F qcow2 "$f"|;
1212
            $register{$f}->{'master'} = $masterpath;
1213
            $postreply .= "Status=Error $master not found in any user dir. You must provide this backing file to use this image.\n";
1214
            $postreply .= `$cmd` . "\n";
1215
        }
1216 95b003ff Origo
    }
1217 3657de20 Origo
    tied(%register)->commit;
1218 95b003ff Origo
    return $postreply;
1219
}
1220
1221
# List files in a mounted image. Mount image if not mounted.
1222
sub Listfiles {
1223
    my ($curimg, $action, $obj) = @_;
1224
    if ($help) {
1225
        return <<END
1226
GET:image,path:
1227
Try to mount the file system on the given image, and list the files from the given path in the mounted file system.
1228
The image must contain a bootable file system, in order to locate a mount point.
1229
END
1230
    }
1231
    my $res;
1232
    my $curpath = $obj->{'restorepath'};
1233
    $res .= header('application/json') unless ($console);
1234
1235 04c16f26 hq
    my($bname, $dirpath, $suffix) = fileparse($curimg, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
1236 95b003ff Origo
    my $mountpath = "$dirpath.$bname$suffix";
1237
	my @files;
1238
	my @dirs;
1239
    my $mounted = (Mount($curimg) =~ /\w=OK/);
1240
1241
    if ($mounted) {
1242
        my @patterns = ('');
1243
        $curpath .= '/' unless ($curpath =~ /\/$/);
1244
        $mountpath .= "$curpath";
1245
        if (-d $mountpath) { # We are listing a directory
1246
            # loop through the files contained in the directory
1247
            @patterns = ('*', '.*');
1248
        }
1249
        foreach $pat (@patterns) {
1250
            for my $f (bsd_glob($mountpath.$pat)) {
1251
                my %fhash;
1252
                ($bname, $dirpath) = fileparse($f);
1253
                my @stat = stat($f);
1254
                my $size = $stat[7];
1255
                my $realsize = $stat[12] * 512;
1256
                my $mtime = $stat[9];
1257
1258
                $fhash{'name'} = $bname;
1259
                $fhash{'mtime'} = $mtime;
1260
                ## if the file is a directory
1261
                if( -d $f) {
1262
                    $fhash{'size'} = 0;
1263
                    $fhash{'fullpath'} = $f . '/';
1264
                    $fhash{'path'} = $curpath . $bname . '/';
1265
                    push(@dirs, \%fhash) unless ($bname eq '.' || $bname eq '..');
1266
                } else {
1267
                    $fhash{'size'} = $size;
1268
                    $fhash{'fullpath'} = $f;
1269
                    $fhash{'path'} = $curpath . $bname;
1270
                    push(@files, \%fhash);
1271
                }
1272
            }
1273
        }
1274
1275
        if ($console) {
1276
            my $t2 = Text::SimpleTable->new(48,16,28);
1277
            $t2->row('name', 'size', 'mtime');
1278
            $t2->hr;
1279
            foreach my $fref (@dirs) {
1280
                $t2->row($fref->{'name'}, $fref->{'size'}, scalar localtime( $fref->{'mtime'} )) unless ($bname eq '.' || $bname eq '..');
1281
            }
1282
            foreach my $fref (@files) {
1283
                $t2->row($fref->{'name'}, $fref->{'size'}, scalar localtime( $fref->{'mtime'} ) ) unless ($bname eq '.' || $bname eq '..');
1284
            }
1285
            return $t2->draw;
1286
        } else {
1287
            my @comb = (@dirs, @files);
1288
            $res .= to_json(\@comb, {pretty => 1});
1289
        }
1290
    } else {
1291 2a63870a Christian Orellana
        $res .= qq|{"status": "Error", "message": "Image $curimg not mounted. Mount first."}|;
1292 95b003ff Origo
    }
1293
    return $res;
1294
}
1295
1296
sub Restorefiles {
1297
    my ($path, $action, $obj) = @_;
1298
    if ($help) {
1299
        return <<END
1300
GET:image,files:
1301
Restores files from the given path in the given image to a newly created ISO image. The given image must be mountable.
1302
END
1303
    }
1304
    my $res;
1305
    $curfiles = $obj->{'files'};
1306
    $path = $path || $curimg;
1307
1308
    return "Status=ERROR Your account does not have the necessary privileges\n" if ($isreadonly);
1309
    return "Status=ERROR You must specify which files you want to restore\n" unless ($curfiles);
1310
1311
    my $name = $register{$path}->{'name'};
1312 04c16f26 hq
    my($bname, $dirpath, $suffix) = fileparse($path, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
1313 95b003ff Origo
    my $mountpath = "$dirpath.$bname$suffix";
1314
#    my $mounts = decode('ascii-escape', `/bin/cat /proc/mounts`);
1315
    my $mounts = `/bin/cat /proc/mounts`;
1316
    my $mmounts = `/bin/df`;
1317
    my $mounted = ($mounts =~ /$mountpath/ && $mmounts =~ /$mountpath/);
1318
    my $restorepath = "$dirpath$bname.iso";
1319
1320
    if (-e $restorepath) {
1321
        my $i = 1;
1322
        while (-e "$dirpath$bname.$i.iso") {$i++;}
1323
        $restorepath = "$dirpath$bname.$i.iso";
1324
    }
1325
1326
    my $uistatus = "frestoring";
1327
    if ($mounted && $curfiles) {
1328
        my $ug = new Data::UUID;
1329
        my $newuuid = $ug->create_str();
1330
        $register{$restorepath} = {
1331
                            uuid=>$newuuid,
1332
                            status=>$uistatus,
1333
                            name=>"Files from: $name",
1334
                            size=>0,
1335
                            realsize=>0,
1336
                            virtualsize=>0,
1337
                            type=>"iso",
1338
                            user=>$user
1339
                        };
1340
1341
        eval {
1342
                my $oldstatus = $register{$path}->{'status'};
1343
#                my $cmd = qq|steamExec $user $uistatus $oldstatus "$path" "$curfiles"|;
1344
#                my $cmdres = `$cmd`;
1345
            if ($mounted) {
1346
                $res .= "Restoring files to: /tmp/restore/$user/$bname$suffix -> $restorepath\n";
1347
                $res .= `/bin/echo $status > "$restorepath.meta"`;
1348
1349
                `/bin/mkdir -p "/tmp/restore/$user/$bname$suffix"` unless (-e "/tmp/restore/$user/$bname$suffix");
1350
                my @files = split(/:/, uri_unescape($curfiles));
1351
                foreach $f (@files) {
1352
                    if (-e "$mountpath$f" && chdir($mountpath)) {
1353
                        $f = substr($f,1) if ($f =~ /^\//);
1354
                        eval {`/usr/bin/rsync -aR --sparse "$f" /tmp/restore/$user/$bname$suffix`; 1;}
1355
                            or do {$e=1; $res .= "ERROR Problem restoring files $@\n";};
1356
                    } else {
1357
                        $res .= "Status=Error $f not found in $mountpath\n";
1358
                    }
1359
                }
1360
                if (chdir "/tmp/restore/$user/$bname$suffix") {
1361
                    eval {$res .= `/usr/bin/genisoimage -o "$restorepath" -iso-level 4 .`; 1;}
1362
                        or do {$e=1; $res .= "Status=ERROR Problem restoring files $@\n";};
1363
                    $res .= `/bin/rm -rf /tmp/restore/$user/$bname$suffix`;
1364
                    $res .= "Status=OK Restored files from /tmp/restore/$user/$bname$suffix to $restorepath\n";
1365
                } else {
1366
                    $res .= "Status=ERROR Unable to chdir to /tmp/restore/$user/$bname$suffix\n";
1367
                }
1368
                $main::updateUI->({tab=>"images", user=>$user, type=>"update"});
1369
1370
                # Update billing
1371
                my $newvirtualsize = getVirtualSize($restorepath);
1372
                unlink "$restorepath.meta";
1373
                $res .= Unmount($path);
1374
                $register{$restorepath}->{'status'} = 'unused';
1375
                $register{$restorepath}->{'virtualsize'} = $newvirtualsize;
1376
                $register{$restorepath}->{'realsize'} = $newvirtualsize;
1377
                $register{$restorepath}->{'size'} = $newvirtualsize;
1378
                $postmsg = "OK - restored your files into a new ISO.";
1379
            } else {
1380
                $res .= "Status=Error You must mount image on $mountpath before restoring\n";
1381
            }
1382
            $res .=  "Status=OK $uistatus files from $name to iso, $newuuid, $cmd\n";
1383
            $main::syslogit->($user, "info", "$uistatus files from $path to iso, $newuuid");
1384
            1;
1385
        } or do {$res .= "Status=ERROR $@\n";}
1386
1387
    } else {
1388
        $res .= "Status=ERROR Image not mounted, mount before restoring: ". $curfiles ."\n";
1389
    }
1390
    return $res;
1391
}
1392
1393
sub trim{
1394
   my $string = shift;
1395
   $string =~ s/^\s+|\s+$//g;
1396
   return $string;
1397
}
1398
1399 80e0b3f5 hq
sub do_overquota {
1400
    my ($path, $action, $obj) = @_;
1401
    if ($help) {
1402
        return <<END
1403
GET:inc,onnode:
1404
Check if 'inc' bytes will bring you over your storage quota. Set onnode to 1 to check node storage quota.
1405
END
1406
    }
1407
    if (overQuotas($obj->{inc}, $obj->{onnode})) {
1408
        return "Status=Error Over storage quota\n";
1409
    } else {
1410
        return "Status=OK Not over storage quota\n";
1411
    }
1412
}
1413
1414 95b003ff Origo
sub overQuotas {
1415
    my $inc = shift;
1416
    my $onnode = shift;
1417
	my $usedstorage = 0;
1418
	my $overquota = 0;
1419 80e0b3f5 hq
    return 0 if ($Stabile::userprivileges =~ /a/); # Don't enforce quotas for admins
1420 95b003ff Origo
	my $storagequota = ($onnode)?$Stabile::usernodestoragequota:$Stabile::userstoragequota;
1421 80e0b3f5 hq
1422 95b003ff Origo
	if (!$storagequota) { # 0 or empty quota means use defaults
1423
        $storagequota = (($onnode)?$Stabile::config->get('NODESTORAGE_QUOTA'):$Stabile::config->get('STORAGE_QUOTA')) + 0;
1424
	}
1425 80e0b3f5 hq
    return 0 if ($storagequota == -1); # -1 means no quota
1426 95b003ff Origo
1427
    my @regkeys = (tied %register)->select_where("user = '$user'");
1428
    foreach my $k (@regkeys) {
1429
        my $val = $register{$k};
1430
		if ($val->{'user'} eq $user) {
1431
		    $usedstorage += $val->{'virtualsize'} if ((!$onnode &&  $val->{'storagepool'}!=-1) || ($onnode &&  $val->{'storagepool'}==-1));
1432
		}
1433
	}
1434 80e0b3f5 hq
    if ($usedstorage+$inc > $storagequota * 1024 *1024) {
1435
        $overquota = $usedstorage+$inc;
1436
    }
1437 95b003ff Origo
	return $overquota;
1438
}
1439
1440
sub overStorage {
1441
    my ($reqstor, $spool, $mac) = @_;
1442
    my $storfree;
1443
    if ($spool == -1) {
1444
        if ($mac) {
1445
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
1446
            $storfree = $nodereg{$mac}->{'storfree'};
1447
            $storfree = $storfree *1024 * $nodestorageovercommission;
1448
            untie %nodereg;
1449
        } else {
1450
            return 1;
1451
        }
1452
    } else {
1453
        my $storpath = $spools[$spool]->{'path'};
1454
        $storfree = `df $storpath`;
1455
        $storfree =~ m/(\d\d\d\d+)(\s+)(\d\d*)(\s+)(\d\d+)(\s+)(\S+)/i;
1456
        my $stortotal = $1;
1457
        my $storused = $3;
1458
        $storfree = $5 *1024;
1459
    }
1460
    return ($reqstor > $storfree);
1461
}
1462
1463
sub updateBilling {
1464
    my $event = shift;
1465
    my %billing;
1466
1467
    my @regkeys = (tied %register)->select_where("user = '$user'");
1468
    foreach my $k (@regkeys) {
1469
        my $valref = $register{$k};
1470
        my %val = %{$valref}; # Deference and assign to new array, effectively cloning object
1471
        $val{'virtualsize'} += 0;
1472
        $val{'realsize'} += 0;
1473
        $val{'backupsize'} += 0;
1474
1475
        if ($val{'user'} eq $user && (defined $spools[$val{'storagepool'}]->{'id'} || $val{'storagepool'}==-1)) {
1476
            $billing{$val{'storagepool'}}->{'virtualsize'} += $val{'virtualsize'};
1477
            $billing{$val{'storagepool'}}->{'realsize'} += $val{'realsize'};
1478
            $billing{$val{'storagepool'}}->{'backupsize'} += $val{'backupsize'};
1479
        }
1480
    }
1481
1482
    my %billingreg;
1483
1484
    unless (tie %billingreg,'Tie::DBI', {
1485
            db=>'mysql:steamregister',
1486
            table=>'billing_images',
1487
            key=>'userstoragepooltime',
1488
            autocommit=>0,
1489
            CLOBBER=>3,
1490
            user=>$dbiuser,
1491
            password=>$dbipasswd}) {throw Error::Simple("Stroke=Error Billing register (images) could not be accessed")};
1492
1493
    my $monthtimestamp = timelocal(0,0,0,1,$mon,$year); #$sec,$min,$hour,$mday,$mon,$year
1494
1495
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'billing_images', key=>'userstoragepooltime'}, $Stabile::dbopts)) )
1496
        {throw Error::Simple("Status=Error Billing register could not be accessed")};
1497
1498
    my %pool = ("hostpath", "--",
1499
                "path", "--",
1500
                "name", "local",
1501
                "rdiffenabled", 1,
1502
                "id", -1);
1503
    my @bspools = @spools;
1504
    push @bspools, \%pool;
1505
1506
    foreach my $spool (@bspools) {
1507
        my $storagepool = $spool->{"id"};
1508
        my $b = $billing{$storagepool};
1509
        my $virtualsize = $b->{'virtualsize'} +0;
1510
        my $realsize = $b->{'realsize'} +0;
1511
        my $backupsize = $b->{'backupsize'} +0;
1512
1513
# Setting default start averages for use when no row found under the assumption that we entered a new month
1514
        my $startvirtualsizeavg = 0;
1515
        my $virtualsizeavg = 0;
1516
        my $startrealsizeavg = 0;
1517
        my $realsizeavg = 0;
1518
        my $startbackupsizeavg = 0;
1519
        my $backupsizeavg = 0;
1520
        my $starttimestamp = $current_time;
1521
# We have proably entered a new month if less than 4 hours since change of month, since this is run hourly
1522
        if ($current_time - $monthtimestamp < 4*3600) {
1523
            $starttimestamp = $monthtimestamp;
1524
            $startvirtualsizeavg = $virtualsizeavg = $virtualsize;
1525
            $startrealsizeavg = $realsizeavg = $realsize;
1526
            $startbackupsizeavg = $backupsizeavg = $backupsize;
1527
        }
1528
        # Update existing row
1529
        if ($billingreg{"$user-$storagepool-$year-$month"}) {
1530
            if (
1531
                ($virtualsize != $billingreg{"$user-$storagepool-$year-$month"}->{'virtualsize'})
1532
                || ($realsize != $billingreg{"$user-$storagepool-$year-$month"}->{'realsize'})
1533
                || ($backupsize != $billingreg{"$user-$storagepool-$year-$month"}->{'backupsize'})
1534
            )
1535
            {
1536
            # Sizes changed, update start averages and time, i.e. move the marker
1537
            # Averages and start averages are the same when a change has occurred
1538
                $startvirtualsizeavg = $virtualsizeavg = $billingreg{"$user-$storagepool-$year-$month"}->{'virtualsizeavg'};
1539
                $startrealsizeavg = $realsizeavg = $billingreg{"$user-$storagepool-$year-$month"}->{'realsizeavg'};
1540
                $startbackupsizeavg = $backupsizeavg = $billingreg{"$user-$storagepool-$year-$month"}->{'backupsizeavg'};
1541
                $starttimestamp = $current_time;
1542
            } else {
1543
            # Update averages and timestamp when no change on existing row
1544
                $startvirtualsizeavg = $billingreg{"$user-$storagepool-$year-$month"}->{'startvirtualsizeavg'};
1545
                $startrealsizeavg = $billingreg{"$user-$storagepool-$year-$month"}->{'startrealsizeavg'};
1546
                $startbackupsizeavg = $billingreg{"$user-$storagepool-$year-$month"}->{'startbackupsizeavg'};
1547
                $starttimestamp = $billingreg{"$user-$storagepool-$year-$month"}->{'starttimestamp'};
1548
1549
                $virtualsizeavg = ($startvirtualsizeavg*($starttimestamp - $monthtimestamp) + $virtualsize*($current_time - $starttimestamp)) /
1550
                                ($current_time - $monthtimestamp);
1551
                $realsizeavg = ($startrealsizeavg*($starttimestamp - $monthtimestamp) + $realsize*($current_time - $starttimestamp)) /
1552
                                ($current_time - $monthtimestamp);
1553
                $backupsizeavg = ($startbackupsizeavg*($starttimestamp - $monthtimestamp) + $backupsize*($current_time - $starttimestamp)) /
1554
                                ($current_time - $monthtimestamp);
1555
            }
1556
            # Update sizes in DB
1557
                $billingreg{"$user-$storagepool-$year-$month"}->{'virtualsize'} = $virtualsize;
1558
                $billingreg{"$user-$storagepool-$year-$month"}->{'realsize'} = $realsize;
1559
                $billingreg{"$user-$storagepool-$year-$month"}->{'backupsize'} = $backupsize;
1560
            # Update start averages
1561
                $billingreg{"$user-$storagepool-$year-$month"}->{'startvirtualsizeavg'} = $startvirtualsizeavg;
1562
                $billingreg{"$user-$storagepool-$year-$month"}->{'startrealsizeavg'} = $startrealsizeavg;
1563
                $billingreg{"$user-$storagepool-$year-$month"}->{'startbackupsizeavg'} = $startbackupsizeavg;
1564
            # Update current averages with values just calculated
1565
                $billingreg{"$user-$storagepool-$year-$month"}->{'virtualsizeavg'} = $virtualsizeavg;
1566
                $billingreg{"$user-$storagepool-$year-$month"}->{'realsizeavg'} = $realsizeavg;
1567
                $billingreg{"$user-$storagepool-$year-$month"}->{'backupsizeavg'} = $backupsizeavg;
1568
            # Update time stamps and inc
1569
                $billingreg{"$user-$storagepool-$year-$month"}->{'timestamp'} = $current_time;
1570
                $billingreg{"$user-$storagepool-$year-$month"}->{'starttimestamp'} = $starttimestamp;
1571
                $billingreg{"$user-$storagepool-$year-$month"}->{'inc'}++;
1572
1573
        # Write new row
1574
        } else {
1575
            $billingreg{"$user-$storagepool-$year-$month"} = {
1576
                virtualsize=>$virtualsize+0,
1577
                realsize=>$realsize+0,
1578
                backupsize=>$backupsize+0,
1579
1580
                virtualsizeavg=>$virtualsizeavg,
1581
                realsizeavg=>$realsizeavg,
1582
                backupsizeavg=>$backupsizeavg,
1583
1584
                startvirtualsizeavg=>$startvirtualsizeavg,
1585
                startrealsizeavg=>$startrealsizeavg,
1586
                startbackupsizeavg=>$startbackupsizeavg,
1587
1588
                timestamp=>$current_time,
1589
                starttimestamp=>$starttimestamp,
1590
                event=>$event,
1591
                inc=>1,
1592
            };
1593
        }
1594
    }
1595
    tied(%billingreg)->commit;
1596
    untie %billingreg;
1597
}
1598
1599
sub Removeuserimages {
1600
    my ($path, $action, $obj) = @_;
1601
    if ($help) {
1602
        return <<END
1603
GET::
1604
Removes all images belonging to a user from storage, i.e. completely deletes the image and its backups (be careful).
1605
END
1606
    }
1607
1608
    $postreply = removeUserImages($user) unless ($isreadonly);
1609
    return $postreply;
1610
}
1611
1612
sub removeUserImages {
1613
    my $username = shift;
1614
    return unless ($username && ($isadmin || $user eq $username) && !$isreadonly);
1615
    $user = $username;
1616
    foreach my $path (keys %register) {
1617
        if ($register{$path}->{'user'} eq $user) {
1618 d3805c61 hq
            $postreply .=  "Status=OK Removing " . ($Stabile::preserveimagesonremove?"(preserving) ":"") . " $username image $register{$path}->{'name'}, $register{$path}->{'uuid'}" . ($console?'':'<br>') . "\n";
1619
            Remove($path, 'remove', 0, $Stabile::preserveimagesonremove);
1620 95b003ff Origo
        }
1621
    }
1622
    $postreply .= "Status=Error No storage pools!\n" unless (@spools);
1623
    foreach my $spool (@spools) {
1624
        my $pooldir = $spool->{"path"};
1625
        unless (-e $pooldir) {
1626
            $postreply .= "Status=Error Storage $pooldir, $spool->{name} does not exist\n" unless (@spools);
1627
            next;
1628
        }
1629
1630
        $postreply .= "Status=OK Removing user dir $pooldir/$username ";
1631
        $postreply .= `/bin/rm "$pooldir/$username/.htaccess"` if (-e "$pooldir/$username/.htaccess");
1632
        $postreply .= `/bin/rmdir --ignore-fail-on-non-empty "$pooldir/$username/fuel"` if (-e "$pooldir/$username/fuel");
1633
        $postreply .= `/bin/rmdir --ignore-fail-on-non-empty "$pooldir/$username"` if (-e "$pooldir/$username");
1634
        $postreply .= "\n";
1635
    }
1636
1637
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
1638
1639
    foreach $mac (keys %nodereg) {
1640
        $macip = $nodereg{$mac}->{'ip'};
1641
        my $esc_path = "/mnt/stabile/node/$username";
1642
        $esc_path =~ s/([ ])/\\$1/g;
1643 d3805c61 hq
        if (!$Stabile::preserveimagesonremove) {
1644 95b003ff Origo
            `$sshcmd $macip "/bin/rmdir $esc_path"`;
1645
            $postreply .= "Status=OK Removing node user dir /mnt/stabile/node/$username on node $mac\n";
1646
        }
1647
    }
1648
    untie %nodereg;
1649
1650
    return $postreply;
1651
}
1652
1653
sub Remove {
1654 d3805c61 hq
    my ($path, $action, $obj, $preserve, $mac) = @_;
1655 95b003ff Origo
    if ($help) {
1656
        return <<END
1657 d3805c61 hq
DELETE:image,mac:
1658 95b003ff Origo
Removes an image from storage, i.e. completely deletes the image and its backups (be careful).
1659
END
1660
    }
1661
    $path = $imagereg{$path}->{'path'} if ($imagereg{$path}); # Check if we were passed a uuid
1662
    $path = $curimg if (!$path && $register{$curimg});
1663 d24d9a01 hq
    if (!$curimg && $path && !($path =~ /^\//)) {
1664
        $curimg = $path;
1665
        $path = '';
1666
    }
1667 95b003ff Origo
    if (!$path && $curimg && !($curimg =~ /\//) ) { # Allow passing only image name if we are deleting an app master
1668
        my $dspool = $stackspool;
1669
        $dspool = $spools[0]->{'path'} unless ($engineid eq $valve001id);
1670
        if ($curimg =~ /\.master.qcow2$/ && $register{"$dspool/$user/$curimg"}) {
1671
            $path = "$dspool/$user/$curimg";
1672
        } elsif ($isadmin && $curimg =~ /\.master.qcow2$/ && $register{"$dspool/common/$curimg"}) {
1673
            $path = "$dspool/common/$curimg";
1674
        }
1675
    }
1676
    utf8::decode($path);
1677
1678
    my $img = $register{$path};
1679
    my $status = $img->{'status'};
1680 d3805c61 hq
    $mac = $mac || $obj->{mac} || $img->{'mac'}; # Remove an image from a specific node
1681 95b003ff Origo
    my $name = $img->{'name'};
1682
    my $uuid = $img->{'uuid'};
1683
    utf8::decode($name);
1684
    my $type = $img->{'type'};
1685
    my $username = $img->{'user'};
1686
1687
    unless ($username && ($isadmin || $user eq $username) && !$isreadonly) {
1688
        return qq|[]|;
1689
#        $postmsg = "Cannot delete image";
1690
#        $postreply .= "Status=Error $postmsg\n";
1691
#        return $postreply;
1692
    }
1693
1694
    $uistatus = "deleting";
1695
    if ($status eq "unused" || $status eq "uploading" || $path =~ /(.+)\.master\.$type/) {
1696 3657de20 Origo
        my $haschildren;
1697
        my $child;
1698
        my $hasprimary;
1699
        my $primary;
1700 95b003ff Origo
        my $master = ($img->{'master'} && $img->{'master'} ne '--')?$img->{'master'}:'';
1701
        my $usedmaster = '';
1702
        my @regvalues = values %register;
1703
        foreach my $valref (@regvalues) {
1704
            if ($valref->{'master'} eq $path) {
1705
                $haschildren = 1;
1706
                $child = $valref->{'name'};
1707
            #    last;
1708
            }
1709
            if ($master) {
1710
                $usedmaster = 1 if ($valref->{'master'} eq $master && $valref->{'path'} ne $path); # Check if another image is also using this master
1711
            }
1712
        }
1713 9de5a3f1 hq
        if ($master && !$usedmaster && $register{$master}) {
1714 95b003ff Origo
            $register{$master}->{'status'} = 'unused';
1715
            $main::syslogit->($user, "info", "Freeing master $master");
1716
        }
1717 3657de20 Origo
        if ($type eq "qcow2") {
1718
            my @regkeys = (tied %register)->select_where("image2 = '$path'");
1719
            foreach my $k (@regkeys) {
1720
                my $val = $register{$k};
1721
                if ($val->{'image2'} eq $path) {
1722
                    $hasprimary = 1;
1723
                    $primary = $val->{'name'};
1724
                    last;
1725
                }
1726
            }
1727
        }
1728 95b003ff Origo
1729
        if ($haschildren) {
1730
            $postmsg = "Cannot delete image. This image is used as master by: $child";
1731
            $postreply .= "Status=Error $postmsg\n";
1732 3657de20 Origo
#        } elsif ($hasprimary) {
1733
#            $postmsg = "Cannot delete image. This image is used as secondary image by: $primary";
1734
#            $postreply .= "Status=Error $postmsg\n";
1735 95b003ff Origo
        } else {
1736
            if ($mac && $path =~ /\/mnt\/stabile\/node\//) {
1737
                unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return "Status=Error Cannot connect to DB\n";};
1738
                $macip = $nodereg{$mac}->{'ip'};
1739
                my $wakenode = ($nodereg{$mac}->{'status'} eq 'asleep' || $nodereg{$mac}->{'status'} eq 'waking');
1740
1741
                if ($wakenode) {
1742
                    my $tasks = $nodereg{$mac}->{'tasks'};
1743
                    my $upath = URI::Escape::uri_escape($path);
1744
                    $tasks .= "REMOVE $upath $user\n";
1745
                    $nodereg{$mac}->{'tasks'} = $tasks;
1746
                    tied(%nodereg)->commit;
1747
                    $postmsg = "We are waking up the node your image $name is on - it will be removed shortly";
1748
                    if ($nodereg{$mac}->{'status'} eq 'asleep') {
1749
                        require "$Stabile::basedir/cgi/nodes.cgi";
1750
                        $Stabile::Nodes::console = 1;
1751
                        Stabile::Nodes::wake($mac);
1752
                    }
1753
                    $register{$path}->{'status'} = $uistatus;
1754
                } else {
1755
                    my $esc_path = $path;
1756
                    $esc_path =~ s/([ ])/\\$1/g;
1757
                    if ($preserve) {
1758
                        `$sshcmd $macip "/bin/mv $esc_path $esc_path.bak"`;
1759
                    } else {
1760
                        `$sshcmd $macip "/usr/bin/unlink $esc_path"`;
1761
                    }
1762
                    `$sshcmd $macip "/usr/bin/unlink $esc_path.meta"`;
1763
                    delete $register{$path};
1764
                }
1765
                untie %nodereg;
1766
1767
            } else {
1768
                if ($preserve) {
1769
                    `/bin/mv "$path" "$path.bak"`;
1770
                } else {
1771
                    unlink $path;
1772
                }
1773
                if (substr($path,-5) eq '.vmdk') {
1774
                    if ( -s (substr($path,0,-5) . "-flat.vmdk")) {
1775
                        my $flat = substr($path,0,-5) . "-flat.vmdk";
1776
                        if ($preserve) {
1777
                            `/bin/mv $flat "$flat.bak"`;
1778
                        } else {
1779
                            unlink($flat);
1780
                        }
1781
                    } elsif ( -e (substr($path,0,-5) . "-s001.vmdk")) {
1782
                        my $i = 1;
1783
                        my $rmpath = substr($path,0,-5);
1784
                        while (-e "$rmpath-s00$i.vmdk") {
1785
                            if ($preserve) {
1786
                                `/bin/mv "$rmpath-s00$i.vmdk" "$rmpath-s00$i.vmdk.bak"`;
1787
                            } else {
1788
                                unlink("$rmpath-s00$i.vmdk");
1789
                            }
1790
                            $i++;
1791
                        }
1792
                    }
1793
                }
1794
                unlink "$path.meta" if (-e "$path.meta");
1795
                delete $register{$path};
1796
            }
1797
1798
            my $subdir = "";
1799
            my($bname, $dirpath) = fileparse($path);
1800 27512919 Origo
            if ($dirpath =~ /.+\/$buser(\/.+)?\//) {
1801 95b003ff Origo
                $subdir = $1;
1802
            }
1803
            my $bpath = "$backupdir/$user$subdir/$bname";
1804
            $bpath = $1 if ($bpath =~ /(.+)/);
1805
            # Remove backup of image if it exists
1806
            if (-d "$bpath") {
1807
                `/bin/rm -rf "$bpath"`;
1808
            }
1809
1810
#            $postmsg = "Deleted image $name ($path, $uuid, $mac)";
1811 6372a66e hq
#            $postreply =  "[]";
1812 95b003ff Origo
#            $postreply .=  "Status=deleting OK $postmsg\n";
1813
            updateBilling("delete $path");
1814
            $main::syslogit->($user, "info", "$uistatus $type image: $name: $path");
1815
            if ($status eq 'downloading') {
1816
                my $daemon = Proc::Daemon->new(
1817
                    work_dir => '/usr/local/bin',
1818
                    exec_command => qq|pkill -f "$path"|
1819
                ) or do {$postreply .= "Status=ERROR $@\n";};
1820
                my $pid = $daemon->Init();
1821
            }
1822
            sleep 1;
1823
        }
1824
    } else {
1825
        $postmsg = "Cannot delete $type image with status: $status";
1826
        $postreply .= "Status=ERROR $postmsg\n";
1827
    }
1828 6372a66e hq
    return "[]";
1829 95b003ff Origo
}
1830
1831
# Clone image $path to destination storage pool $istoragepool, possibly changing backup schedule $bschedule
1832
sub Clone {
1833 c899e439 Origo
    my ($path, $action, $obj, $istoragepool, $imac, $name, $bschedule, $buildsystem, $managementlink, $appid, $wait, $vcpu, $mem) = @_;
1834 95b003ff Origo
    if ($help) {
1835
        return <<END
1836
GET:image,name,storagepool,wait:
1837
Clones an image. In the case of cloning a master image, a child is produced.
1838
Only cloning to same storagepool is supported, with the exception of cloning to nodes (storagepool -1).
1839
If you want to perform the clone synchronously, set wait to 1;
1840
END
1841
    }
1842
    $postreply = "" if ($buildsystem);
1843
    return "Status=Error no valid user\n" unless ($user);
1844
1845
    unless ($register{$path} && ($register{$path}->{'user'} eq $user
1846
                || $register{$path}->{'user'} eq 'common'
1847
                || $register{$path}->{'user'} eq $billto
1848 04c16f26 hq
                || $register{$path}->{'user'} eq $Stabile::Systems::billto
1849 95b003ff Origo
                || $isadmin)) {
1850
        $postreply .= "Status=ERROR Cannot clone!\n";
1851
        return;
1852
    }
1853 51e32e00 hq
    if ($register{$path}->{master}) { # master has a master - must exist
1854
        unless ( $register{$register{$path}->{master}} ) {
1855
            $main::syslogit->($user, "info", "Unable to clone $path - missing parent image");
1856
            $postreply .= "Status=ERROR A parent image is missing, please wait for download to finish or download again!\n";
1857
            return "Status=ERROR A parent image is missing, please wait for download to finish or download again!\n";
1858
        }
1859
    }
1860 95b003ff Origo
    $istoragepool = $istoragepool || $obj->{storagepool};
1861
    $name = $name || $obj->{name};
1862
    $wait = $wait || $obj->{wait};
1863 51e32e00 hq
    my $img = $register{$path};
1864
    my $status = $img->{'status'};
1865
    my $type = $img->{'type'};
1866
    my $master = $img->{'master'};
1867
    my $notes = $img->{'notes'};
1868
    my $image2 = $img->{'image2'};
1869
    my $snap1 = $img->{'snap1'};
1870
    $managementlink = $img->{'managementlink'} unless ($managementlink);
1871
    $appid = $img->{'appid'} unless ($appid);
1872
    my $upgradelink = $img->{'upgradelink'} || '';
1873
    my $terminallink = $img->{'terminallink'} || '';
1874
    my $version = $img->{'version'} || '';
1875
    my $regmac = $img->{'mac'};
1876
1877
    my $virtualsize = $img->{'virtualsize'};
1878 95b003ff Origo
    my $dindex = 0;
1879
1880 04c16f26 hq
    my($bname, $dirpath, $suffix) = fileparse($path, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
1881 95b003ff Origo
    $path =~ /(.+)\.$type/;
1882
    my $namepath = $1;
1883
    if ($namepath =~ /(.+)\.master/) {
1884
        $namepath = $1;
1885
    }
1886
    if ($namepath =~ /(.+)\.clone\d+/) {
1887
        $namepath = $1;
1888
    }
1889
    if ($namepath =~ /.+\/common\/(.+)/) { # Support one subdir
1890
        $namepath = $1;
1891
    } elsif ($namepath =~ /.+\/$user\/(.+)/) { # Support one subdir
1892
        $namepath = $1;
1893
    } elsif ($namepath =~ /.+\/(.+)/) { # Extract only the name
1894
        $namepath = $1;
1895
    }
1896
1897
    # Find unique path in DB across storage pools
1898
    my $upath;
1899
    my $npath = "/mnt/stabile/node/$user/$namepath"; # Also check for uniqueness on nodes
1900
    my $i = 1;
1901
    foreach my $spool (@spools) {
1902
        $upath = $spool->{'path'} . "/$user/$namepath";
1903
        while ($register{"$upath.clone$i.$type"} || $register{"$npath.clone$i.$type"}) {$i++;};
1904
    }
1905
    $upath = "$spools[$istoragepool]->{'path'}/$user/$namepath";
1906
1907 51e32e00 hq
    my $iname = $img->{'name'};
1908 95b003ff Origo
    $iname = "$name" if ($name); # Used when name supplied when building a system
1909
    $iname =~ /(.+)( \(master\))/;
1910
    $iname = $1 if $2;
1911
    $iname =~ /(.+)( \(clone\d*\))/;
1912
    $iname = $1 if $2;
1913
    $iname =~ /(.+)( \(child\d*\))/;
1914
    $iname = $1 if $2;
1915
    my $ippath = $path;
1916
    my $macip;
1917
    my $ug = new Data::UUID;
1918
    my $newuuid = $ug->create_str();
1919
    my $wakenode;
1920
    my $identity;
1921
1922
    # We only support cloning images to nodes - not the other way round
1923
    if ($imac && $regmac && $imac ne $regmac) {
1924
        $postreply .= "Status=ERROR Cloning from a node not supported\n";
1925
        return $postreply;
1926
    }
1927
1928
    if ($istoragepool==-1) {
1929
    # Find the ip address of target node
1930 c899e439 Origo
        ($imac, $macip, $dindex, $wakenode, $identity) = locateNode($virtualsize, $imac, $vcpu, $mem);
1931 95b003ff Origo
        if ($identity eq 'local_kvm') {
1932 c899e439 Origo
            $postreply .= "Status=OK Cloning to local node with index: $dindex\n";
1933 95b003ff Origo
            $istoragepool = 0; # cloning to local node
1934 3657de20 Origo
            $upath = "$spools[$istoragepool]->{'path'}/$user/$namepath";
1935 95b003ff Origo
        } elsif (!$macip) {
1936 c899e439 Origo
            $postreply .= "Status=ERROR Unable to locate node with sufficient ressources\n";
1937
            $postmsg = "Unable to locate node with sufficient ressources!";
1938 95b003ff Origo
            $main::updateUI->({tab=>"images", user=>$user, type=>"message", message=>$postmsg});
1939
            return $postreply;
1940
        } else {
1941 c899e439 Origo
            $postreply .= "Status=OK Cloning to $macip with index: $dindex\n";
1942 95b003ff Origo
            $ippath = "$macip:$path";
1943
            $upath = "/mnt/stabile/node/$user/$namepath";
1944
        }
1945
    }
1946
    my $ipath = "$upath.clone$i.$type";
1947
1948
    if ($bschedule eq 'daily7' || $bschedule eq 'daily14') {
1949
         $bschedule = "manually" if ($istoragepool!=-1 && (!$spools[$istoragepool]->{'rdiffenabled'} || !$spools[$istoragepool]->{'lvm'}));
1950
    } elsif ($bschedule ne 'manually') {
1951
        $bschedule = '';
1952
    }
1953
1954
# Find storage pool with space
1955
    my $foundstorage = 1;
1956
    if (overStorage($virtualsize, $istoragepool, $imac)) {
1957
        $foundstorage = 0;
1958
        foreach my $p (@spools) {
1959
            if (overStorage($virtualsize, $p->{'id'}, $imac)) {
1960
                ;
1961
            } else {
1962
                $istoragepool = $p->{'id'};
1963
                $foundstorage = 1;
1964
                last;
1965
            }
1966
        }
1967
    }
1968
1969
# We allow multiple clone operations on master images
1970
    if ($status ne "used" && $status ne "unused" && $status ne "paused" && $path !~ /(.+)\.master\.$type/) {
1971
        $postreply .= "Status=ERROR Please shut down your virtual machine before cloning\n";
1972
1973
    } elsif ($type eq 'vmdk' && (-e "$dirpath$bname-s001$suffix" || -e "$dirpath$bname-flat$suffix")) {
1974
        $postreply .= "Status=ERROR Cannot clone this image - please convert first!\n";
1975
1976
    } elsif (overQuotas($virtualsize, ($istoragepool==-1))) {
1977
        $postreply .= "Status=ERROR Over quota (". overQuotas($virtualsize, ($istoragepool==-1)) . ") cloning: $name\n";
1978
1979
    } elsif (!$foundstorage) {
1980
        $postreply .= "Status=ERROR Not enough storage ($virtualsize) in destination pool $istoragepool $imac cloning: $name\n";
1981
1982
    } elsif ($wakenode && !($path =~ /(.+)\.master\.$type/)) { # For now we dont support simply copying images on sleeping nodes
1983
        $postreply .= "Status=ERROR We are waking up the node your image $name is on, please try again later\n";
1984
        require "$Stabile::basedir/cgi/nodes.cgi";
1985
        $Stabile::Nodes::console = 1;
1986
        Stabile::Nodes::wake($imac);
1987
    } elsif ($type eq "img" || $type eq "qcow2" || $type eq "vmdk") {
1988
        my $masterimage2 = $register{"$path"}->{'image2'};
1989
    # Cloning a master produces a child
1990
        if ($type eq "qcow2" && $path =~ /(.+)\.master\.$type/) {
1991
            $uistatus = "cloning";
1992
    # VBoxManage probably does a more efficient job at cloning than simply copying
1993 04c16f26 hq
        } elsif ($type eq "vdi" || $type eq "vhd" || $type eq "vhdx") {
1994 95b003ff Origo
            $uistatus = "vcloning";
1995
    # Cloning another child produces a sibling with the same master
1996
        } else {
1997
            $uistatus = "copying";
1998
        }
1999
        $uipath = $path;
2000
        eval {
2001
            $register{$ipath} = {
2002
                uuid=>$newuuid,
2003
                master=>($uistatus eq 'cloning')?$path:$master,
2004
                name=>"$iname (clone$i)",
2005
                notes=>$notes,
2006
                image2=>$image2,
2007
                snap1=>($uistatus eq 'copying')?$snap1:'',
2008
                storagepool=>$istoragepool,
2009
                status=>$uistatus,
2010
                mac=>($istoragepool == -1)?$imac:"",
2011
                size=>0,
2012
                realsize=>0,
2013
                virtualsize=>$virtualsize,
2014
                bschedule=>$bschedule,
2015
                type=>"qcow2",
2016
                created=>$current_time,
2017
                user=>$user
2018
            };
2019
            $register{$ipath}->{'managementlink'} = $managementlink if ($managementlink);
2020
            $register{$ipath}->{'appid'} = $appid if ($appid);
2021
            $register{$ipath}->{'upgradelink'} = $upgradelink if ($upgradelink);
2022
            $register{$ipath}->{'terminallink'} = $terminallink if ($terminallink);
2023
            $register{$ipath}->{'version'} = $version if ($version);
2024
            $register{$path}->{'status'} = $uistatus;
2025
            my $dstatus = ($buildsystem)?'bcloning':$uistatus;
2026
            if ($wakenode) { # We are waking a node for clone operation, so ask movepiston to do the work
2027
                unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2028
                my $tasks = $nodereg{$imac}->{'tasks'};
2029
                $upath = URI::Escape::uri_escape($ipath);
2030
                $tasks .= "BCLONE $upath $user\n";
2031
                $nodereg{$imac}->{'tasks'} = $tasks;
2032
                tied(%nodereg)->commit;
2033
                untie %nodereg;
2034
            } elsif ($wait) {
2035
                my $cmd = qq|steamExec $user $dstatus $status "$ippath" "$ipath"|;
2036
                $cmd = $1 if ($cmd =~ /(.+)/);
2037
                `$cmd`;
2038
            } else {
2039
                my $daemon = Proc::Daemon->new(
2040
                        work_dir => '/usr/local/bin',
2041
                        exec_command => "perl -U steamExec $user $dstatus $status \"$ippath\" \"$ipath\""
2042
                    ) or do {$postreply .= "Status=ERROR $@\n";};
2043
                my $pid = $daemon->Init();
2044
            }
2045
            $postreply .= "Status=$uistatus OK $uistatus to: $iname (clone$i)" . ($isadmin? " -> $ipath ":"") . "\n";
2046
            $postreply .= "Status=OK uuid: $newuuid\n"; # if ($console || $api);
2047
            $postreply .= "Status=OK path: $ipath\n"; # if ($console || $api);
2048
            $postreply .= "Status=OK mac: $imac\n"; # if ($console || $api);
2049
            $postreply .= "Status=OK wakenode: $wakenode\n"; # if ($console || $api);
2050
            $main::syslogit->($user, "info", "$uistatus $wakenode $type image: $name $uuid to $ipath");
2051
            1;
2052
        } or do {$postreply .= "Status=ERROR $@\n";}
2053
2054
    } else {
2055
        $postreply .= "Status=ERROR Not a valid type: $type\n";
2056
    }
2057
    tied(%register)->commit;
2058
    $main::updateUI->({tab=>"images", user=>$user, type=>"update"});
2059
    return $postreply;
2060
}
2061
2062
2063
# Link master image to fuel
2064
sub Linkmaster {
2065
    my ($mpath, $action) = @_;
2066
    if ($help) {
2067
        return <<END
2068
GET:image:
2069
Link master image to fuel
2070
END
2071
    }
2072
    my $res;
2073
2074
    return "Your account does not have the necessary privileges\n" if ($isreadonly);
2075
    return "Please specify master image to link\n" unless ($mpath);
2076
2077
    unless ($mpath =~ /^\//) { # We did not get an absolute path, look for it in users storagepools
2078
        foreach my $p (@spools) {
2079
            my $dir = $p->{'path'};
2080
            my $cpath = "$dir/common/$mpath";
2081
            my $upath = "$dir/$user/$mpath";
2082
            if (-e $cpath) {
2083
                $mpath = $cpath;
2084
                last;
2085
            } elsif (-e $upath) {
2086
                $mpath = $upath;
2087
                last;
2088
            }
2089
        }
2090
    }
2091
    my $img = $register{$mpath};
2092
    $mpath = $img->{"path"};
2093
    $imguser = $img->{"user"};
2094
    if (!$mpath || ($imguser ne $user && $imguser ne 'common' && !$isadmin)) {
2095
        $postreply = qq|{"status": "Error", "message": "No privs. or not found @_[0]"}|;
2096
        return $postreply;
2097
    }
2098
    my $status = $img->{"status"};
2099
    my $type = $img->{"type"};
2100
    $mpath =~ /(.+)\/(.+)\.master\.$type$/;
2101
    my $namepath = $2;
2102
    my $msg;
2103
    if ($status ne "unused" && $status ne "used") {
2104
        $res .= qq|{"status": "Error", "message": "Only used and unused images may be linked ($status, $mpath)."}|;
2105
    } elsif (!( $mpath =~ /(.+)\.master\.$type$/ ) ) {
2106
        $res .= qq|{"status": "Error", "message": "You can only link master images"}|;
2107
    } elsif ($type eq "qcow2") {
2108
        my $pool = $img->{'storagepool'};
2109
        `chmod 444 "$mpath"`;
2110
        my $linkpath = $tenderpathslist[$pool] . "/$user/fuel/$namepath.link.master.$type";
2111
        my $fuellinkpath = "/mnt/fuel/pool$pool/$namepath.link.master.$type";
2112
        if (-e $tenderpathslist[$pool] . "/$user/fuel") { # master should be on fuel-enabled storage
2113
            unlink ($linkpath) if (-e $linkpath);
2114
            `ln "$mpath" "$linkpath"`;
2115
        } else {
2116
            foreach my $p (@spools) {
2117
                my $dir = $p->{'path'};
2118
                my $poolid = $p->{'id'};
2119
                if (-e "$dir/$user/fuel") {
2120
                    $linkpath = "$dir/$user/fuel/$namepath.copy.master.$type";
2121
                    $fuellinkpath = "/mnt/fuel/pool$poolid/$namepath.copy.master.$type";
2122
                    unlink ($linkpath) if (-e $linkpath);
2123
                    `cp "$mpath" "$linkpath"`;
2124
                    $msg = "Different file systems, master copied";
2125
                    last;
2126
                }
2127
            }
2128
        }
2129
        $res .= qq|{"status": "OK", "message": "$msg", "path": "$fuellinkpath", "linkpath": "$linkpath", "masterpath": "$mpath"}|;
2130
    } else {
2131
        $res .= qq|{"status": "Error", "message": "You can only link qcow2 images"}|;
2132
    }
2133
    $postreply = $res;
2134
    return $res;
2135
}
2136
2137
# Link master image to fuel
2138
sub unlinkMaster {
2139
    my $mpath = shift;
2140
    unless ($mpath =~ /^\//) { # We did not get an absolute path, look for it in users storagepools
2141
        foreach my $p (@spools) {
2142
            my $dir = $p->{'path'};
2143
            my $upath = "$dir/$user/fuel/$mpath";
2144
            if (-e $upath) {
2145
                $mpath = "/mnt/fuel/pool$p->{id}/$mpath";
2146
                last;
2147
            }
2148
        }
2149
    }
2150
2151
    $mpath =~ /\/pool(\d+)\/(.+)\.link\.master\.qcow2$/;
2152
    my $pool = $1;
2153
    my $namepath = $2;
2154
    if (!( $mpath =~ /\/pool(\d+)\/(.+)\.link\.master\.qcow2$/ ) ) {
2155
        $postreply = qq|{"status": "Error", "message": "You can only unlink linked master images ($mpath)"}|;
2156
    } else {
2157
        my $linkpath = $tenderpathslist[$pool] . "/$user/fuel/$namepath.link.master.qcow2";
2158
        if (-e $linkpath) {
2159
            `chmod 644 "$linkpath"`;
2160
            `rm "$linkpath"`;
2161
            $postreply = qq|{"status": "OK", "message": "Link removed", "path": "/mnt/fuel/pool$pool/$namepath.qcow2", "linkpath": "$linkpath"}|;
2162
        } else {
2163
            $postreply = qq|{"status": "Error", "message": "Link $linkpath does not exists."}|;
2164
        }
2165
    }
2166
}
2167
2168
#sub do_getstatus {
2169
#    my ($img, $action) = @_;
2170
#    if ($help) {
2171
#        return <<END
2172
#GET::
2173
#END
2174
#    }
2175
#    # Allow passing only image name if we are dealing with an app master
2176
#    my $dspool = $stackspool;
2177
#    my $masteruser = $params{'masteruser'};
2178
#    my $destuser = $params{'destuser'};
2179
#    my $destpath;
2180
#    $dspool = $spools[0]->{'path'} unless ($engineid eq $valve001id);
2181
#    if (!$register{$img} && $img && !($img =~ /\//) && $masteruser) {
2182
#        if ($img =~ /\.master\.qcow2$/ && $register{"$dspool/$masteruser/$img"}) {
2183
#            if ($ismanager || $isadmin
2184
#                || ($userreg{$masteruser}->{'billto'} eq $user)
2185
#            ) {
2186
#                $img = "$dspool/$masteruser/$img";
2187
#            }
2188
#        }
2189
#    }
2190
#    my $status = $register{$img}->{'status'};
2191
#    if ($status) {
2192
#        my $iuser = $register{$img}->{'user'};
2193
#        # First check if user is allowed to access image
2194
#        if ($iuser ne $user && $iuser ne 'common' && $userreg{$iuser}->{'billto'} ne $user) {
2195
#            $status = '' unless ($isadmin || $ismanager);
2196
#        }
2197
#        if ($destuser) { # User is OK, now check if destination exists
2198
#            my ($dest, $folder) = fileparse($img);
2199
#            $destpath = "$dspool/$destuser/$dest";
2200
#            $status = 'exists' if ($register{$destpath} || -e ($destpath));
2201
#        }
2202
#    }
2203
#    my $res;
2204
#    $res .= $Stabile::q->header('text/plain') unless ($console);
2205
#    $res .= "$status";
2206
#    return $res;
2207
#}
2208
2209 d3805c61 hq
sub do_move {
2210
    my ($image, $action, $obj) = @_;
2211
    if ($help) {
2212
        return <<END
2213
GET:image,user,storagepool,mac,precreate:
2214
Move image to a different storage pool or user
2215
END
2216
    }
2217
    return "Your account does not have the necessary privileges\n" if ($isreadonly);
2218
#    $postreply = qq/"$curimg || $image, $obj->{user} || $user, $obj->{storagepool}, $obj->{mac}, 0, $obj->{precreate}, $nodereg->{$obj->{mac}}->{name}"/;
2219
#    return $postreply;
2220
    my $res = Move($curimg || $image, $obj->{user} || $user, $obj->{storagepool}, $obj->{mac},0, $obj->{precreate});
2221
    return header() . $res;
2222
}
2223 95b003ff Origo
2224
sub Move {
2225 d3805c61 hq
    my ($path, $iuser, $istoragepool, $mac, $force, $precreate) = @_;
2226 95b003ff Origo
    # Allow passing only image name if we are deleting an app master
2227
    my $dspool = $stackspool;
2228
    my $masteruser = $params{'masteruser'};
2229
    $dspool = $spools[0]->{'path'} unless ($engineid eq $valve001id);
2230
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2231
    if (!$register{$path} && $path && !($path =~ /\//) && $masteruser) {
2232
        if ($path =~ /\.master\.qcow2$/ && $register{"$dspool/$masteruser/$path"}) {
2233
            if ($ismanager || $isadmin
2234
                || ($userreg{$masteruser}->{'billto'} eq $user && $iuser eq $user)
2235
                || ($masteruser eq $user && $userreg{$iuser}->{'billto'} eq $user)
2236
            ) {
2237
                $path = "$dspool/$masteruser/$path";
2238
            }
2239
        }
2240
    }
2241 48fcda6b Origo
    my $regimg = $register{$path};
2242
    $istoragepool = ($istoragepool eq '0' || $istoragepool)? $istoragepool: $regimg->{'storagepool'};
2243 d3805c61 hq
    $mac = $mac || $regimg->{'mac'}; # destination mac
2244 48fcda6b Origo
    my $bschedule = $regimg->{'bschedule'};
2245
    my $name = $regimg->{'name'};
2246
    my $status = $regimg->{'status'};
2247
    my $type = $regimg->{'type'};
2248
    my $reguser = $regimg->{'user'};
2249
    my $regstoragepool = $regimg->{'storagepool'};
2250
    my $virtualsize = $regimg->{'virtualsize'};
2251 95b003ff Origo
2252
    my $newpath;
2253
    my $newdirpath;
2254
    my $oldpath = $path;
2255 d24d9a01 hq
    my $olddirpath = $path;
2256 95b003ff Origo
    my $newuser = $reguser;
2257
    my $newstoragepool = $regstoragepool;
2258
    my $haschildren;
2259 3657de20 Origo
    my $hasprimary;
2260 95b003ff Origo
    my $child;
2261 3657de20 Origo
    my $primary;
2262 95b003ff Origo
    my $macip;
2263
    my $alreadyexists;
2264
    my $subdir;
2265 27512919 Origo
#    $subdir = $1 if ($path =~ /\/$reguser(\/.+)\//);
2266
    $subdir = $1 if ($path =~ /.+\/$reguser(\/.+)?\//);
2267 95b003ff Origo
    my $restpath;
2268 27512919 Origo
    $restpath = $1 if ($path =~ /.+\/$reguser\/(.+)/);
2269 95b003ff Origo
2270
    if ($type eq "qcow2" && $path =~ /(.+)\.master\.$type/) {
2271
        my @regkeys = (tied %register)->select_where("master = '$path'");
2272
        foreach my $k (@regkeys) {
2273
            my $val = $register{$k};
2274
            if ($val->{'master'} eq $path) {
2275
                $haschildren = 1;
2276
                $child = $val->{'name'};
2277
                last;
2278
            }
2279
        }
2280
    }
2281 3657de20 Origo
    if ($type eq "qcow2") {
2282
        my @regkeys = (tied %register)->select_where("image2 = '$path'");
2283
        foreach my $k (@regkeys) {
2284
            my $val = $register{$k};
2285
            if ($val->{'image2'} eq $path) {
2286
                $hasprimary = 1;
2287
                $primary = $val->{'name'};
2288
                last;
2289
            }
2290
        }
2291
    }
2292 95b003ff Origo
    if (!$register{$path}) {
2293
        $postreply .= "Status=ERROR Unable to move $path (invalid path, $path, $masteruser)\n" unless ($istoragepool eq '--' || $regstoragepool eq '--');
2294
    } elsif ($type eq 'vmdk' && -s (substr($path,0,-5) . "-flat.vmdk") || -s (substr($path,0,-5) . "-s001.vmdk")) {
2295
        $postreply .= "Status=Error Cannot move this image. Please convert before moving\n";
2296 d3805c61 hq
    } elsif ($precreate && ($register{$path}->{snap1} && $register{$path}->{snap1} ne '--') && !$force) {
2297
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"Please remove snapshots from image $name before stormoving server."});
2298
        $postreply .= "Status=Error Cannot stormove an image with snapshots\n";
2299 95b003ff Origo
# Moving an image to a different users dir
2300
    } elsif ($iuser ne $reguser && ($status eq "unused" || $status eq "used")) {
2301
        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2302
        my @accounts = split(/,\s*/, $userreg{$tktuser}->{'accounts'});
2303
        my @accountsprivs = split(/,\s*/, $userreg{$tktuser}->{'accountsprivileges'});
2304
        %ahash = ($tktuser, $userreg{$tktuser}->{'privileges'} || 'r' ); # Include tktuser in accounts hash
2305
        for my $i (0 .. scalar @accounts)
2306
        {
2307
            next unless $accounts[$i];
2308
            $ahash{$accounts[$i]} = $accountsprivs[$i] || 'u';
2309
        }
2310
2311
        if ((($isadmin || $ismanager ) && $iuser eq 'common') # Check if user is allowed to access account
2312
                || ($isadmin && $userreg{$iuser})
2313
                || ($user eq $engineuser)
2314
                || ($userreg{$iuser}->{'billto'} eq $user)
2315
                || ($ahash{$iuser} && !($ahash{$iuser} =~ /r/))
2316
        ) {
2317
            if ($haschildren) {
2318 3657de20 Origo
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"Error Cannot move image. This image is used as master by: $child"});
2319 95b003ff Origo
                $postreply .= "Status=Error Cannot move image. This image is used as master by: $child\n";
2320 3657de20 Origo
            } elsif ($hasprimary) {
2321
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"Error Cannot move image. This image is used as secondary image by: $primary"});
2322
                $postreply .= "Status=Error Cannot move image. This image is used as secondary image by: $primary\n";
2323 95b003ff Origo
            } else {
2324
                if ($regstoragepool == -1) { # The image is located on a node
2325
                    my $uprivs = $userreg{$iuser}->{'privileges'};
2326
                    if ($uprivs =~ /[an]/) {
2327
                        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2328
                        $macip = $nodereg{$mac}->{'ip'};
2329 d3805c61 hq
                        my $oldmacip = $nodereg{$regimg->{'mac'}}->{'ip'};
2330 95b003ff Origo
                        untie %nodereg;
2331 d3805c61 hq
                        $oldpath = "$oldmacip:/mnt/stabile/node/$reguser/$restpath";
2332 95b003ff Origo
                        $newdirpath = "/mnt/stabile/node/$iuser/$restpath";
2333
                        $newpath = "$macip:$newdirpath";
2334
                        $newuser = $iuser;
2335
                        $newstoragepool = $istoragepool;
2336
                # Check if image already exists in target dir
2337
                        $alreadyexists = `ssh -l irigo -i /var/www/.ssh/id_rsa_www -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $macip "perl -e 'print 1 if -e q{/mnt/stabile/node/$iuser/$restpath}'"`;
2338
                    } else {
2339
                        $postreply .= "Status=Error Target account $iuser cannot use node storage\n";
2340
                    }
2341
                } else {
2342 48fcda6b Origo
                    my $reguser = $userreg{$iuser};
2343
                    my $upools = $reguser->{'storagepools'} || $Stabile::config->get('STORAGE_POOLS_DEFAULTS') || "0";
2344 95b003ff Origo
                    my @nspools = split(/, ?/, $upools);
2345
                    my %ispools = map {$_=>1} @nspools; # Build a hash with destination users storagepools
2346
                    if ($ispools{$regstoragepool}) { # Destination user has access to image's storagepool
2347
                        $newpath = "$spools[$regstoragepool]->{'path'}/$iuser/$restpath";
2348
                    } else {
2349
                        $newpath = "$spools[0]->{'path'}/$iuser/$restpath";
2350
                    }
2351
                    $newdirpath = $newpath;
2352
                    $newuser = $iuser;
2353
            # Check if image already exists in target dir
2354
                    $alreadyexists = -e $newpath;
2355
                }
2356
            }
2357
        } else {
2358
            $postreply .= "Status=Error Cannot move image to account $iuser $ahash{$iuser} - not allowed\n";
2359
        }
2360
# Moving an image to a different storage pool
2361
    } elsif ($istoragepool ne '--' &&  $regstoragepool ne '--' && $istoragepool ne $regstoragepool
2362 d3805c61 hq
            && ($status eq "unused" || $status eq "used" || $status eq "paused" || ($status eq "active" && $precreate))) {
2363 95b003ff Origo
2364
        my $dindex;
2365
        my $wakenode;
2366
        if ($istoragepool == -1 && $regstoragepool != -1) {
2367
            ($mac, $macip, $dindex, $wakenode) = locateNode($virtualsize, $mac);
2368
        }
2369
2370
        $main::syslogit->($user, "info", "Moving $name from $regstoragepool to $istoragepool $macip $wakenode");
2371
2372
        if ($haschildren) {
2373 3657de20 Origo
            $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"ERROR Unable to move $name (has children)"});
2374 95b003ff Origo
            $postreply .= "Status=ERROR Unable to move $name (has children)\n";
2375 3657de20 Origo
        } elsif ($hasprimary) {
2376
            $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"Error Cannot move image. This image is used as secondary image by: $primary"});
2377
            $postreply .= "Status=Error Cannot move image. This image is used as secondary image by: $primary\n";
2378 95b003ff Origo
        } elsif ($wakenode) {
2379
            $postreply .= "Status=ERROR All available nodes are asleep moving $name, waking $mac, please try again later\n";
2380 3657de20 Origo
            $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"All available nodes are asleep moving $name, waking $mac, please try again later"});
2381 95b003ff Origo
            require "$Stabile::basedir/cgi/nodes.cgi";
2382
            $Stabile::Nodes::console = 1;
2383
            Stabile::Nodes::wake($mac);
2384
        } elsif (overStorage($virtualsize, $istoragepool+0, $mac)) {
2385 3657de20 Origo
            $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"ERROR Out of storage in destination pool $istoragepool $mac moving: $name"});
2386 95b003ff Origo
            $postreply .= "Status=ERROR Out of storage in destination pool $istoragepool $mac moving: $name\n";
2387
        } elsif (overQuotas($virtualsize, ($istoragepool==-1))) {
2388 3657de20 Origo
            $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"ERROR Over quota (". overQuotas($virtualsize, ($istoragepool==-1)) . ") moving: $name"});
2389 95b003ff Origo
            $postreply .= "Status=ERROR Over quota (". overQuotas($virtualsize, ($istoragepool==-1)) . ") moving: $name\n";
2390
        } elsif ($istoragepool == -1 && $regstoragepool != -1 && $path =~ /\.master\.$type/) {
2391
            $postreply .= "Status=ERROR Unable to move $name (master images are not supported on node storage)\n";
2392 3657de20 Origo
            $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"Unable to move $name (master images are not supported on node storage)"});
2393 95b003ff Origo
    # Moving to node
2394
        } elsif ($istoragepool == -1 && $regstoragepool != -1) {
2395 d3805c61 hq
            if (index($privileges,"a")!=-1 || index($privileges,"n")!=-1 || index($Stabile::privileges,"a")!=-1 || index($Stabile::privileges,"n")!=-1) { # Privilege "n" means user may use node storage
2396 95b003ff Origo
                if ($macip) {
2397
                    $newdirpath = "/mnt/stabile/node/$reguser/$restpath";
2398
                    $newpath = "$macip:$newdirpath";
2399
                    $newstoragepool = $istoragepool;
2400
            # Check if image already exists in target dir
2401
                    $alreadyexists = `ssh -l irigo -i /var/www/.ssh/id_rsa_www -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $macip "perl -e 'print 1 if -e q{/mnt/stabile/node/$reguser/$restpath}'"`;
2402 d24d9a01 hq
2403 95b003ff Origo
                } else {
2404
                    $postreply .= "Status=ERROR Unable to move $name (not enough space)\n";
2405
                }
2406
            } else {
2407 d3805c61 hq
                $postreply .= "Status=ERROR Unable to move $name (no node privileges)\n";
2408 95b003ff Origo
            }
2409
    # Moving from node
2410
        } elsif ($regstoragepool == -1 && $istoragepool != -1 && $spools[$istoragepool]) {
2411 d3805c61 hq
            if (index($privileges,"a")!=-1 || index($privileges,"n")!=-1 && $mac || index($Stabile::privileges,"a")!=-1 || index($Stabile::privileges,"n")!=-1 && $mac) { # Privilege "n" means user may use node storage
2412 95b003ff Origo
                unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2413 d3805c61 hq
                $macip = $nodereg{$mac}->{'ip'}; # $mac is set to existing image's mac since no destination mac was specified
2414 95b003ff Origo
                untie %nodereg;
2415
                $newpath = "$spools[$istoragepool]->{'path'}/$reguser/$restpath";
2416
                $newdirpath = $newpath;
2417
                $oldpath = "$macip:/mnt/stabile/node/$reguser/$restpath";
2418
                $newstoragepool = $istoragepool;
2419
        # Check if image already exists in target dir
2420
                $alreadyexists = -e $newpath;
2421
            } else {
2422 d3805c61 hq
                $postreply .= "Status=ERROR Unable to move $name - you must specify a node\n";
2423 95b003ff Origo
            }
2424
        } elsif ($spools[$istoragepool]) { # User has access to storagepool
2425
            $newpath = "$spools[$istoragepool]->{'path'}/$reguser/$restpath";
2426
            $newdirpath = $newpath;
2427
            $newstoragepool = $istoragepool;
2428
            $alreadyexists = -e $newpath && -s $newpath;
2429
        } else {
2430
            $postreply .= "Status=ERROR Cannot move image. This image is used as master by: $child\n";
2431
        }
2432
    } else {
2433
        $postreply .= "Status=ERROR Unable to move $path (bad status or pool $status, $reguser, $iuser, $regstoragepool, $istoragepool)\n" unless ($istoragepool eq '--' || $regstoragepool eq '--');
2434
    }
2435
    untie %userreg;
2436
2437 48fcda6b Origo
    if ($alreadyexists && !$force) {
2438
        $postreply = "Status=ERROR Image \"$name\" already exists in destination\n";
2439
        return $postreply;
2440 95b003ff Origo
    }
2441
# Request actual move operation
2442
    elsif ($newpath) {
2443
        if ($newstoragepool == -1) {
2444
            my $diruser = $iuser || $reguser;
2445
            `ssh -l irigo -i /var/www/.ssh/id_rsa_www -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $macip "/bin/mkdir -v /mnt/stabile/node/$diruser"`; # rsync will create the last dir if needed
2446
        }
2447
        if ($subdir && $istoragepool != -1) {
2448
            my $fulldir = "$spools[$istoragepool]->{'path'}/$reguser$subdir";
2449
            `/bin/mkdir -p "$fulldir"` unless -d $fulldir;
2450
        }
2451
        $uistatus = "moving";
2452 d3805c61 hq
        if ($precreate) {
2453
            $uistatus = "stormoving";
2454
        }
2455
2456 95b003ff Origo
        my $ug = new Data::UUID;
2457
        my $tempuuid = $ug->create_str();
2458
2459
        $register{$path}->{'status'} = $uistatus;
2460
        $register{$newdirpath} = \%{$register{$path}}; # Clone db entry
2461 d3805c61 hq
        $register{$newdirpath}->{'snap1'} = '' if ($precreate && $force); # Snapshots are not preserved when live migrating storage
2462
2463 95b003ff Origo
2464
        if ($bschedule eq 'daily7' || $bschedule eq 'daily14') {
2465
             $bschedule = "manually" if (!$spools[$regstoragepool]->{'rdiffenabled'} || !$spools[$regstoragepool]->{'lvm'});
2466
        } elsif ($bschedule ne 'manually') {
2467
            $bschedule = '';
2468
        }
2469
2470
        $register{$path}->{'uuid'} = $tempuuid; # Use new temp uuid for old image
2471
        $register{$newdirpath}->{'storagepool'} = $newstoragepool;
2472
        if ($newstoragepool == -1) {
2473
            $register{$newdirpath}->{'mac'} = $mac;
2474
        } else {
2475
            $register{$newdirpath}->{'mac'} = '';
2476
        }
2477
        $register{$newdirpath}->{'user'} = $newuser;
2478
        tied(%register)->commit;
2479 d24d9a01 hq
        my $domuuid = $register{$path}->{'domains'};
2480 d3805c61 hq
        if ($status eq "used" || $status eq "paused" || $status eq "moving" || $status eq "stormoving" || $status eq "active") {
2481 95b003ff Origo
            my $dom = $domreg{$domuuid};
2482 d24d9a01 hq
            if ($dom->{'image'} eq $olddirpath) {
2483 48fcda6b Origo
                $dom->{'image'} = $newdirpath;
2484 d24d9a01 hq
            } elsif ($dom->{'image2'} eq $olddirpath) {
2485 48fcda6b Origo
                $dom->{'image2'} = $newdirpath;
2486 d24d9a01 hq
            } elsif ($dom->{'image3'} eq $olddirpath) {
2487 48fcda6b Origo
                $dom->{'image3'} = $newdirpath;
2488 d24d9a01 hq
            } elsif ($dom->{'image4'} eq $olddirpath) {
2489 48fcda6b Origo
                $dom->{'image4'} = $newdirpath;
2490
            }
2491 d3805c61 hq
            # Moving an image to a node effectively ties the associated domain to that node. When live migrating this should not be done until after move is completed.
2492
            $dom->{'mac'} = $mac if ($newstoragepool == -1 && !$precreate);
2493 95b003ff Origo
            if ($dom->{'system'} && $dom->{'system'} ne '--') {
2494
                unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
2495
                my $sys = $sysreg{$dom->{'system'}};
2496 d24d9a01 hq
                $sys->{'image'} = $newdirpath if ($sys->{'image'} eq $olddirpath);
2497 95b003ff Origo
                untie %sysreg;
2498
            }
2499
        }
2500
        my $cmd = qq|/usr/local/bin/steamExec $user $uistatus $status "$oldpath" "$newpath"|;
2501 48fcda6b Origo
        `$cmd`;
2502 d3805c61 hq
        $main::syslogit->($user, "info", "$uistatus $type image $name ($oldpath -> $newpath) ($regstoragepool -> $istoragepool)");
2503 48fcda6b Origo
        return "$newdirpath\n";
2504 95b003ff Origo
    } else {
2505 48fcda6b Origo
        return $postreply;
2506 95b003ff Origo
    }
2507
2508
}
2509
2510
sub locateNode {
2511 c899e439 Origo
    my ($virtualsize, $mac, $vcpu, $mem) = @_;
2512 95b003ff Origo
    $vcpu = $vcpu || 1;
2513
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) ) {return 0};
2514
    my $macip;
2515
    my $dmac;
2516
    my $dindex;
2517
    my $asleep;
2518
    my $identity;
2519 c899e439 Origo
    my $node;
2520 95b003ff Origo
    if ($mac && $mac ne "--") { # A node was specified
2521
        if (1024 * $nodestorageovercommission * $nodereg{$mac}->{'storfree'} > $virtualsize && $nodereg{$mac}->{'status'} eq 'running') {
2522 c899e439 Origo
            $node = $nodereg{$mac};
2523 95b003ff Origo
        }
2524
    } else { # Locate a node
2525
        require "$Stabile::basedir/cgi/servers.cgi";
2526
        $Stabile::Servers::console = 1;
2527
        my ($temp1, $temp2, $temp3, $temp4, $ahashref) = Stabile::Servers::locateTargetNode();
2528
        my @avalues = values %$ahashref;
2529
        my @sorted_values = (sort {$b->{'index'} <=> $a->{'index'}} @avalues);
2530 c899e439 Origo
        foreach my $snode (@sorted_values) {
2531 95b003ff Origo
            if (
2532 c899e439 Origo
                (1024 * $nodestorageovercommission * $snode->{'storfree'} > $virtualsize)
2533
                && ($snode->{'cpuindex'} > $vcpu)
2534
                && ($snode->{'memfree'} > $mem+512*1024)
2535
                && !($snode->{'maintenance'})
2536
                && ($snode->{'status'} eq 'running' || $snode->{'status'} eq 'asleep' || $snode->{'status'} eq 'waking')
2537
                && ($snode->{'index'} > 0)
2538 95b003ff Origo
            ) {
2539 d24d9a01 hq
                next if (!($mem) && $snode->{'identity'} eq 'local_kvm'); # Ugly hack - prevent moving images from default storage to local_kvm node
2540 c899e439 Origo
                $node = $snode;
2541 95b003ff Origo
                last;
2542
            }
2543
        }
2544
    }
2545 c899e439 Origo
    $macip = $node->{'ip'};
2546
    $dmac = $node->{'mac'};
2547
    $dindex = $node->{'index'};
2548
    $asleep = ($node->{'status'} eq 'asleep' || $node->{'status'} eq 'waking');
2549
    $identity = $node->{'identity'};
2550 95b003ff Origo
    untie %nodereg;
2551
    return ($dmac, $macip, $dindex, $asleep, $identity);
2552
}
2553
2554
sub do_getimagestatus {
2555
    my ($image, $action) = @_;
2556
    if ($help) {
2557
        return <<END
2558
GET:image:
2559
Check if image already exists. Pass image name including suffix.
2560
END
2561
    }
2562
    my $res;
2563
    $imagename = $params{'name'} || $image;
2564 d3805c61 hq
    if ($register{"/mnt/stabile/node/$user/$imagename"}) {
2565
        $res .= q|Status=OK Image /mnt/stabile/node/$imagename found with status | . $register{"/mnt/stabile/node/$user/$imagename"}->{status}. "\n";
2566
    }
2567 95b003ff Origo
    foreach my $spool (@spools) {
2568
        my $ipath = $spool->{'path'} . "/$user/$imagename";
2569
        if ($register{$ipath}) {
2570
            $res .= "Status=OK Image $ipath found with status $register{$ipath}->{'status'}\n";
2571
        } elsif (-f "$ipath" && -s "$ipath") {
2572
            $res .= "Status=OK Image $ipath found on disk, please wait for it to be updated in DB\n";
2573
        }
2574
    }
2575 d3805c61 hq
    $res .= "Status=ERROR Image $imagename not found\n" unless ($res);
2576 95b003ff Origo
    return $res;;
2577
}
2578
2579
# Check if image already exists.
2580
# Pass image name including suffix.
2581
sub imageExists {
2582
    my $imagename = shift;
2583
    foreach my $spool (@spools) {
2584
        my $ipath = $spool->{'path'} . "/$user/$imagename";
2585
        if ($register{$ipath}) {
2586
            return $register{$ipath}->{'status'} || 1;
2587
        } elsif (-e "$ipath") {
2588
            return 1
2589
        }
2590
    }
2591
    return '';
2592
}
2593
2594
# Pass image name including suffix.
2595
# Returns incremented name of an image which does not already exist.
2596
sub getValidName {
2597
    my $imagename = shift;
2598
    my $name = $imagename;
2599
    my $type;
2600
    if ($imagename =~ /(.+)\.(.+)/) {
2601
        $name = $1;
2602
        $type = $2;
2603
    }
2604
    if (imageExists($imagename)) {
2605
        my $i = 1;
2606
        while (imageExists("$name.$i.$type")) {$i++;};
2607
        $imagename = "$name.$i.$type";
2608
    }
2609
    return $imagename;
2610
}
2611
2612
# Print list of available actions on objects
2613
sub do_plainhelp {
2614
    my $res;
2615
    $res .= header('text/plain') unless $console;
2616
    $res .= <<END
2617
* new [size="size", name="name"]: Creates a new image
2618
* clone: Creates new clone of an image. A clone of a master image is a child of the master. A clone of a child or regular
2619
image is a regular copy.
2620
* convert: Creates a copy of a non-qcow2 image in qcow2 format
2621
* snapshot: Takes a qcow2 snapshot of the image. Server can not be running.
2622
* unsnap: Removes a qcow2 snapshot.
2623
* revert: Applies a snapshot, reverting the image to the state it was in, when the snapshot was taken.
2624
* master: Turns an image into a master image which child images may be cloned from. Image can not be in use.
2625
* unmaster: Turns a master image into a regular image, which can not be used to clone child images from.
2626
* backup: Backs up an image using rdiff-backup. Rdiff-backup must be enabled in admin server configuration. This is a
2627
very expensive operation, since typically the entire image must be read.
2628
* buildsystem [master="master image"]: Constructs one or optionally multiple servers, images and networks and assembles
2629
them in one app.
2630
* restore [backup="backup"]: Restores an image from a backup. The restore is named after the backup.
2631
* delete: Deletes an image. Use with care. Image can not be in use.
2632
* mount: Mounts an image for restorefiles and listfiles operations.
2633
* unmount: Unmounts an image
2634
END
2635
    ;
2636
    return $res;
2637
}
2638
2639
# Print list of images
2640
# Showing a single image is also handled by specifying uuid or path in $curuuid or $curimg
2641
# When showing a single image a single action may be performed on image
2642
sub do_list {
2643
    my ($img, $action, $obj) = @_;
2644
    if ($help) {
2645
        return <<END
2646
GET:image,uuid:
2647
Lists all the images a user has access to. This is also the default action for the endpoint, so if no action is specified this is what you get.
2648
The returned list may be filtered by specifying storagepool, type, name, path or uuid, like e.g.:
2649
2650
<a href="/stabile/images/type:user" target="_blank">/stabile/images/type:user</a>
2651
<a href="/stabile/images/name:test* AND storagepool:shared" target="_blank">/stabile/images/name:test* AND storagepool:shared</a>
2652
<a href="/stabile/images/storagepool:shared AND path:test*" target="_blank">/stabile/images/storagepool:shared AND path:test*</a>
2653
<a href="/stabile/images/name:* AND storagepool:all AND type:usercdroms" target="_blank">/stabile/images/name:* AND storagepool:all AND type:usercdroms</a>
2654
<a href="/stabile/images/[uuid]" target="_blank">/stabile/images/[uuid]</a>
2655
2656
storagepool may be either of: all, node, shared
2657
type may be either of: user, usermasters, commonmasters, usercdroms
2658
2659
May also be called as tablelist or tablelistall, for use by stash.
2660
2661
END
2662
    }
2663
    my $res;
2664
    my $filter;
2665
    my $storagepoolfilter;
2666
    my $typefilter;
2667
    my $pathfilter;
2668
    my $uuidfilter;
2669
    $curimg = $img if ($img);
2670 c899e439 Origo
    my $regimg = $register{$curimg};
2671
#    if ($curimg && ($isadmin || $regimg->{'user'} eq $user || $regimg->{'user'} eq 'common') ) {
2672
    if ($curimg) { # security is enforced below, we hope...
2673 95b003ff Origo
        $pathfilter = $curimg;
2674
    } elsif ($uripath =~ /images(\.cgi)?\/(\?|)(name|storagepool|type|path)/) {
2675
        $filter = $3 if ($uripath =~ /images(\.cgi)?\/.*name(:|=)(.+)/);
2676
        $filter = $1 if ($filter =~ /(.*) AND storagepool/);
2677
        $filter = $1 if ($filter =~ /(.*) AND type/);
2678
        $filter = $1 if ($filter =~ /(.*)\*$/);
2679
        $storagepoolfilter = $2 if ($uripath =~ /images(\.cgi)?\/.*storagepool:(\w+)/);
2680
        $typefilter = $2 if ($uripath =~ /images(\.cgi)?\/.*type:(\w+)/);
2681
        $typefilter = $2 if ($uripath =~ /images(\.cgi)?\/.*type=(\w+)/);
2682
        $pathfilter = $2 if ($uripath =~ /images(\.cgi)?\/.*path:(.+)/);
2683
        $pathfilter = $2 if ($uripath =~ /images(\.cgi)?\/.*path=(.+)/);
2684
    } elsif ($uripath =~ /images(\.cgi)?\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})\/?(\w*)/) {
2685
        $uuidfilter = $2;
2686
        $curaction = lc $3;
2687
    }
2688
    $uuidfilter = $options{u} unless $uuidfilter;
2689
2690
    if ($uuidfilter && $curaction) {
2691
        if ($imagereg{$uuidfilter}) {
2692
            $curuuid = $uuidfilter;
2693
            my $obj = getObj(%params);
2694
            # Now perform the requested action
2695
            my $objfunc = "obj_$curaction";
2696
            if (defined &$objfunc) { # If a function named objfunc exists, call it
2697
                $res = $objfunc->($obj);
2698
                chomp $postreply;
2699
                unless ($res) {
2700
                    $res .= qq|{"status": "OK", "message": "$postreply"}|;
2701
                    $res = join(", ", split("\n", $res));
2702
                }
2703
                unless ($curaction eq 'download') {
2704
                    $res = header('application/json; charset=UTF8') . $res unless ($console);
2705
                }
2706
            } else {
2707
                $res .= header('application/json') unless $console;
2708
                $res .= qq|{"status": "Error", "message": "Unknown image action: $curaction"}|;
2709
            }
2710
        } else {
2711
            $res .= header('application/json') unless $console;
2712
            $res .= qq|{"status": "Error", "message": "Unknown image $uuidfilter"}|;
2713
        }
2714
        return $res;
2715
    }
2716
2717
2718
    my %userregister; # User specific register
2719
2720
    $res .= header('application/json; charset=UTF8') unless $console;
2721
    unless (tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access user register"}|; return $res;};
2722
2723
    my @busers = @users;
2724
    my @billusers = (tied %userreg)->select_where("billto = '$user'");
2725
    push (@busers, $billto) if ($billto && $billto ne '--'); # We include images from 'parent' user
2726
    push (@busers, @billusers) if (@billusers); # We include images from 'child' users
2727
    untie %userreg;
2728
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2729
    foreach my $u (@busers) {
2730
        my @regkeys = (tied %register)->select_where("user = '$u'");
2731
        foreach my $k (@regkeys) {
2732
            my $valref = $register{$k};
2733
            # Only update info for images the user has access to.
2734
            if ($valref->{'user'} eq $u && (defined $spools[$valref->{'storagepool'}]->{'id'} || $valref->{'storagepool'}==-1)) {
2735
                # Only list installable master images from billto account
2736 e837d785 hq
                next if ($billto && ($billto ne $user) && ($u eq $billto) && ($valref->{'type'} ne 'qcow2' || $valref->{'installable'} ne 'true'));
2737 95b003ff Origo
                my $path = $valref->{'path'};
2738
                my %val = %{$valref}; # Deference and assign to new array, effectively cloning object
2739
                my $spool = $spools[$val{'storagepool'}];
2740
                # Skip images which are in DB e.g. because of change of storage pool difinitions
2741
                next unless ($val{'storagepool'}==-1 || $val{'path'} =~ /$spool->{'path'}/);
2742
                $val{'virtualsize'} += 0;
2743
                $val{'realsize'} += 0;
2744
                $val{'size'} += 0;
2745
                #$val{'lvm'} = 0+( (($spools[$val{'storagepool'}]->{"hostpath"} eq "local") && $spools[$val{'storagepool'}]->{"rdiffenabled"}) || $val{'storagepool'}==-1);
2746
                if ($val{'storagepool'}==-1) {
2747
                    my $node = $nodereg{$val{'mac'}};
2748
                    $val{'lvm'} = 0+($node->{stor} eq 'lvm');
2749
                } else {
2750
                    $val{'lvm'} = 0+$spool->{"lvm"};
2751
                }
2752
                # If image has a master, update the master with child info.
2753
                # This info is specific to each user, so we don't store it in the db
2754
                if ($valref->{'master'} && $register{$valref->{'master'}} && ((grep $_ eq $valref->{'user'}, @users))) {
2755
                    $register{$valref->{'master'}}->{'status'} = 'used';
2756
                    unless ($userregister{$val{'master'}}) { # If we have not yet parsed master, it is not yet in userregister, so put it there
2757
                        my %mval = %{$register{$val{'master'}}};
2758
                        $userregister{$val{'master'}} = \%mval;
2759
                    }
2760
                    #   $userregister{$val{'master'}}->{'user'} = $u;
2761
                    $userregister{$val{'master'}}->{'status'} = 'used';
2762
                    if ($val{'domains'}) {
2763
                        $userregister{$val{'master'}}->{'domainnames'} .= ", " if ($userregister{$val{'master'}}->{'domainnames'});
2764
                        $userregister{$val{'master'}}->{'domainnames'} .= $val{'domainnames'};
2765
                        $userregister{$val{'master'}}->{'domainnames'} .= " (".$val{'user'}.")" if (index($privileges,"a")!=-1);
2766
2767
                        $userregister{$val{'master'}}->{'domains'} .= ", " if ($userregister{$val{'master'}}->{'domains'});
2768
                        $userregister{$val{'master'}}->{'domains'} .= $val{'domains'};
2769
                    }
2770
                }
2771
                my $status = $valref->{'status'};
2772
                if ($rdiffenabled && ($userrdiffenabled || index($privileges,"a")!=-1) &&
2773
                    ( ($spools[$valref->{'storagepool'}]->{'rdiffenabled'} &&
2774
                        ($spools[$valref->{'storagepool'}]->{'lvm'} || $status eq 'unused' || $status eq 'used' || $status eq 'paused') )
2775
                        || $valref->{'storagepool'}==-1 )
2776
                ) {
2777
                    $val{'backup'} = "" ;
2778
                } else {
2779
                    $val{'backup'} = "disabled" ;
2780
                }
2781
                $val{'status'} = 'backingup' if ($status =~ /backingup/);
2782 f222b89c hq
                Updateregister($k, "updateregister") if ($status =~ /(downloading|uploading)/);
2783 95b003ff Origo
                $userregister{$path} = \%val unless ($userregister{$path});
2784
            }
2785
        }
2786
    }
2787
    untie(%nodereg);
2788
2789
    my @uservalues;
2790
    if ($filter || $storagepoolfilter || $typefilter || $pathfilter || $uuidfilter) { # List filtered images
2791
        foreach $uvalref (values %userregister) {
2792
            my $fmatch;
2793
            my $smatch;
2794
            my $tmatch;
2795
            my $pmatch;
2796
            my $umatch;
2797
            $fmatch = 1 if (!$filter || $uvalref->{'name'}=~/$filter/i);
2798
            $smatch = 1 if (!$storagepoolfilter || $storagepoolfilter eq 'all'
2799
                || ($storagepoolfilter eq 'node' && $uvalref->{'storagepool'}==-1)
2800
                || ($storagepoolfilter eq 'shared' && $uvalref->{'storagepool'}>=0)
2801
            );
2802
            $tmatch = 1 if (!$typefilter || $typefilter eq 'all'
2803
                || ($typefilter eq 'user' && $uvalref->{'user'} eq $user
2804
                # && $uvalref->{'type'} ne 'iso'
2805
                # && $uvalref->{'path'} !~ /\.master\.qcow2$/
2806
                    )
2807
                || ($typefilter eq 'usermasters' && $uvalref->{'user'} eq $user && $uvalref->{'path'} =~ /\.master\.qcow2$/)
2808
                || ($typefilter eq 'usercdroms' && $uvalref->{'user'} eq $user && $uvalref->{'type'} eq 'iso')
2809
                || ($typefilter eq 'commonmasters' && $uvalref->{'user'} ne $user && $uvalref->{'path'} =~ /\.master\.qcow2$/)
2810
                || ($typefilter eq 'commoncdroms' && $uvalref->{'user'} ne $user && $uvalref->{'type'} eq 'iso')
2811
            );
2812
            $pmatch = 1 if ($pathfilter && $uvalref->{'path'}=~/$pathfilter/i);
2813
            $umatch = 1 if ($uvalref->{'uuid'} eq $uuidfilter);
2814
            if ((!$pathfilter &&!$uuidfilter && $fmatch && $smatch && $tmatch) || $pmatch) {
2815
                push @uservalues,$uvalref if ($uvalref->{'uuid'});
2816
            } elsif ($umatch && $uvalref->{'uuid'}) {
2817
                push @uservalues,$uvalref;
2818
                last;
2819
            }
2820
        }
2821
    } else {
2822
        @uservalues = values %userregister;
2823
    }
2824
2825
    # Sort @uservalues
2826 2a63870a Christian Orellana
    @uservalues = (sort {$a->{'name'} cmp $b->{'name'}} @uservalues); # Always sort by name first
2827 95b003ff Origo
    my $sort = 'status';
2828
    $sort = $2 if ($uripath =~ /sort\((\+|\-)(\S+)\)/);
2829
    my $reverse;
2830
    $reverse = 1 if ($1 eq '-');
2831
    if ($reverse) { # sort reverse
2832
        if ($sort =~ /realsize|virtualsize|size/) {
2833
            @uservalues = (sort {$b->{$sort} <=> $a->{$sort}} @uservalues); # Sort as number
2834
        } else {
2835
            @uservalues = (sort {$b->{$sort} cmp $a->{$sort}} @uservalues); # Sort as string
2836
        }
2837
    } else {
2838
        if ($sort =~ /realsize|virtualsize|size/) {
2839
            @uservalues = (sort {$a->{$sort} <=> $b->{$sort}} @uservalues); # Sort as number
2840
        } else {
2841
            @uservalues = (sort {$a->{$sort} cmp $b->{$sort}} @uservalues); # Sort as string
2842
        }
2843
    }
2844
2845
    if ($uuidfilter || $curimg) {
2846 48fcda6b Origo
        if (scalar @uservalues > 1) { # prioritize user's own images
2847
            foreach my $val (@uservalues) {
2848
                if ($val->{'user'} eq 'common') {
2849
                    next;
2850
                } else {
2851
                    $json_text = to_json($val, {pretty => 1});
2852
                }
2853
            }
2854
        } else {
2855
            $json_text = to_json($uservalues[0], {pretty => 1}) if (@uservalues);
2856
        }
2857 95b003ff Origo
    } else {
2858 2a63870a Christian Orellana
    #    $json_text = JSON->new->canonical(1)->pretty(1)->encode(\@uservalues) if (@uservalues);
2859 95b003ff Origo
        $json_text = to_json(\@uservalues, {pretty => 1}) if (@uservalues);
2860
    }
2861
    $json_text = "{}" unless $json_text;
2862
    $json_text =~ s/""/"--"/g;
2863
    $json_text =~ s/null/"--"/g;
2864
    $json_text =~ s/"notes" {0,1}: {0,1}"--"/"notes":""/g;
2865
    $json_text =~ s/"installable" {0,1}: {0,1}"(true|false)"/"installable":$1/g;
2866
2867
    if ($action eq 'tablelist' || $action eq 'tablelistall') {
2868
        my $t2 = Text::SimpleTable->new(36,26,5,20,14,10,7);
2869
        $t2->row('uuid', 'name', 'type', 'domainnames', 'virtualsize', 'user', 'status');
2870
        $t2->hr;
2871
        my $pattern = $options{m};
2872
        foreach $rowref (@uservalues){
2873
            next unless ($action eq 'tablelistall' || $rowref->{'user'} eq $user);
2874
            if ($pattern) {
2875
                my $rowtext = $rowref->{'uuid'} . " " . $rowref->{'name'} . " " . $rowref->{'type'} . " " . $rowref->{'domainnames'}
2876
                    . " " .  $rowref->{'virtualsize'} . " " . $rowref->{'user'} . " " . $rowref->{'status'};
2877
                $rowtext .= " " . $rowref->{'mac'} if ($isadmin);
2878
                next unless ($rowtext =~ /$pattern/i);
2879
            }
2880
            $t2->row($rowref->{'uuid'}, $rowref->{'name'}, $rowref->{'type'}, $rowref->{'domainnames'}||'--',
2881
                $rowref->{'virtualsize'}, $rowref->{'user'}, $rowref->{'status'});
2882
        }
2883
        $res .= $t2->draw;
2884
    } elsif ($console) {
2885
        $res .= Dumper(\@uservalues);
2886
    } else {
2887
        $res .= $json_text;
2888
    }
2889
    return $res;
2890
}
2891
2892
# Internal action for looking up a uuid or part of a uuid and returning the complete uuid
2893
sub do_uuidlookup {
2894
    my ($img, $action) = @_;
2895
    if ($help) {
2896
        return <<END
2897
GET:image,path:
2898
END
2899
    }
2900
    my $res;
2901
    $res .= header('text/plain') unless $console;
2902
    my $u = $options{u};
2903
    $u = $curuuid unless ($u || $u eq '0');
2904
    my $ruuid;
2905
    if ($u || $u eq '0') {
2906
        foreach my $uuid (keys %register) {
2907
            if (($register{$uuid}->{'user'} eq $user || $register{$uuid}->{'user'} eq 'common' || $fulllist)
2908
                && ($register{$uuid}->{'uuid'} =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/)) {
2909
                $ruuid = $register{$uuid}->{'uuid'};
2910
                last;
2911
            }
2912
        }
2913
        if (!$ruuid && $isadmin) { # If no match and user is admin, do comprehensive lookup
2914
            foreach $uuid (keys %register) {
2915
                if ($register{$uuid}->{'uuid'} =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/) {
2916
                    $ruuid = $register{$uuid}->{'uuid'};
2917
                    last;
2918
                }
2919
            }
2920
        }
2921
    }
2922
    $res .= "$ruuid\n" if ($ruuid);
2923
    return $res;
2924
}
2925
2926
# Internal action for showing a single image
2927
sub do_uuidshow {
2928
    my ($img, $action) = @_;
2929
    if ($help) {
2930
        return <<END
2931
GET:image,path:
2932
END
2933
    }
2934
    my $res;
2935
    $res .= header('text/plain') unless $console;
2936
    my $u = $options{u};
2937
    $u = $curuuid unless ($u || $u eq '0');
2938
    if ($u || $u eq '0') {
2939
        foreach my $uuid (keys %register) {
2940
            if (($register{$uuid}->{'user'} eq $user || $register{$uuid}->{'user'} eq 'common' || index($privileges,"a")!=-1)
2941
                && $register{$uuid}->{'uuid'} =~ /^$u/) {
2942
                my %hash = %{$register{$uuid}};
2943
                delete $hash{'action'};
2944
                my $dump = Dumper(\%hash);
2945
                $dump =~ s/undef/"--"/g;
2946
                $res .= $dump;
2947
                last;
2948
            }
2949
        }
2950
    }
2951
    return $res;
2952
}
2953
2954
sub do_updatebilling {
2955
    my ($img, $action) = @_;
2956
    if ($help) {
2957
        return <<END
2958
GET:image,path:
2959
END
2960
    }
2961
    my $res;
2962
    $res .= header('text/plain') unless ($console);
2963
    updateBilling($params{"event"});
2964
    $res .= "Status=OK Updated billing for $user\n";
2965
    return $res;
2966
}
2967
2968
# If used with the -f switch ($fulllist) from console, all users images are updated in the db
2969
# If used with the -p switch ($fullupdate), also updates status information (ressource intensive - runs through all domains)
2970
sub dont_updateregister {
2971
    my ($img, $action) = @_;
2972
    my $res;
2973
    if ($help) {
2974
        return <<END
2975
GET:image,path:
2976
END
2977
    }
2978
    return "Status=ERROR You must be an admin to do this!\n" unless ($isadmin);
2979
    $fullupdate = 1 if ((!$fullupdate && $params{'fullupdate'}) || $action eq 'fullupdateregister');
2980
    my $force = $params{'force'};
2981
    Updateregister($force);
2982
    $res .= "Status=OK Updated image register for " . join(', ', @users) . "\n";
2983
}
2984
2985
sub do_urlupload {
2986
    my ($img, $action) = @_;
2987
    if ($help) {
2988
        return <<END
2989
GET:image,path:
2990
END
2991
    }
2992
    my $res;
2993
    $res .= header('application/json') unless ($console);
2994
    if ($params{'probe'} && $params{'url'}) {
2995
        my $url = $params{'url'};
2996 2a63870a Christian Orellana
        my $cmd = qq!curl --http1.1 -kIL "$url" 2>&1!;
2997 95b003ff Origo
        my $headers = `$cmd`;
2998
        my $filename;
2999
        my $filesize = 0;
3000
        $filename = $1 if ($headers =~ /content-disposition: .+filename="(.+)"/i);
3001
        $filesize = $1 if ($headers =~ /content-length: (\d+)/i);
3002
        my $ok;
3003
        if (!$filename) {
3004 2a63870a Christian Orellana
            my $cmd = qq[curl --http1.1 -kIL "$url" 2>&1 | grep -i " 200 OK"];
3005 95b003ff Origo
            $ok =  `$cmd`; chomp $ok;
3006
            $filename = `basename "$url"` if ($ok);
3007
            chomp $filename;
3008
        }
3009 04c16f26 hq
        if ($filename =~ /\S+\.(vmdk|img|vhd|vhdx|qcow|qcow2|vdi|iso)$/) {
3010 95b003ff Origo
            $filename = $2 if ($filename =~ /(=|\?)(.+)/);
3011
            $filename = $2 if ($filename =~ /(=|\?)(.+)/);
3012
            $filename = getValidName($filename);
3013
            my $filepath = $spools[0]->{'path'} . "/$user/$filename";
3014
            $res .= qq|{"status": "OK", "name": "$filename", "message": "200 OK", "size": $filesize, "path": "$filepath"}|;
3015
        } else {
3016 2a63870a Christian Orellana
            $res .= qq|{"status": "ERROR", "message": "An image file cannot be downloaded from this URL.", "url": "$url", "filename": "$filename"}|;
3017 95b003ff Origo
        }
3018
    } elsif ($params{'path'} && $params{'url'} && $params{'name'} && defined $params{'size'}) {
3019
        my $imagepath = $params{'path'};
3020
        my $imagename = $params{'name'};
3021
        my $imagesize = $params{'size'};
3022
        my $imageurl = $params{'url'};
3023 2a63870a Christian Orellana
        if (-e "$imagepath.meta" && $imagepath =~ /\.master\.qcow2$/) { # This image is being downloaded by pressurecontrol
3024
            $res .= qq|{"status": "OK", "name": "$imagename", "message": "Now downloading master", "path": "$imagepath"}|;
3025
        } elsif (-e $imagepath) {
3026 f222b89c hq
            $res .= qq|{"status": "OK", "message": "An image file with this name already exists on the server.", "name": "$imagename", "path": "$imagepath"}|;
3027
            `/bin/echo "uploading" > "$imagepath.meta"`;
3028
            my $ksize = $imagesize / 1024;
3029
            `/bin/echo "$ksize" . "K 100%" >> "$imagepath.meta"`;
3030
            `/bin/echo "" >> "$imagepath.meta"`;
3031 95b003ff Origo
        } elsif ($imagepath !~ /^$spools[0]->{'path'}\/$user\/.+/) {
3032
            $res .= qq|{"status": "ERROR", "message": "Invalid path"}|;
3033
        } elsif (overQuotas($virtualsize)) {
3034
            $res .= qq|{"status": "ERROR", "message": "Over quota (". overQuotas($virtualsize) . ") uploading: $imagename"}|;
3035
        } elsif (overStorage($imagesize, 0)) {
3036
            $res .= qq|{"status": "ERROR", "message": "Out of storage in destination pool uploading: $imagename"}|;
3037 04c16f26 hq
        } elsif ($imagepath =~ /^$spools[0]->{'path'}.+\.(vmdk|img|vhd|vhdx|qcow|qcow2|vdi|iso)$/) {
3038 95b003ff Origo
            my $imagetype = $1;
3039
            my $ug = new Data::UUID;
3040
            my $newuuid = $ug->create_str();
3041
            my $name = $imagename;
3042 04c16f26 hq
            $name = $1 if ($name =~ /(.+)\.(vmdk|img|vhd|vhdx|qcow|qcow2|vdi|iso)$/);
3043 95b003ff Origo
            $register{$imagepath} = {
3044
                uuid => $newuuid,
3045
                path => $imagepath,
3046
                name => $name,
3047
                user => $user,
3048
                type => $imagetype,
3049
                virtualsize => $imagesize,
3050
                realsize => $imagesize,
3051
                size => $imagesize,
3052
                storagepool => 0,
3053
                status => 'uploading'
3054
            };
3055
            `/bin/echo uploading > "$imagepath.meta"`;
3056
            eval {
3057
                my $daemon = Proc::Daemon->new(
3058
                    work_dir => '/usr/local/bin',
3059
                    exec_command => "perl -U steamExec $user urluploading unused \"$imagepath\" \"$imageurl\""
3060
                ) or do {$postreply .= "Status=ERROR $@\n";};
3061
                my $pid = $daemon->Init();
3062
                $main::syslogit->($user, "info", "urlupload $imageurl, $imagepath");
3063
                1;
3064
            } or do {$res .= qq|{"status": "ERROR", "message": "ERROR $@"}|;};
3065
            $res .= qq|{"status": "OK", "name": "$imagename", "message": "Now uploading", "path": "$imagepath"}|;
3066
        }
3067
    } elsif ($params{'path'} && $params{'getsize'}) {
3068
        my $imagepath = $params{'path'};
3069 f222b89c hq
        if (-e "$imagepath.meta") {
3070
            my $imagesize = `grep -Po '\\d+K' "$imagepath.meta" | tail -n1`;
3071
            chomp $imagesize;
3072
            $imagesize = 1024 * $imagesize;
3073 2a63870a Christian Orellana
            $res .= qq|{"status": "OK", "size": $imagesize, "path": "$imagepath"}|;
3074 f222b89c hq
        } else {
3075
            if (!(-e $imagepath)) {
3076
                $res .= qq|{"status": "ERROR", "message": "Image not found.", "path": "$imagepath"}|;
3077
            } elsif ($imagepath !~ /^$spools[0]->{'path'}\/$user\/.+/  && $imagepath !~ /^$spools[0]->{'path'}\/common\/.+/) {
3078
                $res .= qq|{"status": "ERROR", "message": "Invalid path"}|;
3079
            } else {
3080
                my @stat = stat($imagepath);
3081
                my $imagesize = $stat[7];
3082
                $res .= qq|{"status": "OK", "size": $imagesize, "path": "$imagepath"}|;
3083
            }
3084
3085 95b003ff Origo
        }
3086
    }
3087
    return $res;
3088
}
3089
3090
sub do_upload {
3091
    my ($img, $action) = @_;
3092
    if ($help) {
3093
        return <<END
3094
POST:image,path:
3095
END
3096
    }
3097
    my $res;
3098
    $res .= header("text/html") unless ($console);
3099
3100
    my $uname = $params{'name'};
3101
3102 04c16f26 hq
    my($name, $dirpath, $suffix) = fileparse($uname, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
3103 95b003ff Origo
3104
    $name = $1 if ($name =~ /^\.+(.*)/); # Don't allow hidden files
3105
    #        my $f = lc $name;
3106
    my $f = $name;
3107
    $f = $spools[0]->{'path'} . "/$user/$f$suffix";
3108
3109
    my $chunk = int($params{'chunk'});
3110
    my $chunks = int($params{'chunks'});
3111
3112
    if ($chunk == 0 && -e $f) {
3113 2a63870a Christian Orellana
        $res .= qq|Error: File $f already exists $name|;
3114 95b003ff Origo
    } else {
3115
        open (FILE, ">>$f");
3116
3117
        if ($params{'file'}) {
3118
            my $uh = $Stabile::q->upload("file");
3119
            while ( <$uh> ) {
3120
                print FILE;
3121
            }
3122
            close FILE;
3123
3124
            if ($chunk == 0) {
3125
                `/usr/local/bin/steamExec updateimagestatus "$f" uploading`;
3126
            }
3127
            if ($chunk >= ($chunks - 1) ) { # Done
3128
                unlink("$f.meta");
3129
                `/usr/local/bin/steamExec updateimagestatus "$f" unused`;
3130
            } else {
3131
                my $upload_meta_data = "status=uploading&chunk=$chunk&chunks=$chunks";
3132
                `echo "$upload_meta_data" > "$f.meta"`;
3133
            }
3134
            $res .= qq|OK: Chunk $chunk uploaded of $name|;
3135
        } else {
3136
            $res .= qq|OK: No file $name.|;
3137
        }
3138
    }
3139
    return $res;
3140
}
3141
3142
# .htaccess files are created hourly, giving the image user access
3143
# when download is clicked by another user (in @users, so with permission), this user is also given access until .htaccess is rewritten
3144
sub Download {
3145
    my ($f, $action, $argref) = @_;
3146
    #    my ($name, $managementlink, $upgradelink, $terminallink, $version) = @{$argref};
3147
    if ($help) {
3148
        return <<END
3149 2a63870a Christian Orellana
GET:image,console:
3150 95b003ff Origo
Returns http redirection with URL to download image
3151
END
3152
    }
3153 2a63870a Christian Orellana
    $baseurl = $argref->{baseurl} || $baseurl;
3154 95b003ff Origo
    my %uargs = %{$argref};
3155
    $f = $uargs{'image'} unless ($f);
3156
    $baseurl = $uargs{'baseurl'} || $baseurl;
3157 2a63870a Christian Orellana
    $console = $console || $uargs{'console'};
3158 95b003ff Origo
    my $res;
3159
    my $uf =  URI::Escape::uri_unescape($f);
3160
    if (! $f) {
3161
        $res .= header('text/html', '500 Internal Server Error') unless ($console);
3162
        $res .= "Status=ERROR You must specify an image.\n";
3163
    }
3164
    my $txt = <<EOT
3165
order deny,allow
3166
AuthName "Download"
3167
AuthType None
3168
TKTAuthLoginURL $baseurl/login/
3169
TKTAuthIgnoreIP on
3170
deny from all
3171
Satisfy any
3172
require user $user
3173
require user $tktuser
3174
Options -Indexes
3175
EOT
3176
    ;
3177
    my $fid;
3178
    my $fpath;
3179
    foreach my $p (@spools) {
3180
        foreach my $suser (@users) {
3181
            my $dir = $p->{'path'};
3182
            my $id = $p->{'id'};
3183
            if (-d "$dir/$suser" && $uf =~ /\/$suser\//) {
3184
                if ($uf =~ /$dir\/(.+)\/(.+)/) {
3185
                    my $filename = $2;
3186
                    utf8::encode($filename);
3187
                    utf8::decode($filename);
3188
                    $fpath = "$1/" . URI::Escape::uri_escape($filename);
3189
                    #$fpath = "$1/" . $filename;
3190
                    `chmod o+rw "$uf"`;
3191
                    `/bin/echo "$txt" > "$dir/$suser/.htaccess"`;
3192
                    `chmod 644 "$dir/$suser/.htaccess"`;
3193
                    `/bin/mkdir "$Stabile::basedir/download"` unless (-e "$Stabile::basedir/download");
3194
                    `/bin/ln -s "$dir" "$Stabile::basedir/download/$id"` unless (-e "$Stabile::basedir/download/$id");
3195
                    $fid = $id;
3196
                    last;
3197
                }
3198
            }
3199
        }
3200
    }
3201
    if (($fid || $fid eq '0') && $fpath && -e "$f") {
3202
        my $fileurl = "$baseurl/download/$fid/$fpath";
3203
        if ($console) {
3204 2a63870a Christian Orellana
            $res .= header(). $fileurl;
3205 95b003ff Origo
        } else {
3206
            $res .= "Status: 302 Moved\nLocation: $fileurl\n\n";
3207
            $res .= "$fileurl\n";
3208
        }
3209
    } else {
3210
        $res .= header('text/html', '500 Internal Server Error') unless ($console);
3211
        $res .= "Status=ERROR File not found $f, $fid, $fpath, $uargs{image}\n";
3212
    }
3213
    return $res;
3214
}
3215
3216
3217
sub Liststoragedevices {
3218 2a63870a Christian Orellana
    my ($image, $action, $obj) = @_;
3219 95b003ff Origo
    if ($help) {
3220
        return <<END
3221
GET::
3222
Returns available physical disks and partitions.
3223
Partitions currently used for holding backup and primary images directories are marked as such.
3224
May also be called as 'getimagesdevice', 'getbackupdevice', 'listimagesdevices' or 'listbackupdevices'.
3225
END
3226
    }
3227
    unless ($isadmin || ($user eq $engineuser)) {
3228
        return '' if ($action eq 'getimagesdevice' || $action eq 'getbackupdevice');
3229
        return qq|[]|;
3230
    }
3231
    my %devs;
3232
    # Check if we have unmounted ZFS file systems
3233
#    if (`grep "stabile-images" /etc/stabile/config.cfg` && !(`df` =~ /stabile-images/)) {
3234
    if (!(`df` =~ /stabile-images/)) {
3235 a2e0bc7e hq
        `zpool import stabile-images 2>/dev/null`;
3236
        `zfs mount stabile-images 2>/dev/null`;
3237
        `zfs mount stabile-images/images 2>/dev/null`;
3238 95b003ff Origo
    }
3239
    if (!(`df` =~ /stabile-backup/)) {
3240 a2e0bc7e hq
        `zpool import stabile-backup 2>/dev/null`;
3241
        `zfs mount stabile-backup 2>/dev/null`;
3242
        `zfs mount stabile-backup/images 2>/dev/null`;
3243
        `zfs mount stabile-backup/backup 2>/dev/null`;
3244 95b003ff Origo
    }
3245
    # Add active and mounted filesystems
3246
    my %filesystems;
3247
    $cmd = q/LANG=en df -hT | tr -s ' ' ',' | jq -nR '[( input | split(",") ) as $keys | ( inputs | split(",") ) as $vals | [ [$keys, $vals] | transpose[] | {key:.[0],value:.[1]} ] | from_entries ]'/;
3248
    my $json = `$cmd`;
3249
    my $jobj = JSON::from_json($json);
3250
    my $rootdev;
3251
    my $backupdev;
3252
    my $imagesdev;
3253
    foreach my $fs (sort {$a->{'Filesystem'} cmp $b->{'Filesystem'}} @{$jobj}) {
3254
        # Note that physical disk devicess in general may be either disks, partitions with regular file systems (like ext4) or zfs pools, which may contain many file systems
3255
        if ($fs->{Filesystem} =~ /\/dev\/(.+)/) {
3256 2a63870a Christian Orellana
            next if ($fs->{Type} eq 'squashfs');
3257
            next if ($fs->{Filesystem} =~ /\/dev\/loop/);
3258 95b003ff Origo
            my $name = $1;
3259
            if ($name =~ /mapper\/(\w+-)(.+)/) {
3260
                $name = "$1$2";
3261
            }
3262
            $fs->{Name} = $name;
3263
            delete $fs->{on};
3264
            my $mp = $fs->{Mounted};
3265
            if ($fs->{Mounted} eq '/') {
3266
                $rootdev = $name;
3267
            } else {
3268
                if ($backupdir =~ /^$fs->{Mounted}/) {
3269
                    next if ($action eq 'listimagesdevices'); # Current backup dev is not available as images dev
3270
                    $fs->{isbackupdev} = 1;
3271
                    $backupdev = $name;
3272
                    return $name if ($action eq 'getbackupdevice');
3273
                }
3274
                if ($tenderpathslist[0] =~ /^$fs->{Mounted}/) {
3275
                    next if ($action eq 'listbackupdevices'); # Current images dev is not available as backup dev
3276
                    $fs->{isimagesdev} = 1;
3277
                    $imagesdev = $name;
3278
                    return $name if ($action eq 'getimagesdevice');
3279
                }
3280
            }
3281
            $fs->{dev} = $name;
3282
            $fs->{nametype} = "$name ($fs->{Type} - " .  ($mp?$mp:"not mounted") . " $fs->{Size})";
3283
            $filesystems{$name} = $fs;
3284
        } elsif ( $fs->{Type} eq 'zfs') {
3285
            my $name = $fs->{Filesystem};
3286 71b897d3 hq
            # only include zfs pools but look for use as backup and images, exclude shapshots
3287
            if ($name =~ /(.+)\/(.+)/
3288
                && !($name =~ /SNAPSHOT/)
3289
                && !($name =~ /stabile-backup\/images/)
3290
                && !($name =~ /stabile-backup\/node/)
3291
            ) {
3292 89cb0977 hq
                $name = $1;
3293 95b003ff Origo
                if ($fs->{Mounted} eq $backupdir) {
3294
                    if ($action eq 'listimagesdevices') {
3295 89cb0977 hq
                        delete $filesystems{$name}; # not available for images - used for backup
3296 95b003ff Origo
                    } else {
3297 89cb0977 hq
                        $filesystems{$name}->{isbackupdev} = 1;
3298
                        $fs->{isbackupdev} = 1;
3299 95b003ff Origo
                        $backupdev = $name;
3300
                    }
3301 89cb0977 hq
                    return $name if ($action eq 'getbackupdevice');
3302
                } elsif ($fs->{Mounted} eq $tenderpathslist[0]) {
3303 95b003ff Origo
                    if ($action eq 'listbackupdevices') {
3304 89cb0977 hq
                        delete $filesystems{$name}; # not available for backup - used for images
3305 95b003ff Origo
                    } else {
3306 89cb0977 hq
                        $filesystems{$name}->{isimagesdev} = 1;
3307
                        $fs->{isimagesdev} = 1;
3308 95b003ff Origo
                        $imagesdev = $name;
3309
                    }
3310 89cb0977 hq
                    return $name if ($action eq 'getimagesdevice');
3311 95b003ff Origo
                }
3312 71b897d3 hq
                $fs->{Name} = $name;
3313
                $fs->{nametype} = "$name ($fs->{Type} $fs->{Size})";
3314
                delete $fs->{on};
3315
                $filesystems{$name} = $fs;
3316 95b003ff Origo
            }
3317
        }
3318
    }
3319
    if ($action eq 'getbackupdevice' || $action eq 'getimagesdevice') {
3320
        return $rootdev;
3321
    }
3322 71b897d3 hq
    $filesystems{$rootdev}->{isbackupdev} = 1 unless ($backupdev || $action eq 'listimagesdevices');
3323
    $filesystems{$rootdev}->{isimagesdev} = 1 unless ($imagesdev || $action eq 'listbackupdevices');
3324 95b003ff Origo
    # Lowercase keys
3325
    foreach my $k (keys %filesystems) {
3326
        my %hash = %{$filesystems{$k}};
3327
        %hash = map { lc $_ => $hash{$_} } keys %hash;
3328
        $filesystems{$k} = \%hash;
3329
    }
3330
    # Identify physical devices used for zfs
3331
    $cmd = "zpool list -vH";
3332
    my $zpools = `$cmd`;
3333
    my $zdev;
3334
    my %zdevs;
3335 e9af6c24 Origo
3336 95b003ff Origo
    # Now parse the rather strange output with every other line representing physical dev
3337
    foreach my $line (split "\n", $zpools) {
3338
        my ($zname, $zsize, $zalloc) = split "\t", $line;
3339
        if (!$zdev) {
3340
            if ($zname =~ /stabile-/) {
3341
                $zdev = {
3342
                    name=>$zname,
3343
                    size=>$zsize,
3344
                    alloc=>$zalloc
3345
                }
3346
            }
3347
        } else {
3348
            my $dev = $zsize;
3349
            $zdev->{dev} = $dev;
3350
            if ( $filesystems{$zdev->{name}}) {
3351
                if (
3352
                    ($action eq 'listimagesdevices' && $zdev->{name} =~ /backup/) ||
3353
                        ($action eq 'listbackupdevices' && $zdev->{name} =~ /images/)
3354
                ) {
3355
                    delete $filesystems{$zdev->{name}}; # Don't include backup devs in images listing and vice-versa
3356
                } else {
3357 e9af6c24 Origo
                    if ($filesystems{$zdev->{name}}->{dev}) {
3358
                        $filesystems{$zdev->{name}}->{dev} .= " $dev";
3359
                    } else {
3360
                        $filesystems{$zdev->{name}}->{dev} = $dev;
3361
                    }
3362
        #            $filesystems{$zdev->{name}}->{nametype} =~ s/zfs/zfs pool/;
3363 95b003ff Origo
                }
3364
            }
3365
            $zdevs{$dev} = $zdev->{name};
3366 e9af6c24 Origo
        #    $zdev = '';
3367 95b003ff Origo
        }
3368
    }
3369
3370
    # Add blockdevices
3371
    $cmd = q|lsblk --json|;
3372
    my $json2 = `$cmd`;
3373
    my $jobj2 = JSON::from_json($json2);
3374
    foreach my $fs (@{$jobj2->{blockdevices}}) {
3375
        my $rootdev = $1 if ($fs->{name} =~ /([A-Za-z]+)\d*/);
3376
        if ($fs->{children}) {
3377
            foreach my $fs2 (@{$fs->{children}}) {
3378 2a63870a Christian Orellana
                next if ($fs2->{type} eq 'loop');
3379
                next if ($fs2->{type} eq 'squashfs');
3380 71b897d3 hq
                next if ($fs2->{size} =~ /K$/);
3381 95b003ff Origo
                if ($filesystems{$fs2->{name}}) {
3382
                    $filesystems{$fs2->{name}}->{blocksize} = $fs2->{size};
3383
                } elsif (!$zdevs{$fs2->{name}} && !$zdevs{$rootdev}) { # Don't add partitions already used for ZFS
3384
                    next if (($action eq 'listimagesdevices' || $action eq 'listbackupdevices') && $fs2->{mountpoint} eq '/');
3385
                    my $mp = $fs2->{mountpoint};
3386
                    $filesystems{$fs2->{name}} = {
3387
                        name=>$fs2->{name},
3388
                        blocksize=>$fs2->{size},
3389
                        mountpoint=>$mp,
3390
                        type=>$fs2->{type},
3391
                        nametype=> "$fs2->{name} ($fs2->{type} - " . ($mp?$mp:"not mounted") . " $fs2->{size})",
3392
                        dev=>$fs2->{name}
3393
                    }
3394
                }
3395
            }
3396
        } elsif (!$zdevs{$fs->{name}}) { # Don't add disks already used for ZFS
3397 2a63870a Christian Orellana
            next if ($fs->{type} eq 'loop');
3398
            next if ($fs->{type} eq 'squashfs');
3399 95b003ff Origo
            my $mp = $fs->{mountpoint};
3400
            next if ($fs->{type} eq 'rom');
3401
            $filesystems{$fs->{name}} = {
3402
                name=>$fs->{name},
3403
                blocksize=>$fs->{size},
3404
                mountpoint=>$fs->{mountpoint},
3405
                type=>$fs->{type},
3406
                nametype=> "$fs->{name} ($fs->{type} - " . ($mp?$mp:"not mounted") . " $fs->{size})",
3407
            }
3408
        }
3409
    }
3410
3411
    # Identify physical devices used for lvm
3412
    $cmd = "pvdisplay -c";
3413
    my $pvs = `$cmd`;
3414
    my @backupdevs; my @imagesdevs;
3415
    foreach my $line (split "\n", $pvs) {
3416
        my ($pvdev, $vgname) = split ":", $line;
3417
        $pvdev = $1 if ($pvdev =~ /\s+(\S+)/);
3418
        $pvdev = $1 if ($pvdev =~ /\/dev\/(\S+)/);
3419
        if ($filesystems{"$vgname-backupvol"}) {
3420
            push @backupdevs, $pvdev unless ($action eq 'listimagesdevices');
3421
        } elsif ($filesystems{"$vgname-imagesvol"}) {
3422
            push @imagesdevs, $pvdev unless ($action eq 'listbackupdevices');
3423
        }
3424
        if (@backupdevs) {
3425
            $filesystems{"$vgname-backupvol"}->{dev} = join(" ", @backupdevs);
3426
            $filesystems{"$vgname-backupvol"}->{nametype} = $filesystems{"$vgname-backupvol"}->{name} . " (lvm with " . $filesystems{"$vgname-backupvol"}->{type} . " on " . join(" ", @backupdevs) . " " . $filesystems{"$vgname-backupvol"}->{size} . ")";
3427
        }
3428
        if (@imagesdevs) {
3429
            $filesystems{"$vgname-imagesvol"}->{dev} = join(" ", @imagesdevs);
3430
            $filesystems{"$vgname-imagesvol"}->{nametype} = $filesystems{"$vgname-imagesvol"}->{name} . " (lvm with " . $filesystems{"$vgname-imagesvol"}->{type} . " on " . join(" ", @imagesdevs) . " " . $filesystems{"$vgname-imagesvol"}->{size} . ")";
3431
        }
3432
        delete $filesystems{$pvdev} if ($filesystems{$pvdev}); # Don't also list as physical device
3433
    }
3434
    my $jsonreply;
3435
    if ($action eq 'getbackupdevice' || $action eq 'getimagesdevice') {
3436
        return ''; # We should not get here
3437 2a63870a Christian Orellana
    } elsif ($action eq 'getstoragedevices') {
3438
        return \%filesystems;
3439 95b003ff Origo
    } elsif ($action eq 'listimagesdevices') {
3440
        $jsonreply .= qq|{"identifier": "name", "label": "nametype", "action": "$action", "items": |;
3441
        my @vals = sort {$b->{'isimagesdev'} cmp $a->{'isimagesdev'}} values %filesystems;
3442
        $jsonreply .= JSON->new->canonical(1)->pretty(1)->encode(\@vals);
3443
        $jsonreply .= "}";
3444
    } elsif ($action eq 'listbackupdevices') {
3445
        $jsonreply .= qq|{"identifier": "name", "label": "nametype", "action": "$action", "items": |;
3446
        my @vals = sort {$b->{'isbackupdev'} cmp $a->{'isbackupdev'}} values %filesystems;
3447
        $jsonreply .= JSON->new->canonical(1)->pretty(1)->encode(\@vals);
3448
        $jsonreply .= "}";
3449
    } else {
3450
        $jsonreply .= JSON->new->canonical(1)->pretty(1)->encode(\%filesystems);
3451
    }
3452
    return $jsonreply;
3453
}
3454
3455
sub do_liststoragepools {
3456
    my ($image, $action) = @_;
3457
    if ($help) {
3458
        return <<END
3459
GET:dojo:
3460
Returns available storage pools. If parameter dojo is set, JSON is padded for Dojo use.
3461
END
3462
    }
3463
    my %npool = (
3464
        "hostpath", "node",
3465
        "path", "--",
3466
        "name", "On node",
3467
        "rdiffenabled", 1,
3468
        "id", "-1");
3469
    my @p = @spools;
3470
    # Present node storage pool if user has sufficient privileges
3471
    if (index($privileges,"a")!=-1 || index($privileges,"n")!=-1) {
3472
        @p = (\%npool);
3473
        push @p, @spools;
3474
    }
3475
3476
    my $jsonreply;
3477
    $jsonreply .= "{\"identifier\": \"id\", \"label\": \"name\", \"items\":" if ($params{'dojo'});
3478
    $jsonreply .= to_json(\@p, {pretty=>1});
3479
    $jsonreply .= "}" if ($params{'dojo'});
3480
    return $jsonreply;
3481
}
3482
3483
# List images available for attaching to server
3484
sub do_listimages {
3485
    my ($img, $action) = @_;
3486
    if ($help) {
3487
        return <<END
3488
GET:image,image1:
3489
List images available for attaching to server. This is different from [list] since images must be unused and e.g. master images cannot be attached to a server.
3490
An image may be passed as parameter. This image is assumed to be already attached to the server, so it is included, even though it is not unused.
3491
If image1 is passed, we assume user is selecting an optional second image for the server, and an empty entry is included in the response, in order for the user to select "no image".
3492
END
3493
    }
3494
    my $res;
3495
    $res .= header('application/json') unless ($console);
3496
    my $curimg1 = URI::Escape::uri_unescape($params{'image1'});
3497
    my @filteredfiles;
3498
    my @curusers = @users;
3499
    # If an admin user is looking at a server not belonging to him, allow him to see the server
3500
    # users images
3501
    if ($isadmin && $img && $img ne '--' && $register{$img} && $register{$img}->{'user'} ne $user) {
3502
        @curusers = ($register{$img}->{'user'}, "common");
3503
    }
3504
3505
    foreach my $u (@curusers) {
3506
        my @regkeys = (tied %register)->select_where("user = '$u'");
3507
        foreach my $k (@regkeys) {
3508
            my $val = $register{$k};
3509
            if ($val->{'user'} eq $u && (defined $spools[$val->{'storagepool'}]->{'id'} || $val->{'storagepool'}==-1)) {
3510
                my $f = $val->{'path'};
3511
                next if ($f =~ /\/images\/dummy.qcow2/);
3512
                my $itype = $val->{'type'};
3513 04c16f26 hq
                if ($itype eq "vmdk" || $itype eq "img" || $itype eq "vhd" || $itype eq "vhdx" || $itype eq "qcow" || $itype eq "qcow2" || $itype eq "vdi") {
3514 95b003ff Origo
                    my $hit = 0;
3515
                    if ($f =~ /(.+)\.master\.$itype/) {$hit = 1;} # don't list master images for user selections
3516
                    if ($f =~ /(.+)\/common\//) {$hit = 1;} # don't list common images for user selections
3517
                    my $dbstatus = $val->{'status'};
3518
                    if ($dbstatus ne "unused") {$hit = 1;} # Image is in a transitional state - do not use
3519
                    if ($hit == 0 || $img eq $f) {
3520 04c16f26 hq
                        my $hypervisor = ($itype eq "vmdk" || $itype eq "vhd" || $itype eq "vhdx" || $itype eq "vdi")?"vbox":"kvm";
3521 95b003ff Origo
                        my $notes = $val->{'notes'};
3522
                        $notes = "" if $notes eq "--";
3523
                        my %img = ("path", $f, "name", $val->{'name'}, "hypervisor", $hypervisor, "notes", $notes,
3524
                            "uuid", $val->{'uuid'}, "master", $val->{'master'}, "managementlink", $val->{'managementlink'}||"",
3525
                            "upgradelink", $val->{'upgradelink'}||"", "terminallink", $val->{'terminallink'}||"", "version", $val->{'version'}||"",
3526
                            "appid", $val->{'appid'}||"");
3527
                        push @filteredfiles, \%img;
3528
                    }
3529
                }
3530
            }
3531
        }
3532
    }
3533
    my %img = ("path", "--", "name", "--", "hypervisor", "kvm,vbox");
3534
    if ($curimg1) {
3535
        push @filteredfiles, \%img;
3536
    }
3537
    my $json_text = to_json(\@filteredfiles, {pretty=>1});
3538
    $res .= qq/{"identifier": "path", "label": "name", "items": $json_text }/;
3539
    return $res;
3540
}
3541
3542
sub Listcdroms {
3543
    my ($image, $action) = @_;
3544
    if ($help) {
3545
        return <<END
3546
GET::
3547
Lists the CD roms a user has access to.
3548
END
3549
    }
3550
    my $res;
3551
    $res .= header('application/json') unless ($console);
3552
    my @filteredfiles;
3553
    foreach my $u (@users) {
3554
        my @regkeys = (tied %register)->select_where("user = '$u'");
3555
        foreach my $k (@regkeys) {
3556
            my $val = $register{$k};
3557
            my $f = $val->{'path'};
3558
            if ($val->{'user'} eq $u && (defined $spools[$val->{'storagepool'}]->{'id'} || $val->{'storagepool'}==-1)) {
3559
                my $itype = $val->{'type'};
3560
                if ($itype eq "iso" || $itype eq "toast") {
3561
                    $notes = $val->{'notes'} || '';
3562
                    if ($u eq $user) {
3563
                        $installable = "true";
3564
                    #    $notes = "This CD/DVD may work just fine, however it has not been tested to work with Irigo Servers.";
3565
                    } else {
3566
                        $installable = $val->{'installable'} || 'false';
3567
                    #    $notes = "This CD/DVD has been tested to work with Irigo Servers." unless $notes;
3568
                    }
3569
                    my %img = ("path", $f, "name", $val->{'name'}, "installable", $installable, "notes", $notes);
3570
                    push @filteredfiles, \%img;
3571
                }
3572
            }
3573
        }
3574
    }
3575
    my %ioimg = ("path", "virtio", "name", "-- VirtIO disk (dummy) --");
3576
    push @filteredfiles, \%ioimg;
3577
    my %dummyimg = ("path", "--", "name", "-- No CD --");
3578
    push @filteredfiles, \%dummyimg;
3579
    #        @filteredfiles = (sort {$a->{'name'} cmp $b->{'name'}} @filteredfiles); # Sort by status
3580
    my $json_text = to_json(\@filteredfiles, {pretty=>1});
3581
    $res .= qq/{"identifier": "path", "label": "name", "items": $json_text }/;
3582
    return $res;
3583
}
3584
3585
sub do_listmasterimages {
3586 f222b89c hq
    my ($image, $action, $obj) = @_;
3587 95b003ff Origo
    if ($help) {
3588
        return <<END
3589
GET::
3590
Lists master images available to the current user.
3591
END
3592
    }
3593
    my $res;
3594
    $res .= header('application/json') unless ($console);
3595
3596
    my @filteredfiles;
3597
    my @busers = @users;
3598 04c16f26 hq
    push (@busers, $billto) if ($billto && $billto ne $user); # We include images from 'parent' user
3599 95b003ff Origo
3600
    foreach my $u (@busers) {
3601
        my @regkeys = (tied %register)->select_where("user = '$u'");
3602
        foreach my $k (@regkeys) {
3603
            my $valref = $register{$k};
3604
            my $f = $valref->{'path'};
3605
            if ($valref->{'user'} eq $u && (defined $spools[$valref->{'storagepool'}]->{'id'} || $valref->{'storagepool'}==-1)) {
3606
                # Only list installable master images from billto account
3607
                next if ($billto && $u eq $billto && $valref->{'installable'} ne 'true');
3608
3609
                my $itype = $valref->{'type'};
3610
                if ($itype eq "qcow2" && $f =~ /(.+)\.master\.$itype/) {
3611
                    my $installable;
3612
                    my $status = $valref->{'status'};
3613
                    my $notes;
3614
                    if ($u eq $user) {
3615
                        $installable = "true";
3616
                        $notes = "This master image may work just fine, however it has not been tested to work with Stabile.";
3617
                    } else {
3618 a91e0e6e hq
                        $installable = $valref->{'installable'} || '';
3619 95b003ff Origo
                        $notes = $valref->{'notes'};
3620 f222b89c hq
                        $notes = "This master image has been tested to work with Stabile." unless $notes;
3621 95b003ff Origo
                    }
3622
                    my %img = (
3623
                        "path", $f,
3624
                        "name", $valref->{'name'},
3625
                        "installable", $installable,
3626
                        "notes", $notes,
3627
                        "managementlink", $valref->{'managementlink'}||"",
3628
                        "upgradelink", $valref->{'upgradelink'}||"",
3629
                        "terminallink", $valref->{'terminallink'}||"",
3630
                        "image2", $valref->{'image2'}||"",
3631
                        "version", $valref->{'version'}||"",
3632
                        "appid", $valref->{'appid'}||"",
3633
                        "status", $status,
3634
                        "user", $valref->{'user'}
3635
                    );
3636
                    push @filteredfiles, \%img;
3637
                }
3638
            }
3639
        }
3640
    }
3641
    my %img = ("path", "--", "name", "--", "installable", "true", "status", "unused");
3642
    push @filteredfiles, \%img;
3643 f222b89c hq
    if ($obj->{raw}) {
3644
        return \@filteredfiles;
3645
    } else {
3646
        my $json_text = JSON::to_json(\@filteredfiles);
3647
        $res .= qq/{"identifier": "path", "label": "name", "items": $json_text }/;
3648
        return $res;
3649
    }
3650 95b003ff Origo
}
3651
3652
sub Updatebtime {
3653
    my ($img, $action, $obj) = @_;
3654
    if ($help) {
3655
        return <<END
3656
GET:image:
3657
END
3658
    }
3659
    my $res;
3660
    $curimg = $curimg || $img;
3661
    my $imguser = $register{$curimg}->{'user'};
3662
    if ($isadmin || $imguser eq $user) {
3663
        my $btime;
3664
        $btime = getBtime($curimg, $imguser) if ($imguser);
3665
        if ($btime) {
3666
            $register{$curimg}->{'btime'} = $btime ;
3667
            $res .= "Status=OK $curimg has btime: " . scalar localtime( $btime ) . "\n";
3668
        } else {
3669 2a63870a Christian Orellana
            $register{$curimg}->{'btime'} = '' ;
3670 95b003ff Origo
            $res .= "Status=OK $curimg has no btime\n";
3671
        }
3672
    } else {
3673
        $res .= "Status=Error no access to $curimg\n";
3674
    }
3675
    return $res;
3676
}
3677
3678
sub Updateallbtimes {
3679
    my ($img, $action) = @_;
3680
    if ($help) {
3681
        return <<END
3682
GET::
3683
END
3684
    }
3685
    if ($isadmin) {
3686
        foreach my $path (keys %register) {
3687
            my $imguser = $register{$path}->{'user'};
3688
            my $btime = getBtime($path, $imguser);
3689
            if ($btime) {
3690
                $register{$path}->{'btime'} = $btime ;
3691
                $postreply .= "Status=OK $register{$path}->{'name'} ($path) has btime: " . scalar localtime( $btime ) . "\n";
3692
            } else {
3693
                $postreply .= "Status=OK $register{$path}->{'name'} ($path) has no btime\n";
3694
            }
3695
        }
3696
    } else {
3697
        $postreply .= "Status=ERROR you are not allowed to do this.\n";
3698
    }
3699
    return $postreply;
3700
}
3701
3702
# Activate image from fuel
3703
sub Activate {
3704
    my ($curimg, $action, $argref) = @_;
3705
    if ($help) {
3706
        return <<END
3707 48fcda6b Origo
GET:image, name, managementlink, upgradelink, terminallink, force:
3708
Activate an image from fuel storage, making it available for regular use.
3709 95b003ff Origo
END
3710
    }
3711
    my %uargs = %{$argref};
3712
    my $name = URI::Escape::uri_unescape($uargs{'name'});
3713
    my $managementlink = URI::Escape::uri_unescape($uargs{'managementlink'});
3714
    my $upgradelink = URI::Escape::uri_unescape($uargs{'upgradelink'});
3715
    my $terminallink = URI::Escape::uri_unescape($uargs{'terminallink'});
3716
    my $version = URI::Escape::uri_unescape($uargs{'version'}) || '1.0b';
3717
    my $image2 =  URI::Escape::uri_unescape($uargs{'image2'});
3718 48fcda6b Origo
    my $force = $uargs{'force'};
3719 95b003ff Origo
3720
    return "Status=ERROR image must be in fuel storage ($curimg)\n" unless ($curimg =~ /^\/mnt\/fuel\/pool(\d+)\/(.+)/);
3721
    my $pool = $1;
3722
    my $ipath = $2;
3723
    return "Status=ERROR image is not a qcow2 image ($curimg, $ipath)\n" unless ($ipath =~ /(.+\.qcow2$)/);
3724
    my $npath = $1;
3725
    my $ppath = '';
3726
    if ($npath =~ /(.*\/)(.+\.qcow2$)/) {
3727
        $npath = $2;
3728
        $ppath = $1;
3729
    }
3730
    my $imagepath = $tenderpathslist[$pool] . "/$user/fuel/$ipath";
3731
    my $newpath = $tenderpathslist[$pool] . "/$user/$npath";
3732
    return "Status=ERROR image not found ($imagepath)\n" unless (-e $imagepath);
3733 48fcda6b Origo
    return "Status=ERROR image already exists in destination ($newpath)\n" if (-e $newpath && !$force);
3734
    return "Status=ERROR image is in use ($newpath)\n" if (-e $newpath && $register{$newpath} && $register{$newpath}->{'status'} ne 'unused');
3735 95b003ff Origo
3736 3657de20 Origo
    my $virtualsize = `qemu-img info --force-share "$imagepath" | sed -n -e 's/^virtual size: .*(//p' | sed -n -e 's/ bytes)//p'`;
3737 95b003ff Origo
    chomp $virtualsize;
3738 991e7f1b hq
#    my $master = `qemu-img info --force-share "$imagepath" | sed -n -e 's/^backing file: //p' | sed -n -e 's/ (actual path:.*)\$//p'`;
3739
    my $master = `qemu-img info --force-share "$imagepath" | sed -n -e 's/^backing file: //p'`;
3740 95b003ff Origo
    chomp $master;
3741
3742
    # Now deal with image2
3743
    my $newpath2 = '';
3744
    if ($image2) {
3745
        $image2 = "/mnt/fuel/pool$pool/$ppath$image2" unless ($image2 =~ /^\//);
3746
        return "Status=ERROR image2 must be in fuel storage ($image2)\n" unless ($image2 =~ /^\/mnt\/fuel\/pool$pool\/(.+)/);
3747
        $ipath = $1;
3748
        return "Status=ERROR image is not a qcow2 image\n" unless ($ipath =~ /(.+\.qcow2$)/);
3749
        $npath = $1;
3750
        $npath = $1 if ($npath =~ /.*\/(.+\.qcow2$)/);
3751
        my $image2path = $tenderpathslist[$pool] . "/$user/fuel/$ipath";
3752
        $newpath2 = $tenderpathslist[$pool] . "/$user/$npath";
3753
        return "Status=ERROR image2 not found ($image2path)\n" unless (-e $image2path);
3754 48fcda6b Origo
        return "Status=ERROR image2 already exists in destination ($newpath2)\n" if (-e $newpath2 && !$force);
3755
        return "Status=ERROR image2 is in use ($newpath2)\n" if (-e $newpath2 && $register{$newpath2} && $register{$newpath2}->{'status'} ne 'unused');
3756 95b003ff Origo
3757 3657de20 Origo
        my $virtualsize2 = `qemu-img info --force-share "$image2path" | sed -n -e 's/^virtual size: .*(//p' | sed -n -e 's/ bytes)//p'`;
3758 95b003ff Origo
        chomp $virtualsize2;
3759 991e7f1b hq
#        my $master2 = `qemu-img info --force-share "$image2path" | sed -n -e 's/^backing file: //p' | sed -n -e 's/ (actual path:.*)\$//p'`;
3760
        my $master2 = `qemu-img info --force-share "$image2path" | sed -n -e 's/^backing file: //p'`;
3761 95b003ff Origo
        chomp $master2;
3762
        if ($register{$master2}) {
3763
            $register{$master2}->{'status'} = 'used';
3764
        }
3765
        `mv "$image2path" "$newpath2"`;
3766
        if (-e $newpath2) {
3767
            my $ug = new Data::UUID;
3768
            my $newuuid = $ug->create_str();
3769
            unless ($name) {
3770
                $name = $npath if ($npath);
3771
                $name = $1 if ($name =~ /(.+)\.(qcow2)$/);
3772
            }
3773
            $register{$newpath2} = {
3774
                uuid => $newuuid,
3775
                path => $newpath2,
3776
                master => $master2,
3777
                name => "$name (data)",
3778
                user => $user,
3779
                storagepool => $pool,
3780
                type => 'qcow2',
3781
                status => 'unused',
3782
                version => $version,
3783
                virtualsize => $virtualsize2
3784
            };
3785
            $postreply .= "Status=OK Activated data image $newpath2, $name (data), $newuuid\n";
3786
        } else {
3787
            $postreply .=  "Status=ERROR Unable to activate $image2path, $newpath2\n";
3788
        }
3789
    }
3790
3791
    # Finish up primary image
3792
    if ($register{$master}) {
3793
        $register{$master}->{'status'} = 'used';
3794
    }
3795
    `mv "$imagepath" "$newpath"`;
3796
    if (-e $newpath) {
3797
        my $ug = new Data::UUID;
3798
        my $newuuid = $ug->create_str();
3799
        unless ($name) {
3800
            $name = $npath if ($npath);
3801
            $name = $1 if ($name =~ /(.+)\.(qcow2)$/);
3802
        }
3803
        $register{$newpath} = {
3804
            uuid => $newuuid,
3805
            path => $newpath,
3806
            master => $master,
3807
            name => $name,
3808
            user => $user,
3809
            storagepool => $pool,
3810
            image2 => $newpath2,
3811
            type => 'qcow2',
3812
            status => 'unused',
3813
            installable => 'true',
3814 48fcda6b Origo
            managementlink => $managementlink || '/stabile/pipe/http://{uuid}:10000/stabile/',
3815 95b003ff Origo
            upgradelink => $upgradelink,
3816
            terminallink => $terminallink,
3817
            version => $version,
3818
            virtualsize => $virtualsize
3819
        };
3820
        $postreply .=  "Status=OK Activated $newpath, $name, $newuuid\n";
3821
    } else {
3822
        $postreply .=  "Status=ERROR Unable to activate $imagepath to $newpath\n";
3823
    }
3824
    return $postreply;
3825
}
3826
3827 2a63870a Christian Orellana
sub Uploadtoregistry {
3828
    my ($path, $action, $obj) = @_;
3829
    if ($help) {
3830
        return <<END
3831
GET:image, force:
3832
Upload an image to the registry. Set [force] if you want to force overwrite images in registry - use with caution.
3833
END
3834
    }
3835
    $force = $obj->{'force'};
3836
    if (-e $path && ($register{$path}->{'user'} eq $user || $isadmin)) {
3837
        $postreply .= $main::uploadToOrigo->($engineid, $path, $force);
3838
    } else {
3839
        $postreply .= "Status=Error Not allowed\n";
3840
    }
3841
    return $postreply;
3842
}
3843
3844 95b003ff Origo
sub Publish {
3845
    my ($uuid, $action, $parms) = @_;
3846
    if ($help) {
3847
        return <<END
3848 48fcda6b Origo
GET:image,appid,appstore,force:
3849
Publish a stack to registry. Set [force] if you want to force overwrite images in registry - use with caution.
3850 95b003ff Origo
END
3851
    }
3852
    my $res;
3853
    $uuid = $parms->{'uuid'} if ($uuid =~ /^\// || !$uuid);
3854 48fcda6b Origo
    my $force = $parms->{'force'};
3855 d24d9a01 hq
    my $freshen = $parms->{'freshen'};
3856 95b003ff Origo
3857
    if ($isreadonly) {
3858
        $res .= "Status=ERROR Your account does not have the necessary privilege.s\n";
3859
    } elsif (!$uuid || !$imagereg{$uuid}) {
3860
        $res .= "Status=ERROR At least specify activated master image uuid [uuid or path] to publish.\n";
3861
    } elsif ($imagereg{$uuid}->{'user'} ne $user && !$isadmin) {
3862
        $res .= "Status=ERROR Your account does not have the necessary privileges.\n";
3863
    } elsif ($imagereg{$uuid}->{'path'} =~ /.+\.master\.qcow2$/) {
3864
        if ($engineid eq $valve001id) { # On valve001 - check if meta file exists
3865
            if (-e $imagereg{$uuid}->{'path'} . ".meta") {
3866
                $res .= "On valve001. Found meta file $imagereg{$uuid}->{'path'}.meta\n";
3867
                my $appid = `cat $imagereg{$uuid}->{'path'}.meta | sed -n -e 's/^APPID=//p'`;
3868
                chomp $appid;
3869
                if ($appid) {
3870
                    $parms->{'appid'} = $appid;
3871
                    $register{$imagereg{$uuid}->{'path'}}->{'appid'} = $appid;
3872
                    tied(%register)->commit;
3873
                }
3874
            }
3875
        # On valve001 - move image to stacks
3876
            if ($imagereg{$uuid}->{'storagepool'} ne '0') {
3877
                $res .= "Status=OK Moving image: " . Move($imagereg{$uuid}->{'path'}, $user, 0) . "\n";
3878
            } else {
3879 48fcda6b Origo
                $res .= "Status=OK Image is already available in registry\n";
3880 95b003ff Origo
            }
3881
        } else {
3882 48fcda6b Origo
        #    $console = 1;
3883
        #    my $link = Download($imagereg{$uuid}->{'path'});
3884
        #    chomp $link;
3885
        #    $parms->{'downloadlink'} = $link; # We now upload instead
3886
        #    $res .= "Status=OK Asking registry to download $parms->{'APPID'} image: $link\n";
3887 95b003ff Origo
            if ($appstores) {
3888
                $parms->{'appstore'} = $appstores;
3889
            } elsif ($appstoreurl =~ /www\.(.+)\//) {
3890
                $parms->{'appstore'} = $1;
3891 48fcda6b Origo
                $res .= "Status=OK Adding registry: $1\n";
3892 95b003ff Origo
            }
3893
        }
3894 6fdc8676 hq
#        $parms->{'appstore'} = 1 if ($freshen);
3895 95b003ff Origo
3896
        my %imgref = %{$imagereg{$uuid}};
3897
        $parms = Hash::Merge::merge($parms, \%imgref);
3898
        my $postdata = to_json($parms);
3899
        my $postres = $main::postToOrigo->($engineid, 'publishapp', $postdata);
3900 48fcda6b Origo
        $res .= $postres;
3901 95b003ff Origo
        my $appid;
3902
        $appid = $1 if ($postres =~ /appid: (\d+)/);
3903
        my $path = $imagereg{$uuid}->{'path'};
3904 d24d9a01 hq
        if ($freshen && $appid) {
3905
            $res .= "Status=OK Freshened the stack description\n";
3906
        } elsif ($appid) {
3907 48fcda6b Origo
            $register{$path}->{'appid'} = $appid if ($register{$path});
3908
            $res .= "Status=OK Received appid $appid for $path, uploading image to registry, hang on...\n";
3909
            my $upres .= $main::uploadToOrigo->($engineid, $path, $force);
3910
            $res .= $upres;
3911
            my $image2 = $register{$path}->{'image2'} if ($register{$path});
3912
            if ($upres =~ /Status=OK/ && $image2 && $image2 ne '--') { # Stack has a data image
3913
                $res .= $main::uploadToOrigo->($engineid, $image2, $force);
3914
            }
3915
        } else {
3916
            $res .= "Status=Error Did not get an appid\n";
3917
        }
3918 95b003ff Origo
    } else {
3919
        $res .= "Status=ERROR You can only publish a master image.\n";
3920
    }
3921
    return $res;
3922
}
3923
3924 48fcda6b Origo
sub Release {
3925
    my ($uuid, $action, $parms) = @_;
3926
    if ($help) {
3927
        return <<END
3928
GET:image,appid,appstore,force,unrelease:
3929
Releases a stack in the registry, i.e. moves it from being a private stack only owner and owner's users can see and use to being a public stack, everyone can use. Set [force] if you want to force overwrite images in registry - use with caution.
3930
END
3931
    }
3932
    my $res;
3933
    $uuid = $parms->{'uuid'} if ($uuid =~ /^\// || !$uuid);
3934
    my $force = $parms->{'force'};
3935
    my $unrelease = $parms->{'unrelease'};
3936
3937
    if (!$uuid || !$imagereg{$uuid}) {
3938
        $res .= "Status=ERROR At least specify master image uuid [uuid or path] to release.\n";
3939
    } elsif (!$isadmin) {
3940
        $res .= "Status=ERROR Your account does not have the necessary privileges.\n";
3941
    } elsif ($imagereg{$uuid}->{'path'} =~ /.+\.master\.qcow2$/ && $imagereg{$uuid}->{'appid'}) {
3942
        my $action = 'release';
3943
        my $targetuser = 'common';
3944
        if ($unrelease) {
3945
            $action = 'unrelease';
3946
            $targetuser = $user;
3947
        }
3948
        if ($appstores) {
3949
            $parms->{'appstore'} = $appstores;
3950
        } elsif ($appstoreurl =~ /www\.(.+)\//) {
3951
            $parms->{'appstore'} = $1;
3952
            $res .= "Status=OK Adding registry: $1\n";
3953
        }
3954
        $parms->{'appid'} = $imagereg{$uuid}->{'appid'};
3955
        $parms->{'force'} = $force if ($force);
3956
        $parms->{'unrelease'} = $unrelease if ($unrelease);
3957
        my $postdata = to_json($parms);
3958
        my $postres = $main::postToOrigo->($engineid, 'releaseapp', $postdata);
3959
        $res .= $postres;
3960
        my $appid;
3961
        $appid = $1 if ($postres =~ /Status=OK Moved (\d+)/);
3962
        my $path = $imagereg{$uuid}->{'path'};
3963
        if ($appid) {
3964
            $res.= "Now moving local stack to $targetuser\n";
3965
            # First move data image
3966
            my $image2 = $register{$path}->{'image2'} if ($register{$path});
3967
            my $newimage2 = $image2;
3968
            if ($image2 && $image2 ne '--' && $register{$image2}) { # Stack has a data image
3969
                if ($unrelease) {
3970
                    $newimage2 =~ s/common/$register{$image2}->{'user'}/;
3971
                } else {
3972
                    $newimage2 =~ s/$register{$image2}->{'user'}/common/;
3973
                }
3974
                $register{$path}->{'image2'} = $newimage2;
3975
                tied(%register)->commit;
3976
                $res .= Move($image2, $targetuser, '', '', 1);
3977
            }
3978
            # Move image
3979
            $res .= Move($path, $targetuser, '', '', 1);
3980
            $res .= "Status=OK $action $appid\n";
3981
        } else {
3982
            $res .= "Status=Error $action failed\n";
3983
        }
3984
    } else {
3985
        $res .= "Status=ERROR You can only $action a master image that has been published.\n";
3986
    }
3987
    return $res;
3988
}
3989
3990 95b003ff Origo
sub do_unlinkmaster {
3991
    my ($img, $action) = @_;
3992
    if ($help) {
3993
        return <<END
3994
GET:image,path:
3995
END
3996
    }
3997
    my $res;
3998
    $res .= header('text/html') unless ($console);
3999
    if ($isreadonly) {
4000
        $res .= "Your account does not have the necessary privileges\n";
4001
    } elsif ($curimg) {
4002
        $res .= unlinkMaster($curimg) . "\n";
4003
    } else {
4004
        $res .= "Please specify master image to link\n";
4005
    }
4006
    return $res;
4007
}
4008
4009
# Simple action for unmounting all images
4010
sub do_unmountall {
4011
    my ($img, $action) = @_;
4012
    if ($help) {
4013
        return <<END
4014
GET:image,path:
4015
END
4016
    }
4017
    return "Your account does not have the necessary privileges\n" if ($isreadonly);
4018
    my $res;
4019
    $res .= header('text/plain') unless ($console);
4020 27512919 Origo
    $res .= "Unmounting all images for $user\n";
4021 95b003ff Origo
    unmountAll();
4022
    $res .= "\n$postreply" if ($postreply);
4023
    return $res;
4024
}
4025
4026
sub Updatedownloads {
4027
    my ($img, $action) = @_;
4028
    if ($help) {
4029
        return <<END
4030
GET:image,path:
4031
END
4032
    }
4033
    my $res;
4034
    $res .= header('text/html') unless ($console);
4035
    my $txt1 = <<EOT
4036
Options -Indexes
4037
EOT
4038
    ;
4039
    `/bin/mkdir "$Stabile::basedir/download"` unless (-e "$Stabile::basedir/download");
4040
    $res .= "Writing .htaccess: -> $Stabile::basedir/download/.htaccess\n";
4041
    unlink("$Stabile::basedir/download/.htaccess");
4042
    `chown www-data:www-data "$Stabile::basedir/download"`;
4043
    `/bin/echo "$txt1" | sudo -u www-data tee "$Stabile::basedir/download/.htaccess"`; #This ugliness is needed because of ownership issues with Synology NFS
4044
    `chmod 644 "$Stabile::basedir/download/.htaccess"`;
4045
    foreach my $p (@spools) {
4046
        my $dir = $p->{'path'};
4047
        my $id = $p->{'id'};
4048
        `/bin/rm "$Stabile::basedir/download/$id"; /bin/ln -s "$dir" "$Stabile::basedir/download/$id"`;
4049
        $res .= "Writing .htaccess: $id -> $dir/.htaccess\n";
4050
        unlink("$dir/.htaccess");
4051
        `/bin/echo "$txt1" | tee "$dir/.htaccess"`;
4052
        `chown www-data:www-data "$dir/.htaccess"`;
4053
        `chmod 644 "$dir/.htaccess"`;
4054
    }
4055
4056
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4057
4058
    foreach my $username (keys %userreg) {
4059
        my $require = '';
4060
        my $txt = <<EOT
4061
order deny,allow
4062
AuthName "Download"
4063
AuthType None
4064
TKTAuthLoginURL $baseurl/auth/login.cgi
4065
TKTAuthIgnoreIP on
4066
deny from all
4067
Satisfy any
4068
require user $username
4069
Options -Indexes
4070
EOT
4071
        ;
4072
        foreach my $p (@spools) {
4073
            my $dir = $p->{'path'};
4074
            my $id = $p->{'id'};
4075
            if (-d "$dir/$username") {
4076
                $res .= "Writing .htaccess: $id -> $dir/$username/.htaccess\n";
4077
                unlink("$dir/$username/.htaccess");
4078
                `/bin/echo "$txt1" | sudo -u www-data tee $dir/$username/.htaccess`;
4079 3657de20 Origo
                if ($tenderlist[$p->{'id'}] eq 'local') {
4080 d24d9a01 hq
                    if (!(-e "$dir/$username/fuel") && -e "$dir/$username") {
4081 3657de20 Origo
                        `mkdir "$dir/$username/fuel"`;
4082
                        `chmod 777 "$dir/$username/fuel"`;
4083
                    }
4084
                }
4085 95b003ff Origo
            }
4086
        }
4087
    }
4088
    untie %userreg;
4089
    return $res;
4090
}
4091
4092
sub do_listpackages($action) {
4093
    my ($image, $action) = @_;
4094
    if ($help) {
4095
        return <<END
4096
GET:image:
4097
Tries to mount and list software packages installed on the operating system on an image. The image must be mountable and contain a valid operating system.
4098
END
4099
    }
4100
    my $res;
4101
    $res .= header('text/plain') unless ($console);
4102
4103
    my $mac = $register{$image}->{'mac'};
4104
    my $macip;
4105
    if ($mac && $mac ne '--') {
4106
        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4107
        $macip = $nodereg{$mac}->{'ip'};
4108
        untie %nodereg;
4109
    }
4110
    $image =~ /(.+)/; $image = $1;
4111
    my $apps;
4112
4113
    if ($macip && $macip ne '--') {
4114
        my $cmd = qq[eval \$(/usr/bin/guestfish --ro -a "$image" --i --listen); ]; # sets $GUESTFISH_PID shell var
4115
        $cmd .= qq[root="\$(/usr/bin/guestfish --remote inspect-get-roots)"; ];
4116
        $cmd .= qq[guestfish --remote inspect-list-applications "\$root"; ];
4117
        $cmd .= qq[guestfish --remote inspect-get-product-name "\$root"; ];
4118
        $cmd .= qq[guestfish --remote exit];
4119
        $cmd = "$sshcmd $macip '$cmd'";
4120
        $apps = `$cmd`;
4121
    } else {
4122
        my $cmd;
4123
        #        my $pid = open my $cmdpipe, "-|",qq[/usr/bin/guestfish --ro -a "$image" --i --listen];
4124
        $cmd .= qq[eval \$(/usr/bin/guestfish --ro -a "$image" --i --listen); ];
4125
        # Start listening guestfish
4126
        my $daemon = Proc::Daemon->new(
4127
            work_dir => '/usr/local/bin',
4128
            setuid => 'www-data',
4129
            exec_command => $cmd
4130
        ) or do {$postreply .= "Status=ERROR $@\n";};
4131
        my $pid = $daemon->Init();
4132
        while ($daemon->Status($pid)) {
4133
            sleep 1;
4134
        }
4135
        # Find pid of the listening guestfish
4136
        my $pid2;
4137
        my $t = new Proc::ProcessTable;
4138
        foreach $p ( @{$t->table} ){
4139
            my $pcmd = $p->cmndline;
4140
            if ($pcmd =~ /guestfish.+$image/) {
4141
                $pid2 = $p->pid;
4142
                last;
4143
            }
4144
        }
4145
4146
        my $cmd2;
4147
        if ($pid2) {
4148
            $cmd2 .= qq[root="\$(/usr/bin/guestfish --remote=$pid2 inspect-get-roots)"; ];
4149
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-list-applications "\$root"; ];
4150
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-get-product-name "\$root"; ];
4151
            $cmd2 .= qq[guestfish --remote=$pid2 exit];
4152
        }
4153
        $apps = `$cmd2`;
4154
    }
4155
    if ($console) {
4156
        $res .= $apps;
4157
    } else {
4158
        my @packages;
4159
        my @packages2;
4160
        open my $fh, '<', \$apps or die $!;
4161
        my $i;
4162
        while (<$fh>) {
4163
            if ($_ =~ /\[(\d+)\]/) {
4164
                push @packages2, $packages[$i];
4165
                $i = $1;
4166
            } elsif ($_ =~ /(\S+): (.+)/ && $2) {
4167
                $packages[$i]->{$1} = $2;
4168
            }
4169
        }
4170
        close $fh or die $!;
4171
        $res .= to_json(\@packages, {pretty => 1});
4172
    }
4173
    return $res;
4174
}
4175
4176
sub Inject {
4177
    my ($image, $action, $obj) = @_;
4178
    if ($help) {
4179
        return <<END
4180
GET:image:
4181
Tries to inject drivers into a qcow2 image with a Windows OS installed on it. Image must not be in use.
4182
END
4183
    }
4184
    $uistatus = "injecting";
4185
    my $path = $obj->{path} || $curimg;
4186
    my $status = $obj->{status};
4187
    my $esc_localpath = shell_esc_chars($path);
4188
4189
    # Find out if we are dealing with a Windows image
4190 04c16f26 hq
    # my $xml = `bash -c '/usr/bin/virt-inspector -a $esc_localpath'`;
4191
    my $xml = `bash -c '/usr/bin/virt-inspector -a $esc_localpath' 2>&1`;
4192 95b003ff Origo
    # $res .= $xml . "\n";
4193
    my $xmlref;
4194
    my $osname;
4195
    $xmlref = XMLin($xml) if ($xml =~ /^<\?xml/);
4196
    $osname = $xmlref->{operatingsystem}->{name} if ($xmlref);
4197
    if ($xmlref && $osname eq 'windows') {
4198 04c16f26 hq
    #    my $upath = $esc_localpath;
4199
        my $upath = $path;
4200 95b003ff Origo
        # We need write privileges
4201
        $res .= `chmod 666 "$upath"`;
4202
        # First try to merge storage registry keys into Windows registry. If not a windows vm it simply fails.
4203
        $res .= `bash -c 'cat /usr/share/stabile/mergeide.reg | /usr/bin/virt-win-reg --merge "$upath"' 2>&1`;
4204
        # Then try to merge the critical device keys. This has been removed in win8 and 2012, so will simply fail for these.
4205
        $res .= `bash -c 'cat /usr/share/stabile/mergeide-CDDB.reg | /usr/bin/virt-win-reg --merge "$upath"' 2>&1`;
4206 04c16f26 hq
        if ($res) { $main::syslogit->($user, "info", $res); $res = ''; }
4207 95b003ff Origo
4208
        # Try to copy viostor.sys into image
4209
        my @winpaths = (
4210
            '/Windows/System32/drivers',
4211 04c16f26 hq
            '/WINDOWS/system32/drivers',
4212
            '/WINDOWS/System32/drivers',
4213
            '/WINNT/system32/drivers'
4214 95b003ff Origo
        );
4215
        foreach my $winpath (@winpaths) {
4216
            my $lscmd = qq|bash -c 'virt-ls -a "$upath" "$winpath"'|;
4217
            my $drivers = `$lscmd`;
4218
            if ($drivers =~ /viostor/i) {
4219 04c16f26 hq
                $postreply .= "Status=$status viostor already installed in $winpath in $upath\n";
4220 95b003ff Origo
                $main::syslogit->($user, "info", "viostor already installed in $winpath in $upath");
4221
                last;
4222
            } elsif ($drivers) {
4223 04c16f26 hq
                `umount "$upath"`; # Unmount if mounted by browse operation or similar
4224
                my $cmd = qq|bash -c 'guestfish --rw -i -a "$upath" upload /usr/share/stabile/VIOSTOR.SYS $winpath/viostor.sys' 2>&1|;
4225 95b003ff Origo
                my $error = `$cmd`;
4226
                if ($error) {
4227 04c16f26 hq
                    $postreply .= "$cmd\n";
4228
                    $postreply .= "Status=ERROR Problem injecting virtio drivers into $winpath on $upath: $error\n";
4229 95b003ff Origo
                    $main::syslogit->($user, "info", "Error injecting virtio drivers into $upath: $error");
4230
                } else {
4231 04c16f26 hq
                    $postreply .= "Status=$status Injected virtio drivers into $upath\n";
4232 95b003ff Origo
                    $main::syslogit->($user, "info", "Injected virtio drivers into $upath");
4233
                }
4234
                last;
4235
            } else {
4236
                $postreply .= "Status=ERROR No drivers found in $winpath\n";
4237
            }
4238
        }
4239
4240
    } else {
4241
        $postreply .= "Status=ERROR No Windows OS found in $osname image, not injecting drivers.\n";
4242
        $main::syslogit->($user, "info", "No Windows OS found ($osname) in image, not injecting drivers.");
4243
    }
4244
    my $msg = $postreply;
4245
    $msg = $1 if ($msg =~ /\w+=\w+ (.+)/);
4246
    chomp $msg;
4247
    $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status, message=>$msg});
4248 04c16f26 hq
    $postreply .=  "Status=$uistatus $obj->{type} image: $obj->{name}\n";
4249 95b003ff Origo
    $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4250
    return $postreply;
4251
}
4252
4253
sub Convert {
4254
    my ($image, $action, $obj) = @_;
4255
    if ($help) {
4256
        return <<END
4257
GET:image:
4258
Converts an image to qcow2 format. Image must not be in use.
4259
END
4260
    }
4261
    my $path = $obj->{path};
4262
    $uistatus = "converting";
4263
    $uipath = $path;
4264
    if ($obj->{status} ne "unused" && $obj->{status} ne "used" && $obj->{status} ne "paused") {
4265
        $postreply .= "Status=ERROR Problem $uistatus $obj->{type} image: $obj->{name}\n";
4266 04c16f26 hq
    } elsif ($obj->{type} eq "img" || $obj->{type} eq "vmdk" || $obj->{type} eq "vhd" || $obj->{type} eq "vhdx") {
4267 95b003ff Origo
        my $oldpath = $path;
4268
        my $newpath = "$path.qcow2";
4269
        if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
4270
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4271
            $macip = $nodereg{$obj->{mac}}->{'ip'};
4272
            untie %nodereg;
4273
            $oldpath = "$macip:$path";
4274
        } else { # We are not on a node - check that image is not on a read-only filesystem
4275
            my ($fname, $destfolder) = fileparse($path);
4276
            my $ro = `touch "$destfolder/test.tmp" && { rm "$destfolder/test.tmp"; } || echo "read-only" 2>/dev/null`;
4277
            if ($ro) { # Destinationfolder is not writable
4278
                my $npath = "$spools[0]->{'path'}/$register{$path}->{'user'}/$fname.qcow2";
4279
                $newpath = $npath;
4280
            }
4281
            if (-e $newpath) { # Don't overwrite existing file
4282
                my $subpath = substr($newpath,0,-6);
4283
                my $i = 1;
4284
                if ($newpath =~ /(.+)\.(\d+)\.qcow2/) {
4285
                    $i = $2;
4286
                    $subpath = $1;
4287
                }
4288
                while (-e $newpath) {
4289
                    $newpath = $subpath . ".$i.qcow2";
4290
                    $i++;
4291
                }
4292
            }
4293
        }
4294
        eval {
4295
            my $ug = new Data::UUID;
4296
            my $newuuid = $ug->create_str();
4297
4298
            $register{$newpath} = {
4299
                uuid=>$newuuid,
4300
                name=>"$obj->{name} (converted)",
4301
                notes=>$obj->{notes},
4302
                image2=>$obj->{image2},
4303
                managementlink=>$obj->{managementlink},
4304
                upgradelink=>$obj->{managementlink},
4305
                terminallink=>$obj->{terminallink},
4306
                storagepool=>$obj->{regstoragepool},
4307
                status=>$uistatus,
4308
                mac=>($obj->{regstoragepool} == -1)?$obj->{mac}:"",
4309
                size=>0,
4310
                realsize=>0,
4311
                virtualsize=>$obj->{virtualsize},
4312
                type=>"qcow2",
4313
                user=>$user
4314
            };
4315
            $register{$path}->{'status'} = $uistatus;
4316
4317
            my $daemon = Proc::Daemon->new(
4318
                work_dir => '/usr/local/bin',
4319
                exec_command => "perl -U steamExec $user $uistatus $obj->{status} \"$oldpath\" \"$newpath\""
4320
            ) or do {$postreply .= "Status=ERROR $@\n";};
4321
            my $pid = $daemon->Init() or do {$postreply .= "Status=ERROR $@\n";};
4322
            $postreply .=  "Status=OK $uistatus $obj->{type} image: $obj->{name}\n";
4323
            $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4324
        } or do {$postreply .= "Status=ERROR $@\n";};
4325
        $main::updateUI->({tab=>"images", user=>$user, type=>"update"});
4326
    } else {
4327
        $postreply .= "Status=ERROR Only img and vmdk images can be converted\n";
4328
    }
4329 2a63870a Christian Orellana
    return $postreply;
4330 95b003ff Origo
}
4331
4332
sub Snapshot {
4333
    my ($image, $action, $obj) = @_;
4334
    if ($help) {
4335
        return <<END
4336
GET:image:
4337
Adds a snapshot to a qcow2 image. Image can not be in use by a running server.
4338
END
4339
    }
4340
    my $status = $obj->{status};
4341
    my $path = $obj->{path};
4342
    my $macip;
4343
    $uistatus = "snapshotting";
4344
    $uiuuid = $obj->{uuid};
4345
    if ($status ne "unused" && $status ne "used") {
4346
        $postreply .= "Status=ERROR Problem $uistatus $obj->{type} image: $obj->{name}\n";
4347
    } elsif ($obj->{type} eq "qcow2") {
4348
        my $newpath = $path;
4349
        my $hassnap;
4350
        my $snaptime = time;
4351
        if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
4352
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4353
            $macip = $nodereg{$obj->{mac}}->{'ip'};
4354
            untie %nodereg;
4355
            $newpath = "$macip:$path";
4356
            my $esc_path = $path;
4357
            $esc_path =~ s/([ ])/\\$1/g;
4358
            my $qinfo = `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -l $esc_path"`;
4359
            $hassnap = ($qinfo =~ /snap1/g);
4360
            $postreply .= `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -d snap1 $esc_path"` if ($hassnap);
4361
        } else {
4362
            my $qinfo = `/usr/bin/qemu-img snapshot -l "$path"`;
4363
            $hassnap = ($qinfo =~ /snap1/g);
4364
            $postreply .= `/usr/bin/qemu-img snapshot -d snap1 "$path\n"` if ($hassnap);
4365
        }
4366
        eval {
4367
            if ($hassnap) {
4368
                $postreply .= "Status=Error Only one snapshot per image is supported for $obj->{type} image: $obj->{name} ";
4369
            } else {
4370
                $register{$path}->{'status'} = $uistatus;
4371
                $register{$path}->{'snap1'} = $snaptime;
4372
4373
                if ($macip) {
4374
                    my $esc_localpath = shell_esc_chars($path);
4375
                    $res .= `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -c snap1 $esc_localpath"`;
4376
                } else {
4377
                    $res .= `/usr/bin/qemu-img snapshot -c snap1 "$path"`;
4378
                }
4379
                $register{$path}->{'status'} = $status;
4380
                $postreply .=  "Status=$uistatus OK $uistatus $obj->{type} image: $obj->{name}\n";
4381
                $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4382
            }
4383
            1;
4384
        } or do {$postreply .= "Status=ERROR $@\n";};
4385
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status, snap1=>$snaptime});
4386
    } else {
4387
        $postreply .= "Status=ERROR Only qcow2 images can be snapshotted\n";
4388
    }
4389
    return $postreply;
4390
}
4391
4392
sub Unsnap {
4393
    my ($image, $action, $obj) = @_;
4394
    if ($help) {
4395
        return <<END
4396
GET:image:
4397
Removes a snapshot from a qcow2 image. Image can not be in use by a running server.
4398
END
4399
    }
4400
    my $status = $obj->{status};
4401
    my $path = $obj->{path};
4402
    $uistatus = "unsnapping";
4403
    $uiuuid = $obj->{uuid};
4404
    my $macip;
4405
4406
    if ($status ne "unused" && $status ne "used") {
4407
        $postreply .= "Status=ERROR Problem $uistatus $obj->{type} image: $obj->{name}\n";
4408
    } elsif ($obj->{type} eq "qcow2") {
4409
        my $newpath = $path;
4410
        my $hassnap;
4411
        my $qinfo;
4412
        my $esc_path;
4413
        if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
4414
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4415
            $macip = $nodereg{$obj->{mac}}->{'ip'};
4416
            untie %nodereg;
4417
            $newpath = "$macip:$path";
4418
            $esc_path = $path;
4419
            $esc_path =~ s/([ ])/\\$1/g;
4420
            $qinfo = `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -l $esc_path"`;
4421
            $hassnap = ($qinfo =~ /snap1/g);
4422
        } else {
4423
            $qinfo = `/usr/bin/qemu-img snapshot -l "$path"`;
4424
            $hassnap = ($qinfo =~ /snap1/g);
4425
        }
4426
        eval {
4427
            my $snaptime = time;
4428
            if ($hassnap) {
4429
                delete $register{$path}->{'snap1'};
4430
                $register{$path}->{'status'} = $uistatus;
4431
                if ($macip) {
4432
                    my $esc_localpath = shell_esc_chars($path);
4433
                    $res .= `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -d snap1 $esc_localpath"`;
4434
                } else {
4435
                    $res .= `/usr/bin/qemu-img snapshot -d snap1 "$path"`;
4436
                }
4437
                $register{$path}->{'status'} = $status;
4438
                $postreply .=  "Status=$uistatus OK $uistatus $obj->{type} image: $obj->{name}\n";
4439
                $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4440
            } else {
4441
                $postreply .= "Status=ERROR No snapshot found in $obj->{name}\n";
4442
                delete $register{$path}->{'snap1'};
4443
                $uistatus = $status;
4444
            }
4445
            1;
4446
        } or do {$postreply .= "Status=ERROR $@\n";};
4447
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status, snap1=>'--'});
4448
    } else {
4449
        $postreply .= "Status=ERROR Only qcow2 images can be unsnapped\n";
4450
    }
4451
    return $postreply;
4452
}
4453
4454
sub Revert {
4455
    my ($image, $action, $obj) = @_;
4456
    if ($help) {
4457
        return <<END
4458
GET:image:
4459
Applies a snapshot to a qcow2 image, i.e. the image is reverted to the state it was in when the snapshot was taken. Image can not be in use by a running server.
4460
END
4461
    }
4462
    my $status = $obj->{status};
4463
    my $path = $obj->{path};
4464
    $uistatus = "reverting";
4465
    $uipath = $path;
4466
    my $macip;
4467
    if ($status ne "used" && $status ne "unused") {
4468
        $postreply .= "Status=ERROR Please shut down or pause your virtual machine before reverting\n";
4469
    } elsif ($obj->{type} eq "qcow2") {
4470
        my $newpath = $path;
4471
        my $hassnap;
4472
        if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
4473
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4474
            $macip = $nodereg{$obj->{mac}}->{'ip'};
4475
            untie %nodereg;
4476
            $newpath = "$macip:$path";
4477
            my $esc_path = $path;
4478
            $esc_path =~ s/([ ])/\\$1/g;
4479
            my $qinfo = `ssh -l irigo -i /var/www/.ssh/id_rsa_www -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $macip "/usr/bin/qemu-img snapshot -l $esc_path"`;
4480
            $hassnap = ($qinfo =~ /snap1/g);
4481
        } else {
4482
            my $qinfo = `/usr/bin/qemu-img snapshot -l "$path"`;
4483
            $hassnap = ($qinfo =~ /snap1/g);
4484
        }
4485
        eval {
4486
            if ($hassnap) {
4487
                $register{$path}->{'status'} = $uistatus;
4488
                if ($macip) {
4489
                    my $esc_localpath = shell_esc_chars($path);
4490
                    $res .= `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -a snap1 $esc_localpath"`;
4491
                } else {
4492 80e0b3f5 hq
                    $res .= `/usr/bin/qemu-img snapshot -a snap1 "$path"`;
4493 95b003ff Origo
                }
4494
                $register{$path}->{'status'} = $status;
4495
                $postreply .=  "Status=OK $uistatus $obj->{type} image: $obj->{name}\n";
4496
                $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4497
            } else {
4498
                $postreply .= "Status=ERROR no snapshot found\n";
4499
                $uistatus = $status;
4500
            }
4501
            1;
4502
        } or do {$postreply .= "Status=ERROR $@\n";};
4503
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status, snap1=>'--'});
4504
    } else {
4505
        $postreply .= "Status=ERROR Only qcow2 images can be reverted\n";
4506
    }
4507
    return;
4508
}
4509
4510
sub Zbackup {
4511
    my ($image, $action, $obj) = @_;
4512
    if ($help) {
4513
        return <<END
4514 c899e439 Origo
GET:mac, storagepool, synconly, snaponly, imageretention, backupretention:
4515 95b003ff Origo
Backs all images on ZFS storage up by taking a storage snapshot. By default all shared storagepools are backed up.
4516
If storagepool -1 is specified, all ZFS node storages is backed up. If "mac" is specified, only specific node is backed up.
4517
If "synconly" is set, no new snapshots are taken - only syncing of snapshots is performed.
4518
If "snaponly" is set, only local active storage snapshot is taken - no sending to backup storage is done.
4519
"xretention" can be either simply number of snapshots to keep, or max age of snapshot to keep in seconds [s], hours [h] or days [d],
4520
e.g. "imageretention=10" will keep 10 image snapshots, "imageretention=600s" will purte image snapshots older than 600 seconds if possible, or "backretention=14d" will purge backup snapshots older than 14 days.
4521
END
4522
    }
4523
    if ($isadmin) {
4524
        my $synconly = $obj->{'synconly'};
4525
        my $snaponly = $obj->{'snaponly'};
4526
        my $mac = $obj->{'mac'};
4527
        my $storagepool = $obj->{'storagepool'};
4528
        $storagepool = -1 if ($mac);
4529
        my $imageretention = $obj->{'imageretention'} || $imageretention;
4530
        my $backupretention = $obj->{'backupretention'} || $backupretention;
4531
4532
        my $basepath = "stabile-backup";
4533
        my $bpath = $basepath;
4534
        my $mounts = `/bin/cat /proc/mounts`;
4535
        my $zbackupavailable = (($mounts =~ /$bpath (\S+) zfs/)?$1:'');
4536
        unless ($zbackupavailable) {$postreply .= "Status=OK ZFS backup not available, only doing local snapshots\n";}
4537
        my $zfscmd = "zfs";
4538
        my $macip;
4539
        my $ipath = $spools[0]->{'zfs'} || 'stabile-images/images';
4540
        my @nspools = @spools;
4541
        if (!(defined $obj->{'storagepool'}) || $storagepool == -1) {
4542
            @nspools = () if ($storagepool == -1); # Only do node backups
4543
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4544 27512919 Origo
#            my $nipath = $ipath;
4545
#            $nipath = "$1/node" if ($nipath =~ /(.+)\/(.+)/);
4546
            my $nipath = 'stabile-node/node';
4547 95b003ff Origo
            foreach my $node (values %nodereg) {
4548
                push @nspools, {
4549
                    mac=>$node->{'mac'},
4550
                    macip=>$node->{'ip'},
4551
                    zfs=>$nipath,
4552
                    id=>-1
4553
                } if ($node->{'stor'} eq 'zfs' && (!$mac || $node->{'mac'} eq $mac))
4554
            }
4555
            untie %nodereg;
4556
        }
4557
        if (`pgrep zfs`) {
4558
            $postreply .= "Status=ERROR Another ZFS backup is running. Please wait a minute...\n";
4559
            $postmsg = "ERROR ERROR Another ZFS backup is running. Please wait a minute...";
4560
            return $postreply;
4561
        }
4562
        $postreply .= "Status=OK Performing ZFS backup on " . (scalar @nspools) . " storage pools with image retention $imageretention, backup retention $backupretention\n";
4563
4564
        foreach my $spool (@nspools) {
4565
            $ipath = $spool->{'zfs'};
4566
            if ($spool->{'id'} == -1) { # We're doing a node backup
4567
                $mac = $spool->{'mac'};
4568
                $macip = $spool->{'macip'};
4569
                $bpath = "$basepath/node-$mac";
4570
            } else {
4571
                next unless ($ipath);
4572
                next if (($storagepool || $storagepool eq '0') && $storagepool ne $spool->{'id'});
4573
                $bpath = "$basepath/$1" if ($ipath =~ /.+\/(.+)/);
4574
                $mac = '';
4575
                $macip = '';
4576
            }
4577 27512919 Origo
            if ($macip) {$zfscmd = "$sshcmd $macip sudo zfs";}
4578 95b003ff Origo
            else {$zfscmd = "zfs";}
4579
4580 04c16f26 hq
            $postreply .= "Status=OK Commencing ZFS backup of $ipath $macip, storagepool=$storagepool, synconly=$synconly, snaponly=$snaponly\n";
4581 95b003ff Origo
            my $res;
4582
            my $cmd;
4583
            my @imagesnaps;
4584
            my @backupsnaps;
4585
4586
            # example: stabile-images/images@SNAPSHOT-20200524172901
4587
            $cmd = qq/$zfscmd list -t snapshot | grep '$ipath'/;
4588
            my $snaplist = `$cmd`;
4589
            my @snaplines = split("\n", $snaplist);
4590
            foreach my $snap (@snaplines) {
4591
                push @imagesnaps, $2 if ($snap =~ /(.*)\@SNAPSHOT-(\d+)/);
4592
            }
4593
            if ($zbackupavailable) {
4594
                $cmd = qq/zfs list -t snapshot | grep '$bpath'/;
4595
                $snaplist = `$cmd`;
4596
                @snaplines = split("\n", $snaplist);
4597
                foreach my $snap (@snaplines) {
4598
                    push @backupsnaps, $2 if ($snap =~ /(.*)\@SNAPSHOT-(\d+)/);
4599
                }
4600
            }
4601
        # Find matching snapshots
4602
            my $matches=0;
4603
            my $matchbase = 0;
4604
            foreach my $bsnap (@backupsnaps) {
4605
                if ($bsnap eq $imagesnaps[$matchbase + $matches]) { # matching snapshot found
4606
                    $matches++;
4607
                } elsif ($matches) { # backup snapshots are ahead of image snapshots - correct manually, i.e. delete them.
4608
                    $postreply .= "Status=ERROR Snapshots are out of sync.\n";
4609
                    $postmsg = "ERROR Snapshots are out of sync";
4610
                    $main::syslogit->($user, 'info', "ERROR snapshots of $ipath and $bpath are out of sync.");
4611
                    return $postreply;
4612
                } elsif (!$matchbase) { # Possibly there are image snapshots older than there are backup snapshots, find the match base i.e. first match in @imagesnaps
4613
                    my $mb=0;
4614
                    foreach my $isnap (@imagesnaps) {
4615
                        if ($bsnap eq $isnap) { # matching snapshot found
4616
                            $matchbase = $mb;
4617
                            $matches++;
4618
                            last;
4619
                        }
4620
                        $mb++;
4621
                    }
4622
                }
4623
            }
4624 27512919 Origo
4625 95b003ff Origo
            my $lastisnap = $imagesnaps[scalar @imagesnaps -1];
4626
            my $lastisnaptime = timelocal($6,$5,$4,$3,$2-1,$1) if ($lastisnap =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/);
4627
            my $td = ($current_time - $lastisnaptime);
4628
            if ($td<=5) {
4629
                $postreply .= "Status=ERROR Last backup was taken $td seconds ago. Please wait a minute...\n";
4630
                $postmsg = "ERROR ERROR Last backup was taken $td seconds ago. Please wait a minute...";
4631
                return $postreply;
4632
            }
4633
            my $ni = scalar @imagesnaps;
4634
            my $nb = scalar @backupsnaps;
4635 6372a66e hq
4636
            # If there are unsynced image snaps - sync them
4637 95b003ff Origo
            if ($zbackupavailable && !$snaponly) {
4638
                if (scalar @imagesnaps > $matches+$matchbase) {
4639 6372a66e hq
                    if ($matches > 0) { # We must have at least one common shapshot to sync
4640
                        for (my $j=$matches+$matchbase; $j < scalar @imagesnaps; $j++) {
4641
                            if ($macip) {
4642
                                $cmd = qq[$zfscmd "send -i $ipath\@SNAPSHOT-$imagesnaps[$j-1] $ipath\@SNAPSHOT-$imagesnaps[$j] | ssh 10.0.0.1 sudo zfs receive $bpath"]; # -R
4643
                            } else {
4644
                                $cmd = qq[zfs send -i $ipath\@SNAPSHOT-$imagesnaps[$j-1] $ipath\@SNAPSHOT-$imagesnaps[$j] | zfs receive $bpath]; # -R
4645
                            }
4646
                            $res = `$cmd 2>&1`;
4647
                            unless (
4648
                                ($res && !$macip) #ssh will warn about adding to list of known hosts
4649
                                    || $res =~ /cannot receive/
4650
                            ) {
4651
                                $matches++;
4652
                                $nb++;
4653
                                $postreply .= "Status=OK Sending ZFS snapshot $j $imagesnaps[$j-1]->$imagesnaps[$j] of $macip $ipath to $bpath $res\n";
4654
                                $main::syslogit->($user, 'info', "OK Sending ZFS snapshot $imagesnaps[$j-1]->$imagesnaps[$j] of $macip $ipath to $bpath $res");
4655
                            } else {
4656
                                $postreply .= "Status=Error Problem sending ZFS snapshot $j $imagesnaps[$j-1]->$imagesnaps[$j] of $macip $ipath to $bpath $res\n";
4657
                                $main::syslogit->($user, 'info', "Error Problem sending ZFS snapshot $imagesnaps[$j-1]->$imagesnaps[$j] of $macip $ipath to $bpath $res");
4658
                            }
4659 95b003ff Origo
                        }
4660 6372a66e hq
                    } else {
4661
                        $postreply .= "Status=OK Unable to sync $ni snapshots, no common snapshot, trying to start from scratch.\n";
4662 95b003ff Origo
                    }
4663
                }
4664
            }
4665
            $res = '';
4666 27512919 Origo
4667 6372a66e hq
            if ($matches && !$synconly) { # There was at least one match, snapshots are now assumed to be in sync
4668 95b003ff Origo
        # Then perform the actual snapshot
4669
                my $snap1 = sprintf "%4d%02d%02d%02d%02d%02d",$year,$mon+1,$mday,$hour,$min,$sec;
4670
                my $oldsnap = $imagesnaps[$matches+$matchbase-1];
4671
                $cmd = qq|$zfscmd snapshot -r $ipath\@SNAPSHOT-$snap1|;
4672
                $postreply .= "Status=OK Performing ZFS snapshot with $matches matches and base $matchbase $res\n";
4673
                $res = `$cmd 2>&1`;
4674
                unless ($res && !$macip) {
4675
                    $ni++;
4676
                    push @imagesnaps, $snap1;
4677
                }
4678
        # Send it to backup if asked to
4679
                unless ($snaponly || !$zbackupavailable) {
4680
                    if ($macip) {
4681 27512919 Origo
                        $cmd = qq[$zfscmd "send -i $ipath\@SNAPSHOT-$oldsnap $ipath\@SNAPSHOT-$snap1 | ssh 10.0.0.1 sudo zfs receive $bpath"];
4682 95b003ff Origo
                    } else {
4683
                        $cmd = qq[zfs send -i $ipath\@SNAPSHOT-$oldsnap $ipath\@SNAPSHOT-$snap1 | zfs receive $bpath]; # -R
4684
                    }
4685
                    $res .= `$cmd 2>&1`;
4686
                    unless ($res && !$macip) {
4687
                        $matches++;
4688
                        $nb++;
4689
                        push @backupsnaps, $snap1;
4690
                    }
4691
                    $postreply .= "Status=OK Sending ZFS snapshot of $macip $ipath $oldsnap->$snap1 to $bpath $res\n";
4692
                    $main::syslogit->($user, 'info', "OK Sending ZFS snapshot of $macip $ipath $oldsnap->$snap1 to $bpath $res");
4693
                }
4694 27512919 Origo
                $postreply .= "Status=OK Synced $matches ZFS snapshots. There are now $ni image snapshots, $nb backup snapshots.\n";
4695 95b003ff Origo
            } elsif ($matches) {
4696 27512919 Origo
                $postreply .= "Status=OK Synced $matches ZFS snapshots. There are $ni image snapshots, $nb backup snapshots.\n";
4697 95b003ff Origo
#            } elsif ($ni==0 && $nb==0) { # We start from a blank slate
4698
            } elsif ($nb==0) { # We start from a blank slate
4699
                my $snap1 = sprintf "%4d%02d%02d%02d%02d%02d",$year,$mon+1,$mday,$hour,$min,$sec;
4700
                $cmd = qq|$zfscmd snapshot -r $ipath\@SNAPSHOT-$snap1|;
4701
                $res = `$cmd 2>&1`;
4702 04c16f26 hq
                $postreply .= "Status=OK Performing ZFS snapshot from scratch $res $macip\n";
4703
        # Send it to backup by creating new filesystem (created autotically)
4704 95b003ff Origo
                unless ($snaponly || !$zbackupavailable) {
4705
                    if ($macip) {
4706 27512919 Origo
                        $cmd = qq[$zfscmd "send $ipath\@SNAPSHOT-$snap1 | ssh 10.0.0.1 sudo zfs receive $bpath"];
4707 95b003ff Origo
                        $res .= `$cmd 2>&1`;
4708
                        $cmd = qq|zfs set readonly=on $bpath|;
4709
                        $res .= `$cmd 2>&1`;
4710
                        $cmd = qq|zfs mount $bpath|;
4711
                        $res .= `$cmd 2>&1`;
4712
                    } else {
4713
                        $cmd = qq[zfs send -R $ipath\@SNAPSHOT-$snap1 | zfs receive $bpath];
4714
                        $res .= `$cmd 2>&1`;
4715 2a63870a Christian Orellana
                        $cmd = qq|zfs set readonly=on $bpath|;
4716
                        $res .= `$cmd 2>&1`;
4717 95b003ff Origo
                    }
4718
                    $postreply .= "Status=OK Sending complete ZFS snapshot of $macip:$ipath\@$snap1 to $bpath $res\n";
4719
                    $main::syslogit->($user, 'info', "OK Sending complete ZFS snapshot of $macip:$ipath\@$snap1 to $bpath $res");
4720
                    $matches++;
4721
                    $nb++;
4722
                }
4723
                $ni++;
4724 04c16f26 hq
                $postreply .= "Status=OK Synced 0 ZFS snapshots. There are $ni image snapshots, $nb backup snapshots.\n";
4725 95b003ff Origo
            } else {
4726
                $postreply .= "Status=ERROR Unable to sync snapshots.\n";
4727
                $postmsg = "ERROR Unable to sync snapshots";
4728
            }
4729
            my $i=0;
4730
        # Purge image snapshots if asked to
4731
            if ($imageretention && $matches>1) {
4732
                my $rtime;
4733
                if ($imageretention =~ /(\d+)(s|h|d)/) {
4734
                    $rtime = $1;
4735
                    $rtime = $1*60*60 if ($2 eq 'h');
4736
                    $rtime = $1*60*60*24 if ($2 eq 'd');
4737
                    $postreply .= "Status=OK Keeping image snapshots newer than $imageretention out of $ni.\n";
4738
                } elsif ($imageretention =~ /(\d+)$/) {
4739
                    $postreply .= "Status=OK Keeping " . (($imageretention>$ni)?$ni:$imageretention) . " image snapshots out of $ni.\n";
4740
                } else {
4741
                    $imageretention = 0;
4742
                }
4743
                if ($imageretention) {
4744
                    foreach my $isnap (@imagesnaps) {
4745
                        my $purge;
4746
                        if ($rtime) {
4747
                            my $snaptime = timelocal($6,$5,$4,$3,$2-1,$1) if ($isnap =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/);
4748
                            my $tdiff = ($current_time - $snaptime);
4749
                            if ( $matches>1 && $tdiff>$rtime )
4750
                                {$purge = 1;}
4751
                            else
4752
                                {last;}
4753
                        } else { # a simple number was specified
4754
#                            if ( $matches>1 && $matches+$matchbase>$imageretention )
4755
                            if ( $matches>1 && $ni>$imageretention )
4756
                                {$purge = 1;}
4757
                            else
4758
                                {last;}
4759
                        }
4760
                        if ($purge) {
4761
                            $cmd = qq|$zfscmd destroy $ipath\@SNAPSHOT-$isnap|;
4762
                            $res = `$cmd 2>&1`;
4763
                            $postreply .= "Status=OK Purging image snapshot $isnap from $ipath.\n";
4764
                            $main::syslogit->($user, 'info', "OK Purging image snapshot $isnap from $ipath");
4765
                            $matches-- if ($i>=$matchbase);
4766
                            $ni--;
4767
                        }
4768
                        $i++;
4769
                    }
4770
                }
4771
            }
4772
            # Purge backup snapshots if asked to
4773
            if ($backupretention && $matches) {
4774
                my $rtime;
4775
                if ($backupretention =~ /(\d+)(s|h|d)/) {
4776
                    $rtime = $1;
4777
                    $rtime = $1*60*60 if ($2 eq 'h');
4778
                    $rtime = $1*60*60*24 if ($2 eq 'd');
4779
                    $postreply .= "Status=OK Keeping backup snapshots newer than $backupretention out of $nb.\n";
4780
                } elsif ($backupretention =~ /(\d+)$/) {
4781
                    $postreply .= "Status=OK Keeping " . (($backupretention>$nb)?$nb:$backupretention) . " backup snapshots out of $nb.\n";
4782
                } else {
4783
                    $backupretention = 0;
4784
                }
4785
                if ($backupretention && $zbackupavailable) {
4786
                    foreach my $bsnap (@backupsnaps) {
4787
                        my $purge;
4788
                        if ($bsnap eq $imagesnaps[$matchbase+$matches-1]) { # We need to keep the last snapshot synced
4789
                            $postreply .= "Status=OK Not purging backup snapshot $matchbase $bsnap.\n";
4790
                            last;
4791
                        } else {
4792
                            if ($rtime) {
4793
                                my $snaptime = timelocal($6,$5,$4,$3,$2-1,$1) if ($bsnap =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/);
4794
                                my $tdiff = ($current_time - $snaptime);
4795
                                if ( $matches>1 && $tdiff>$rtime )
4796
                                    {$purge = 1;}
4797
                            } else {
4798
                                if ( $nb>$backupretention )
4799
                                    {$purge = 1;}
4800
                            }
4801
                            if ($purge) {
4802
                                $cmd = qq|zfs destroy $bpath\@SNAPSHOT-$bsnap|;
4803
                                $res = `$cmd 2>&1`;
4804
                                $postreply .= "Status=OK Purging backup snapshot $bsnap from $bpath.\n";
4805
                                $main::syslogit->($user, 'info', "OK Purging backup snapshot $bsnap from $bpath");
4806
                                $nb--;
4807
                            } else {
4808
                                last;
4809
                            }
4810
                        }
4811
                    }
4812
                }
4813
            }
4814 27512919 Origo
            $postmsg .= "OK Performing ZFS backup of $bpath. There are $ni image snapshots and $nb backup snapshots. ";
4815 95b003ff Origo
        }
4816 27512919 Origo
        $postreply .= "Status=OK Updating all btimes\n";
4817
        Updateallbtimes();
4818 95b003ff Origo
    } else {
4819
        $postreply .= "Status=ERROR Not allowed\n";
4820
        $postmsg = "ERROR Not allowed";
4821
    }
4822
    $main::updateUI->({tab=>"images", user=>$user, type=>"message", message=>$postmsg});
4823
    return $postreply;
4824
}
4825
4826 2a63870a Christian Orellana
sub Backupfuel {
4827
    my ($image, $action, $obj) = @_;
4828
    if ($help) {
4829
        return <<END
4830
GET:username, dozfs:
4831
Backs up a user's fuel storage. If [dozfs] is set, fuel on ZFS volumes is backed up, even if it should be handled by regular ZFS backups.
4832
END
4833
    }
4834
    my $username = $obj->{'username'} || $user;
4835
    return "Status=Error Not allowed\n" unless ($isadmin || $username eq $user);
4836
4837
    my $remolder = "14D";
4838
    my $stordevs = Liststoragedevices('', 'getstoragedevices');
4839
    my $backupdev = Getbackupdevice('', 'getbackupdevice');
4840
    my $backupdevtype = $stordevs->{$backupdev}->{type};
4841
    foreach my $spool (@spools) {
4842
        my $ppath = $spool->{"path"};
4843
        my $pid = $spool->{"id"};
4844
        if (($spool->{"zfs"} && $backupdevtype eq 'zfs') && !$obj->{'dozfs'}) {
4845
            $postreply .= "Status=OK Skipping fuel on ZFS storage: $ppath/$username/fuel\n";
4846
        } elsif ($pid eq '-1') {
4847
            ;
4848
        } elsif (!$backupdir || !(-d $backupdir)) {
4849
            $postreply .= "Status=OK Backup dir $backupdir does not exist\n";
4850
        } elsif (-d "$ppath/$username/fuel" && !is_folder_empty("$ppath/$username/fuel")) {
4851
            my $srcdir = "$ppath/$username/fuel";
4852
            my $destdir = "$backupdir/$username/fuel/$pid";
4853
4854
            `mkdir -p "$destdir"` unless (-e "$destdir");
4855
            # Do the backup
4856
            my $cmd = qq|/usr/bin/rdiff-backup --print-statistics "$srcdir" "$destdir"|;
4857
            my $res = `$cmd`;
4858
            $cmd = qq|/usr/bin/rdiff-backup --print-statistics --force --remove-older-than $remolder "$destdir"|;
4859
            $res .= `$cmd`;
4860
            if ($res =~ /Errors 0/) {
4861
                my $change = $1 if ($res =~ /TotalDestinationSizeChange \d+ \((.+)\)/);
4862
                $postreply .= "Status=OK Backed up $change, $srcdir -> $destdir\n";
4863
                $main::syslogit->($user, "info", "OK backed up $change, $srcdir -> $destdir") if ($change);
4864
            } else {
4865
                $res =~ s/\n/ /g;
4866
                $postreply .= "Status=Error There was a problem backup up $srcdir -> $destdir: $res\n";
4867
                $main::syslogit->($user, "there was a problem backup up $srcdir -> $destdir");
4868
            }
4869
        } else {
4870
            $postreply .= "Status=OK Skipping empty fuel on: $ppath/$username/fuel\n";
4871
        }
4872
    }
4873
    return $postreply;
4874
}
4875
4876
sub is_folder_empty {
4877
    my $dirname = shift;
4878
    opendir(my $dh, $dirname) or die "Not a directory";
4879
    return scalar(grep { $_ ne "." && $_ ne ".." } readdir($dh)) == 0;
4880
}
4881
4882 95b003ff Origo
sub Backup {
4883
    my ($image, $action, $obj) = @_;
4884
    if ($help) {
4885
        return <<END
4886 2a63870a Christian Orellana
GET:image, skipzfs:
4887 d3805c61 hq
Backs an image up. Set [skipzfs] if ZFS backup is configured, and you want to skip images on ZFS storage.
4888 95b003ff Origo
END
4889
    }
4890 2a63870a Christian Orellana
    my $path = $obj->{path} || $image;
4891 95b003ff Origo
    my $status = $obj->{status};
4892 2a63870a Christian Orellana
    my $skipzfs = $obj->{skipzfs};
4893 95b003ff Origo
    $uistatus = "backingup";
4894
    $uipath = $path;
4895
    my $remolder;
4896
    $remolder = "14D" if ($obj->{bschedule} eq "daily14");;
4897
    $remolder = "7D" if ($obj->{bschedule} eq "daily7");
4898 a2e0bc7e hq
    my $breply = '';
4899 2a63870a Christian Orellana
4900
    my $stordevs = Liststoragedevices('', 'getstoragedevices');
4901
    my $backupdev = Getbackupdevice('', 'getbackupdevice');
4902
    my $backupdevtype = $stordevs->{$backupdev}->{type};
4903
    # Nodes are assumed to alwasy use ZFS
4904
    if ($backupdevtype eq 'zfs' && $skipzfs && ($obj->{regstoragepool} == -1 || $spools[$obj->{regstoragepool}]->{'zfs'})) {
4905
        return "Status=OK Skipping image on ZFS $path\n";
4906
    }
4907 95b003ff Origo
    if ($status eq "snapshotting" || $status eq "unsnapping" || $status eq "reverting" || $status eq "cloning" ||
4908
        $status eq "moving" || $status eq "converting") {
4909 a2e0bc7e hq
        $breply .= "Status=ERROR Problem backing up $obj->{type} image $obj->{name}\n";
4910 95b003ff Origo
    } elsif ($obj->{regstoragepool} == -1) {
4911 a2e0bc7e hq
        my $res = createNodeTask($obj->{mac}, "BACKUP $user $uistatus $status \"$path\" \"$backupdir\" $remolder", $status,  '', $path);
4912 54401133 hq
        if ($res) {
4913 a2e0bc7e hq
            $breply .= "Status=ERROR Suspend serverer befora backing up (image $obj->{name} is not on an LVM partition)\n";
4914 95b003ff Origo
        } else {
4915
            $register{$path}->{'status'} = $uistatus;
4916
            $uistatus = "lbackingup" if ($status eq "active"); # Do lvm snapshot before backing up
4917
            $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4918 a2e0bc7e hq
            $breply .= "Status=backingup OK backingup image: $obj->{name} (on node)\n";
4919 95b003ff Origo
        }
4920
    } elsif (!$spools[$obj->{regstoragepool}]->{'rdiffenabled'}) {
4921 a2e0bc7e hq
        $breply .= "Status=ERROR Rdiff-backup has not been enabled for this storagepool ($spools[$obj->{regstoragepool}]->{'name'})\n";
4922 95b003ff Origo
    } else {
4923
        if ($spools[$obj->{regstoragepool}]->{'hostpath'} eq "local" && $status eq "active") {
4924
            my $poolpath = $spools[$obj->{regstoragepool}]->{'path'};
4925
            # We only need to worry about taking an LVM snapshot if the image is in active use
4926
            # We also check if the images is actually on an LVM partition
4927
            my $qi = `/bin/cat /proc/mounts | grep "$poolpath"`; # Find the lvm volume mounted on /mnt/images
4928
            ($qi =~ m/\/dev\/mapper\/(\S+)-(\S+) $pool.+/g)[-1]; # Select last match
4929
            my $lvolgroup = $1;
4930
            my $lvol = $2;
4931
            if ($lvolgroup && $lvol) {
4932
                $uistatus = "lbackingup";
4933
            }
4934
        }
4935
        if ($uistatus ne "lbackingup" && $status eq "active") {
4936 a2e0bc7e hq
            $breply .= "Status=ERROR Suspend serverer befora backing up (image $obj->{name} is not on an LVM partition)\n";
4937
        #    $main::updateUI->({tab=>"images", user=>$user, type=>"update", path=>$path, status=>$uistatus, message=>"Image $obj->{name} is not on an LVM partition - suspend before backing up"});
4938 95b003ff Origo
        } else {
4939
            my $buser;
4940
            my $bname;
4941
            if ($path =~ /.*\/(common|$user)\/(.+)/) {
4942
                $buser = $1;
4943
                $bname = $2;
4944
            }
4945
            if ($buser && $bname) {
4946
                my $dirpath = $spools[$obj->{regstoragepool}]->{'path'};
4947
                #chop $dirpath; # Remove last /
4948
                eval {
4949
                    $register{$path}->{'status'} = $uistatus;
4950
                    my $daemon = Proc::Daemon->new(
4951
                        work_dir => '/usr/local/bin',
4952
                        exec_command => "perl -U steamExec $buser $uistatus $status \"$bname\" \"$dirpath\" \"$backupdir\" $remolder"
4953 a2e0bc7e hq
                    ) or do {$breply .= "Status=ERROR $@\n";};
4954 95b003ff Origo
                    my $pid = $daemon->Init();
4955 a2e0bc7e hq
                    $breply .=  "Status=backingup OK backingup image: $obj->{name}\n";
4956 95b003ff Origo
                    $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $bname");
4957
                    1;
4958 a2e0bc7e hq
                } or do {$breply .= "Status=ERROR $@\n";}
4959 95b003ff Origo
            } else {
4960 a2e0bc7e hq
                $breply .= "Status=ERROR Problem backing up $path\n";
4961 95b003ff Origo
            }
4962
        }
4963
    }
4964 a2e0bc7e hq
    return $breply;
4965 95b003ff Origo
}
4966
4967
sub Restore {
4968
    my ($image, $action, $obj) = @_;
4969
    if ($help) {
4970
        return <<END
4971
GET:image:
4972
Backs an image up.
4973
END
4974
    }
4975
    my $path = $obj->{path};
4976
    my $status = $obj->{status};
4977
    $uistatus = "restoring";
4978 04c16f26 hq
    my($bname, $dirpath, $suffix) = fileparse($path, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
4979 95b003ff Origo
    my $backup = $params{"backup"} || $obj->{backup};
4980
    my $pool = $register{$path}->{'storagepool'};
4981
    $pool = "0" if ($pool == -1);
4982
    my $poolpath = $spools[$pool]->{'path'};
4983
    my $restorefromdir = $backupdir;
4984
    my $inc = $backup;
4985
    my $subdir; # 1 level of subdirs supported
4986 27512919 Origo
    $subdir = $1 if ($dirpath =~ /.+\/$obj->{user}(\/.+)?\//);
4987 95b003ff Origo
4988
    if ($backup =~ /^SNAPSHOT-(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/) { # We are dealing with a zfs restore
4989
        $inc = "$1-$2-$3-$4-$5-$6";
4990
        foreach my $spool (@spools) {
4991
            my $ppath = $spool->{"path"};
4992
            if (-e "$ppath/.zfs/snapshot/$backup/$obj->{user}$subdir/$bname$suffix") {
4993
                $restorefromdir = "$ppath/.zfs/snapshot/$backup";
4994
                last;
4995
            }
4996
        }
4997
    } else {
4998
        if ($backup eq "mirror") {
4999
            my $mir = `/bin/ls "$backupdir/$obj->{user}/$bname$suffix/rdiff-backup-data" | grep current_mirror`;
5000
            if ($mir =~ /current_mirror\.(\S+)\.data/) {
5001
                $inc = $1;
5002
            }
5003
        }
5004
        $inc =~ tr/:T/-/; # qemu-img does not like colons in file names - go figure...
5005
        $inc = substr($inc,0,-6);
5006
    }
5007
    $uipath = "$poolpath/$obj->{user}$subdir/$bname.$inc$suffix";
5008
    my $i;
5009
    if (-e $uipath) {
5010
        $i = 1;
5011
        while (-e "$poolpath/$obj->{user}$subdir/$bname.$inc.$i$suffix") {$i++;}
5012
        $uipath = "$poolpath/$obj->{user}$subdir/$bname.$inc.$i$suffix";
5013
    }
5014
5015
    if (-e $uipath) {
5016
        $postreply .= "Status=ERROR This image is already being restored\n";
5017
    } elsif ($obj->{user} ne $user && !$isadmin) {
5018
        $postreply .= "Status=ERROR No restore privs\n";
5019
    } elsif (!$backup || $backup eq "--") {
5020
        $postreply .= "Status=ERROR No backup selected\n";
5021
    } elsif (overQuotas($obj->{virtualsize})) {
5022
        $postreply .= "Status=ERROR Over quota (". overQuotas($obj->{virtualsize}) . ") restoring: $obj->{name}\n";
5023
    } elsif (overStorage($obj->{ksize}*1024, $pool+0)) {
5024
        $postreply .= "Status=ERROR Out of storage in destination pool restoring: $obj->{name}\n";
5025
    } else {
5026
        my $ug = new Data::UUID;
5027
        my $newuuid = $ug->create_str();
5028
        $register{$uipath} = {
5029
            uuid=>$newuuid,
5030
            status=>"restoring",
5031
            name=>"$obj->{name} ($inc)" . (($i)?" $i":''),
5032
            notes=>$obj->{notes},
5033
            image2=>$obj->{image2},
5034
            managementlink=>$obj->{managementlink},
5035
            upgradelink=>$obj->{upgradelink},
5036
            terminallink=>$obj->{terminallink},
5037
            size=>0,
5038
            realsize=>0,
5039
            virtualsize=>$obj->{virtualsize},
5040
            type=>$obj->{type},
5041
            user=>$user
5042
        };
5043
        eval {
5044
            $register{$path}->{'status'} = $uistatus;
5045
            my $daemon = Proc::Daemon->new(
5046
                work_dir => '/usr/local/bin',
5047
                exec_command => "perl -U steamExec $obj->{user} $uistatus $status \"$path\" \"$restorefromdir\" \"$backup\" \"$uipath\""
5048
            ) or do {$postreply .= "Status=ERROR $@\n";};
5049
            my $pid = $daemon->Init();
5050
            $postreply .=  "Status=$uistatus OK $uistatus $obj->{type} image: $obj->{name} ($inc)". ($console?", $newuuid\n":"\n");
5051
            $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name} ($inc), $uipath, $newuuid: $uuid");
5052
            1;
5053
        } or do {$postreply .= "Status=ERROR $@\n";};
5054
        $main::updateUI->({tab=>"images", user=>$user, type=>"update"});
5055
    }
5056
    return $postreply;
5057
}
5058
5059
sub Master {
5060
    my ($image, $action, $obj) = @_;
5061
    if ($help) {
5062
        return <<END
5063
GET:image:
5064
Converts an image to a master image. Image must not be in use.
5065
END
5066
    }
5067
    my $path = $obj->{path};
5068
    my $status = $register{$path}->{status};
5069
    $path =~ /(.+)\.$obj->{type}$/;
5070
    my $namepath = $1;
5071
    my $uiname;
5072
    if (!$register{$path}) {
5073
        $postreply .= "Status=ERROR Image $path not found\n";
5074
    } elsif ($status ne "unused") {
5075
        $postreply .= "Status=ERROR Only unused images may be mastered\n";
5076 3657de20 Origo
#    } elsif ($namepath =~ /(.+)\.master/ || $register{$path}->{'master'}) {
5077
#        $postreply .= "Status=ERROR Only one level of mastering is supported\n";
5078 95b003ff Origo
    } elsif ($obj->{istoragepool} == -1 || $obj->{regstoragepool} == -1) {
5079
        $postreply .= "Status=ERROR Unable to master $obj->{name} (master images are not supported on node storage)\n";
5080
    } elsif ($obj->{type} eq "qcow2") {
5081
        # Promoting a regular image to master
5082
        # First find an unused path
5083
        if (-e "$namepath.master.$obj->{type}") {
5084
            my $i = 1;
5085
            while ($register{"$namepath.$i.master.$obj->{type}"} || -e "$namepath.$i.master.$obj->{type}") {$i++;};
5086
            $uinewpath = "$namepath.$i.master.$obj->{type}";
5087
        } else {
5088
            $uinewpath = "$namepath.master.$obj->{type}";
5089
        }
5090
5091
        $uipath = $path;
5092
        $uiname = "$obj->{name}";
5093
        eval {
5094
            my $qinfo = `/bin/mv -iv "$path" "$uinewpath"`;
5095
            $register{$path}->{'name'} = $uiname;
5096
            $register{$uinewpath} = $register{$path};
5097
            delete $register{$path};
5098
            $postreply .= "Status=$status Mastered $obj->{type} image: $obj->{name}\n";
5099
            chop $qinfo;
5100
            $main::syslogit->($user, "info", $qinfo);
5101
            1;
5102
        } or do {$postreply .= "Status=ERROR $@\n";};
5103
        sleep 1;
5104
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, newpath=>$uinewpath, status=>$status, name=>$uiname});
5105
    } else {
5106
        $postreply .= "Status=ERROR Only qcow2 images may be mastered\n";
5107
    }
5108
    return $postreply;
5109
}
5110
5111
sub Unmaster {
5112
    my ($image, $action, $obj) = @_;
5113
    if ($help) {
5114
        return <<END
5115
GET:image:
5116
Converts a master image to a regular image. Image must not be in use.
5117
END
5118
    }
5119
    my $path = $obj->{path};
5120
    my $status = $register{$path}->{status};
5121
    $path =~ /(.+)\.$obj->{type}$/;
5122
    my $namepath = $1;
5123
    my $haschildren = 0;
5124
    my $child;
5125
    my $uinewpath;
5126
    my $iname;
5127
    my @regvalues = values %register;
5128
    foreach my $val (@regvalues) {
5129
        if ($val->{'master'} eq $path) {
5130
            $haschildren = 1;
5131
            $child = $val->{'name'};
5132
            last;
5133
        }
5134
    }
5135
    if (!$register{$path}) {
5136
        $postreply .= "Status=ERROR Image $path not found\n";
5137
    } elsif ($haschildren) {
5138
        $postreply .= "Status=Error Cannot unmaster image. This image is used as master by: $child\n";
5139
    } elsif ($status ne "unused" && $status ne "used") {
5140
        $postreply .= "Status=ERROR Only used and unused images may be unmastered\n";
5141
    } elsif (!( ($namepath =~ /(.+)\.master/) || ($obj->{master} && $obj->{master} ne "--")) ) {
5142
        $postreply .= "Status=ERROR You can only unmaster master or child images\n";
5143
    } elsif (($obj->{istoragepool} == -1 || $obj->{regstoragepool} == -1) && $namepath =~ /(.+)\.master/) {
5144
        $postreply .= "Status=ERROR Unable to unmaster $obj->{name} (master images are not supported on node storage)\n";
5145
    } elsif ($obj->{type} eq "qcow2") {
5146
        # Demoting a master to regular image
5147 3657de20 Origo
        if ($action eq 'unmaster' && $namepath =~ /(.+)\.master$/) {
5148 95b003ff Origo
            $namepath = $1;
5149
            $uipath = $path;
5150
            # First find an unused path
5151
            if (-e "$namepath.$obj->{type}") {
5152
                my $i = 1;
5153
                while ($register{"$namepath.$i.$obj->{type}"} || -e "$namepath.$i.$obj->{type}") {$i++;};
5154
                $uinewpath = "$namepath.$i.$obj->{type}";
5155
            } else {
5156
                $uinewpath = "$namepath.$obj->{type}";
5157
            }
5158
5159
            $iname = $obj->{name};
5160
            $iname =~ /(.+)( \(master\))/;
5161
            $iname = $1 if $2;
5162
            eval {
5163
                my $qinfo = `/bin/mv -iv "$path" "$uinewpath"`;
5164
                $register{$path}->{'name'} = $iname;
5165
                $register{$uinewpath} = $register{$path};
5166
                delete $register{$path};
5167
                $postreply .=  "Status=$status Unmastered $obj->{type} image: $obj->{name}\n";
5168
                chomp $qinfo;
5169
                $main::syslogit->($user, "info", $qinfo);
5170
                1;
5171
            } or do {$postreply .= "Status=ERROR $@\n";}
5172
    # Rebasing a child image
5173 3657de20 Origo
        } elsif ($action eq 'rebase' && $obj->{master} && $obj->{master} ne "--") {
5174 95b003ff Origo
            $uistatus = "rebasing";
5175
            $uipath = $path;
5176
            $iname = $obj->{name};
5177
            $iname =~ /(.+)( \(child\d*\))/;
5178
            $iname = $1 if $2;
5179
            my $temppath = "$path.temp";
5180
            $uipath = $path;
5181
            $uimaster = "--";
5182
            my $macip;
5183
5184
            if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
5185
                unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
5186
                $macip = $nodereg{$obj->{mac}}->{'ip'};
5187
                untie %nodereg;
5188
            }
5189
            eval {
5190
                my $master = $register{$path}->{'master'};
5191
                my $usedmaster = '';
5192
#                @regvalues = values %register;
5193
                if ($master && $master ne '--') {
5194
                    foreach my $valref (@regvalues) {
5195
                        $usedmaster = 1 if ($valref->{'master'} eq $master && $valref->{'path'} ne $path); # Check if another image is also using this master
5196
                    }
5197
                }
5198 48fcda6b Origo
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$uistatus});
5199 95b003ff Origo
                $register{$path} = {
5200
                    master=>"",
5201
                    name=>"$iname",
5202
                    notes=>$obj->{notes},
5203
                    status=>$uistatus,
5204
                    snap1=>$obj->{snap1},
5205
                    managementlink=>$obj->{managementlink},
5206
                    upgradelink=>$obj->{upgradelink},
5207
                    terminallink=>$obj->{terminallink},
5208
                    image2=>$obj->{image2},
5209
                    storagepool=>$obj->{istoragepool},
5210
                    status=>$uistatus
5211
                };
5212
5213
                if ($macip) {
5214
                    my $esc_localpath = shell_esc_chars($path);
5215
                    my $esc_localpath2 = shell_esc_chars($temppath);
5216
                    $res .= `$sshcmd $macip "/usr/bin/qemu-img convert $esc_localpath -O qcow2 $esc_localpath2"`;
5217
                    $res .= `$sshcmd $macip "if [ -f $esc_localpath2 ]; then /bin/mv -v $esc_localpath2 $esc_localpath; fi"`;
5218
                } else {
5219
                    $res .= `/usr/bin/qemu-img convert -O qcow2 "$path" "$temppath"`;
5220
                    $res .= `if [ -f "$temppath" ]; then /bin/mv -v "$temppath" "$path"; fi`;
5221
                }
5222
                if ($master && !$usedmaster) {
5223
                    $register{$master}->{'status'} = 'unused';
5224
                    $main::syslogit->('info', "Freeing master $master");
5225
                }
5226
                $register{$path}->{'master'} = '';
5227
                $register{$path}->{'status'} = $status;
5228
5229
                $postreply .= "Status=OK $uistatus $obj->{type} image: $obj->{name}\n";
5230 48fcda6b Origo
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status});
5231 95b003ff Origo
                $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
5232
                1;
5233
            } or do {$postreply .= "Status=ERROR $@\n";}
5234
        } else {
5235
            $postreply .= "Status=ERROR Not a master, not a child \"$obj->{name}\"\n";
5236
        }
5237
        sleep 1;
5238
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, newpath=>$uinewpath, name=>$iname, status=>$status});
5239
    } else {
5240
        $postreply .= "Status=ERROR Only qcow2 images may be unmastered\n";
5241
    }
5242
    return $postreply;
5243
}
5244
5245
# Save or create new image
5246
sub Save {
5247
    my ($img, $action, $obj) = @_;
5248
    if ($help) {
5249
        return <<END
5250
POST:path, uuid, name, type, virtualsize, storagepool, user:
5251
To save a collection of images you either PUT or POST a JSON array to the main endpoint with objects representing the images with the changes you want.
5252
Depending on your privileges not all changes are permitted. If you save without specifying a uuid or path, a new image is created.
5253
END
5254
    }
5255
    my $path = $obj->{path};
5256
    my $uuid = $obj->{uuid};
5257
    my $status = $obj->{status};
5258
    if ($status eq "new") {
5259
        # Create new image
5260
        my $ug = new Data::UUID;
5261
        if (!$uuid || $uuid eq '--') {
5262
            $uuid = $ug->create_str();
5263
        } else { # Validate
5264
            my $valuuid  = $ug->from_string($uuid);
5265
            if ($ug->to_string($valuuid) eq $uuid) {
5266
                ;
5267
            } else {
5268
                $uuid = $ug->create_str();
5269
            }
5270
        }
5271
        my $newuuid = $uuid;
5272
        my $pooldir = $spools[$obj->{storagepool}]->{'path'};
5273
        my $cmd;
5274
        my $name = $obj->{name};
5275
        $name =~ s/\./_/g; # Remove unwanted chars
5276
        $name =~ s/\//_/g;
5277
        eval {
5278
            my $ipath = "$pooldir/$user/$name.$obj->{type}";
5279
            $obj->{type} = "qcow2" unless ($obj->{type});
5280
            # Find an unused path
5281
            if ($register{$ipath} || -e "$ipath") {
5282
                my $i = 1;
5283
                while ($register{"$pooldir/$user/$name.$i.$obj->{type}"} || -e "$pooldir/$user/$name.$i.$obj->{type}") {$i++;};
5284
                $ipath = "$pooldir/$user/$name.$i.$obj->{type}";
5285
                $name = "$name.$i";
5286
            }
5287
5288
            if ($obj->{type} eq 'qcow2' || $obj->{type} eq 'vmdk') {
5289
                my $size = ($obj->{msize})."M";
5290
                my $format = "qcow2";
5291
                $format = "vmdk" if ($path1 =~ /\.vmdk$/);
5292
                $cmd = qq|/usr/bin/qemu-img create -f $format "$ipath" "$size"|;
5293
            } elsif ($obj->{type} eq 'img') {
5294
                my $size = ($obj->{msize})."M";
5295
                $cmd = qq|/usr/bin/qemu-img create -f raw "$ipath" "$size"|;
5296
            } elsif ($obj->{type} eq 'vdi') {
5297
                my $size = $obj->{msize};
5298
                $cmd = qq|/usr/bin/VBoxManage createhd --filename "$ipath" --size "$size" --format VDI|;
5299
            }
5300
            $obj->{name} = 'New Image' if (!$obj->{name} || $obj->{name} eq '--' || $obj->{name} =~ /^\./ || $obj->{name} =~ /\//);
5301
            if (-e $ipath) {
5302
                $postreply .= "Status=ERROR Image already exists: \"$obj->{name}\" in \"$ipath\”\n";
5303
            } elsif (overQuotas($obj->{ksize}*1024)) {
5304
                $postreply .= "Status=ERROR Over quota (". overQuotas($obj->{ksize}*1024) . ") creating: $obj->{name}\n";
5305 80e0b3f5 hq
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", message=>"Over quota in storage pool $obj->{storagepool}"});
5306 95b003ff Origo
                $main::syslogit->($user, "info", "Over quota in storage pool $obj->{storagepool}, not creating $obj->{type} image $obj->{name}");
5307
            } elsif (overStorage($obj->{ksize}*1024, $obj->{storagepool}+0)) {
5308
                $postreply .= "Status=ERROR Out of storage in destination pool creating: $obj->{name}\n";
5309
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", message=>"Out of storage in storage pool $obj->{storagepool}"});
5310
                $main::syslogit->($user, "info", "Out of storage in storage pool $obj->{storagepool}, not creating $obj->{type} image $obj->{name}");
5311
            } elsif ($obj->{virtualsize} > 10*1024*1024 && $obj->{name} && $obj->{name} ne '--') {
5312
                $register{$ipath} = {
5313
                    uuid=>$newuuid,
5314
                    name=>$obj->{name},
5315
                    user=>$user,
5316
                    notes=>$obj->{notes},
5317
                    type=>$obj->{type},
5318
                    size=>0,
5319
                    realsize=>0,
5320
                    virtualsize=>$obj->{virtualsize},
5321
                    storagepool=>$spools[0]->{'id'},
5322
                    created=>$current_time,
5323
                    managementlink=>$obj->{managementlink},
5324
                    upgradelink=>$obj->{upgradelink},
5325
                    terminallink=>$obj->{terminallink},
5326
                    status=>"creating"
5327
                };
5328
                $uipath = $ipath;
5329
                my $res = `$cmd`;
5330
                $register{$ipath}->{'status'} = 'unused';
5331
5332
                $postreply .= "Status=OK Created $obj->{type} image: $obj->{name}\n";
5333
                $postreply .= "Status=OK uuid: $newuuid\n"; # if ($console || $api);
5334
                $postreply .= "Status=OK path: $ipath\n"; # if ($console || $api);
5335
                sleep 1; # Needed to give updateUI a chance to reload
5336 8d7785ff Origo
                $main::updateUI->({tab=>"images", user=>$user, type=>"update"});
5337
#                $main::updateUI->({tab=>"images", uuid=>$newuuid, user=>$user, type=>"update", name=>$obj->{name}, path=>$obj->{path}});
5338 95b003ff Origo
                $main::syslogit->($user, "info", "Created $obj->{type} image: $obj->{name}: $newuuid");
5339 8d7785ff Origo
                updateBilling("New image: $obj->{name}");
5340 95b003ff Origo
            } else {
5341
                $postreply .= "Status=ERROR Problem creating image: $obj->{name} of size $obj->{virtualsize}\n";
5342
            }
5343
            1;
5344
        } or do {$postreply .= "Status=ERROR $@\n";}
5345
    } else {
5346 d24d9a01 hq
        # Moving images because of owner change or storagepool change
5347 95b003ff Origo
        if ($obj->{user} ne $obj->{reguser} || $obj->{storagepool} ne $obj->{regstoragepool}) {
5348
            $uipath = Move($path, $obj->{user}, $obj->{storagepool}, $obj->{mac});
5349
    # Resize a qcow2 image
5350
        } elsif ($obj->{virtualsize} != $register{$path}->{'virtualsize'} &&
5351
            ($obj->{user} eq $obj->{reguser} || index($privileges,"a")!=-1)) {
5352
            if ($status eq "active" || $status eq "paused") {
5353
                $postreply .= "Status=ERROR Cannot resize active images $path, $status.\n";
5354
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", status=>'ERROR', message=>"ERROR Cannot resize active images"});
5355
            } elsif ($obj->{type} eq "qcow2" || $obj->{type} eq "img") {
5356
                if ($obj->{virtualsize} < $register{$path}->{'virtualsize'}) {
5357
                    $postreply .= "Status=ERROR Only growing of images supported.\n";
5358
                } elsif (overQuotas($obj->{virtualsize}, ($register{$path}->{'storagepool'}==-1))) {
5359
                    $postreply .= "Status=ERROR Over quota (". overQuotas($obj->{virtualsize}, ($register{$path}->{'storagepool'}==-1)) . ") resizing: $obj->{name}\n";
5360
                } elsif (overStorage(
5361
                    $obj->{virtualsize},
5362
                    $register{$path}->{'storagepool'},
5363
                    $register{$path}->{'mac'}
5364
                )) {
5365
                    $postreply .= "Status=ERROR Not enough storage ($obj->{virtualsize}) in destination pool $obj->{storagepool} resizing: $obj->{name}\n";
5366
                } else {
5367
                    $uistatus = "resizing";
5368
                    $uipath = $path;
5369
                    my $mpath = $path;
5370
                    if ($obj->{mac} && $obj->{mac} ne '--') {
5371
                        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
5372
                        $macip = $nodereg{$obj->{mac}}->{'ip'};
5373
                        untie %nodereg;
5374
                    }
5375
                    $mpath = "$macip:$mpath" if ($macip && $macip ne '--');
5376
                    $register{$path}->{'status'} = $uistatus;
5377
                    $register{$path}->{'virtualsize'} = $obj->{virtualsize};
5378
                    my $cmd = qq|steamExec $user $uistatus $status "$mpath" "$obj->{ksize}"|;
5379
                    if ($action eq 'sync_save') { # We wait for result
5380
                        my $res = `$cmd`;
5381
                        $res =~ s/\n/ /g; $res = lc $res;
5382
                        $postreply .= "Status=OK $res\n";
5383
                    } else {
5384
                        my $daemon = Proc::Daemon->new(
5385
                            work_dir => '/usr/local/bin',
5386
                            exec_command => $cmd,
5387
#                            exec_command => "suidperl -U steamExec $user $uistatus $status \"$mpath\" \"$obj->{ksize}\""
5388
                        ) or do {$postreply .= "Status=ERROR $@\n";};
5389
                        my $pid = $daemon->Init();
5390
                    }
5391
                    $postreply .=  "Status=OK $uistatus $obj->{type} image: $obj->{name} ($obj->{ksize}k)\n";
5392
                    $main::syslogit->($user, "info", "$uistatus $obj->{type} image $obj->{name} $uuid $mpath ($obj->{virtualsize})");
5393
                }
5394
            } else {
5395
                $postreply .= "Status=ERROR Can only resize .qcow2 and .img images.\n";
5396
            }
5397
        } else {
5398
            # Regular save
5399
            if ($obj->{user} eq $obj->{reguser} || $isadmin) {
5400
                my $qinfo;
5401
                my $e;
5402
                $obj->{bschedule} = "" if ($obj->{bschedule} eq "--");
5403
                if ($obj->{bschedule}) {
5404
                    # Remove backups
5405 8d7785ff Origo
                    if ($obj->{bschedule} eq "none") {
5406
                        if ($spools[$obj->{regstoragepool}]->{'rdiffenabled'}) {
5407
                            my($bname, $dirpath) = fileparse($path);
5408
                            if ($path =~ /\/($user|common)\/(.+)/) {
5409
                                my $buser = $1;
5410
                                if (-d "$backupdir/$buser/$bname" && $backupdir && $bname && $buser) {
5411
                                    eval {
5412
                                        $qinfo = `/bin/rm -rf "$backupdir/$buser/$bname"`;
5413
                                        1;
5414
                                    } or do {$postreply .= "Status=ERROR $@\n"; $e=1;};
5415
                                    if (!$e) {
5416
                                        $postreply .=  "Status=OK Removed all rdiff backups of $obj->{name}\n";
5417
                                        chomp $qinfo;
5418
                                        $register{$path} = {backupsize=>0};
5419
                                        $main::syslogit->($user, "info", "Removed all backups of $obj->{name}: $path: $qinfo");
5420
                                        $main::updateUI->({
5421
                                            user=>$user,
5422
                                            message=>"Removed all backups of $obj->{name}",
5423
                                            backup=>$path
5424
                                        });
5425
                                        updateBilling("no backup $path");
5426
                                        delete $register{$path}->{'btime'};
5427
                                    }
5428 95b003ff Origo
                                }
5429
                            }
5430
                        }
5431
                        $obj->{bschedule} = "manually";
5432
                        $register{$path}->{'bschedule'} = $obj->{bschedule};
5433
                    }
5434
                }
5435
5436
                $register{$path} = {
5437
                    name=>$obj->{name},
5438
                    user=>$obj->{user},
5439
                    notes=>$obj->{notes},
5440
                    bschedule=>$obj->{bschedule},
5441
                    installable=>$obj->{installable},
5442
                    managementlink=>$obj->{managementlink},
5443
                    upgradelink=>$obj->{upgradelink},
5444
                    terminallink=>$obj->{terminallink},
5445
                    action=>""
5446
                };
5447
                my $domains = $register{$path}->{'domains'};
5448
                if ($status eq 'downloading') {
5449
                    unless (`pgrep $obj->{name}`) { # Check if image is in fact being downloaded
5450
                        $status = 'unused';
5451
                        $register{$path}->{'status'} = $status;
5452
                        unlink ("$path.meta") if (-e "$path.meta");
5453
                    }
5454
                }
5455
                elsif ($status ne 'unused') {
5456
                    my $match;
5457
                    if ($path =~ /\.master\.qcow2$/) {
5458
                        my @regkeys = (tied %register)->select_where("master = '$path'");
5459
                        $match = 2 if (@regkeys);
5460
                    } else {
5461
                        if (!$domreg{$domains}) { # Referenced domain no longer exists
5462
                            ;
5463
                        } else { # Verify if referenced domain still uses image
5464
                            my @imgkeys = ('image', 'image2', 'image3', 'image4');
5465
                            for (my $i=0; $i<4; $i++) {
5466
                                $match = 1 if ($domreg{$domains}->{$imgkeys[$i]} eq $path);
5467
                            }
5468
                        }
5469
                    }
5470
                    unless ($match) {
5471
                        $status = 'unused';
5472
                        $register{$path}->{'status'} = $status;
5473
                    }
5474
                }
5475
                if ($status eq 'unused') {
5476
                    delete $register{$path}->{'domains'};
5477
                    delete $register{$path}->{'domainnames'};
5478
                }
5479
                $uipath = $path;
5480
                $postreply .= "Status=OK Saved $obj->{name} ($uuid)\n";
5481 d3805c61 hq
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", path=>$path, name=>  $obj->{name}, status=>$status});
5482 95b003ff Origo
            } else {
5483
                $postreply .= "Status=ERROR Unable to save $obj->{name}\n";
5484
            }
5485
        }
5486
    }
5487
    if ($postreply) {
5488
        $postmsg = $postreply;
5489
    } else {
5490 3657de20 Origo
        $postreply = to_json(\%{$register{$uipath}}, {pretty=>1}) if ($uipath && $register{$uipath});
5491 95b003ff Origo
        $postreply =~ s/""/"--"/g;
5492
        $postreply =~ s/null/"--"/g;
5493
        $postreply =~ s/"notes" {0,1}: {0,1}"--"/"notes":""/g;
5494
        $postreply =~ s/"installable" {0,1}: {0,1}"(true|false)"/"installable":$1/g;
5495
    }
5496 3657de20 Origo
    return $postreply || "Status=OK Saved $uipath\n";
5497 95b003ff Origo
}
5498
5499
sub Setstoragedevice {
5500
    my ($image, $action, $obj) = @_;
5501
    if ($help) {
5502
        return <<END
5503
GET:device,type:
5504
Changes the device - disk or partition, used for images or backup storage.
5505
[type] is either images or backup.
5506
END
5507
    }
5508
    my $dev = $obj->{device};
5509
    my $force = $obj->{force};
5510
    my $type = 'backup';
5511
    $type = 'images' if ($obj->{type} eq 'images');
5512
    return "Status=Error Not allowed\n" unless ($isadmin);
5513
    my $backupdevice = Getbackupdevice('', 'getbackupdevice');
5514
    my $imagesdevice = Getimagesdevice('', 'getimagesdevice');
5515
    my $devices_obj = from_json(Liststoragedevices('', 'liststoragedevices'));
5516
    my %devices = %$devices_obj;
5517
    my $backupdev = $devices{$backupdevice}->{dev};
5518
    my $imagesdev = $devices{$imagesdevice}->{dev};
5519
    if (!$devices{$dev}) {
5520
        $postreply = "Status=Error You must specify a valid device ($dev)\n";
5521
        return $postreply;
5522
    }
5523
    if (!$force && (($backupdev =~ /$dev/) || ($imagesdev =~ /$dev/))  && $dev !~ /vda/ && $dev !~ /sda/) { # make exception to allow returning to default setup
5524
        $postreply = "Status=Error $dev is already in use as images or backup device\n";
5525
        return $postreply;
5526
    }
5527
    my $stordir = $tenderpathslist[0];
5528
    my $stordevice = $imagesdevice;
5529
    if ($type eq 'backup') {
5530
        $stordir = $backupdir;
5531
        $stordevice = $backupdevice;
5532
    }
5533
    return "Status=Error Storage device not found\n" unless ($stordevice);
5534
    my $mp = $devices{$dev}->{mounted};
5535
    my $newstordir;
5536 e9af6c24 Origo
    # my $oldstordir;
5537 95b003ff Origo
    if ($devices{$dev}->{type} eq 'zfs') {
5538
        my $cmd = qq|zfs list stabile-$type/$type -Ho mountpoint|;
5539
        my $zmp = `$cmd`;
5540
        chomp $zmp;
5541
        if ($zmp =~ /^\//) {
5542
            `zfs mount stabile-$type/$type`;
5543
            $mp = $zmp;
5544
            $newstordir = $mp;
5545
        } else {
5546
            `zfs create stabile-$type/$type`;
5547
            $mp = "/stabile-$type/$type";
5548
            $newstordir = $mp;
5549
        }
5550
    } else {
5551
        $newstordir = (($type eq 'images')?"$mp/images":"$mp/backups");
5552
        $newstordir = $1 if ($newstordir =~ /(.+\/images)\/images$/);
5553
        $newstordir = $1 if ($newstordir =~ /(.+\/backups)\/backups$/);
5554
    }
5555
    if ($mp eq '/') {
5556
        $newstordir = (($type eq 'images')?"/mnt/stabile/images":"/mnt/stabile/backups");
5557
        `umount "$newstordir"`; # in case it's mounted
5558
    }
5559
    `mkdir "$newstordir"` unless (-e $newstordir);
5560
    `chmod 777 "$newstordir"`;
5561
5562
    my $cfg = new Config::Simple("/etc/stabile/config.cfg");
5563
    if ($type eq 'backup') {
5564
        $cfg->param('STORAGE_BACKUPDIR', $newstordir);
5565
        $cfg->save();
5566
    } elsif ($type eq 'images') {
5567 e9af6c24 Origo
5568
    # Handle shared storage config
5569
    #    $oldstordir = $stordir;
5570
        my $i = 0;
5571
        for($i = 0; $i <= $#tenderpathslist; $i++) {
5572
            my $dir = $tenderpathslist[$i];
5573
            last if ($dir eq $newstordir);
5574
        }
5575
        # $tenderpathslist[0] = $newstordir;
5576
        splice(@tenderpathslist, $i,1); # Remove existing entry
5577
        unshift(@tenderpathslist, $newstordir); # Then add the new path
5578
        $cfg->param('STORAGE_POOLS_LOCAL_PATHS', join(',', @tenderpathslist));
5579
5580
        # $tenderlist[0] = 'local';
5581
        splice(@tenderlist, $i,1);
5582
        unshift(@tenderlist, 'local');
5583
        $cfg->param('STORAGE_POOLS_ADDRESS_PATHS', join(',', @tenderlist));
5584
5585
        # $tendernameslist[0] = 'Default';
5586
        splice(@tendernameslist, $i,1);
5587
        unshift(@tendernameslist, 'Default');
5588
5589
        if ($i) { # We've actually changed storage device
5590
            my $oldstorname = $tenderpathslist[1];
5591
            $oldstorname = $1 if ($oldstorname =~ /.*\/(.+)/);
5592
            $tendernameslist[1] = "$oldstorname on $imagesdevice"; # Give the previous default pool a fitting name
5593
5594
            $storagepools = "$storagepools,$i" unless ($storagepools =~ /,\s*$i,?/ || $storagepools =~ /,\s*$i$/ || $storagepools =~ /^$i$/);
5595
            $cfg->param('STORAGE_POOLS_DEFAULTS', $storagepools);
5596
        }
5597
        $cfg->param('STORAGE_POOLS_NAMES', join(',', @tendernameslist));
5598
5599 95b003ff Origo
        $cfg->save();
5600
5601 e9af6c24 Origo
5602
    # Handle node storage configs
5603 95b003ff Origo
        unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities',key=>'identity',CLOBBER=>3}, $Stabile::dbopts)) ) {return "Unable to access id register"};
5604
        # Build hash of known node config files
5605 e9af6c24 Origo
        my @nodeconfigs;
5606
        push @nodeconfigs, "/etc/stabile/nodeconfig.cfg";
5607 95b003ff Origo
        foreach my $valref (values %idreg) {
5608
            my $nodeconfigfile = $valref->{'path'} . "/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
5609
            next if ($nodeconfigs{$nodeconfigfile}); # Node identities may share basedir and node config file
5610
            if (-e $nodeconfigfile) {
5611
                push @nodeconfigs, $nodeconfigfile;
5612
            }
5613
        }
5614
        untie %idreg;
5615
        foreach my $nodeconfig (@nodeconfigs) {
5616
            my $nodecfg = new Config::Simple($nodeconfig);
5617 e9af6c24 Origo
            my @ltenderlist = $nodecfg->param('STORAGE_SERVERS_ADDRESS_PATHS');
5618
            my $ltenders = join(", ", @ltenderlist);
5619
            next if ($ltenders =~ /10\.0\.0\.1:$newstordir$/ || $ltenders =~ /10\.0\.0\.1:$newstordir,/); # This entry already exists
5620
            #my @ltenderlist = split(/,\s*/, $ltenders);
5621
            #$ltenderlist[0] = "10.0.0.1:$newstordir";
5622
            unshift(@ltenderlist, "10.0.0.1:$newstordir");
5623
            $nodecfg->param('STORAGE_SERVERS_ADDRESS_PATHS', join(',', @ltenderlist));
5624
            my @ltenderpathslist = $nodecfg->param('STORAGE_SERVERS_LOCAL_PATHS');
5625
            my $ltenderpaths = join(", ", @ltenderpathslist);
5626
            #my @ltenderpathslist = split(/,\s*/, $ltenderpaths);
5627
            #$ltenderpathslist[0] = $newstordir;
5628
            unshift(@ltenderpathslist, $newstordir);
5629
            $nodecfg->param('STORAGE_SERVERS_LOCAL_PATHS', join(',', @ltenderpathslist));
5630 95b003ff Origo
            $nodecfg->save();
5631
        }
5632
        unless (`grep "$newstordir 10" /etc/exports`) {
5633
            `echo "$newstordir 10.0.0.0/255.255.255.0(sync,no_subtree_check,no_root_squash,rw)" >> /etc/exports`;
5634
            `/usr/sbin/exportfs -r`; #Reexport nfs shares
5635
        }
5636 e9af6c24 Origo
# We no longer undefine storage pools - we add them
5637
#        $oldstordir =~ s/\//\\\//g;
5638
#        `perl -pi -e 's/$oldstordir 10.*\\\n//s;' /etc/exports` if ($oldstordir);
5639
5640 95b003ff Origo
        `mkdir "$newstordir/common"` unless (-e "$newstordir/common");
5641
        `cp "$stordir/ejectcdrom.xml" "$newstordir/ejectcdrom.xml"` unless (-e "$newstordir/ejectcdrom.xml");
5642
        `cp "$stordir/mountvirtio.xml" "$newstordir/mountvirtio.xml"` unless (-e "$newstordir/mountvirtio.xml");
5643
        `cp "$stordir/dummy.qcow2" "$newstordir/dummy.qcow2"` unless (-e "$newstordir/dummy.qcow2");
5644
    }
5645
    Updatedownloads();
5646
5647 27512919 Origo
    # Update /etc/stabile/cgconfig.conf
5648 e9af6c24 Origo
    my $devs = $devices{$dev}->{dev};
5649
    my @pdevs = split(" ", $devs);
5650
    my $majmins;
5651
    foreach my $dev (@pdevs) {
5652
        # It seems that cgroups cannot handle individual partitions for blkio
5653
        my $physdev = $1 if ($dev =~ /(\w+)\d+/);
5654
        if ($physdev && -d "/sys/fs/cgroup" ) {
5655
            my $blkline = `lsblk -l /dev/$physdev`;
5656
            my $majmin = '';
5657
            $majmin = $1 if ($blkline =~ /$physdev +(\d+:\d+)/);
5658
            $postreply .= "Status=OK Setting cgroups block device to $majmin\n";
5659
            if ($majmin) {
5660
                $majmins .= ($majmins)?" $majmin":$majmin;
5661
            }
5662 95b003ff Origo
        }
5663
    }
5664 e9af6c24 Origo
    setCgroupsBlkDevice($majmins) if ($majmins);
5665 95b003ff Origo
5666
    $Stabile::Nodes::console = 1;
5667
    require "$Stabile::basedir/cgi/nodes.cgi";
5668
    $postreply .= Stabile::Nodes::do_reloadall('','reloadall');
5669
5670
    # Update config on stabile.io
5671
    require "$Stabile::basedir/cgi/users.cgi";
5672
    $Stabile::Users::console = 1;
5673
    Stabile::Users::Updateengine('', 'updateengine');
5674
5675
    my $msg = "OK Now using $newstordir for $type on $obj->{device}";
5676
    $main::updateUI->({tab=>'home', user=>$user, type=>'update', message=>$msg});
5677
    $postreply .= "Status=OK Now using $newstordir for $type on $dev\n";
5678
    return $postreply;
5679
}
5680
5681
sub Initializestorage {
5682
    my ($image, $action, $obj) = @_;
5683
    if ($help) {
5684
        return <<END
5685
GET:device,type,fs,activate,force:
5686
Initializes a local disk or partition, and optionally formats it with ZFS and creates a ZFS pool to use as image storage or backup storage.
5687
[device] is a local disk device in /dev like e.g. 'sdd'. [type] may be either 'images' (default) or 'backup'. [fs] may be 'lvm' (default) or 'zfs'.
5688 e9af6c24 Origo
Set [activate] if you want to put the device into use immediately. Set [force] if you want to destroy existing ZFS pool and recreate (obviously use with care).
5689 95b003ff Origo
END
5690
    }
5691
    my $fs = $obj->{fs} || 'zfs';
5692
    my $dev = $obj->{device};
5693
    my $force = $obj->{force};
5694
    my $activate = $obj->{activate};
5695
    my $type = 'backup';
5696
    $type = 'images' if ($obj->{type} eq 'images');
5697
    return "Status=Error Not allowed\n" unless ($isadmin);
5698
    my $backupdevice = Getbackupdevice('', 'getbackupdevice');
5699
    my $imagesdevice = Getimagesdevice('', 'getimagesdevice');
5700
    my $devices_obj = from_json(Liststoragedevices('', 'liststoragedevices'));
5701
    my %devices = %$devices_obj;
5702
    my $backupdev = $devices{$backupdevice}->{dev};
5703
    my $imagesdev = $devices{$imagesdevice}->{dev};
5704
    if (!$dev || !(-e "/dev/$dev")) {
5705
        $postreply = "Status=Error You must specify a valid device\n";
5706
        return $postreply;
5707
    }
5708
    if (($backupdev =~ /$dev/) || ($imagesdev =~ /$dev/)) {
5709
        $postreply = "Status=Error $dev is already in use as images or backup device\n";
5710
        return $postreply;
5711
    }
5712
    my $stordir = "/stabile-$type/$type";
5713
    if ($fs eq 'lvm') {
5714
        if ($type eq 'backup') {
5715
            $stordir = "/mnt/stabile/backups";
5716
        } else {
5717
            $stordir = "/mnt/stabile/images";
5718
        }
5719
    }
5720
    `chmod 666 /dev/zfs` if (-e '/dev/zfs'); # TODO: This should be removed once we upgrade to Bionic and zfs allow is supported
5721
5722
    my $vol = $type . "vol";
5723
    my $mounts = `cat /proc/mounts`;
5724
    my $zpools = `zpool list -v`;
5725
    my $pvs = `pvdisplay -c`;
5726 e9af6c24 Origo
    my $z;
5727 95b003ff Origo
    $postreply = '';
5728
    # Unconfigure existing zfs or lvm if $force and zfs/lvm configured or device is in use by either
5729
    if ($zpools =~ /stabile-$type/ || $mounts =~ /dev\/mapper\/stabile$type/ || $zpools =~ /$dev/ || $pvs =~ /$dev/) {
5730 e9af6c24 Origo
        if ($fs eq 'zfs' || $zpools =~ /$dev/) {
5731
            if ($force) { # ZFS needs to be unconfigured
5732
                my $umount = `LANG=en_US.UTF-8 umount -v "/stabile-$type/$type" 2>&1`;
5733 95b003ff Origo
                unless ($umount =~ /(unmounted|not mounted|no mount point)/) {
5734 e9af6c24 Origo
                    $postreply .= "Status=Error Unable to unmount zfs $type storage on $dev - $umount\n";
5735 95b003ff Origo
                    return $postreply;
5736
                }
5737
                `umount "/stabile-$type"`;
5738
                my $res = `zpool destroy "stabile-$type" 2>&1`;
5739
                chomp $res;
5740
                $postreply .= "Status=OK Unconfigured zfs - $res\n";
5741
            } else {
5742
                $postreply .= "Status=Error ZFS is already configured for $type\n";
5743 e9af6c24 Origo
                $z = 1;
5744
            #    return $postreply;
5745 95b003ff Origo
            }
5746
        }
5747
        if ($fs eq 'lvm' || $pvs =~ /$dev/) {
5748
            if ($force) {
5749
                my $udir = (($type eq 'backup')?"/mnt/stabile/backups":"/mnt/stabile/images");
5750
                my $umount = `umount -v "$udir" 2>&1`;
5751
                unless ($umount =~ /unmounted|not mounted|no mount point/) {
5752
                    $postreply .= "Status=Error Unable to unmount lvm $type storage - $umount\n";
5753
                    return $postreply;
5754
                }
5755
                my $res = `lvremove --yes /dev/stabile$type/$vol  2>&1`;
5756
                chomp $res;
5757
                $res .= `vgremove -f stabile$type 2>&1`;
5758
                chomp $res;
5759
                my $pdev = "/dev/$dev";
5760
                $pdev .= '1' unless ($pdev =~ /1$/);
5761
                $res .= `pvremove $pdev 2>&1`;
5762
                chomp $res;
5763
                $postreply .= "Status=OK Unconfigured lvm - $res\n";
5764
            } else {
5765
                $postreply .= "Status=Error LVM is already configured for $type\n";
5766
                return $postreply;
5767
            }
5768
        }
5769
    }
5770
    # Check if $dev is still in use
5771
    $mounts = `cat /proc/mounts`;
5772
    $zpools = `zpool list -v`;
5773
    $pvs = `pvdisplay -c`;
5774
    if ($mounts =~ /\/dev\/$dev/ || $pvs =~ /$dev/ || $zpools =~ /$dev/) {
5775 e9af6c24 Origo
        $postreply .= "Status=Error $dev is already in use - use force.\n";
5776 95b003ff Origo
        return $postreply;
5777
    }
5778
    # Now format
5779
    my $ispart = 1 if ($dev =~ /[a-zA-Z]+\d+/);
5780
    if ($fs eq 'zfs') { # ZFS was specified
5781
        $postreply = "Status=OK Initializing $dev disk with ZFS for $type...\n";
5782
        if (!$ispart) {
5783
            my $fres = `parted -s /dev/$dev mklabel GPT 2>&1`;
5784
            $postreply .= "Status=OK partitioned $dev: $fres\n";
5785
        }
5786 e9af6c24 Origo
        if ($z) { # zpool already created
5787
            `zpool add stabile-$type /dev/$dev`;
5788
        } else {
5789
            `zpool create stabile-$type /dev/$dev`;
5790
            `zfs create stabile-$type/$type`;
5791
            `zfs set atime=off stabile-$type/$type`;
5792
        }
5793 95b003ff Origo
#        if ($force) {
5794
#            $postreply .= "Status=OK Forcibly removing all files in $stordir to allow ZFS mount\n";
5795
#            `rm -r $stordir/*`;
5796
#        }
5797
#        `zfs set mountpoint=$stordir stabile-$type/$type`;
5798
        $stordir = "/stabile-$type/$type" if (`zfs mount stabile-$type/$type`);
5799
        `/bin/chmod 777 $stordir`;
5800
        $postreply .= "Status=OK Mounted stabile-$type/$type as $type storage on $stordir.\n";
5801
        if ($activate) {
5802
            $postreply .= "Status=OK Setting $type storage device to $dev.\n";
5803
            Setstoragedevice('', 'setstoragedevice', {device=>"stabile-$type", type=>$type});
5804
        }
5805
    } else { # Assume LVM
5806
        $postreply = "Status=OK Initializing $dev with LVM for $type...\n";
5807
        my $part = $dev;
5808
        if (!$ispart) {
5809
            $part = $dev.'1';
5810
            `/sbin/sfdisk -d /dev/$dev > /root/$dev-partition-sectors.save`;
5811
            my $fres = `sfdisk /dev/$dev << EOF\n;\nEOF`;
5812
            $postreply .= "Status=OK partitioned $dev: $fres\n";
5813
        }
5814
        `/sbin/vgcreate -f stabile$type /dev/$part`;
5815
        `/sbin/vgchange -a y stabile$type`;
5816
        my $totalpe =`/sbin/vgdisplay stabile$type | grep "Total PE"`;
5817
        $totalpe =~ /Total PE\s+(\d+)/;
5818
        my $size = $1 -2000;
5819
#        my $size = "10000";
5820
        if ($size <100) {
5821
            $postreply .= "Status=Error Volume is too small to make sense...\n";
5822
            return $postreply;
5823
        }
5824
        my $vol = $type . "vol";
5825
        `/sbin/lvcreate --yes -l $size stabile$type -n $vol`;
5826
#        `/sbin/mkfs.ext4 /dev/stabile$type/$vol`;
5827
        `mkfs.btrfs /dev/stabile$type/$vol`;
5828
        my $mounted = `mount -v /dev/stabile$type/$vol $stordir`;
5829
        `chmod 777 $stordir`;
5830
        if ($mounted) {
5831
            $postreply .= "Status=OK Mounted /dev/stabile$type/$vol as $type storage on $stordir.\n";
5832
        } else {
5833
            $postreply .= "Status=Error Could not mount /dev/stabile$type/$vol as $type storage on $stordir.\n";
5834
        }
5835
        if ($activate){
5836
            Setstoragedevice('', 'setstoragedevice', {device=>"stabile$type-$type".'vol', type=>$type});
5837
        }
5838
    }
5839
    return $postreply;
5840
}
5841
5842
sub setCgroupsBlkDevice {
5843 e9af6c24 Origo
    my @majmins = split(" ", shift);
5844 27512919 Origo
    my $file = "/etc/stabile/cgconfig.conf";
5845
    my %options = (
5846
        blkio.throttle.read_bps_device => $valve_readlimit,
5847
        blkio.throttle.write_bps_device => $valve_writelimit,
5848
        blkio.throttle.read_iops_device => $valve_iopsreadlimit,
5849
        blkio.throttle.write_iops_device => $valve_iopswritelimit
5850
        );
5851
    my @groups = ('stabile', 'stabilevm');
5852 95b003ff Origo
    my @newlines;
5853 27512919 Origo
    foreach my $majmin (@majmins) {
5854
        foreach my $group (@groups) {
5855
            my $mline = qq|group $group {|; push @newlines, $mline;
5856
            my $mline = qq|    blkio {|; push @newlines, $mline;
5857
            foreach my $option (keys %options) {
5858
                my $mline = qq|        $option = "$majmin $options{$option}";|;
5859
                push @newlines, $mline;
5860 e9af6c24 Origo
            }
5861 27512919 Origo
            my $mline = qq|    }|; push @newlines, $mline;
5862
            my $mline = qq|}|; push @newlines, $mline;
5863 95b003ff Origo
        }
5864
    }
5865
    unless (open(FILE, "> $file")) {
5866
        $postreply .= "Status=Error Problem opening $file\n";
5867
        return $postreply;
5868
    }
5869
    print FILE join("\n", @newlines);
5870
    close(FILE);
5871
    return;
5872
}