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
    $istoragepool = $istoragepool || $obj->{storagepool};
1854
    $name = $name || $obj->{name};
1855
    $wait = $wait || $obj->{wait};
1856
    my $status = $register{$path}->{'status'};
1857
    my $type = $register{$path}->{'type'};
1858
    my $master = $register{$path}->{'master'};
1859
    my $notes = $register{$path}->{'notes'};
1860
    my $image2 = $register{$path}->{'image2'};
1861
    my $snap1 = $register{$path}->{'snap1'};
1862
    $managementlink = $register{$path}->{'managementlink'} unless ($managementlink);
1863
    $appid = $register{$path}->{'appid'} unless ($appid);
1864
    my $upgradelink = $register{$path}->{'upgradelink'} || '';
1865
    my $terminallink = $register{$path}->{'terminallink'} || '';
1866
    my $version = $register{$path}->{'version'} || '';
1867
    my $regmac = $register{$path}->{'mac'};
1868
1869
    my $virtualsize = $register{$path}->{'virtualsize'};
1870
    my $dindex = 0;
1871
1872 04c16f26 hq
    my($bname, $dirpath, $suffix) = fileparse($path, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
1873 95b003ff Origo
    $path =~ /(.+)\.$type/;
1874
    my $namepath = $1;
1875
    if ($namepath =~ /(.+)\.master/) {
1876
        $namepath = $1;
1877
    }
1878
    if ($namepath =~ /(.+)\.clone\d+/) {
1879
        $namepath = $1;
1880
    }
1881
    if ($namepath =~ /.+\/common\/(.+)/) { # Support one subdir
1882
        $namepath = $1;
1883
    } elsif ($namepath =~ /.+\/$user\/(.+)/) { # Support one subdir
1884
        $namepath = $1;
1885
    } elsif ($namepath =~ /.+\/(.+)/) { # Extract only the name
1886
        $namepath = $1;
1887
    }
1888
1889
    # Find unique path in DB across storage pools
1890
    my $upath;
1891
    my $npath = "/mnt/stabile/node/$user/$namepath"; # Also check for uniqueness on nodes
1892
    my $i = 1;
1893
    foreach my $spool (@spools) {
1894
        $upath = $spool->{'path'} . "/$user/$namepath";
1895
        while ($register{"$upath.clone$i.$type"} || $register{"$npath.clone$i.$type"}) {$i++;};
1896
    }
1897
    $upath = "$spools[$istoragepool]->{'path'}/$user/$namepath";
1898
1899
    my $iname = $register{$path}->{'name'};
1900
    $iname = "$name" if ($name); # Used when name supplied when building a system
1901
    $iname =~ /(.+)( \(master\))/;
1902
    $iname = $1 if $2;
1903
    $iname =~ /(.+)( \(clone\d*\))/;
1904
    $iname = $1 if $2;
1905
    $iname =~ /(.+)( \(child\d*\))/;
1906
    $iname = $1 if $2;
1907
    my $ippath = $path;
1908
    my $macip;
1909
    my $ug = new Data::UUID;
1910
    my $newuuid = $ug->create_str();
1911
    my $wakenode;
1912
    my $identity;
1913
1914
    # We only support cloning images to nodes - not the other way round
1915
    if ($imac && $regmac && $imac ne $regmac) {
1916
        $postreply .= "Status=ERROR Cloning from a node not supported\n";
1917
        return $postreply;
1918
    }
1919
1920
    if ($istoragepool==-1) {
1921
    # Find the ip address of target node
1922 c899e439 Origo
        ($imac, $macip, $dindex, $wakenode, $identity) = locateNode($virtualsize, $imac, $vcpu, $mem);
1923 95b003ff Origo
        if ($identity eq 'local_kvm') {
1924 c899e439 Origo
            $postreply .= "Status=OK Cloning to local node with index: $dindex\n";
1925 95b003ff Origo
            $istoragepool = 0; # cloning to local node
1926 3657de20 Origo
            $upath = "$spools[$istoragepool]->{'path'}/$user/$namepath";
1927 95b003ff Origo
        } elsif (!$macip) {
1928 c899e439 Origo
            $postreply .= "Status=ERROR Unable to locate node with sufficient ressources\n";
1929
            $postmsg = "Unable to locate node with sufficient ressources!";
1930 95b003ff Origo
            $main::updateUI->({tab=>"images", user=>$user, type=>"message", message=>$postmsg});
1931
            return $postreply;
1932
        } else {
1933 c899e439 Origo
            $postreply .= "Status=OK Cloning to $macip with index: $dindex\n";
1934 95b003ff Origo
            $ippath = "$macip:$path";
1935
            $upath = "/mnt/stabile/node/$user/$namepath";
1936
        }
1937
    }
1938
    my $ipath = "$upath.clone$i.$type";
1939
1940
    if ($bschedule eq 'daily7' || $bschedule eq 'daily14') {
1941
         $bschedule = "manually" if ($istoragepool!=-1 && (!$spools[$istoragepool]->{'rdiffenabled'} || !$spools[$istoragepool]->{'lvm'}));
1942
    } elsif ($bschedule ne 'manually') {
1943
        $bschedule = '';
1944
    }
1945
1946
# Find storage pool with space
1947
    my $foundstorage = 1;
1948
    if (overStorage($virtualsize, $istoragepool, $imac)) {
1949
        $foundstorage = 0;
1950
        foreach my $p (@spools) {
1951
            if (overStorage($virtualsize, $p->{'id'}, $imac)) {
1952
                ;
1953
            } else {
1954
                $istoragepool = $p->{'id'};
1955
                $foundstorage = 1;
1956
                last;
1957
            }
1958
        }
1959
    }
1960
1961
# We allow multiple clone operations on master images
1962
    if ($status ne "used" && $status ne "unused" && $status ne "paused" && $path !~ /(.+)\.master\.$type/) {
1963
        $postreply .= "Status=ERROR Please shut down your virtual machine before cloning\n";
1964
1965
    } elsif ($type eq 'vmdk' && (-e "$dirpath$bname-s001$suffix" || -e "$dirpath$bname-flat$suffix")) {
1966
        $postreply .= "Status=ERROR Cannot clone this image - please convert first!\n";
1967
1968
    } elsif (overQuotas($virtualsize, ($istoragepool==-1))) {
1969
        $postreply .= "Status=ERROR Over quota (". overQuotas($virtualsize, ($istoragepool==-1)) . ") cloning: $name\n";
1970
1971
    } elsif (!$foundstorage) {
1972
        $postreply .= "Status=ERROR Not enough storage ($virtualsize) in destination pool $istoragepool $imac cloning: $name\n";
1973
1974
    } elsif ($wakenode && !($path =~ /(.+)\.master\.$type/)) { # For now we dont support simply copying images on sleeping nodes
1975
        $postreply .= "Status=ERROR We are waking up the node your image $name is on, please try again later\n";
1976
        require "$Stabile::basedir/cgi/nodes.cgi";
1977
        $Stabile::Nodes::console = 1;
1978
        Stabile::Nodes::wake($imac);
1979
    } elsif ($type eq "img" || $type eq "qcow2" || $type eq "vmdk") {
1980
        my $masterimage2 = $register{"$path"}->{'image2'};
1981
    # Cloning a master produces a child
1982
        if ($type eq "qcow2" && $path =~ /(.+)\.master\.$type/) {
1983
            $uistatus = "cloning";
1984
    # VBoxManage probably does a more efficient job at cloning than simply copying
1985 04c16f26 hq
        } elsif ($type eq "vdi" || $type eq "vhd" || $type eq "vhdx") {
1986 95b003ff Origo
            $uistatus = "vcloning";
1987
    # Cloning another child produces a sibling with the same master
1988
        } else {
1989
            $uistatus = "copying";
1990
        }
1991
        $uipath = $path;
1992
        eval {
1993
            $register{$ipath} = {
1994
                uuid=>$newuuid,
1995
                master=>($uistatus eq 'cloning')?$path:$master,
1996
                name=>"$iname (clone$i)",
1997
                notes=>$notes,
1998
                image2=>$image2,
1999
                snap1=>($uistatus eq 'copying')?$snap1:'',
2000
                storagepool=>$istoragepool,
2001
                status=>$uistatus,
2002
                mac=>($istoragepool == -1)?$imac:"",
2003
                size=>0,
2004
                realsize=>0,
2005
                virtualsize=>$virtualsize,
2006
                bschedule=>$bschedule,
2007
                type=>"qcow2",
2008
                created=>$current_time,
2009
                user=>$user
2010
            };
2011
            $register{$ipath}->{'managementlink'} = $managementlink if ($managementlink);
2012
            $register{$ipath}->{'appid'} = $appid if ($appid);
2013
            $register{$ipath}->{'upgradelink'} = $upgradelink if ($upgradelink);
2014
            $register{$ipath}->{'terminallink'} = $terminallink if ($terminallink);
2015
            $register{$ipath}->{'version'} = $version if ($version);
2016
            $register{$path}->{'status'} = $uistatus;
2017
            my $dstatus = ($buildsystem)?'bcloning':$uistatus;
2018
            if ($wakenode) { # We are waking a node for clone operation, so ask movepiston to do the work
2019
                unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2020
                my $tasks = $nodereg{$imac}->{'tasks'};
2021
                $upath = URI::Escape::uri_escape($ipath);
2022
                $tasks .= "BCLONE $upath $user\n";
2023
                $nodereg{$imac}->{'tasks'} = $tasks;
2024
                tied(%nodereg)->commit;
2025
                untie %nodereg;
2026
            } elsif ($wait) {
2027
                my $cmd = qq|steamExec $user $dstatus $status "$ippath" "$ipath"|;
2028
                $cmd = $1 if ($cmd =~ /(.+)/);
2029
                `$cmd`;
2030
            } else {
2031
                my $daemon = Proc::Daemon->new(
2032
                        work_dir => '/usr/local/bin',
2033
                        exec_command => "perl -U steamExec $user $dstatus $status \"$ippath\" \"$ipath\""
2034
                    ) or do {$postreply .= "Status=ERROR $@\n";};
2035
                my $pid = $daemon->Init();
2036
            }
2037
            $postreply .= "Status=$uistatus OK $uistatus to: $iname (clone$i)" . ($isadmin? " -> $ipath ":"") . "\n";
2038
            $postreply .= "Status=OK uuid: $newuuid\n"; # if ($console || $api);
2039
            $postreply .= "Status=OK path: $ipath\n"; # if ($console || $api);
2040
            $postreply .= "Status=OK mac: $imac\n"; # if ($console || $api);
2041
            $postreply .= "Status=OK wakenode: $wakenode\n"; # if ($console || $api);
2042
            $main::syslogit->($user, "info", "$uistatus $wakenode $type image: $name $uuid to $ipath");
2043
            1;
2044
        } or do {$postreply .= "Status=ERROR $@\n";}
2045
2046
    } else {
2047
        $postreply .= "Status=ERROR Not a valid type: $type\n";
2048
    }
2049
    tied(%register)->commit;
2050
    $main::updateUI->({tab=>"images", user=>$user, type=>"update"});
2051
    return $postreply;
2052
}
2053
2054
2055
# Link master image to fuel
2056
sub Linkmaster {
2057
    my ($mpath, $action) = @_;
2058
    if ($help) {
2059
        return <<END
2060
GET:image:
2061
Link master image to fuel
2062
END
2063
    }
2064
    my $res;
2065
2066
    return "Your account does not have the necessary privileges\n" if ($isreadonly);
2067
    return "Please specify master image to link\n" unless ($mpath);
2068
2069
    unless ($mpath =~ /^\//) { # We did not get an absolute path, look for it in users storagepools
2070
        foreach my $p (@spools) {
2071
            my $dir = $p->{'path'};
2072
            my $cpath = "$dir/common/$mpath";
2073
            my $upath = "$dir/$user/$mpath";
2074
            if (-e $cpath) {
2075
                $mpath = $cpath;
2076
                last;
2077
            } elsif (-e $upath) {
2078
                $mpath = $upath;
2079
                last;
2080
            }
2081
        }
2082
    }
2083
    my $img = $register{$mpath};
2084
    $mpath = $img->{"path"};
2085
    $imguser = $img->{"user"};
2086
    if (!$mpath || ($imguser ne $user && $imguser ne 'common' && !$isadmin)) {
2087
        $postreply = qq|{"status": "Error", "message": "No privs. or not found @_[0]"}|;
2088
        return $postreply;
2089
    }
2090
    my $status = $img->{"status"};
2091
    my $type = $img->{"type"};
2092
    $mpath =~ /(.+)\/(.+)\.master\.$type$/;
2093
    my $namepath = $2;
2094
    my $msg;
2095
    if ($status ne "unused" && $status ne "used") {
2096
        $res .= qq|{"status": "Error", "message": "Only used and unused images may be linked ($status, $mpath)."}|;
2097
    } elsif (!( $mpath =~ /(.+)\.master\.$type$/ ) ) {
2098
        $res .= qq|{"status": "Error", "message": "You can only link master images"}|;
2099
    } elsif ($type eq "qcow2") {
2100
        my $pool = $img->{'storagepool'};
2101
        `chmod 444 "$mpath"`;
2102
        my $linkpath = $tenderpathslist[$pool] . "/$user/fuel/$namepath.link.master.$type";
2103
        my $fuellinkpath = "/mnt/fuel/pool$pool/$namepath.link.master.$type";
2104
        if (-e $tenderpathslist[$pool] . "/$user/fuel") { # master should be on fuel-enabled storage
2105
            unlink ($linkpath) if (-e $linkpath);
2106
            `ln "$mpath" "$linkpath"`;
2107
        } else {
2108
            foreach my $p (@spools) {
2109
                my $dir = $p->{'path'};
2110
                my $poolid = $p->{'id'};
2111
                if (-e "$dir/$user/fuel") {
2112
                    $linkpath = "$dir/$user/fuel/$namepath.copy.master.$type";
2113
                    $fuellinkpath = "/mnt/fuel/pool$poolid/$namepath.copy.master.$type";
2114
                    unlink ($linkpath) if (-e $linkpath);
2115
                    `cp "$mpath" "$linkpath"`;
2116
                    $msg = "Different file systems, master copied";
2117
                    last;
2118
                }
2119
            }
2120
        }
2121
        $res .= qq|{"status": "OK", "message": "$msg", "path": "$fuellinkpath", "linkpath": "$linkpath", "masterpath": "$mpath"}|;
2122
    } else {
2123
        $res .= qq|{"status": "Error", "message": "You can only link qcow2 images"}|;
2124
    }
2125
    $postreply = $res;
2126
    return $res;
2127
}
2128
2129
# Link master image to fuel
2130
sub unlinkMaster {
2131
    my $mpath = shift;
2132
    unless ($mpath =~ /^\//) { # We did not get an absolute path, look for it in users storagepools
2133
        foreach my $p (@spools) {
2134
            my $dir = $p->{'path'};
2135
            my $upath = "$dir/$user/fuel/$mpath";
2136
            if (-e $upath) {
2137
                $mpath = "/mnt/fuel/pool$p->{id}/$mpath";
2138
                last;
2139
            }
2140
        }
2141
    }
2142
2143
    $mpath =~ /\/pool(\d+)\/(.+)\.link\.master\.qcow2$/;
2144
    my $pool = $1;
2145
    my $namepath = $2;
2146
    if (!( $mpath =~ /\/pool(\d+)\/(.+)\.link\.master\.qcow2$/ ) ) {
2147
        $postreply = qq|{"status": "Error", "message": "You can only unlink linked master images ($mpath)"}|;
2148
    } else {
2149
        my $linkpath = $tenderpathslist[$pool] . "/$user/fuel/$namepath.link.master.qcow2";
2150
        if (-e $linkpath) {
2151
            `chmod 644 "$linkpath"`;
2152
            `rm "$linkpath"`;
2153
            $postreply = qq|{"status": "OK", "message": "Link removed", "path": "/mnt/fuel/pool$pool/$namepath.qcow2", "linkpath": "$linkpath"}|;
2154
        } else {
2155
            $postreply = qq|{"status": "Error", "message": "Link $linkpath does not exists."}|;
2156
        }
2157
    }
2158
}
2159
2160
#sub do_getstatus {
2161
#    my ($img, $action) = @_;
2162
#    if ($help) {
2163
#        return <<END
2164
#GET::
2165
#END
2166
#    }
2167
#    # Allow passing only image name if we are dealing with an app master
2168
#    my $dspool = $stackspool;
2169
#    my $masteruser = $params{'masteruser'};
2170
#    my $destuser = $params{'destuser'};
2171
#    my $destpath;
2172
#    $dspool = $spools[0]->{'path'} unless ($engineid eq $valve001id);
2173
#    if (!$register{$img} && $img && !($img =~ /\//) && $masteruser) {
2174
#        if ($img =~ /\.master\.qcow2$/ && $register{"$dspool/$masteruser/$img"}) {
2175
#            if ($ismanager || $isadmin
2176
#                || ($userreg{$masteruser}->{'billto'} eq $user)
2177
#            ) {
2178
#                $img = "$dspool/$masteruser/$img";
2179
#            }
2180
#        }
2181
#    }
2182
#    my $status = $register{$img}->{'status'};
2183
#    if ($status) {
2184
#        my $iuser = $register{$img}->{'user'};
2185
#        # First check if user is allowed to access image
2186
#        if ($iuser ne $user && $iuser ne 'common' && $userreg{$iuser}->{'billto'} ne $user) {
2187
#            $status = '' unless ($isadmin || $ismanager);
2188
#        }
2189
#        if ($destuser) { # User is OK, now check if destination exists
2190
#            my ($dest, $folder) = fileparse($img);
2191
#            $destpath = "$dspool/$destuser/$dest";
2192
#            $status = 'exists' if ($register{$destpath} || -e ($destpath));
2193
#        }
2194
#    }
2195
#    my $res;
2196
#    $res .= $Stabile::q->header('text/plain') unless ($console);
2197
#    $res .= "$status";
2198
#    return $res;
2199
#}
2200
2201 d3805c61 hq
sub do_move {
2202
    my ($image, $action, $obj) = @_;
2203
    if ($help) {
2204
        return <<END
2205
GET:image,user,storagepool,mac,precreate:
2206
Move image to a different storage pool or user
2207
END
2208
    }
2209
    return "Your account does not have the necessary privileges\n" if ($isreadonly);
2210
#    $postreply = qq/"$curimg || $image, $obj->{user} || $user, $obj->{storagepool}, $obj->{mac}, 0, $obj->{precreate}, $nodereg->{$obj->{mac}}->{name}"/;
2211
#    return $postreply;
2212
    my $res = Move($curimg || $image, $obj->{user} || $user, $obj->{storagepool}, $obj->{mac},0, $obj->{precreate});
2213
    return header() . $res;
2214
}
2215 95b003ff Origo
2216
sub Move {
2217 d3805c61 hq
    my ($path, $iuser, $istoragepool, $mac, $force, $precreate) = @_;
2218 95b003ff Origo
    # Allow passing only image name if we are deleting an app master
2219
    my $dspool = $stackspool;
2220
    my $masteruser = $params{'masteruser'};
2221
    $dspool = $spools[0]->{'path'} unless ($engineid eq $valve001id);
2222
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2223
    if (!$register{$path} && $path && !($path =~ /\//) && $masteruser) {
2224
        if ($path =~ /\.master\.qcow2$/ && $register{"$dspool/$masteruser/$path"}) {
2225
            if ($ismanager || $isadmin
2226
                || ($userreg{$masteruser}->{'billto'} eq $user && $iuser eq $user)
2227
                || ($masteruser eq $user && $userreg{$iuser}->{'billto'} eq $user)
2228
            ) {
2229
                $path = "$dspool/$masteruser/$path";
2230
            }
2231
        }
2232
    }
2233 48fcda6b Origo
    my $regimg = $register{$path};
2234
    $istoragepool = ($istoragepool eq '0' || $istoragepool)? $istoragepool: $regimg->{'storagepool'};
2235 d3805c61 hq
    $mac = $mac || $regimg->{'mac'}; # destination mac
2236 48fcda6b Origo
    my $bschedule = $regimg->{'bschedule'};
2237
    my $name = $regimg->{'name'};
2238
    my $status = $regimg->{'status'};
2239
    my $type = $regimg->{'type'};
2240
    my $reguser = $regimg->{'user'};
2241
    my $regstoragepool = $regimg->{'storagepool'};
2242
    my $virtualsize = $regimg->{'virtualsize'};
2243 95b003ff Origo
2244
    my $newpath;
2245
    my $newdirpath;
2246
    my $oldpath = $path;
2247 d24d9a01 hq
    my $olddirpath = $path;
2248 95b003ff Origo
    my $newuser = $reguser;
2249
    my $newstoragepool = $regstoragepool;
2250
    my $haschildren;
2251 3657de20 Origo
    my $hasprimary;
2252 95b003ff Origo
    my $child;
2253 3657de20 Origo
    my $primary;
2254 95b003ff Origo
    my $macip;
2255
    my $alreadyexists;
2256
    my $subdir;
2257 27512919 Origo
#    $subdir = $1 if ($path =~ /\/$reguser(\/.+)\//);
2258
    $subdir = $1 if ($path =~ /.+\/$reguser(\/.+)?\//);
2259 95b003ff Origo
    my $restpath;
2260 27512919 Origo
    $restpath = $1 if ($path =~ /.+\/$reguser\/(.+)/);
2261 95b003ff Origo
2262
    if ($type eq "qcow2" && $path =~ /(.+)\.master\.$type/) {
2263
        my @regkeys = (tied %register)->select_where("master = '$path'");
2264
        foreach my $k (@regkeys) {
2265
            my $val = $register{$k};
2266
            if ($val->{'master'} eq $path) {
2267
                $haschildren = 1;
2268
                $child = $val->{'name'};
2269
                last;
2270
            }
2271
        }
2272
    }
2273 3657de20 Origo
    if ($type eq "qcow2") {
2274
        my @regkeys = (tied %register)->select_where("image2 = '$path'");
2275
        foreach my $k (@regkeys) {
2276
            my $val = $register{$k};
2277
            if ($val->{'image2'} eq $path) {
2278
                $hasprimary = 1;
2279
                $primary = $val->{'name'};
2280
                last;
2281
            }
2282
        }
2283
    }
2284 95b003ff Origo
    if (!$register{$path}) {
2285
        $postreply .= "Status=ERROR Unable to move $path (invalid path, $path, $masteruser)\n" unless ($istoragepool eq '--' || $regstoragepool eq '--');
2286
    } elsif ($type eq 'vmdk' && -s (substr($path,0,-5) . "-flat.vmdk") || -s (substr($path,0,-5) . "-s001.vmdk")) {
2287
        $postreply .= "Status=Error Cannot move this image. Please convert before moving\n";
2288 d3805c61 hq
    } elsif ($precreate && ($register{$path}->{snap1} && $register{$path}->{snap1} ne '--') && !$force) {
2289
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"Please remove snapshots from image $name before stormoving server."});
2290
        $postreply .= "Status=Error Cannot stormove an image with snapshots\n";
2291 95b003ff Origo
# Moving an image to a different users dir
2292
    } elsif ($iuser ne $reguser && ($status eq "unused" || $status eq "used")) {
2293
        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2294
        my @accounts = split(/,\s*/, $userreg{$tktuser}->{'accounts'});
2295
        my @accountsprivs = split(/,\s*/, $userreg{$tktuser}->{'accountsprivileges'});
2296
        %ahash = ($tktuser, $userreg{$tktuser}->{'privileges'} || 'r' ); # Include tktuser in accounts hash
2297
        for my $i (0 .. scalar @accounts)
2298
        {
2299
            next unless $accounts[$i];
2300
            $ahash{$accounts[$i]} = $accountsprivs[$i] || 'u';
2301
        }
2302
2303
        if ((($isadmin || $ismanager ) && $iuser eq 'common') # Check if user is allowed to access account
2304
                || ($isadmin && $userreg{$iuser})
2305
                || ($user eq $engineuser)
2306
                || ($userreg{$iuser}->{'billto'} eq $user)
2307
                || ($ahash{$iuser} && !($ahash{$iuser} =~ /r/))
2308
        ) {
2309
            if ($haschildren) {
2310 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"});
2311 95b003ff Origo
                $postreply .= "Status=Error Cannot move image. This image is used as master by: $child\n";
2312 3657de20 Origo
            } elsif ($hasprimary) {
2313
                $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"});
2314
                $postreply .= "Status=Error Cannot move image. This image is used as secondary image by: $primary\n";
2315 95b003ff Origo
            } else {
2316
                if ($regstoragepool == -1) { # The image is located on a node
2317
                    my $uprivs = $userreg{$iuser}->{'privileges'};
2318
                    if ($uprivs =~ /[an]/) {
2319
                        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2320
                        $macip = $nodereg{$mac}->{'ip'};
2321 d3805c61 hq
                        my $oldmacip = $nodereg{$regimg->{'mac'}}->{'ip'};
2322 95b003ff Origo
                        untie %nodereg;
2323 d3805c61 hq
                        $oldpath = "$oldmacip:/mnt/stabile/node/$reguser/$restpath";
2324 95b003ff Origo
                        $newdirpath = "/mnt/stabile/node/$iuser/$restpath";
2325
                        $newpath = "$macip:$newdirpath";
2326
                        $newuser = $iuser;
2327
                        $newstoragepool = $istoragepool;
2328
                # Check if image already exists in target dir
2329
                        $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}'"`;
2330
                    } else {
2331
                        $postreply .= "Status=Error Target account $iuser cannot use node storage\n";
2332
                    }
2333
                } else {
2334 48fcda6b Origo
                    my $reguser = $userreg{$iuser};
2335
                    my $upools = $reguser->{'storagepools'} || $Stabile::config->get('STORAGE_POOLS_DEFAULTS') || "0";
2336 95b003ff Origo
                    my @nspools = split(/, ?/, $upools);
2337
                    my %ispools = map {$_=>1} @nspools; # Build a hash with destination users storagepools
2338
                    if ($ispools{$regstoragepool}) { # Destination user has access to image's storagepool
2339
                        $newpath = "$spools[$regstoragepool]->{'path'}/$iuser/$restpath";
2340
                    } else {
2341
                        $newpath = "$spools[0]->{'path'}/$iuser/$restpath";
2342
                    }
2343
                    $newdirpath = $newpath;
2344
                    $newuser = $iuser;
2345
            # Check if image already exists in target dir
2346
                    $alreadyexists = -e $newpath;
2347
                }
2348
            }
2349
        } else {
2350
            $postreply .= "Status=Error Cannot move image to account $iuser $ahash{$iuser} - not allowed\n";
2351
        }
2352
# Moving an image to a different storage pool
2353
    } elsif ($istoragepool ne '--' &&  $regstoragepool ne '--' && $istoragepool ne $regstoragepool
2354 d3805c61 hq
            && ($status eq "unused" || $status eq "used" || $status eq "paused" || ($status eq "active" && $precreate))) {
2355 95b003ff Origo
2356
        my $dindex;
2357
        my $wakenode;
2358
        if ($istoragepool == -1 && $regstoragepool != -1) {
2359
            ($mac, $macip, $dindex, $wakenode) = locateNode($virtualsize, $mac);
2360
        }
2361
2362
        $main::syslogit->($user, "info", "Moving $name from $regstoragepool to $istoragepool $macip $wakenode");
2363
2364
        if ($haschildren) {
2365 3657de20 Origo
            $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$register{$path}->{'uuid'}, status=>$status, message=>"ERROR Unable to move $name (has children)"});
2366 95b003ff Origo
            $postreply .= "Status=ERROR Unable to move $name (has children)\n";
2367 3657de20 Origo
        } elsif ($hasprimary) {
2368
            $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"});
2369
            $postreply .= "Status=Error Cannot move image. This image is used as secondary image by: $primary\n";
2370 95b003ff Origo
        } elsif ($wakenode) {
2371
            $postreply .= "Status=ERROR All available nodes are asleep moving $name, waking $mac, please try again later\n";
2372 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"});
2373 95b003ff Origo
            require "$Stabile::basedir/cgi/nodes.cgi";
2374
            $Stabile::Nodes::console = 1;
2375
            Stabile::Nodes::wake($mac);
2376
        } elsif (overStorage($virtualsize, $istoragepool+0, $mac)) {
2377 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"});
2378 95b003ff Origo
            $postreply .= "Status=ERROR Out of storage in destination pool $istoragepool $mac moving: $name\n";
2379
        } elsif (overQuotas($virtualsize, ($istoragepool==-1))) {
2380 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"});
2381 95b003ff Origo
            $postreply .= "Status=ERROR Over quota (". overQuotas($virtualsize, ($istoragepool==-1)) . ") moving: $name\n";
2382
        } elsif ($istoragepool == -1 && $regstoragepool != -1 && $path =~ /\.master\.$type/) {
2383
            $postreply .= "Status=ERROR Unable to move $name (master images are not supported on node storage)\n";
2384 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)"});
2385 95b003ff Origo
    # Moving to node
2386
        } elsif ($istoragepool == -1 && $regstoragepool != -1) {
2387 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
2388 95b003ff Origo
                if ($macip) {
2389
                    $newdirpath = "/mnt/stabile/node/$reguser/$restpath";
2390
                    $newpath = "$macip:$newdirpath";
2391
                    $newstoragepool = $istoragepool;
2392
            # Check if image already exists in target dir
2393
                    $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}'"`;
2394 d24d9a01 hq
2395 95b003ff Origo
                } else {
2396
                    $postreply .= "Status=ERROR Unable to move $name (not enough space)\n";
2397
                }
2398
            } else {
2399 d3805c61 hq
                $postreply .= "Status=ERROR Unable to move $name (no node privileges)\n";
2400 95b003ff Origo
            }
2401
    # Moving from node
2402
        } elsif ($regstoragepool == -1 && $istoragepool != -1 && $spools[$istoragepool]) {
2403 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
2404 95b003ff Origo
                unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2405 d3805c61 hq
                $macip = $nodereg{$mac}->{'ip'}; # $mac is set to existing image's mac since no destination mac was specified
2406 95b003ff Origo
                untie %nodereg;
2407
                $newpath = "$spools[$istoragepool]->{'path'}/$reguser/$restpath";
2408
                $newdirpath = $newpath;
2409
                $oldpath = "$macip:/mnt/stabile/node/$reguser/$restpath";
2410
                $newstoragepool = $istoragepool;
2411
        # Check if image already exists in target dir
2412
                $alreadyexists = -e $newpath;
2413
            } else {
2414 d3805c61 hq
                $postreply .= "Status=ERROR Unable to move $name - you must specify a node\n";
2415 95b003ff Origo
            }
2416
        } elsif ($spools[$istoragepool]) { # User has access to storagepool
2417
            $newpath = "$spools[$istoragepool]->{'path'}/$reguser/$restpath";
2418
            $newdirpath = $newpath;
2419
            $newstoragepool = $istoragepool;
2420
            $alreadyexists = -e $newpath && -s $newpath;
2421
        } else {
2422
            $postreply .= "Status=ERROR Cannot move image. This image is used as master by: $child\n";
2423
        }
2424
    } else {
2425
        $postreply .= "Status=ERROR Unable to move $path (bad status or pool $status, $reguser, $iuser, $regstoragepool, $istoragepool)\n" unless ($istoragepool eq '--' || $regstoragepool eq '--');
2426
    }
2427
    untie %userreg;
2428
2429 48fcda6b Origo
    if ($alreadyexists && !$force) {
2430
        $postreply = "Status=ERROR Image \"$name\" already exists in destination\n";
2431
        return $postreply;
2432 95b003ff Origo
    }
2433
# Request actual move operation
2434
    elsif ($newpath) {
2435
        if ($newstoragepool == -1) {
2436
            my $diruser = $iuser || $reguser;
2437
            `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
2438
        }
2439
        if ($subdir && $istoragepool != -1) {
2440
            my $fulldir = "$spools[$istoragepool]->{'path'}/$reguser$subdir";
2441
            `/bin/mkdir -p "$fulldir"` unless -d $fulldir;
2442
        }
2443
        $uistatus = "moving";
2444 d3805c61 hq
        if ($precreate) {
2445
            $uistatus = "stormoving";
2446
        }
2447
2448 95b003ff Origo
        my $ug = new Data::UUID;
2449
        my $tempuuid = $ug->create_str();
2450
2451
        $register{$path}->{'status'} = $uistatus;
2452
        $register{$newdirpath} = \%{$register{$path}}; # Clone db entry
2453 d3805c61 hq
        $register{$newdirpath}->{'snap1'} = '' if ($precreate && $force); # Snapshots are not preserved when live migrating storage
2454
2455 95b003ff Origo
2456
        if ($bschedule eq 'daily7' || $bschedule eq 'daily14') {
2457
             $bschedule = "manually" if (!$spools[$regstoragepool]->{'rdiffenabled'} || !$spools[$regstoragepool]->{'lvm'});
2458
        } elsif ($bschedule ne 'manually') {
2459
            $bschedule = '';
2460
        }
2461
2462
        $register{$path}->{'uuid'} = $tempuuid; # Use new temp uuid for old image
2463
        $register{$newdirpath}->{'storagepool'} = $newstoragepool;
2464
        if ($newstoragepool == -1) {
2465
            $register{$newdirpath}->{'mac'} = $mac;
2466
        } else {
2467
            $register{$newdirpath}->{'mac'} = '';
2468
        }
2469
        $register{$newdirpath}->{'user'} = $newuser;
2470
        tied(%register)->commit;
2471 d24d9a01 hq
        my $domuuid = $register{$path}->{'domains'};
2472 d3805c61 hq
        if ($status eq "used" || $status eq "paused" || $status eq "moving" || $status eq "stormoving" || $status eq "active") {
2473 95b003ff Origo
            my $dom = $domreg{$domuuid};
2474 d24d9a01 hq
            if ($dom->{'image'} eq $olddirpath) {
2475 48fcda6b Origo
                $dom->{'image'} = $newdirpath;
2476 d24d9a01 hq
            } elsif ($dom->{'image2'} eq $olddirpath) {
2477 48fcda6b Origo
                $dom->{'image2'} = $newdirpath;
2478 d24d9a01 hq
            } elsif ($dom->{'image3'} eq $olddirpath) {
2479 48fcda6b Origo
                $dom->{'image3'} = $newdirpath;
2480 d24d9a01 hq
            } elsif ($dom->{'image4'} eq $olddirpath) {
2481 48fcda6b Origo
                $dom->{'image4'} = $newdirpath;
2482
            }
2483 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.
2484
            $dom->{'mac'} = $mac if ($newstoragepool == -1 && !$precreate);
2485 95b003ff Origo
            if ($dom->{'system'} && $dom->{'system'} ne '--') {
2486
                unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
2487
                my $sys = $sysreg{$dom->{'system'}};
2488 d24d9a01 hq
                $sys->{'image'} = $newdirpath if ($sys->{'image'} eq $olddirpath);
2489 95b003ff Origo
                untie %sysreg;
2490
            }
2491
        }
2492
        my $cmd = qq|/usr/local/bin/steamExec $user $uistatus $status "$oldpath" "$newpath"|;
2493 48fcda6b Origo
        `$cmd`;
2494 d3805c61 hq
        $main::syslogit->($user, "info", "$uistatus $type image $name ($oldpath -> $newpath) ($regstoragepool -> $istoragepool)");
2495 48fcda6b Origo
        return "$newdirpath\n";
2496 95b003ff Origo
    } else {
2497 48fcda6b Origo
        return $postreply;
2498 95b003ff Origo
    }
2499
2500
}
2501
2502
sub locateNode {
2503 c899e439 Origo
    my ($virtualsize, $mac, $vcpu, $mem) = @_;
2504 95b003ff Origo
    $vcpu = $vcpu || 1;
2505
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) ) {return 0};
2506
    my $macip;
2507
    my $dmac;
2508
    my $dindex;
2509
    my $asleep;
2510
    my $identity;
2511 c899e439 Origo
    my $node;
2512 95b003ff Origo
    if ($mac && $mac ne "--") { # A node was specified
2513
        if (1024 * $nodestorageovercommission * $nodereg{$mac}->{'storfree'} > $virtualsize && $nodereg{$mac}->{'status'} eq 'running') {
2514 c899e439 Origo
            $node = $nodereg{$mac};
2515 95b003ff Origo
        }
2516
    } else { # Locate a node
2517
        require "$Stabile::basedir/cgi/servers.cgi";
2518
        $Stabile::Servers::console = 1;
2519
        my ($temp1, $temp2, $temp3, $temp4, $ahashref) = Stabile::Servers::locateTargetNode();
2520
        my @avalues = values %$ahashref;
2521
        my @sorted_values = (sort {$b->{'index'} <=> $a->{'index'}} @avalues);
2522 c899e439 Origo
        foreach my $snode (@sorted_values) {
2523 95b003ff Origo
            if (
2524 c899e439 Origo
                (1024 * $nodestorageovercommission * $snode->{'storfree'} > $virtualsize)
2525
                && ($snode->{'cpuindex'} > $vcpu)
2526
                && ($snode->{'memfree'} > $mem+512*1024)
2527
                && !($snode->{'maintenance'})
2528
                && ($snode->{'status'} eq 'running' || $snode->{'status'} eq 'asleep' || $snode->{'status'} eq 'waking')
2529
                && ($snode->{'index'} > 0)
2530 95b003ff Origo
            ) {
2531 d24d9a01 hq
                next if (!($mem) && $snode->{'identity'} eq 'local_kvm'); # Ugly hack - prevent moving images from default storage to local_kvm node
2532 c899e439 Origo
                $node = $snode;
2533 95b003ff Origo
                last;
2534
            }
2535
        }
2536
    }
2537 c899e439 Origo
    $macip = $node->{'ip'};
2538
    $dmac = $node->{'mac'};
2539
    $dindex = $node->{'index'};
2540
    $asleep = ($node->{'status'} eq 'asleep' || $node->{'status'} eq 'waking');
2541
    $identity = $node->{'identity'};
2542 95b003ff Origo
    untie %nodereg;
2543
    return ($dmac, $macip, $dindex, $asleep, $identity);
2544
}
2545
2546
sub do_getimagestatus {
2547
    my ($image, $action) = @_;
2548
    if ($help) {
2549
        return <<END
2550
GET:image:
2551
Check if image already exists. Pass image name including suffix.
2552
END
2553
    }
2554
    my $res;
2555
    $imagename = $params{'name'} || $image;
2556 d3805c61 hq
    if ($register{"/mnt/stabile/node/$user/$imagename"}) {
2557
        $res .= q|Status=OK Image /mnt/stabile/node/$imagename found with status | . $register{"/mnt/stabile/node/$user/$imagename"}->{status}. "\n";
2558
    }
2559 95b003ff Origo
    foreach my $spool (@spools) {
2560
        my $ipath = $spool->{'path'} . "/$user/$imagename";
2561
        if ($register{$ipath}) {
2562
            $res .= "Status=OK Image $ipath found with status $register{$ipath}->{'status'}\n";
2563
        } elsif (-f "$ipath" && -s "$ipath") {
2564
            $res .= "Status=OK Image $ipath found on disk, please wait for it to be updated in DB\n";
2565
        }
2566
    }
2567 d3805c61 hq
    $res .= "Status=ERROR Image $imagename not found\n" unless ($res);
2568 95b003ff Origo
    return $res;;
2569
}
2570
2571
# Check if image already exists.
2572
# Pass image name including suffix.
2573
sub imageExists {
2574
    my $imagename = shift;
2575
    foreach my $spool (@spools) {
2576
        my $ipath = $spool->{'path'} . "/$user/$imagename";
2577
        if ($register{$ipath}) {
2578
            return $register{$ipath}->{'status'} || 1;
2579
        } elsif (-e "$ipath") {
2580
            return 1
2581
        }
2582
    }
2583
    return '';
2584
}
2585
2586
# Pass image name including suffix.
2587
# Returns incremented name of an image which does not already exist.
2588
sub getValidName {
2589
    my $imagename = shift;
2590
    my $name = $imagename;
2591
    my $type;
2592
    if ($imagename =~ /(.+)\.(.+)/) {
2593
        $name = $1;
2594
        $type = $2;
2595
    }
2596
    if (imageExists($imagename)) {
2597
        my $i = 1;
2598
        while (imageExists("$name.$i.$type")) {$i++;};
2599
        $imagename = "$name.$i.$type";
2600
    }
2601
    return $imagename;
2602
}
2603
2604
# Print list of available actions on objects
2605
sub do_plainhelp {
2606
    my $res;
2607
    $res .= header('text/plain') unless $console;
2608
    $res .= <<END
2609
* new [size="size", name="name"]: Creates a new image
2610
* 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
2611
image is a regular copy.
2612
* convert: Creates a copy of a non-qcow2 image in qcow2 format
2613
* snapshot: Takes a qcow2 snapshot of the image. Server can not be running.
2614
* unsnap: Removes a qcow2 snapshot.
2615
* revert: Applies a snapshot, reverting the image to the state it was in, when the snapshot was taken.
2616
* master: Turns an image into a master image which child images may be cloned from. Image can not be in use.
2617
* unmaster: Turns a master image into a regular image, which can not be used to clone child images from.
2618
* backup: Backs up an image using rdiff-backup. Rdiff-backup must be enabled in admin server configuration. This is a
2619
very expensive operation, since typically the entire image must be read.
2620
* buildsystem [master="master image"]: Constructs one or optionally multiple servers, images and networks and assembles
2621
them in one app.
2622
* restore [backup="backup"]: Restores an image from a backup. The restore is named after the backup.
2623
* delete: Deletes an image. Use with care. Image can not be in use.
2624
* mount: Mounts an image for restorefiles and listfiles operations.
2625
* unmount: Unmounts an image
2626
END
2627
    ;
2628
    return $res;
2629
}
2630
2631
# Print list of images
2632
# Showing a single image is also handled by specifying uuid or path in $curuuid or $curimg
2633
# When showing a single image a single action may be performed on image
2634
sub do_list {
2635
    my ($img, $action, $obj) = @_;
2636
    if ($help) {
2637
        return <<END
2638
GET:image,uuid:
2639
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.
2640
The returned list may be filtered by specifying storagepool, type, name, path or uuid, like e.g.:
2641
2642
<a href="/stabile/images/type:user" target="_blank">/stabile/images/type:user</a>
2643
<a href="/stabile/images/name:test* AND storagepool:shared" target="_blank">/stabile/images/name:test* AND storagepool:shared</a>
2644
<a href="/stabile/images/storagepool:shared AND path:test*" target="_blank">/stabile/images/storagepool:shared AND path:test*</a>
2645
<a href="/stabile/images/name:* AND storagepool:all AND type:usercdroms" target="_blank">/stabile/images/name:* AND storagepool:all AND type:usercdroms</a>
2646
<a href="/stabile/images/[uuid]" target="_blank">/stabile/images/[uuid]</a>
2647
2648
storagepool may be either of: all, node, shared
2649
type may be either of: user, usermasters, commonmasters, usercdroms
2650
2651
May also be called as tablelist or tablelistall, for use by stash.
2652
2653
END
2654
    }
2655
    my $res;
2656
    my $filter;
2657
    my $storagepoolfilter;
2658
    my $typefilter;
2659
    my $pathfilter;
2660
    my $uuidfilter;
2661
    $curimg = $img if ($img);
2662 c899e439 Origo
    my $regimg = $register{$curimg};
2663
#    if ($curimg && ($isadmin || $regimg->{'user'} eq $user || $regimg->{'user'} eq 'common') ) {
2664
    if ($curimg) { # security is enforced below, we hope...
2665 95b003ff Origo
        $pathfilter = $curimg;
2666
    } elsif ($uripath =~ /images(\.cgi)?\/(\?|)(name|storagepool|type|path)/) {
2667
        $filter = $3 if ($uripath =~ /images(\.cgi)?\/.*name(:|=)(.+)/);
2668
        $filter = $1 if ($filter =~ /(.*) AND storagepool/);
2669
        $filter = $1 if ($filter =~ /(.*) AND type/);
2670
        $filter = $1 if ($filter =~ /(.*)\*$/);
2671
        $storagepoolfilter = $2 if ($uripath =~ /images(\.cgi)?\/.*storagepool:(\w+)/);
2672
        $typefilter = $2 if ($uripath =~ /images(\.cgi)?\/.*type:(\w+)/);
2673
        $typefilter = $2 if ($uripath =~ /images(\.cgi)?\/.*type=(\w+)/);
2674
        $pathfilter = $2 if ($uripath =~ /images(\.cgi)?\/.*path:(.+)/);
2675
        $pathfilter = $2 if ($uripath =~ /images(\.cgi)?\/.*path=(.+)/);
2676
    } elsif ($uripath =~ /images(\.cgi)?\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})\/?(\w*)/) {
2677
        $uuidfilter = $2;
2678
        $curaction = lc $3;
2679
    }
2680
    $uuidfilter = $options{u} unless $uuidfilter;
2681
2682
    if ($uuidfilter && $curaction) {
2683
        if ($imagereg{$uuidfilter}) {
2684
            $curuuid = $uuidfilter;
2685
            my $obj = getObj(%params);
2686
            # Now perform the requested action
2687
            my $objfunc = "obj_$curaction";
2688
            if (defined &$objfunc) { # If a function named objfunc exists, call it
2689
                $res = $objfunc->($obj);
2690
                chomp $postreply;
2691
                unless ($res) {
2692
                    $res .= qq|{"status": "OK", "message": "$postreply"}|;
2693
                    $res = join(", ", split("\n", $res));
2694
                }
2695
                unless ($curaction eq 'download') {
2696
                    $res = header('application/json; charset=UTF8') . $res unless ($console);
2697
                }
2698
            } else {
2699
                $res .= header('application/json') unless $console;
2700
                $res .= qq|{"status": "Error", "message": "Unknown image action: $curaction"}|;
2701
            }
2702
        } else {
2703
            $res .= header('application/json') unless $console;
2704
            $res .= qq|{"status": "Error", "message": "Unknown image $uuidfilter"}|;
2705
        }
2706
        return $res;
2707
    }
2708
2709
2710
    my %userregister; # User specific register
2711
2712
    $res .= header('application/json; charset=UTF8') unless $console;
2713
    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;};
2714
2715
    my @busers = @users;
2716
    my @billusers = (tied %userreg)->select_where("billto = '$user'");
2717
    push (@busers, $billto) if ($billto && $billto ne '--'); # We include images from 'parent' user
2718
    push (@busers, @billusers) if (@billusers); # We include images from 'child' users
2719
    untie %userreg;
2720
    unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
2721
    foreach my $u (@busers) {
2722
        my @regkeys = (tied %register)->select_where("user = '$u'");
2723
        foreach my $k (@regkeys) {
2724
            my $valref = $register{$k};
2725
            # Only update info for images the user has access to.
2726
            if ($valref->{'user'} eq $u && (defined $spools[$valref->{'storagepool'}]->{'id'} || $valref->{'storagepool'}==-1)) {
2727
                # Only list installable master images from billto account
2728 e837d785 hq
                next if ($billto && ($billto ne $user) && ($u eq $billto) && ($valref->{'type'} ne 'qcow2' || $valref->{'installable'} ne 'true'));
2729 95b003ff Origo
                my $path = $valref->{'path'};
2730
                my %val = %{$valref}; # Deference and assign to new array, effectively cloning object
2731
                my $spool = $spools[$val{'storagepool'}];
2732
                # Skip images which are in DB e.g. because of change of storage pool difinitions
2733
                next unless ($val{'storagepool'}==-1 || $val{'path'} =~ /$spool->{'path'}/);
2734
                $val{'virtualsize'} += 0;
2735
                $val{'realsize'} += 0;
2736
                $val{'size'} += 0;
2737
                #$val{'lvm'} = 0+( (($spools[$val{'storagepool'}]->{"hostpath"} eq "local") && $spools[$val{'storagepool'}]->{"rdiffenabled"}) || $val{'storagepool'}==-1);
2738
                if ($val{'storagepool'}==-1) {
2739
                    my $node = $nodereg{$val{'mac'}};
2740
                    $val{'lvm'} = 0+($node->{stor} eq 'lvm');
2741
                } else {
2742
                    $val{'lvm'} = 0+$spool->{"lvm"};
2743
                }
2744
                # If image has a master, update the master with child info.
2745
                # This info is specific to each user, so we don't store it in the db
2746
                if ($valref->{'master'} && $register{$valref->{'master'}} && ((grep $_ eq $valref->{'user'}, @users))) {
2747
                    $register{$valref->{'master'}}->{'status'} = 'used';
2748
                    unless ($userregister{$val{'master'}}) { # If we have not yet parsed master, it is not yet in userregister, so put it there
2749
                        my %mval = %{$register{$val{'master'}}};
2750
                        $userregister{$val{'master'}} = \%mval;
2751
                    }
2752
                    #   $userregister{$val{'master'}}->{'user'} = $u;
2753
                    $userregister{$val{'master'}}->{'status'} = 'used';
2754
                    if ($val{'domains'}) {
2755
                        $userregister{$val{'master'}}->{'domainnames'} .= ", " if ($userregister{$val{'master'}}->{'domainnames'});
2756
                        $userregister{$val{'master'}}->{'domainnames'} .= $val{'domainnames'};
2757
                        $userregister{$val{'master'}}->{'domainnames'} .= " (".$val{'user'}.")" if (index($privileges,"a")!=-1);
2758
2759
                        $userregister{$val{'master'}}->{'domains'} .= ", " if ($userregister{$val{'master'}}->{'domains'});
2760
                        $userregister{$val{'master'}}->{'domains'} .= $val{'domains'};
2761
                    }
2762
                }
2763
                my $status = $valref->{'status'};
2764
                if ($rdiffenabled && ($userrdiffenabled || index($privileges,"a")!=-1) &&
2765
                    ( ($spools[$valref->{'storagepool'}]->{'rdiffenabled'} &&
2766
                        ($spools[$valref->{'storagepool'}]->{'lvm'} || $status eq 'unused' || $status eq 'used' || $status eq 'paused') )
2767
                        || $valref->{'storagepool'}==-1 )
2768
                ) {
2769
                    $val{'backup'} = "" ;
2770
                } else {
2771
                    $val{'backup'} = "disabled" ;
2772
                }
2773
                $val{'status'} = 'backingup' if ($status =~ /backingup/);
2774 f222b89c hq
                Updateregister($k, "updateregister") if ($status =~ /(downloading|uploading)/);
2775 95b003ff Origo
                $userregister{$path} = \%val unless ($userregister{$path});
2776
            }
2777
        }
2778
    }
2779
    untie(%nodereg);
2780
2781
    my @uservalues;
2782
    if ($filter || $storagepoolfilter || $typefilter || $pathfilter || $uuidfilter) { # List filtered images
2783
        foreach $uvalref (values %userregister) {
2784
            my $fmatch;
2785
            my $smatch;
2786
            my $tmatch;
2787
            my $pmatch;
2788
            my $umatch;
2789
            $fmatch = 1 if (!$filter || $uvalref->{'name'}=~/$filter/i);
2790
            $smatch = 1 if (!$storagepoolfilter || $storagepoolfilter eq 'all'
2791
                || ($storagepoolfilter eq 'node' && $uvalref->{'storagepool'}==-1)
2792
                || ($storagepoolfilter eq 'shared' && $uvalref->{'storagepool'}>=0)
2793
            );
2794
            $tmatch = 1 if (!$typefilter || $typefilter eq 'all'
2795
                || ($typefilter eq 'user' && $uvalref->{'user'} eq $user
2796
                # && $uvalref->{'type'} ne 'iso'
2797
                # && $uvalref->{'path'} !~ /\.master\.qcow2$/
2798
                    )
2799
                || ($typefilter eq 'usermasters' && $uvalref->{'user'} eq $user && $uvalref->{'path'} =~ /\.master\.qcow2$/)
2800
                || ($typefilter eq 'usercdroms' && $uvalref->{'user'} eq $user && $uvalref->{'type'} eq 'iso')
2801
                || ($typefilter eq 'commonmasters' && $uvalref->{'user'} ne $user && $uvalref->{'path'} =~ /\.master\.qcow2$/)
2802
                || ($typefilter eq 'commoncdroms' && $uvalref->{'user'} ne $user && $uvalref->{'type'} eq 'iso')
2803
            );
2804
            $pmatch = 1 if ($pathfilter && $uvalref->{'path'}=~/$pathfilter/i);
2805
            $umatch = 1 if ($uvalref->{'uuid'} eq $uuidfilter);
2806
            if ((!$pathfilter &&!$uuidfilter && $fmatch && $smatch && $tmatch) || $pmatch) {
2807
                push @uservalues,$uvalref if ($uvalref->{'uuid'});
2808
            } elsif ($umatch && $uvalref->{'uuid'}) {
2809
                push @uservalues,$uvalref;
2810
                last;
2811
            }
2812
        }
2813
    } else {
2814
        @uservalues = values %userregister;
2815
    }
2816
2817
    # Sort @uservalues
2818 2a63870a Christian Orellana
    @uservalues = (sort {$a->{'name'} cmp $b->{'name'}} @uservalues); # Always sort by name first
2819 95b003ff Origo
    my $sort = 'status';
2820
    $sort = $2 if ($uripath =~ /sort\((\+|\-)(\S+)\)/);
2821
    my $reverse;
2822
    $reverse = 1 if ($1 eq '-');
2823
    if ($reverse) { # sort reverse
2824
        if ($sort =~ /realsize|virtualsize|size/) {
2825
            @uservalues = (sort {$b->{$sort} <=> $a->{$sort}} @uservalues); # Sort as number
2826
        } else {
2827
            @uservalues = (sort {$b->{$sort} cmp $a->{$sort}} @uservalues); # Sort as string
2828
        }
2829
    } else {
2830
        if ($sort =~ /realsize|virtualsize|size/) {
2831
            @uservalues = (sort {$a->{$sort} <=> $b->{$sort}} @uservalues); # Sort as number
2832
        } else {
2833
            @uservalues = (sort {$a->{$sort} cmp $b->{$sort}} @uservalues); # Sort as string
2834
        }
2835
    }
2836
2837
    if ($uuidfilter || $curimg) {
2838 48fcda6b Origo
        if (scalar @uservalues > 1) { # prioritize user's own images
2839
            foreach my $val (@uservalues) {
2840
                if ($val->{'user'} eq 'common') {
2841
                    next;
2842
                } else {
2843
                    $json_text = to_json($val, {pretty => 1});
2844
                }
2845
            }
2846
        } else {
2847
            $json_text = to_json($uservalues[0], {pretty => 1}) if (@uservalues);
2848
        }
2849 95b003ff Origo
    } else {
2850 2a63870a Christian Orellana
    #    $json_text = JSON->new->canonical(1)->pretty(1)->encode(\@uservalues) if (@uservalues);
2851 95b003ff Origo
        $json_text = to_json(\@uservalues, {pretty => 1}) if (@uservalues);
2852
    }
2853
    $json_text = "{}" unless $json_text;
2854
    $json_text =~ s/""/"--"/g;
2855
    $json_text =~ s/null/"--"/g;
2856
    $json_text =~ s/"notes" {0,1}: {0,1}"--"/"notes":""/g;
2857
    $json_text =~ s/"installable" {0,1}: {0,1}"(true|false)"/"installable":$1/g;
2858
2859
    if ($action eq 'tablelist' || $action eq 'tablelistall') {
2860
        my $t2 = Text::SimpleTable->new(36,26,5,20,14,10,7);
2861
        $t2->row('uuid', 'name', 'type', 'domainnames', 'virtualsize', 'user', 'status');
2862
        $t2->hr;
2863
        my $pattern = $options{m};
2864
        foreach $rowref (@uservalues){
2865
            next unless ($action eq 'tablelistall' || $rowref->{'user'} eq $user);
2866
            if ($pattern) {
2867
                my $rowtext = $rowref->{'uuid'} . " " . $rowref->{'name'} . " " . $rowref->{'type'} . " " . $rowref->{'domainnames'}
2868
                    . " " .  $rowref->{'virtualsize'} . " " . $rowref->{'user'} . " " . $rowref->{'status'};
2869
                $rowtext .= " " . $rowref->{'mac'} if ($isadmin);
2870
                next unless ($rowtext =~ /$pattern/i);
2871
            }
2872
            $t2->row($rowref->{'uuid'}, $rowref->{'name'}, $rowref->{'type'}, $rowref->{'domainnames'}||'--',
2873
                $rowref->{'virtualsize'}, $rowref->{'user'}, $rowref->{'status'});
2874
        }
2875
        $res .= $t2->draw;
2876
    } elsif ($console) {
2877
        $res .= Dumper(\@uservalues);
2878
    } else {
2879
        $res .= $json_text;
2880
    }
2881
    return $res;
2882
}
2883
2884
# Internal action for looking up a uuid or part of a uuid and returning the complete uuid
2885
sub do_uuidlookup {
2886
    my ($img, $action) = @_;
2887
    if ($help) {
2888
        return <<END
2889
GET:image,path:
2890
END
2891
    }
2892
    my $res;
2893
    $res .= header('text/plain') unless $console;
2894
    my $u = $options{u};
2895
    $u = $curuuid unless ($u || $u eq '0');
2896
    my $ruuid;
2897
    if ($u || $u eq '0') {
2898
        foreach my $uuid (keys %register) {
2899
            if (($register{$uuid}->{'user'} eq $user || $register{$uuid}->{'user'} eq 'common' || $fulllist)
2900
                && ($register{$uuid}->{'uuid'} =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/)) {
2901
                $ruuid = $register{$uuid}->{'uuid'};
2902
                last;
2903
            }
2904
        }
2905
        if (!$ruuid && $isadmin) { # If no match and user is admin, do comprehensive lookup
2906
            foreach $uuid (keys %register) {
2907
                if ($register{$uuid}->{'uuid'} =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/) {
2908
                    $ruuid = $register{$uuid}->{'uuid'};
2909
                    last;
2910
                }
2911
            }
2912
        }
2913
    }
2914
    $res .= "$ruuid\n" if ($ruuid);
2915
    return $res;
2916
}
2917
2918
# Internal action for showing a single image
2919
sub do_uuidshow {
2920
    my ($img, $action) = @_;
2921
    if ($help) {
2922
        return <<END
2923
GET:image,path:
2924
END
2925
    }
2926
    my $res;
2927
    $res .= header('text/plain') unless $console;
2928
    my $u = $options{u};
2929
    $u = $curuuid unless ($u || $u eq '0');
2930
    if ($u || $u eq '0') {
2931
        foreach my $uuid (keys %register) {
2932
            if (($register{$uuid}->{'user'} eq $user || $register{$uuid}->{'user'} eq 'common' || index($privileges,"a")!=-1)
2933
                && $register{$uuid}->{'uuid'} =~ /^$u/) {
2934
                my %hash = %{$register{$uuid}};
2935
                delete $hash{'action'};
2936
                my $dump = Dumper(\%hash);
2937
                $dump =~ s/undef/"--"/g;
2938
                $res .= $dump;
2939
                last;
2940
            }
2941
        }
2942
    }
2943
    return $res;
2944
}
2945
2946
sub do_updatebilling {
2947
    my ($img, $action) = @_;
2948
    if ($help) {
2949
        return <<END
2950
GET:image,path:
2951
END
2952
    }
2953
    my $res;
2954
    $res .= header('text/plain') unless ($console);
2955
    updateBilling($params{"event"});
2956
    $res .= "Status=OK Updated billing for $user\n";
2957
    return $res;
2958
}
2959
2960
# If used with the -f switch ($fulllist) from console, all users images are updated in the db
2961
# If used with the -p switch ($fullupdate), also updates status information (ressource intensive - runs through all domains)
2962
sub dont_updateregister {
2963
    my ($img, $action) = @_;
2964
    my $res;
2965
    if ($help) {
2966
        return <<END
2967
GET:image,path:
2968
END
2969
    }
2970
    return "Status=ERROR You must be an admin to do this!\n" unless ($isadmin);
2971
    $fullupdate = 1 if ((!$fullupdate && $params{'fullupdate'}) || $action eq 'fullupdateregister');
2972
    my $force = $params{'force'};
2973
    Updateregister($force);
2974
    $res .= "Status=OK Updated image register for " . join(', ', @users) . "\n";
2975
}
2976
2977
sub do_urlupload {
2978
    my ($img, $action) = @_;
2979
    if ($help) {
2980
        return <<END
2981
GET:image,path:
2982
END
2983
    }
2984
    my $res;
2985
    $res .= header('application/json') unless ($console);
2986
    if ($params{'probe'} && $params{'url'}) {
2987
        my $url = $params{'url'};
2988 2a63870a Christian Orellana
        my $cmd = qq!curl --http1.1 -kIL "$url" 2>&1!;
2989 95b003ff Origo
        my $headers = `$cmd`;
2990
        my $filename;
2991
        my $filesize = 0;
2992
        $filename = $1 if ($headers =~ /content-disposition: .+filename="(.+)"/i);
2993
        $filesize = $1 if ($headers =~ /content-length: (\d+)/i);
2994
        my $ok;
2995
        if (!$filename) {
2996 2a63870a Christian Orellana
            my $cmd = qq[curl --http1.1 -kIL "$url" 2>&1 | grep -i " 200 OK"];
2997 95b003ff Origo
            $ok =  `$cmd`; chomp $ok;
2998
            $filename = `basename "$url"` if ($ok);
2999
            chomp $filename;
3000
        }
3001 04c16f26 hq
        if ($filename =~ /\S+\.(vmdk|img|vhd|vhdx|qcow|qcow2|vdi|iso)$/) {
3002 95b003ff Origo
            $filename = $2 if ($filename =~ /(=|\?)(.+)/);
3003
            $filename = $2 if ($filename =~ /(=|\?)(.+)/);
3004
            $filename = getValidName($filename);
3005
            my $filepath = $spools[0]->{'path'} . "/$user/$filename";
3006
            $res .= qq|{"status": "OK", "name": "$filename", "message": "200 OK", "size": $filesize, "path": "$filepath"}|;
3007
        } else {
3008 2a63870a Christian Orellana
            $res .= qq|{"status": "ERROR", "message": "An image file cannot be downloaded from this URL.", "url": "$url", "filename": "$filename"}|;
3009 95b003ff Origo
        }
3010
    } elsif ($params{'path'} && $params{'url'} && $params{'name'} && defined $params{'size'}) {
3011
        my $imagepath = $params{'path'};
3012
        my $imagename = $params{'name'};
3013
        my $imagesize = $params{'size'};
3014
        my $imageurl = $params{'url'};
3015 2a63870a Christian Orellana
        if (-e "$imagepath.meta" && $imagepath =~ /\.master\.qcow2$/) { # This image is being downloaded by pressurecontrol
3016
            $res .= qq|{"status": "OK", "name": "$imagename", "message": "Now downloading master", "path": "$imagepath"}|;
3017
        } elsif (-e $imagepath) {
3018 f222b89c hq
            $res .= qq|{"status": "OK", "message": "An image file with this name already exists on the server.", "name": "$imagename", "path": "$imagepath"}|;
3019
            `/bin/echo "uploading" > "$imagepath.meta"`;
3020
            my $ksize = $imagesize / 1024;
3021
            `/bin/echo "$ksize" . "K 100%" >> "$imagepath.meta"`;
3022
            `/bin/echo "" >> "$imagepath.meta"`;
3023 95b003ff Origo
        } elsif ($imagepath !~ /^$spools[0]->{'path'}\/$user\/.+/) {
3024
            $res .= qq|{"status": "ERROR", "message": "Invalid path"}|;
3025
        } elsif (overQuotas($virtualsize)) {
3026
            $res .= qq|{"status": "ERROR", "message": "Over quota (". overQuotas($virtualsize) . ") uploading: $imagename"}|;
3027
        } elsif (overStorage($imagesize, 0)) {
3028
            $res .= qq|{"status": "ERROR", "message": "Out of storage in destination pool uploading: $imagename"}|;
3029 04c16f26 hq
        } elsif ($imagepath =~ /^$spools[0]->{'path'}.+\.(vmdk|img|vhd|vhdx|qcow|qcow2|vdi|iso)$/) {
3030 95b003ff Origo
            my $imagetype = $1;
3031
            my $ug = new Data::UUID;
3032
            my $newuuid = $ug->create_str();
3033
            my $name = $imagename;
3034 04c16f26 hq
            $name = $1 if ($name =~ /(.+)\.(vmdk|img|vhd|vhdx|qcow|qcow2|vdi|iso)$/);
3035 95b003ff Origo
            $register{$imagepath} = {
3036
                uuid => $newuuid,
3037
                path => $imagepath,
3038
                name => $name,
3039
                user => $user,
3040
                type => $imagetype,
3041
                virtualsize => $imagesize,
3042
                realsize => $imagesize,
3043
                size => $imagesize,
3044
                storagepool => 0,
3045
                status => 'uploading'
3046
            };
3047
            `/bin/echo uploading > "$imagepath.meta"`;
3048
            eval {
3049
                my $daemon = Proc::Daemon->new(
3050
                    work_dir => '/usr/local/bin',
3051
                    exec_command => "perl -U steamExec $user urluploading unused \"$imagepath\" \"$imageurl\""
3052
                ) or do {$postreply .= "Status=ERROR $@\n";};
3053
                my $pid = $daemon->Init();
3054
                $main::syslogit->($user, "info", "urlupload $imageurl, $imagepath");
3055
                1;
3056
            } or do {$res .= qq|{"status": "ERROR", "message": "ERROR $@"}|;};
3057
            $res .= qq|{"status": "OK", "name": "$imagename", "message": "Now uploading", "path": "$imagepath"}|;
3058
        }
3059
    } elsif ($params{'path'} && $params{'getsize'}) {
3060
        my $imagepath = $params{'path'};
3061 f222b89c hq
        if (-e "$imagepath.meta") {
3062
            my $imagesize = `grep -Po '\\d+K' "$imagepath.meta" | tail -n1`;
3063
            chomp $imagesize;
3064
            $imagesize = 1024 * $imagesize;
3065 2a63870a Christian Orellana
            $res .= qq|{"status": "OK", "size": $imagesize, "path": "$imagepath"}|;
3066 f222b89c hq
        } else {
3067
            if (!(-e $imagepath)) {
3068
                $res .= qq|{"status": "ERROR", "message": "Image not found.", "path": "$imagepath"}|;
3069
            } elsif ($imagepath !~ /^$spools[0]->{'path'}\/$user\/.+/  && $imagepath !~ /^$spools[0]->{'path'}\/common\/.+/) {
3070
                $res .= qq|{"status": "ERROR", "message": "Invalid path"}|;
3071
            } else {
3072
                my @stat = stat($imagepath);
3073
                my $imagesize = $stat[7];
3074
                $res .= qq|{"status": "OK", "size": $imagesize, "path": "$imagepath"}|;
3075
            }
3076
3077 95b003ff Origo
        }
3078
    }
3079
    return $res;
3080
}
3081
3082
sub do_upload {
3083
    my ($img, $action) = @_;
3084
    if ($help) {
3085
        return <<END
3086
POST:image,path:
3087
END
3088
    }
3089
    my $res;
3090
    $res .= header("text/html") unless ($console);
3091
3092
    my $uname = $params{'name'};
3093
3094 04c16f26 hq
    my($name, $dirpath, $suffix) = fileparse($uname, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
3095 95b003ff Origo
3096
    $name = $1 if ($name =~ /^\.+(.*)/); # Don't allow hidden files
3097
    #        my $f = lc $name;
3098
    my $f = $name;
3099
    $f = $spools[0]->{'path'} . "/$user/$f$suffix";
3100
3101
    my $chunk = int($params{'chunk'});
3102
    my $chunks = int($params{'chunks'});
3103
3104
    if ($chunk == 0 && -e $f) {
3105 2a63870a Christian Orellana
        $res .= qq|Error: File $f already exists $name|;
3106 95b003ff Origo
    } else {
3107
        open (FILE, ">>$f");
3108
3109
        if ($params{'file'}) {
3110
            my $uh = $Stabile::q->upload("file");
3111
            while ( <$uh> ) {
3112
                print FILE;
3113
            }
3114
            close FILE;
3115
3116
            if ($chunk == 0) {
3117
                `/usr/local/bin/steamExec updateimagestatus "$f" uploading`;
3118
            }
3119
            if ($chunk >= ($chunks - 1) ) { # Done
3120
                unlink("$f.meta");
3121
                `/usr/local/bin/steamExec updateimagestatus "$f" unused`;
3122
            } else {
3123
                my $upload_meta_data = "status=uploading&chunk=$chunk&chunks=$chunks";
3124
                `echo "$upload_meta_data" > "$f.meta"`;
3125
            }
3126
            $res .= qq|OK: Chunk $chunk uploaded of $name|;
3127
        } else {
3128
            $res .= qq|OK: No file $name.|;
3129
        }
3130
    }
3131
    return $res;
3132
}
3133
3134
# .htaccess files are created hourly, giving the image user access
3135
# when download is clicked by another user (in @users, so with permission), this user is also given access until .htaccess is rewritten
3136
sub Download {
3137
    my ($f, $action, $argref) = @_;
3138
    #    my ($name, $managementlink, $upgradelink, $terminallink, $version) = @{$argref};
3139
    if ($help) {
3140
        return <<END
3141 2a63870a Christian Orellana
GET:image,console:
3142 95b003ff Origo
Returns http redirection with URL to download image
3143
END
3144
    }
3145 2a63870a Christian Orellana
    $baseurl = $argref->{baseurl} || $baseurl;
3146 95b003ff Origo
    my %uargs = %{$argref};
3147
    $f = $uargs{'image'} unless ($f);
3148
    $baseurl = $uargs{'baseurl'} || $baseurl;
3149 2a63870a Christian Orellana
    $console = $console || $uargs{'console'};
3150 95b003ff Origo
    my $res;
3151
    my $uf =  URI::Escape::uri_unescape($f);
3152
    if (! $f) {
3153
        $res .= header('text/html', '500 Internal Server Error') unless ($console);
3154
        $res .= "Status=ERROR You must specify an image.\n";
3155
    }
3156
    my $txt = <<EOT
3157
order deny,allow
3158
AuthName "Download"
3159
AuthType None
3160
TKTAuthLoginURL $baseurl/login/
3161
TKTAuthIgnoreIP on
3162
deny from all
3163
Satisfy any
3164
require user $user
3165
require user $tktuser
3166
Options -Indexes
3167
EOT
3168
    ;
3169
    my $fid;
3170
    my $fpath;
3171
    foreach my $p (@spools) {
3172
        foreach my $suser (@users) {
3173
            my $dir = $p->{'path'};
3174
            my $id = $p->{'id'};
3175
            if (-d "$dir/$suser" && $uf =~ /\/$suser\//) {
3176
                if ($uf =~ /$dir\/(.+)\/(.+)/) {
3177
                    my $filename = $2;
3178
                    utf8::encode($filename);
3179
                    utf8::decode($filename);
3180
                    $fpath = "$1/" . URI::Escape::uri_escape($filename);
3181
                    #$fpath = "$1/" . $filename;
3182
                    `chmod o+rw "$uf"`;
3183
                    `/bin/echo "$txt" > "$dir/$suser/.htaccess"`;
3184
                    `chmod 644 "$dir/$suser/.htaccess"`;
3185
                    `/bin/mkdir "$Stabile::basedir/download"` unless (-e "$Stabile::basedir/download");
3186
                    `/bin/ln -s "$dir" "$Stabile::basedir/download/$id"` unless (-e "$Stabile::basedir/download/$id");
3187
                    $fid = $id;
3188
                    last;
3189
                }
3190
            }
3191
        }
3192
    }
3193
    if (($fid || $fid eq '0') && $fpath && -e "$f") {
3194
        my $fileurl = "$baseurl/download/$fid/$fpath";
3195
        if ($console) {
3196 2a63870a Christian Orellana
            $res .= header(). $fileurl;
3197 95b003ff Origo
        } else {
3198
            $res .= "Status: 302 Moved\nLocation: $fileurl\n\n";
3199
            $res .= "$fileurl\n";
3200
        }
3201
    } else {
3202
        $res .= header('text/html', '500 Internal Server Error') unless ($console);
3203
        $res .= "Status=ERROR File not found $f, $fid, $fpath, $uargs{image}\n";
3204
    }
3205
    return $res;
3206
}
3207
3208
3209
sub Liststoragedevices {
3210 2a63870a Christian Orellana
    my ($image, $action, $obj) = @_;
3211 95b003ff Origo
    if ($help) {
3212
        return <<END
3213
GET::
3214
Returns available physical disks and partitions.
3215
Partitions currently used for holding backup and primary images directories are marked as such.
3216
May also be called as 'getimagesdevice', 'getbackupdevice', 'listimagesdevices' or 'listbackupdevices'.
3217
END
3218
    }
3219
    unless ($isadmin || ($user eq $engineuser)) {
3220
        return '' if ($action eq 'getimagesdevice' || $action eq 'getbackupdevice');
3221
        return qq|[]|;
3222
    }
3223
    my %devs;
3224
    # Check if we have unmounted ZFS file systems
3225
#    if (`grep "stabile-images" /etc/stabile/config.cfg` && !(`df` =~ /stabile-images/)) {
3226
    if (!(`df` =~ /stabile-images/)) {
3227 a2e0bc7e hq
        `zpool import stabile-images 2>/dev/null`;
3228
        `zfs mount stabile-images 2>/dev/null`;
3229
        `zfs mount stabile-images/images 2>/dev/null`;
3230 95b003ff Origo
    }
3231
    if (!(`df` =~ /stabile-backup/)) {
3232 a2e0bc7e hq
        `zpool import stabile-backup 2>/dev/null`;
3233
        `zfs mount stabile-backup 2>/dev/null`;
3234
        `zfs mount stabile-backup/images 2>/dev/null`;
3235
        `zfs mount stabile-backup/backup 2>/dev/null`;
3236 95b003ff Origo
    }
3237
    # Add active and mounted filesystems
3238
    my %filesystems;
3239
    $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 ]'/;
3240
    my $json = `$cmd`;
3241
    my $jobj = JSON::from_json($json);
3242
    my $rootdev;
3243
    my $backupdev;
3244
    my $imagesdev;
3245
    foreach my $fs (sort {$a->{'Filesystem'} cmp $b->{'Filesystem'}} @{$jobj}) {
3246
        # 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
3247
        if ($fs->{Filesystem} =~ /\/dev\/(.+)/) {
3248 2a63870a Christian Orellana
            next if ($fs->{Type} eq 'squashfs');
3249
            next if ($fs->{Filesystem} =~ /\/dev\/loop/);
3250 95b003ff Origo
            my $name = $1;
3251
            if ($name =~ /mapper\/(\w+-)(.+)/) {
3252
                $name = "$1$2";
3253
            }
3254
            $fs->{Name} = $name;
3255
            delete $fs->{on};
3256
            my $mp = $fs->{Mounted};
3257
            if ($fs->{Mounted} eq '/') {
3258
                $rootdev = $name;
3259
            } else {
3260
                if ($backupdir =~ /^$fs->{Mounted}/) {
3261
                    next if ($action eq 'listimagesdevices'); # Current backup dev is not available as images dev
3262
                    $fs->{isbackupdev} = 1;
3263
                    $backupdev = $name;
3264
                    return $name if ($action eq 'getbackupdevice');
3265
                }
3266
                if ($tenderpathslist[0] =~ /^$fs->{Mounted}/) {
3267
                    next if ($action eq 'listbackupdevices'); # Current images dev is not available as backup dev
3268
                    $fs->{isimagesdev} = 1;
3269
                    $imagesdev = $name;
3270
                    return $name if ($action eq 'getimagesdevice');
3271
                }
3272
            }
3273
            $fs->{dev} = $name;
3274
            $fs->{nametype} = "$name ($fs->{Type} - " .  ($mp?$mp:"not mounted") . " $fs->{Size})";
3275
            $filesystems{$name} = $fs;
3276
        } elsif ( $fs->{Type} eq 'zfs') {
3277
            my $name = $fs->{Filesystem};
3278 71b897d3 hq
            # only include zfs pools but look for use as backup and images, exclude shapshots
3279
            if ($name =~ /(.+)\/(.+)/
3280
                && !($name =~ /SNAPSHOT/)
3281
                && !($name =~ /stabile-backup\/images/)
3282
                && !($name =~ /stabile-backup\/node/)
3283
            ) {
3284 89cb0977 hq
                $name = $1;
3285 95b003ff Origo
                if ($fs->{Mounted} eq $backupdir) {
3286
                    if ($action eq 'listimagesdevices') {
3287 89cb0977 hq
                        delete $filesystems{$name}; # not available for images - used for backup
3288 95b003ff Origo
                    } else {
3289 89cb0977 hq
                        $filesystems{$name}->{isbackupdev} = 1;
3290
                        $fs->{isbackupdev} = 1;
3291 95b003ff Origo
                        $backupdev = $name;
3292
                    }
3293 89cb0977 hq
                    return $name if ($action eq 'getbackupdevice');
3294
                } elsif ($fs->{Mounted} eq $tenderpathslist[0]) {
3295 95b003ff Origo
                    if ($action eq 'listbackupdevices') {
3296 89cb0977 hq
                        delete $filesystems{$name}; # not available for backup - used for images
3297 95b003ff Origo
                    } else {
3298 89cb0977 hq
                        $filesystems{$name}->{isimagesdev} = 1;
3299
                        $fs->{isimagesdev} = 1;
3300 95b003ff Origo
                        $imagesdev = $name;
3301
                    }
3302 89cb0977 hq
                    return $name if ($action eq 'getimagesdevice');
3303 95b003ff Origo
                }
3304 71b897d3 hq
                $fs->{Name} = $name;
3305
                $fs->{nametype} = "$name ($fs->{Type} $fs->{Size})";
3306
                delete $fs->{on};
3307
                $filesystems{$name} = $fs;
3308 95b003ff Origo
            }
3309
        }
3310
    }
3311
    if ($action eq 'getbackupdevice' || $action eq 'getimagesdevice') {
3312
        return $rootdev;
3313
    }
3314 71b897d3 hq
    $filesystems{$rootdev}->{isbackupdev} = 1 unless ($backupdev || $action eq 'listimagesdevices');
3315
    $filesystems{$rootdev}->{isimagesdev} = 1 unless ($imagesdev || $action eq 'listbackupdevices');
3316 95b003ff Origo
    # Lowercase keys
3317
    foreach my $k (keys %filesystems) {
3318
        my %hash = %{$filesystems{$k}};
3319
        %hash = map { lc $_ => $hash{$_} } keys %hash;
3320
        $filesystems{$k} = \%hash;
3321
    }
3322
    # Identify physical devices used for zfs
3323
    $cmd = "zpool list -vH";
3324
    my $zpools = `$cmd`;
3325
    my $zdev;
3326
    my %zdevs;
3327 e9af6c24 Origo
3328 95b003ff Origo
    # Now parse the rather strange output with every other line representing physical dev
3329
    foreach my $line (split "\n", $zpools) {
3330
        my ($zname, $zsize, $zalloc) = split "\t", $line;
3331
        if (!$zdev) {
3332
            if ($zname =~ /stabile-/) {
3333
                $zdev = {
3334
                    name=>$zname,
3335
                    size=>$zsize,
3336
                    alloc=>$zalloc
3337
                }
3338
            }
3339
        } else {
3340
            my $dev = $zsize;
3341
            $zdev->{dev} = $dev;
3342
            if ( $filesystems{$zdev->{name}}) {
3343
                if (
3344
                    ($action eq 'listimagesdevices' && $zdev->{name} =~ /backup/) ||
3345
                        ($action eq 'listbackupdevices' && $zdev->{name} =~ /images/)
3346
                ) {
3347
                    delete $filesystems{$zdev->{name}}; # Don't include backup devs in images listing and vice-versa
3348
                } else {
3349 e9af6c24 Origo
                    if ($filesystems{$zdev->{name}}->{dev}) {
3350
                        $filesystems{$zdev->{name}}->{dev} .= " $dev";
3351
                    } else {
3352
                        $filesystems{$zdev->{name}}->{dev} = $dev;
3353
                    }
3354
        #            $filesystems{$zdev->{name}}->{nametype} =~ s/zfs/zfs pool/;
3355 95b003ff Origo
                }
3356
            }
3357
            $zdevs{$dev} = $zdev->{name};
3358 e9af6c24 Origo
        #    $zdev = '';
3359 95b003ff Origo
        }
3360
    }
3361
3362
    # Add blockdevices
3363
    $cmd = q|lsblk --json|;
3364
    my $json2 = `$cmd`;
3365
    my $jobj2 = JSON::from_json($json2);
3366
    foreach my $fs (@{$jobj2->{blockdevices}}) {
3367
        my $rootdev = $1 if ($fs->{name} =~ /([A-Za-z]+)\d*/);
3368
        if ($fs->{children}) {
3369
            foreach my $fs2 (@{$fs->{children}}) {
3370 2a63870a Christian Orellana
                next if ($fs2->{type} eq 'loop');
3371
                next if ($fs2->{type} eq 'squashfs');
3372 71b897d3 hq
                next if ($fs2->{size} =~ /K$/);
3373 95b003ff Origo
                if ($filesystems{$fs2->{name}}) {
3374
                    $filesystems{$fs2->{name}}->{blocksize} = $fs2->{size};
3375
                } elsif (!$zdevs{$fs2->{name}} && !$zdevs{$rootdev}) { # Don't add partitions already used for ZFS
3376
                    next if (($action eq 'listimagesdevices' || $action eq 'listbackupdevices') && $fs2->{mountpoint} eq '/');
3377
                    my $mp = $fs2->{mountpoint};
3378
                    $filesystems{$fs2->{name}} = {
3379
                        name=>$fs2->{name},
3380
                        blocksize=>$fs2->{size},
3381
                        mountpoint=>$mp,
3382
                        type=>$fs2->{type},
3383
                        nametype=> "$fs2->{name} ($fs2->{type} - " . ($mp?$mp:"not mounted") . " $fs2->{size})",
3384
                        dev=>$fs2->{name}
3385
                    }
3386
                }
3387
            }
3388
        } elsif (!$zdevs{$fs->{name}}) { # Don't add disks already used for ZFS
3389 2a63870a Christian Orellana
            next if ($fs->{type} eq 'loop');
3390
            next if ($fs->{type} eq 'squashfs');
3391 95b003ff Origo
            my $mp = $fs->{mountpoint};
3392
            next if ($fs->{type} eq 'rom');
3393
            $filesystems{$fs->{name}} = {
3394
                name=>$fs->{name},
3395
                blocksize=>$fs->{size},
3396
                mountpoint=>$fs->{mountpoint},
3397
                type=>$fs->{type},
3398
                nametype=> "$fs->{name} ($fs->{type} - " . ($mp?$mp:"not mounted") . " $fs->{size})",
3399
            }
3400
        }
3401
    }
3402
3403
    # Identify physical devices used for lvm
3404
    $cmd = "pvdisplay -c";
3405
    my $pvs = `$cmd`;
3406
    my @backupdevs; my @imagesdevs;
3407
    foreach my $line (split "\n", $pvs) {
3408
        my ($pvdev, $vgname) = split ":", $line;
3409
        $pvdev = $1 if ($pvdev =~ /\s+(\S+)/);
3410
        $pvdev = $1 if ($pvdev =~ /\/dev\/(\S+)/);
3411
        if ($filesystems{"$vgname-backupvol"}) {
3412
            push @backupdevs, $pvdev unless ($action eq 'listimagesdevices');
3413
        } elsif ($filesystems{"$vgname-imagesvol"}) {
3414
            push @imagesdevs, $pvdev unless ($action eq 'listbackupdevices');
3415
        }
3416
        if (@backupdevs) {
3417
            $filesystems{"$vgname-backupvol"}->{dev} = join(" ", @backupdevs);
3418
            $filesystems{"$vgname-backupvol"}->{nametype} = $filesystems{"$vgname-backupvol"}->{name} . " (lvm with " . $filesystems{"$vgname-backupvol"}->{type} . " on " . join(" ", @backupdevs) . " " . $filesystems{"$vgname-backupvol"}->{size} . ")";
3419
        }
3420
        if (@imagesdevs) {
3421
            $filesystems{"$vgname-imagesvol"}->{dev} = join(" ", @imagesdevs);
3422
            $filesystems{"$vgname-imagesvol"}->{nametype} = $filesystems{"$vgname-imagesvol"}->{name} . " (lvm with " . $filesystems{"$vgname-imagesvol"}->{type} . " on " . join(" ", @imagesdevs) . " " . $filesystems{"$vgname-imagesvol"}->{size} . ")";
3423
        }
3424
        delete $filesystems{$pvdev} if ($filesystems{$pvdev}); # Don't also list as physical device
3425
    }
3426
    my $jsonreply;
3427
    if ($action eq 'getbackupdevice' || $action eq 'getimagesdevice') {
3428
        return ''; # We should not get here
3429 2a63870a Christian Orellana
    } elsif ($action eq 'getstoragedevices') {
3430
        return \%filesystems;
3431 95b003ff Origo
    } elsif ($action eq 'listimagesdevices') {
3432
        $jsonreply .= qq|{"identifier": "name", "label": "nametype", "action": "$action", "items": |;
3433
        my @vals = sort {$b->{'isimagesdev'} cmp $a->{'isimagesdev'}} values %filesystems;
3434
        $jsonreply .= JSON->new->canonical(1)->pretty(1)->encode(\@vals);
3435
        $jsonreply .= "}";
3436
    } elsif ($action eq 'listbackupdevices') {
3437
        $jsonreply .= qq|{"identifier": "name", "label": "nametype", "action": "$action", "items": |;
3438
        my @vals = sort {$b->{'isbackupdev'} cmp $a->{'isbackupdev'}} values %filesystems;
3439
        $jsonreply .= JSON->new->canonical(1)->pretty(1)->encode(\@vals);
3440
        $jsonreply .= "}";
3441
    } else {
3442
        $jsonreply .= JSON->new->canonical(1)->pretty(1)->encode(\%filesystems);
3443
    }
3444
    return $jsonreply;
3445
}
3446
3447
sub do_liststoragepools {
3448
    my ($image, $action) = @_;
3449
    if ($help) {
3450
        return <<END
3451
GET:dojo:
3452
Returns available storage pools. If parameter dojo is set, JSON is padded for Dojo use.
3453
END
3454
    }
3455
    my %npool = (
3456
        "hostpath", "node",
3457
        "path", "--",
3458
        "name", "On node",
3459
        "rdiffenabled", 1,
3460
        "id", "-1");
3461
    my @p = @spools;
3462
    # Present node storage pool if user has sufficient privileges
3463
    if (index($privileges,"a")!=-1 || index($privileges,"n")!=-1) {
3464
        @p = (\%npool);
3465
        push @p, @spools;
3466
    }
3467
3468
    my $jsonreply;
3469
    $jsonreply .= "{\"identifier\": \"id\", \"label\": \"name\", \"items\":" if ($params{'dojo'});
3470
    $jsonreply .= to_json(\@p, {pretty=>1});
3471
    $jsonreply .= "}" if ($params{'dojo'});
3472
    return $jsonreply;
3473
}
3474
3475
# List images available for attaching to server
3476
sub do_listimages {
3477
    my ($img, $action) = @_;
3478
    if ($help) {
3479
        return <<END
3480
GET:image,image1:
3481
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.
3482
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.
3483
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".
3484
END
3485
    }
3486
    my $res;
3487
    $res .= header('application/json') unless ($console);
3488
    my $curimg1 = URI::Escape::uri_unescape($params{'image1'});
3489
    my @filteredfiles;
3490
    my @curusers = @users;
3491
    # If an admin user is looking at a server not belonging to him, allow him to see the server
3492
    # users images
3493
    if ($isadmin && $img && $img ne '--' && $register{$img} && $register{$img}->{'user'} ne $user) {
3494
        @curusers = ($register{$img}->{'user'}, "common");
3495
    }
3496
3497
    foreach my $u (@curusers) {
3498
        my @regkeys = (tied %register)->select_where("user = '$u'");
3499
        foreach my $k (@regkeys) {
3500
            my $val = $register{$k};
3501
            if ($val->{'user'} eq $u && (defined $spools[$val->{'storagepool'}]->{'id'} || $val->{'storagepool'}==-1)) {
3502
                my $f = $val->{'path'};
3503
                next if ($f =~ /\/images\/dummy.qcow2/);
3504
                my $itype = $val->{'type'};
3505 04c16f26 hq
                if ($itype eq "vmdk" || $itype eq "img" || $itype eq "vhd" || $itype eq "vhdx" || $itype eq "qcow" || $itype eq "qcow2" || $itype eq "vdi") {
3506 95b003ff Origo
                    my $hit = 0;
3507
                    if ($f =~ /(.+)\.master\.$itype/) {$hit = 1;} # don't list master images for user selections
3508
                    if ($f =~ /(.+)\/common\//) {$hit = 1;} # don't list common images for user selections
3509
                    my $dbstatus = $val->{'status'};
3510
                    if ($dbstatus ne "unused") {$hit = 1;} # Image is in a transitional state - do not use
3511
                    if ($hit == 0 || $img eq $f) {
3512 04c16f26 hq
                        my $hypervisor = ($itype eq "vmdk" || $itype eq "vhd" || $itype eq "vhdx" || $itype eq "vdi")?"vbox":"kvm";
3513 95b003ff Origo
                        my $notes = $val->{'notes'};
3514
                        $notes = "" if $notes eq "--";
3515
                        my %img = ("path", $f, "name", $val->{'name'}, "hypervisor", $hypervisor, "notes", $notes,
3516
                            "uuid", $val->{'uuid'}, "master", $val->{'master'}, "managementlink", $val->{'managementlink'}||"",
3517
                            "upgradelink", $val->{'upgradelink'}||"", "terminallink", $val->{'terminallink'}||"", "version", $val->{'version'}||"",
3518
                            "appid", $val->{'appid'}||"");
3519
                        push @filteredfiles, \%img;
3520
                    }
3521
                }
3522
            }
3523
        }
3524
    }
3525
    my %img = ("path", "--", "name", "--", "hypervisor", "kvm,vbox");
3526
    if ($curimg1) {
3527
        push @filteredfiles, \%img;
3528
    }
3529
    my $json_text = to_json(\@filteredfiles, {pretty=>1});
3530
    $res .= qq/{"identifier": "path", "label": "name", "items": $json_text }/;
3531
    return $res;
3532
}
3533
3534
sub Listcdroms {
3535
    my ($image, $action) = @_;
3536
    if ($help) {
3537
        return <<END
3538
GET::
3539
Lists the CD roms a user has access to.
3540
END
3541
    }
3542
    my $res;
3543
    $res .= header('application/json') unless ($console);
3544
    my @filteredfiles;
3545
    foreach my $u (@users) {
3546
        my @regkeys = (tied %register)->select_where("user = '$u'");
3547
        foreach my $k (@regkeys) {
3548
            my $val = $register{$k};
3549
            my $f = $val->{'path'};
3550
            if ($val->{'user'} eq $u && (defined $spools[$val->{'storagepool'}]->{'id'} || $val->{'storagepool'}==-1)) {
3551
                my $itype = $val->{'type'};
3552
                if ($itype eq "iso" || $itype eq "toast") {
3553
                    $notes = $val->{'notes'} || '';
3554
                    if ($u eq $user) {
3555
                        $installable = "true";
3556
                    #    $notes = "This CD/DVD may work just fine, however it has not been tested to work with Irigo Servers.";
3557
                    } else {
3558
                        $installable = $val->{'installable'} || 'false';
3559
                    #    $notes = "This CD/DVD has been tested to work with Irigo Servers." unless $notes;
3560
                    }
3561
                    my %img = ("path", $f, "name", $val->{'name'}, "installable", $installable, "notes", $notes);
3562
                    push @filteredfiles, \%img;
3563
                }
3564
            }
3565
        }
3566
    }
3567
    my %ioimg = ("path", "virtio", "name", "-- VirtIO disk (dummy) --");
3568
    push @filteredfiles, \%ioimg;
3569
    my %dummyimg = ("path", "--", "name", "-- No CD --");
3570
    push @filteredfiles, \%dummyimg;
3571
    #        @filteredfiles = (sort {$a->{'name'} cmp $b->{'name'}} @filteredfiles); # Sort by status
3572
    my $json_text = to_json(\@filteredfiles, {pretty=>1});
3573
    $res .= qq/{"identifier": "path", "label": "name", "items": $json_text }/;
3574
    return $res;
3575
}
3576
3577
sub do_listmasterimages {
3578 f222b89c hq
    my ($image, $action, $obj) = @_;
3579 95b003ff Origo
    if ($help) {
3580
        return <<END
3581
GET::
3582
Lists master images available to the current user.
3583
END
3584
    }
3585
    my $res;
3586
    $res .= header('application/json') unless ($console);
3587
3588
    my @filteredfiles;
3589
    my @busers = @users;
3590 04c16f26 hq
    push (@busers, $billto) if ($billto && $billto ne $user); # We include images from 'parent' user
3591 95b003ff Origo
3592
    foreach my $u (@busers) {
3593
        my @regkeys = (tied %register)->select_where("user = '$u'");
3594
        foreach my $k (@regkeys) {
3595
            my $valref = $register{$k};
3596
            my $f = $valref->{'path'};
3597
            if ($valref->{'user'} eq $u && (defined $spools[$valref->{'storagepool'}]->{'id'} || $valref->{'storagepool'}==-1)) {
3598
                # Only list installable master images from billto account
3599
                next if ($billto && $u eq $billto && $valref->{'installable'} ne 'true');
3600
3601
                my $itype = $valref->{'type'};
3602
                if ($itype eq "qcow2" && $f =~ /(.+)\.master\.$itype/) {
3603
                    my $installable;
3604
                    my $status = $valref->{'status'};
3605
                    my $notes;
3606
                    if ($u eq $user) {
3607
                        $installable = "true";
3608
                        $notes = "This master image may work just fine, however it has not been tested to work with Stabile.";
3609
                    } else {
3610 a91e0e6e hq
                        $installable = $valref->{'installable'} || '';
3611 95b003ff Origo
                        $notes = $valref->{'notes'};
3612 f222b89c hq
                        $notes = "This master image has been tested to work with Stabile." unless $notes;
3613 95b003ff Origo
                    }
3614
                    my %img = (
3615
                        "path", $f,
3616
                        "name", $valref->{'name'},
3617
                        "installable", $installable,
3618
                        "notes", $notes,
3619
                        "managementlink", $valref->{'managementlink'}||"",
3620
                        "upgradelink", $valref->{'upgradelink'}||"",
3621
                        "terminallink", $valref->{'terminallink'}||"",
3622
                        "image2", $valref->{'image2'}||"",
3623
                        "version", $valref->{'version'}||"",
3624
                        "appid", $valref->{'appid'}||"",
3625
                        "status", $status,
3626
                        "user", $valref->{'user'}
3627
                    );
3628
                    push @filteredfiles, \%img;
3629
                }
3630
            }
3631
        }
3632
    }
3633
    my %img = ("path", "--", "name", "--", "installable", "true", "status", "unused");
3634
    push @filteredfiles, \%img;
3635 f222b89c hq
    if ($obj->{raw}) {
3636
        return \@filteredfiles;
3637
    } else {
3638
        my $json_text = JSON::to_json(\@filteredfiles);
3639
        $res .= qq/{"identifier": "path", "label": "name", "items": $json_text }/;
3640
        return $res;
3641
    }
3642 95b003ff Origo
}
3643
3644
sub Updatebtime {
3645
    my ($img, $action, $obj) = @_;
3646
    if ($help) {
3647
        return <<END
3648
GET:image:
3649
END
3650
    }
3651
    my $res;
3652
    $curimg = $curimg || $img;
3653
    my $imguser = $register{$curimg}->{'user'};
3654
    if ($isadmin || $imguser eq $user) {
3655
        my $btime;
3656
        $btime = getBtime($curimg, $imguser) if ($imguser);
3657
        if ($btime) {
3658
            $register{$curimg}->{'btime'} = $btime ;
3659
            $res .= "Status=OK $curimg has btime: " . scalar localtime( $btime ) . "\n";
3660
        } else {
3661 2a63870a Christian Orellana
            $register{$curimg}->{'btime'} = '' ;
3662 95b003ff Origo
            $res .= "Status=OK $curimg has no btime\n";
3663
        }
3664
    } else {
3665
        $res .= "Status=Error no access to $curimg\n";
3666
    }
3667
    return $res;
3668
}
3669
3670
sub Updateallbtimes {
3671
    my ($img, $action) = @_;
3672
    if ($help) {
3673
        return <<END
3674
GET::
3675
END
3676
    }
3677
    if ($isadmin) {
3678
        foreach my $path (keys %register) {
3679
            my $imguser = $register{$path}->{'user'};
3680
            my $btime = getBtime($path, $imguser);
3681
            if ($btime) {
3682
                $register{$path}->{'btime'} = $btime ;
3683
                $postreply .= "Status=OK $register{$path}->{'name'} ($path) has btime: " . scalar localtime( $btime ) . "\n";
3684
            } else {
3685
                $postreply .= "Status=OK $register{$path}->{'name'} ($path) has no btime\n";
3686
            }
3687
        }
3688
    } else {
3689
        $postreply .= "Status=ERROR you are not allowed to do this.\n";
3690
    }
3691
    return $postreply;
3692
}
3693
3694
# Activate image from fuel
3695
sub Activate {
3696
    my ($curimg, $action, $argref) = @_;
3697
    if ($help) {
3698
        return <<END
3699 48fcda6b Origo
GET:image, name, managementlink, upgradelink, terminallink, force:
3700
Activate an image from fuel storage, making it available for regular use.
3701 95b003ff Origo
END
3702
    }
3703
    my %uargs = %{$argref};
3704
    my $name = URI::Escape::uri_unescape($uargs{'name'});
3705
    my $managementlink = URI::Escape::uri_unescape($uargs{'managementlink'});
3706
    my $upgradelink = URI::Escape::uri_unescape($uargs{'upgradelink'});
3707
    my $terminallink = URI::Escape::uri_unescape($uargs{'terminallink'});
3708
    my $version = URI::Escape::uri_unescape($uargs{'version'}) || '1.0b';
3709
    my $image2 =  URI::Escape::uri_unescape($uargs{'image2'});
3710 48fcda6b Origo
    my $force = $uargs{'force'};
3711 95b003ff Origo
3712
    return "Status=ERROR image must be in fuel storage ($curimg)\n" unless ($curimg =~ /^\/mnt\/fuel\/pool(\d+)\/(.+)/);
3713
    my $pool = $1;
3714
    my $ipath = $2;
3715
    return "Status=ERROR image is not a qcow2 image ($curimg, $ipath)\n" unless ($ipath =~ /(.+\.qcow2$)/);
3716
    my $npath = $1;
3717
    my $ppath = '';
3718
    if ($npath =~ /(.*\/)(.+\.qcow2$)/) {
3719
        $npath = $2;
3720
        $ppath = $1;
3721
    }
3722
    my $imagepath = $tenderpathslist[$pool] . "/$user/fuel/$ipath";
3723
    my $newpath = $tenderpathslist[$pool] . "/$user/$npath";
3724
    return "Status=ERROR image not found ($imagepath)\n" unless (-e $imagepath);
3725 48fcda6b Origo
    return "Status=ERROR image already exists in destination ($newpath)\n" if (-e $newpath && !$force);
3726
    return "Status=ERROR image is in use ($newpath)\n" if (-e $newpath && $register{$newpath} && $register{$newpath}->{'status'} ne 'unused');
3727 95b003ff Origo
3728 3657de20 Origo
    my $virtualsize = `qemu-img info --force-share "$imagepath" | sed -n -e 's/^virtual size: .*(//p' | sed -n -e 's/ bytes)//p'`;
3729 95b003ff Origo
    chomp $virtualsize;
3730 991e7f1b hq
#    my $master = `qemu-img info --force-share "$imagepath" | sed -n -e 's/^backing file: //p' | sed -n -e 's/ (actual path:.*)\$//p'`;
3731
    my $master = `qemu-img info --force-share "$imagepath" | sed -n -e 's/^backing file: //p'`;
3732 95b003ff Origo
    chomp $master;
3733
3734
    # Now deal with image2
3735
    my $newpath2 = '';
3736
    if ($image2) {
3737
        $image2 = "/mnt/fuel/pool$pool/$ppath$image2" unless ($image2 =~ /^\//);
3738
        return "Status=ERROR image2 must be in fuel storage ($image2)\n" unless ($image2 =~ /^\/mnt\/fuel\/pool$pool\/(.+)/);
3739
        $ipath = $1;
3740
        return "Status=ERROR image is not a qcow2 image\n" unless ($ipath =~ /(.+\.qcow2$)/);
3741
        $npath = $1;
3742
        $npath = $1 if ($npath =~ /.*\/(.+\.qcow2$)/);
3743
        my $image2path = $tenderpathslist[$pool] . "/$user/fuel/$ipath";
3744
        $newpath2 = $tenderpathslist[$pool] . "/$user/$npath";
3745
        return "Status=ERROR image2 not found ($image2path)\n" unless (-e $image2path);
3746 48fcda6b Origo
        return "Status=ERROR image2 already exists in destination ($newpath2)\n" if (-e $newpath2 && !$force);
3747
        return "Status=ERROR image2 is in use ($newpath2)\n" if (-e $newpath2 && $register{$newpath2} && $register{$newpath2}->{'status'} ne 'unused');
3748 95b003ff Origo
3749 3657de20 Origo
        my $virtualsize2 = `qemu-img info --force-share "$image2path" | sed -n -e 's/^virtual size: .*(//p' | sed -n -e 's/ bytes)//p'`;
3750 95b003ff Origo
        chomp $virtualsize2;
3751 991e7f1b hq
#        my $master2 = `qemu-img info --force-share "$image2path" | sed -n -e 's/^backing file: //p' | sed -n -e 's/ (actual path:.*)\$//p'`;
3752
        my $master2 = `qemu-img info --force-share "$image2path" | sed -n -e 's/^backing file: //p'`;
3753 95b003ff Origo
        chomp $master2;
3754
        if ($register{$master2}) {
3755
            $register{$master2}->{'status'} = 'used';
3756
        }
3757
        `mv "$image2path" "$newpath2"`;
3758
        if (-e $newpath2) {
3759
            my $ug = new Data::UUID;
3760
            my $newuuid = $ug->create_str();
3761
            unless ($name) {
3762
                $name = $npath if ($npath);
3763
                $name = $1 if ($name =~ /(.+)\.(qcow2)$/);
3764
            }
3765
            $register{$newpath2} = {
3766
                uuid => $newuuid,
3767
                path => $newpath2,
3768
                master => $master2,
3769
                name => "$name (data)",
3770
                user => $user,
3771
                storagepool => $pool,
3772
                type => 'qcow2',
3773
                status => 'unused',
3774
                version => $version,
3775
                virtualsize => $virtualsize2
3776
            };
3777
            $postreply .= "Status=OK Activated data image $newpath2, $name (data), $newuuid\n";
3778
        } else {
3779
            $postreply .=  "Status=ERROR Unable to activate $image2path, $newpath2\n";
3780
        }
3781
    }
3782
3783
    # Finish up primary image
3784
    if ($register{$master}) {
3785
        $register{$master}->{'status'} = 'used';
3786
    }
3787
    `mv "$imagepath" "$newpath"`;
3788
    if (-e $newpath) {
3789
        my $ug = new Data::UUID;
3790
        my $newuuid = $ug->create_str();
3791
        unless ($name) {
3792
            $name = $npath if ($npath);
3793
            $name = $1 if ($name =~ /(.+)\.(qcow2)$/);
3794
        }
3795
        $register{$newpath} = {
3796
            uuid => $newuuid,
3797
            path => $newpath,
3798
            master => $master,
3799
            name => $name,
3800
            user => $user,
3801
            storagepool => $pool,
3802
            image2 => $newpath2,
3803
            type => 'qcow2',
3804
            status => 'unused',
3805
            installable => 'true',
3806 48fcda6b Origo
            managementlink => $managementlink || '/stabile/pipe/http://{uuid}:10000/stabile/',
3807 95b003ff Origo
            upgradelink => $upgradelink,
3808
            terminallink => $terminallink,
3809
            version => $version,
3810
            virtualsize => $virtualsize
3811
        };
3812
        $postreply .=  "Status=OK Activated $newpath, $name, $newuuid\n";
3813
    } else {
3814
        $postreply .=  "Status=ERROR Unable to activate $imagepath to $newpath\n";
3815
    }
3816
    return $postreply;
3817
}
3818
3819 2a63870a Christian Orellana
sub Uploadtoregistry {
3820
    my ($path, $action, $obj) = @_;
3821
    if ($help) {
3822
        return <<END
3823
GET:image, force:
3824
Upload an image to the registry. Set [force] if you want to force overwrite images in registry - use with caution.
3825
END
3826
    }
3827
    $force = $obj->{'force'};
3828
    if (-e $path && ($register{$path}->{'user'} eq $user || $isadmin)) {
3829
        $postreply .= $main::uploadToOrigo->($engineid, $path, $force);
3830
    } else {
3831
        $postreply .= "Status=Error Not allowed\n";
3832
    }
3833
    return $postreply;
3834
}
3835
3836 95b003ff Origo
sub Publish {
3837
    my ($uuid, $action, $parms) = @_;
3838
    if ($help) {
3839
        return <<END
3840 48fcda6b Origo
GET:image,appid,appstore,force:
3841
Publish a stack to registry. Set [force] if you want to force overwrite images in registry - use with caution.
3842 95b003ff Origo
END
3843
    }
3844
    my $res;
3845
    $uuid = $parms->{'uuid'} if ($uuid =~ /^\// || !$uuid);
3846 48fcda6b Origo
    my $force = $parms->{'force'};
3847 d24d9a01 hq
    my $freshen = $parms->{'freshen'};
3848 95b003ff Origo
3849
    if ($isreadonly) {
3850
        $res .= "Status=ERROR Your account does not have the necessary privilege.s\n";
3851
    } elsif (!$uuid || !$imagereg{$uuid}) {
3852
        $res .= "Status=ERROR At least specify activated master image uuid [uuid or path] to publish.\n";
3853
    } elsif ($imagereg{$uuid}->{'user'} ne $user && !$isadmin) {
3854
        $res .= "Status=ERROR Your account does not have the necessary privileges.\n";
3855
    } elsif ($imagereg{$uuid}->{'path'} =~ /.+\.master\.qcow2$/) {
3856
        if ($engineid eq $valve001id) { # On valve001 - check if meta file exists
3857
            if (-e $imagereg{$uuid}->{'path'} . ".meta") {
3858
                $res .= "On valve001. Found meta file $imagereg{$uuid}->{'path'}.meta\n";
3859
                my $appid = `cat $imagereg{$uuid}->{'path'}.meta | sed -n -e 's/^APPID=//p'`;
3860
                chomp $appid;
3861
                if ($appid) {
3862
                    $parms->{'appid'} = $appid;
3863
                    $register{$imagereg{$uuid}->{'path'}}->{'appid'} = $appid;
3864
                    tied(%register)->commit;
3865
                }
3866
            }
3867
        # On valve001 - move image to stacks
3868
            if ($imagereg{$uuid}->{'storagepool'} ne '0') {
3869
                $res .= "Status=OK Moving image: " . Move($imagereg{$uuid}->{'path'}, $user, 0) . "\n";
3870
            } else {
3871 48fcda6b Origo
                $res .= "Status=OK Image is already available in registry\n";
3872 95b003ff Origo
            }
3873
        } else {
3874 48fcda6b Origo
        #    $console = 1;
3875
        #    my $link = Download($imagereg{$uuid}->{'path'});
3876
        #    chomp $link;
3877
        #    $parms->{'downloadlink'} = $link; # We now upload instead
3878
        #    $res .= "Status=OK Asking registry to download $parms->{'APPID'} image: $link\n";
3879 95b003ff Origo
            if ($appstores) {
3880
                $parms->{'appstore'} = $appstores;
3881
            } elsif ($appstoreurl =~ /www\.(.+)\//) {
3882
                $parms->{'appstore'} = $1;
3883 48fcda6b Origo
                $res .= "Status=OK Adding registry: $1\n";
3884 95b003ff Origo
            }
3885
        }
3886 6fdc8676 hq
#        $parms->{'appstore'} = 1 if ($freshen);
3887 95b003ff Origo
3888
        my %imgref = %{$imagereg{$uuid}};
3889
        $parms = Hash::Merge::merge($parms, \%imgref);
3890
        my $postdata = to_json($parms);
3891
        my $postres = $main::postToOrigo->($engineid, 'publishapp', $postdata);
3892 48fcda6b Origo
        $res .= $postres;
3893 95b003ff Origo
        my $appid;
3894
        $appid = $1 if ($postres =~ /appid: (\d+)/);
3895
        my $path = $imagereg{$uuid}->{'path'};
3896 d24d9a01 hq
        if ($freshen && $appid) {
3897
            $res .= "Status=OK Freshened the stack description\n";
3898
        } elsif ($appid) {
3899 48fcda6b Origo
            $register{$path}->{'appid'} = $appid if ($register{$path});
3900
            $res .= "Status=OK Received appid $appid for $path, uploading image to registry, hang on...\n";
3901
            my $upres .= $main::uploadToOrigo->($engineid, $path, $force);
3902
            $res .= $upres;
3903
            my $image2 = $register{$path}->{'image2'} if ($register{$path});
3904
            if ($upres =~ /Status=OK/ && $image2 && $image2 ne '--') { # Stack has a data image
3905
                $res .= $main::uploadToOrigo->($engineid, $image2, $force);
3906
            }
3907
        } else {
3908
            $res .= "Status=Error Did not get an appid\n";
3909
        }
3910 95b003ff Origo
    } else {
3911
        $res .= "Status=ERROR You can only publish a master image.\n";
3912
    }
3913
    return $res;
3914
}
3915
3916 48fcda6b Origo
sub Release {
3917
    my ($uuid, $action, $parms) = @_;
3918
    if ($help) {
3919
        return <<END
3920
GET:image,appid,appstore,force,unrelease:
3921
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.
3922
END
3923
    }
3924
    my $res;
3925
    $uuid = $parms->{'uuid'} if ($uuid =~ /^\// || !$uuid);
3926
    my $force = $parms->{'force'};
3927
    my $unrelease = $parms->{'unrelease'};
3928
3929
    if (!$uuid || !$imagereg{$uuid}) {
3930
        $res .= "Status=ERROR At least specify master image uuid [uuid or path] to release.\n";
3931
    } elsif (!$isadmin) {
3932
        $res .= "Status=ERROR Your account does not have the necessary privileges.\n";
3933
    } elsif ($imagereg{$uuid}->{'path'} =~ /.+\.master\.qcow2$/ && $imagereg{$uuid}->{'appid'}) {
3934
        my $action = 'release';
3935
        my $targetuser = 'common';
3936
        if ($unrelease) {
3937
            $action = 'unrelease';
3938
            $targetuser = $user;
3939
        }
3940
        if ($appstores) {
3941
            $parms->{'appstore'} = $appstores;
3942
        } elsif ($appstoreurl =~ /www\.(.+)\//) {
3943
            $parms->{'appstore'} = $1;
3944
            $res .= "Status=OK Adding registry: $1\n";
3945
        }
3946
        $parms->{'appid'} = $imagereg{$uuid}->{'appid'};
3947
        $parms->{'force'} = $force if ($force);
3948
        $parms->{'unrelease'} = $unrelease if ($unrelease);
3949
        my $postdata = to_json($parms);
3950
        my $postres = $main::postToOrigo->($engineid, 'releaseapp', $postdata);
3951
        $res .= $postres;
3952
        my $appid;
3953
        $appid = $1 if ($postres =~ /Status=OK Moved (\d+)/);
3954
        my $path = $imagereg{$uuid}->{'path'};
3955
        if ($appid) {
3956
            $res.= "Now moving local stack to $targetuser\n";
3957
            # First move data image
3958
            my $image2 = $register{$path}->{'image2'} if ($register{$path});
3959
            my $newimage2 = $image2;
3960
            if ($image2 && $image2 ne '--' && $register{$image2}) { # Stack has a data image
3961
                if ($unrelease) {
3962
                    $newimage2 =~ s/common/$register{$image2}->{'user'}/;
3963
                } else {
3964
                    $newimage2 =~ s/$register{$image2}->{'user'}/common/;
3965
                }
3966
                $register{$path}->{'image2'} = $newimage2;
3967
                tied(%register)->commit;
3968
                $res .= Move($image2, $targetuser, '', '', 1);
3969
            }
3970
            # Move image
3971
            $res .= Move($path, $targetuser, '', '', 1);
3972
            $res .= "Status=OK $action $appid\n";
3973
        } else {
3974
            $res .= "Status=Error $action failed\n";
3975
        }
3976
    } else {
3977
        $res .= "Status=ERROR You can only $action a master image that has been published.\n";
3978
    }
3979
    return $res;
3980
}
3981
3982 95b003ff Origo
sub do_unlinkmaster {
3983
    my ($img, $action) = @_;
3984
    if ($help) {
3985
        return <<END
3986
GET:image,path:
3987
END
3988
    }
3989
    my $res;
3990
    $res .= header('text/html') unless ($console);
3991
    if ($isreadonly) {
3992
        $res .= "Your account does not have the necessary privileges\n";
3993
    } elsif ($curimg) {
3994
        $res .= unlinkMaster($curimg) . "\n";
3995
    } else {
3996
        $res .= "Please specify master image to link\n";
3997
    }
3998
    return $res;
3999
}
4000
4001
# Simple action for unmounting all images
4002
sub do_unmountall {
4003
    my ($img, $action) = @_;
4004
    if ($help) {
4005
        return <<END
4006
GET:image,path:
4007
END
4008
    }
4009
    return "Your account does not have the necessary privileges\n" if ($isreadonly);
4010
    my $res;
4011
    $res .= header('text/plain') unless ($console);
4012 27512919 Origo
    $res .= "Unmounting all images for $user\n";
4013 95b003ff Origo
    unmountAll();
4014
    $res .= "\n$postreply" if ($postreply);
4015
    return $res;
4016
}
4017
4018
sub Updatedownloads {
4019
    my ($img, $action) = @_;
4020
    if ($help) {
4021
        return <<END
4022
GET:image,path:
4023
END
4024
    }
4025
    my $res;
4026
    $res .= header('text/html') unless ($console);
4027
    my $txt1 = <<EOT
4028
Options -Indexes
4029
EOT
4030
    ;
4031
    `/bin/mkdir "$Stabile::basedir/download"` unless (-e "$Stabile::basedir/download");
4032
    $res .= "Writing .htaccess: -> $Stabile::basedir/download/.htaccess\n";
4033
    unlink("$Stabile::basedir/download/.htaccess");
4034
    `chown www-data:www-data "$Stabile::basedir/download"`;
4035
    `/bin/echo "$txt1" | sudo -u www-data tee "$Stabile::basedir/download/.htaccess"`; #This ugliness is needed because of ownership issues with Synology NFS
4036
    `chmod 644 "$Stabile::basedir/download/.htaccess"`;
4037
    foreach my $p (@spools) {
4038
        my $dir = $p->{'path'};
4039
        my $id = $p->{'id'};
4040
        `/bin/rm "$Stabile::basedir/download/$id"; /bin/ln -s "$dir" "$Stabile::basedir/download/$id"`;
4041
        $res .= "Writing .htaccess: $id -> $dir/.htaccess\n";
4042
        unlink("$dir/.htaccess");
4043
        `/bin/echo "$txt1" | tee "$dir/.htaccess"`;
4044
        `chown www-data:www-data "$dir/.htaccess"`;
4045
        `chmod 644 "$dir/.htaccess"`;
4046
    }
4047
4048
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4049
4050
    foreach my $username (keys %userreg) {
4051
        my $require = '';
4052
        my $txt = <<EOT
4053
order deny,allow
4054
AuthName "Download"
4055
AuthType None
4056
TKTAuthLoginURL $baseurl/auth/login.cgi
4057
TKTAuthIgnoreIP on
4058
deny from all
4059
Satisfy any
4060
require user $username
4061
Options -Indexes
4062
EOT
4063
        ;
4064
        foreach my $p (@spools) {
4065
            my $dir = $p->{'path'};
4066
            my $id = $p->{'id'};
4067
            if (-d "$dir/$username") {
4068
                $res .= "Writing .htaccess: $id -> $dir/$username/.htaccess\n";
4069
                unlink("$dir/$username/.htaccess");
4070
                `/bin/echo "$txt1" | sudo -u www-data tee $dir/$username/.htaccess`;
4071 3657de20 Origo
                if ($tenderlist[$p->{'id'}] eq 'local') {
4072 d24d9a01 hq
                    if (!(-e "$dir/$username/fuel") && -e "$dir/$username") {
4073 3657de20 Origo
                        `mkdir "$dir/$username/fuel"`;
4074
                        `chmod 777 "$dir/$username/fuel"`;
4075
                    }
4076
                }
4077 95b003ff Origo
            }
4078
        }
4079
    }
4080
    untie %userreg;
4081
    return $res;
4082
}
4083
4084
sub do_listpackages($action) {
4085
    my ($image, $action) = @_;
4086
    if ($help) {
4087
        return <<END
4088
GET:image:
4089
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.
4090
END
4091
    }
4092
    my $res;
4093
    $res .= header('text/plain') unless ($console);
4094
4095
    my $mac = $register{$image}->{'mac'};
4096
    my $macip;
4097
    if ($mac && $mac ne '--') {
4098
        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4099
        $macip = $nodereg{$mac}->{'ip'};
4100
        untie %nodereg;
4101
    }
4102
    $image =~ /(.+)/; $image = $1;
4103
    my $apps;
4104
4105
    if ($macip && $macip ne '--') {
4106
        my $cmd = qq[eval \$(/usr/bin/guestfish --ro -a "$image" --i --listen); ]; # sets $GUESTFISH_PID shell var
4107
        $cmd .= qq[root="\$(/usr/bin/guestfish --remote inspect-get-roots)"; ];
4108
        $cmd .= qq[guestfish --remote inspect-list-applications "\$root"; ];
4109
        $cmd .= qq[guestfish --remote inspect-get-product-name "\$root"; ];
4110
        $cmd .= qq[guestfish --remote exit];
4111
        $cmd = "$sshcmd $macip '$cmd'";
4112
        $apps = `$cmd`;
4113
    } else {
4114
        my $cmd;
4115
        #        my $pid = open my $cmdpipe, "-|",qq[/usr/bin/guestfish --ro -a "$image" --i --listen];
4116
        $cmd .= qq[eval \$(/usr/bin/guestfish --ro -a "$image" --i --listen); ];
4117
        # Start listening guestfish
4118
        my $daemon = Proc::Daemon->new(
4119
            work_dir => '/usr/local/bin',
4120
            setuid => 'www-data',
4121
            exec_command => $cmd
4122
        ) or do {$postreply .= "Status=ERROR $@\n";};
4123
        my $pid = $daemon->Init();
4124
        while ($daemon->Status($pid)) {
4125
            sleep 1;
4126
        }
4127
        # Find pid of the listening guestfish
4128
        my $pid2;
4129
        my $t = new Proc::ProcessTable;
4130
        foreach $p ( @{$t->table} ){
4131
            my $pcmd = $p->cmndline;
4132
            if ($pcmd =~ /guestfish.+$image/) {
4133
                $pid2 = $p->pid;
4134
                last;
4135
            }
4136
        }
4137
4138
        my $cmd2;
4139
        if ($pid2) {
4140
            $cmd2 .= qq[root="\$(/usr/bin/guestfish --remote=$pid2 inspect-get-roots)"; ];
4141
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-list-applications "\$root"; ];
4142
            $cmd2 .= qq[guestfish --remote=$pid2 inspect-get-product-name "\$root"; ];
4143
            $cmd2 .= qq[guestfish --remote=$pid2 exit];
4144
        }
4145
        $apps = `$cmd2`;
4146
    }
4147
    if ($console) {
4148
        $res .= $apps;
4149
    } else {
4150
        my @packages;
4151
        my @packages2;
4152
        open my $fh, '<', \$apps or die $!;
4153
        my $i;
4154
        while (<$fh>) {
4155
            if ($_ =~ /\[(\d+)\]/) {
4156
                push @packages2, $packages[$i];
4157
                $i = $1;
4158
            } elsif ($_ =~ /(\S+): (.+)/ && $2) {
4159
                $packages[$i]->{$1} = $2;
4160
            }
4161
        }
4162
        close $fh or die $!;
4163
        $res .= to_json(\@packages, {pretty => 1});
4164
    }
4165
    return $res;
4166
}
4167
4168
sub Inject {
4169
    my ($image, $action, $obj) = @_;
4170
    if ($help) {
4171
        return <<END
4172
GET:image:
4173
Tries to inject drivers into a qcow2 image with a Windows OS installed on it. Image must not be in use.
4174
END
4175
    }
4176
    $uistatus = "injecting";
4177
    my $path = $obj->{path} || $curimg;
4178
    my $status = $obj->{status};
4179
    my $esc_localpath = shell_esc_chars($path);
4180
4181
    # Find out if we are dealing with a Windows image
4182 04c16f26 hq
    # my $xml = `bash -c '/usr/bin/virt-inspector -a $esc_localpath'`;
4183
    my $xml = `bash -c '/usr/bin/virt-inspector -a $esc_localpath' 2>&1`;
4184 95b003ff Origo
    # $res .= $xml . "\n";
4185
    my $xmlref;
4186
    my $osname;
4187
    $xmlref = XMLin($xml) if ($xml =~ /^<\?xml/);
4188
    $osname = $xmlref->{operatingsystem}->{name} if ($xmlref);
4189
    if ($xmlref && $osname eq 'windows') {
4190 04c16f26 hq
    #    my $upath = $esc_localpath;
4191
        my $upath = $path;
4192 95b003ff Origo
        # We need write privileges
4193
        $res .= `chmod 666 "$upath"`;
4194
        # First try to merge storage registry keys into Windows registry. If not a windows vm it simply fails.
4195
        $res .= `bash -c 'cat /usr/share/stabile/mergeide.reg | /usr/bin/virt-win-reg --merge "$upath"' 2>&1`;
4196
        # Then try to merge the critical device keys. This has been removed in win8 and 2012, so will simply fail for these.
4197
        $res .= `bash -c 'cat /usr/share/stabile/mergeide-CDDB.reg | /usr/bin/virt-win-reg --merge "$upath"' 2>&1`;
4198 04c16f26 hq
        if ($res) { $main::syslogit->($user, "info", $res); $res = ''; }
4199 95b003ff Origo
4200
        # Try to copy viostor.sys into image
4201
        my @winpaths = (
4202
            '/Windows/System32/drivers',
4203 04c16f26 hq
            '/WINDOWS/system32/drivers',
4204
            '/WINDOWS/System32/drivers',
4205
            '/WINNT/system32/drivers'
4206 95b003ff Origo
        );
4207
        foreach my $winpath (@winpaths) {
4208
            my $lscmd = qq|bash -c 'virt-ls -a "$upath" "$winpath"'|;
4209
            my $drivers = `$lscmd`;
4210
            if ($drivers =~ /viostor/i) {
4211 04c16f26 hq
                $postreply .= "Status=$status viostor already installed in $winpath in $upath\n";
4212 95b003ff Origo
                $main::syslogit->($user, "info", "viostor already installed in $winpath in $upath");
4213
                last;
4214
            } elsif ($drivers) {
4215 04c16f26 hq
                `umount "$upath"`; # Unmount if mounted by browse operation or similar
4216
                my $cmd = qq|bash -c 'guestfish --rw -i -a "$upath" upload /usr/share/stabile/VIOSTOR.SYS $winpath/viostor.sys' 2>&1|;
4217 95b003ff Origo
                my $error = `$cmd`;
4218
                if ($error) {
4219 04c16f26 hq
                    $postreply .= "$cmd\n";
4220
                    $postreply .= "Status=ERROR Problem injecting virtio drivers into $winpath on $upath: $error\n";
4221 95b003ff Origo
                    $main::syslogit->($user, "info", "Error injecting virtio drivers into $upath: $error");
4222
                } else {
4223 04c16f26 hq
                    $postreply .= "Status=$status Injected virtio drivers into $upath\n";
4224 95b003ff Origo
                    $main::syslogit->($user, "info", "Injected virtio drivers into $upath");
4225
                }
4226
                last;
4227
            } else {
4228
                $postreply .= "Status=ERROR No drivers found in $winpath\n";
4229
            }
4230
        }
4231
4232
    } else {
4233
        $postreply .= "Status=ERROR No Windows OS found in $osname image, not injecting drivers.\n";
4234
        $main::syslogit->($user, "info", "No Windows OS found ($osname) in image, not injecting drivers.");
4235
    }
4236
    my $msg = $postreply;
4237
    $msg = $1 if ($msg =~ /\w+=\w+ (.+)/);
4238
    chomp $msg;
4239
    $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status, message=>$msg});
4240 04c16f26 hq
    $postreply .=  "Status=$uistatus $obj->{type} image: $obj->{name}\n";
4241 95b003ff Origo
    $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4242
    return $postreply;
4243
}
4244
4245
sub Convert {
4246
    my ($image, $action, $obj) = @_;
4247
    if ($help) {
4248
        return <<END
4249
GET:image:
4250
Converts an image to qcow2 format. Image must not be in use.
4251
END
4252
    }
4253
    my $path = $obj->{path};
4254
    $uistatus = "converting";
4255
    $uipath = $path;
4256
    if ($obj->{status} ne "unused" && $obj->{status} ne "used" && $obj->{status} ne "paused") {
4257
        $postreply .= "Status=ERROR Problem $uistatus $obj->{type} image: $obj->{name}\n";
4258 04c16f26 hq
    } elsif ($obj->{type} eq "img" || $obj->{type} eq "vmdk" || $obj->{type} eq "vhd" || $obj->{type} eq "vhdx") {
4259 95b003ff Origo
        my $oldpath = $path;
4260
        my $newpath = "$path.qcow2";
4261
        if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
4262
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4263
            $macip = $nodereg{$obj->{mac}}->{'ip'};
4264
            untie %nodereg;
4265
            $oldpath = "$macip:$path";
4266
        } else { # We are not on a node - check that image is not on a read-only filesystem
4267
            my ($fname, $destfolder) = fileparse($path);
4268
            my $ro = `touch "$destfolder/test.tmp" && { rm "$destfolder/test.tmp"; } || echo "read-only" 2>/dev/null`;
4269
            if ($ro) { # Destinationfolder is not writable
4270
                my $npath = "$spools[0]->{'path'}/$register{$path}->{'user'}/$fname.qcow2";
4271
                $newpath = $npath;
4272
            }
4273
            if (-e $newpath) { # Don't overwrite existing file
4274
                my $subpath = substr($newpath,0,-6);
4275
                my $i = 1;
4276
                if ($newpath =~ /(.+)\.(\d+)\.qcow2/) {
4277
                    $i = $2;
4278
                    $subpath = $1;
4279
                }
4280
                while (-e $newpath) {
4281
                    $newpath = $subpath . ".$i.qcow2";
4282
                    $i++;
4283
                }
4284
            }
4285
        }
4286
        eval {
4287
            my $ug = new Data::UUID;
4288
            my $newuuid = $ug->create_str();
4289
4290
            $register{$newpath} = {
4291
                uuid=>$newuuid,
4292
                name=>"$obj->{name} (converted)",
4293
                notes=>$obj->{notes},
4294
                image2=>$obj->{image2},
4295
                managementlink=>$obj->{managementlink},
4296
                upgradelink=>$obj->{managementlink},
4297
                terminallink=>$obj->{terminallink},
4298
                storagepool=>$obj->{regstoragepool},
4299
                status=>$uistatus,
4300
                mac=>($obj->{regstoragepool} == -1)?$obj->{mac}:"",
4301
                size=>0,
4302
                realsize=>0,
4303
                virtualsize=>$obj->{virtualsize},
4304
                type=>"qcow2",
4305
                user=>$user
4306
            };
4307
            $register{$path}->{'status'} = $uistatus;
4308
4309
            my $daemon = Proc::Daemon->new(
4310
                work_dir => '/usr/local/bin',
4311
                exec_command => "perl -U steamExec $user $uistatus $obj->{status} \"$oldpath\" \"$newpath\""
4312
            ) or do {$postreply .= "Status=ERROR $@\n";};
4313
            my $pid = $daemon->Init() or do {$postreply .= "Status=ERROR $@\n";};
4314
            $postreply .=  "Status=OK $uistatus $obj->{type} image: $obj->{name}\n";
4315
            $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4316
        } or do {$postreply .= "Status=ERROR $@\n";};
4317
        $main::updateUI->({tab=>"images", user=>$user, type=>"update"});
4318
    } else {
4319
        $postreply .= "Status=ERROR Only img and vmdk images can be converted\n";
4320
    }
4321 2a63870a Christian Orellana
    return $postreply;
4322 95b003ff Origo
}
4323
4324
sub Snapshot {
4325
    my ($image, $action, $obj) = @_;
4326
    if ($help) {
4327
        return <<END
4328
GET:image:
4329
Adds a snapshot to a qcow2 image. Image can not be in use by a running server.
4330
END
4331
    }
4332
    my $status = $obj->{status};
4333
    my $path = $obj->{path};
4334
    my $macip;
4335
    $uistatus = "snapshotting";
4336
    $uiuuid = $obj->{uuid};
4337
    if ($status ne "unused" && $status ne "used") {
4338
        $postreply .= "Status=ERROR Problem $uistatus $obj->{type} image: $obj->{name}\n";
4339
    } elsif ($obj->{type} eq "qcow2") {
4340
        my $newpath = $path;
4341
        my $hassnap;
4342
        my $snaptime = time;
4343
        if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
4344
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4345
            $macip = $nodereg{$obj->{mac}}->{'ip'};
4346
            untie %nodereg;
4347
            $newpath = "$macip:$path";
4348
            my $esc_path = $path;
4349
            $esc_path =~ s/([ ])/\\$1/g;
4350
            my $qinfo = `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -l $esc_path"`;
4351
            $hassnap = ($qinfo =~ /snap1/g);
4352
            $postreply .= `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -d snap1 $esc_path"` if ($hassnap);
4353
        } else {
4354
            my $qinfo = `/usr/bin/qemu-img snapshot -l "$path"`;
4355
            $hassnap = ($qinfo =~ /snap1/g);
4356
            $postreply .= `/usr/bin/qemu-img snapshot -d snap1 "$path\n"` if ($hassnap);
4357
        }
4358
        eval {
4359
            if ($hassnap) {
4360
                $postreply .= "Status=Error Only one snapshot per image is supported for $obj->{type} image: $obj->{name} ";
4361
            } else {
4362
                $register{$path}->{'status'} = $uistatus;
4363
                $register{$path}->{'snap1'} = $snaptime;
4364
4365
                if ($macip) {
4366
                    my $esc_localpath = shell_esc_chars($path);
4367
                    $res .= `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -c snap1 $esc_localpath"`;
4368
                } else {
4369
                    $res .= `/usr/bin/qemu-img snapshot -c snap1 "$path"`;
4370
                }
4371
                $register{$path}->{'status'} = $status;
4372
                $postreply .=  "Status=$uistatus OK $uistatus $obj->{type} image: $obj->{name}\n";
4373
                $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4374
            }
4375
            1;
4376
        } or do {$postreply .= "Status=ERROR $@\n";};
4377
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status, snap1=>$snaptime});
4378
    } else {
4379
        $postreply .= "Status=ERROR Only qcow2 images can be snapshotted\n";
4380
    }
4381
    return $postreply;
4382
}
4383
4384
sub Unsnap {
4385
    my ($image, $action, $obj) = @_;
4386
    if ($help) {
4387
        return <<END
4388
GET:image:
4389
Removes a snapshot from a qcow2 image. Image can not be in use by a running server.
4390
END
4391
    }
4392
    my $status = $obj->{status};
4393
    my $path = $obj->{path};
4394
    $uistatus = "unsnapping";
4395
    $uiuuid = $obj->{uuid};
4396
    my $macip;
4397
4398
    if ($status ne "unused" && $status ne "used") {
4399
        $postreply .= "Status=ERROR Problem $uistatus $obj->{type} image: $obj->{name}\n";
4400
    } elsif ($obj->{type} eq "qcow2") {
4401
        my $newpath = $path;
4402
        my $hassnap;
4403
        my $qinfo;
4404
        my $esc_path;
4405
        if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
4406
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4407
            $macip = $nodereg{$obj->{mac}}->{'ip'};
4408
            untie %nodereg;
4409
            $newpath = "$macip:$path";
4410
            $esc_path = $path;
4411
            $esc_path =~ s/([ ])/\\$1/g;
4412
            $qinfo = `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -l $esc_path"`;
4413
            $hassnap = ($qinfo =~ /snap1/g);
4414
        } else {
4415
            $qinfo = `/usr/bin/qemu-img snapshot -l "$path"`;
4416
            $hassnap = ($qinfo =~ /snap1/g);
4417
        }
4418
        eval {
4419
            my $snaptime = time;
4420
            if ($hassnap) {
4421
                delete $register{$path}->{'snap1'};
4422
                $register{$path}->{'status'} = $uistatus;
4423
                if ($macip) {
4424
                    my $esc_localpath = shell_esc_chars($path);
4425
                    $res .= `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -d snap1 $esc_localpath"`;
4426
                } else {
4427
                    $res .= `/usr/bin/qemu-img snapshot -d snap1 "$path"`;
4428
                }
4429
                $register{$path}->{'status'} = $status;
4430
                $postreply .=  "Status=$uistatus OK $uistatus $obj->{type} image: $obj->{name}\n";
4431
                $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4432
            } else {
4433
                $postreply .= "Status=ERROR No snapshot found in $obj->{name}\n";
4434
                delete $register{$path}->{'snap1'};
4435
                $uistatus = $status;
4436
            }
4437
            1;
4438
        } or do {$postreply .= "Status=ERROR $@\n";};
4439
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status, snap1=>'--'});
4440
    } else {
4441
        $postreply .= "Status=ERROR Only qcow2 images can be unsnapped\n";
4442
    }
4443
    return $postreply;
4444
}
4445
4446
sub Revert {
4447
    my ($image, $action, $obj) = @_;
4448
    if ($help) {
4449
        return <<END
4450
GET:image:
4451
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.
4452
END
4453
    }
4454
    my $status = $obj->{status};
4455
    my $path = $obj->{path};
4456
    $uistatus = "reverting";
4457
    $uipath = $path;
4458
    my $macip;
4459
    if ($status ne "used" && $status ne "unused") {
4460
        $postreply .= "Status=ERROR Please shut down or pause your virtual machine before reverting\n";
4461
    } elsif ($obj->{type} eq "qcow2") {
4462
        my $newpath = $path;
4463
        my $hassnap;
4464
        if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
4465
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4466
            $macip = $nodereg{$obj->{mac}}->{'ip'};
4467
            untie %nodereg;
4468
            $newpath = "$macip:$path";
4469
            my $esc_path = $path;
4470
            $esc_path =~ s/([ ])/\\$1/g;
4471
            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"`;
4472
            $hassnap = ($qinfo =~ /snap1/g);
4473
        } else {
4474
            my $qinfo = `/usr/bin/qemu-img snapshot -l "$path"`;
4475
            $hassnap = ($qinfo =~ /snap1/g);
4476
        }
4477
        eval {
4478
            if ($hassnap) {
4479
                $register{$path}->{'status'} = $uistatus;
4480
                if ($macip) {
4481
                    my $esc_localpath = shell_esc_chars($path);
4482
                    $res .= `$sshcmd $macip "sudo /usr/bin/qemu-img snapshot -a snap1 $esc_localpath"`;
4483
                } else {
4484 80e0b3f5 hq
                    $res .= `/usr/bin/qemu-img snapshot -a snap1 "$path"`;
4485 95b003ff Origo
                }
4486
                $register{$path}->{'status'} = $status;
4487
                $postreply .=  "Status=OK $uistatus $obj->{type} image: $obj->{name}\n";
4488
                $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4489
            } else {
4490
                $postreply .= "Status=ERROR no snapshot found\n";
4491
                $uistatus = $status;
4492
            }
4493
            1;
4494
        } or do {$postreply .= "Status=ERROR $@\n";};
4495
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status, snap1=>'--'});
4496
    } else {
4497
        $postreply .= "Status=ERROR Only qcow2 images can be reverted\n";
4498
    }
4499
    return;
4500
}
4501
4502
sub Zbackup {
4503
    my ($image, $action, $obj) = @_;
4504
    if ($help) {
4505
        return <<END
4506 c899e439 Origo
GET:mac, storagepool, synconly, snaponly, imageretention, backupretention:
4507 95b003ff Origo
Backs all images on ZFS storage up by taking a storage snapshot. By default all shared storagepools are backed up.
4508
If storagepool -1 is specified, all ZFS node storages is backed up. If "mac" is specified, only specific node is backed up.
4509
If "synconly" is set, no new snapshots are taken - only syncing of snapshots is performed.
4510
If "snaponly" is set, only local active storage snapshot is taken - no sending to backup storage is done.
4511
"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],
4512
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.
4513
END
4514
    }
4515
    if ($isadmin) {
4516
        my $synconly = $obj->{'synconly'};
4517
        my $snaponly = $obj->{'snaponly'};
4518
        my $mac = $obj->{'mac'};
4519
        my $storagepool = $obj->{'storagepool'};
4520
        $storagepool = -1 if ($mac);
4521
        my $imageretention = $obj->{'imageretention'} || $imageretention;
4522
        my $backupretention = $obj->{'backupretention'} || $backupretention;
4523
4524
        my $basepath = "stabile-backup";
4525
        my $bpath = $basepath;
4526
        my $mounts = `/bin/cat /proc/mounts`;
4527
        my $zbackupavailable = (($mounts =~ /$bpath (\S+) zfs/)?$1:'');
4528
        unless ($zbackupavailable) {$postreply .= "Status=OK ZFS backup not available, only doing local snapshots\n";}
4529
        my $zfscmd = "zfs";
4530
        my $macip;
4531
        my $ipath = $spools[0]->{'zfs'} || 'stabile-images/images';
4532
        my @nspools = @spools;
4533
        if (!(defined $obj->{'storagepool'}) || $storagepool == -1) {
4534
            @nspools = () if ($storagepool == -1); # Only do node backups
4535
            unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
4536 27512919 Origo
#            my $nipath = $ipath;
4537
#            $nipath = "$1/node" if ($nipath =~ /(.+)\/(.+)/);
4538
            my $nipath = 'stabile-node/node';
4539 95b003ff Origo
            foreach my $node (values %nodereg) {
4540
                push @nspools, {
4541
                    mac=>$node->{'mac'},
4542
                    macip=>$node->{'ip'},
4543
                    zfs=>$nipath,
4544
                    id=>-1
4545
                } if ($node->{'stor'} eq 'zfs' && (!$mac || $node->{'mac'} eq $mac))
4546
            }
4547
            untie %nodereg;
4548
        }
4549
        if (`pgrep zfs`) {
4550
            $postreply .= "Status=ERROR Another ZFS backup is running. Please wait a minute...\n";
4551
            $postmsg = "ERROR ERROR Another ZFS backup is running. Please wait a minute...";
4552
            return $postreply;
4553
        }
4554
        $postreply .= "Status=OK Performing ZFS backup on " . (scalar @nspools) . " storage pools with image retention $imageretention, backup retention $backupretention\n";
4555
4556
        foreach my $spool (@nspools) {
4557
            $ipath = $spool->{'zfs'};
4558
            if ($spool->{'id'} == -1) { # We're doing a node backup
4559
                $mac = $spool->{'mac'};
4560
                $macip = $spool->{'macip'};
4561
                $bpath = "$basepath/node-$mac";
4562
            } else {
4563
                next unless ($ipath);
4564
                next if (($storagepool || $storagepool eq '0') && $storagepool ne $spool->{'id'});
4565
                $bpath = "$basepath/$1" if ($ipath =~ /.+\/(.+)/);
4566
                $mac = '';
4567
                $macip = '';
4568
            }
4569 27512919 Origo
            if ($macip) {$zfscmd = "$sshcmd $macip sudo zfs";}
4570 95b003ff Origo
            else {$zfscmd = "zfs";}
4571
4572 04c16f26 hq
            $postreply .= "Status=OK Commencing ZFS backup of $ipath $macip, storagepool=$storagepool, synconly=$synconly, snaponly=$snaponly\n";
4573 95b003ff Origo
            my $res;
4574
            my $cmd;
4575
            my @imagesnaps;
4576
            my @backupsnaps;
4577
4578
            # example: stabile-images/images@SNAPSHOT-20200524172901
4579
            $cmd = qq/$zfscmd list -t snapshot | grep '$ipath'/;
4580
            my $snaplist = `$cmd`;
4581
            my @snaplines = split("\n", $snaplist);
4582
            foreach my $snap (@snaplines) {
4583
                push @imagesnaps, $2 if ($snap =~ /(.*)\@SNAPSHOT-(\d+)/);
4584
            }
4585
            if ($zbackupavailable) {
4586
                $cmd = qq/zfs list -t snapshot | grep '$bpath'/;
4587
                $snaplist = `$cmd`;
4588
                @snaplines = split("\n", $snaplist);
4589
                foreach my $snap (@snaplines) {
4590
                    push @backupsnaps, $2 if ($snap =~ /(.*)\@SNAPSHOT-(\d+)/);
4591
                }
4592
            }
4593
        # Find matching snapshots
4594
            my $matches=0;
4595
            my $matchbase = 0;
4596
            foreach my $bsnap (@backupsnaps) {
4597
                if ($bsnap eq $imagesnaps[$matchbase + $matches]) { # matching snapshot found
4598
                    $matches++;
4599
                } elsif ($matches) { # backup snapshots are ahead of image snapshots - correct manually, i.e. delete them.
4600
                    $postreply .= "Status=ERROR Snapshots are out of sync.\n";
4601
                    $postmsg = "ERROR Snapshots are out of sync";
4602
                    $main::syslogit->($user, 'info', "ERROR snapshots of $ipath and $bpath are out of sync.");
4603
                    return $postreply;
4604
                } elsif (!$matchbase) { # Possibly there are image snapshots older than there are backup snapshots, find the match base i.e. first match in @imagesnaps
4605
                    my $mb=0;
4606
                    foreach my $isnap (@imagesnaps) {
4607
                        if ($bsnap eq $isnap) { # matching snapshot found
4608
                            $matchbase = $mb;
4609
                            $matches++;
4610
                            last;
4611
                        }
4612
                        $mb++;
4613
                    }
4614
                }
4615
            }
4616 27512919 Origo
4617 95b003ff Origo
            my $lastisnap = $imagesnaps[scalar @imagesnaps -1];
4618
            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)/);
4619
            my $td = ($current_time - $lastisnaptime);
4620
            if ($td<=5) {
4621
                $postreply .= "Status=ERROR Last backup was taken $td seconds ago. Please wait a minute...\n";
4622
                $postmsg = "ERROR ERROR Last backup was taken $td seconds ago. Please wait a minute...";
4623
                return $postreply;
4624
            }
4625
            my $ni = scalar @imagesnaps;
4626
            my $nb = scalar @backupsnaps;
4627 6372a66e hq
4628
            # If there are unsynced image snaps - sync them
4629 95b003ff Origo
            if ($zbackupavailable && !$snaponly) {
4630
                if (scalar @imagesnaps > $matches+$matchbase) {
4631 6372a66e hq
                    if ($matches > 0) { # We must have at least one common shapshot to sync
4632
                        for (my $j=$matches+$matchbase; $j < scalar @imagesnaps; $j++) {
4633
                            if ($macip) {
4634
                                $cmd = qq[$zfscmd "send -i $ipath\@SNAPSHOT-$imagesnaps[$j-1] $ipath\@SNAPSHOT-$imagesnaps[$j] | ssh 10.0.0.1 sudo zfs receive $bpath"]; # -R
4635
                            } else {
4636
                                $cmd = qq[zfs send -i $ipath\@SNAPSHOT-$imagesnaps[$j-1] $ipath\@SNAPSHOT-$imagesnaps[$j] | zfs receive $bpath]; # -R
4637
                            }
4638
                            $res = `$cmd 2>&1`;
4639
                            unless (
4640
                                ($res && !$macip) #ssh will warn about adding to list of known hosts
4641
                                    || $res =~ /cannot receive/
4642
                            ) {
4643
                                $matches++;
4644
                                $nb++;
4645
                                $postreply .= "Status=OK Sending ZFS snapshot $j $imagesnaps[$j-1]->$imagesnaps[$j] of $macip $ipath to $bpath $res\n";
4646
                                $main::syslogit->($user, 'info', "OK Sending ZFS snapshot $imagesnaps[$j-1]->$imagesnaps[$j] of $macip $ipath to $bpath $res");
4647
                            } else {
4648
                                $postreply .= "Status=Error Problem sending ZFS snapshot $j $imagesnaps[$j-1]->$imagesnaps[$j] of $macip $ipath to $bpath $res\n";
4649
                                $main::syslogit->($user, 'info', "Error Problem sending ZFS snapshot $imagesnaps[$j-1]->$imagesnaps[$j] of $macip $ipath to $bpath $res");
4650
                            }
4651 95b003ff Origo
                        }
4652 6372a66e hq
                    } else {
4653
                        $postreply .= "Status=OK Unable to sync $ni snapshots, no common snapshot, trying to start from scratch.\n";
4654 95b003ff Origo
                    }
4655
                }
4656
            }
4657
            $res = '';
4658 27512919 Origo
4659 6372a66e hq
            if ($matches && !$synconly) { # There was at least one match, snapshots are now assumed to be in sync
4660 95b003ff Origo
        # Then perform the actual snapshot
4661
                my $snap1 = sprintf "%4d%02d%02d%02d%02d%02d",$year,$mon+1,$mday,$hour,$min,$sec;
4662
                my $oldsnap = $imagesnaps[$matches+$matchbase-1];
4663
                $cmd = qq|$zfscmd snapshot -r $ipath\@SNAPSHOT-$snap1|;
4664
                $postreply .= "Status=OK Performing ZFS snapshot with $matches matches and base $matchbase $res\n";
4665
                $res = `$cmd 2>&1`;
4666
                unless ($res && !$macip) {
4667
                    $ni++;
4668
                    push @imagesnaps, $snap1;
4669
                }
4670
        # Send it to backup if asked to
4671
                unless ($snaponly || !$zbackupavailable) {
4672
                    if ($macip) {
4673 27512919 Origo
                        $cmd = qq[$zfscmd "send -i $ipath\@SNAPSHOT-$oldsnap $ipath\@SNAPSHOT-$snap1 | ssh 10.0.0.1 sudo zfs receive $bpath"];
4674 95b003ff Origo
                    } else {
4675
                        $cmd = qq[zfs send -i $ipath\@SNAPSHOT-$oldsnap $ipath\@SNAPSHOT-$snap1 | zfs receive $bpath]; # -R
4676
                    }
4677
                    $res .= `$cmd 2>&1`;
4678
                    unless ($res && !$macip) {
4679
                        $matches++;
4680
                        $nb++;
4681
                        push @backupsnaps, $snap1;
4682
                    }
4683
                    $postreply .= "Status=OK Sending ZFS snapshot of $macip $ipath $oldsnap->$snap1 to $bpath $res\n";
4684
                    $main::syslogit->($user, 'info', "OK Sending ZFS snapshot of $macip $ipath $oldsnap->$snap1 to $bpath $res");
4685
                }
4686 27512919 Origo
                $postreply .= "Status=OK Synced $matches ZFS snapshots. There are now $ni image snapshots, $nb backup snapshots.\n";
4687 95b003ff Origo
            } elsif ($matches) {
4688 27512919 Origo
                $postreply .= "Status=OK Synced $matches ZFS snapshots. There are $ni image snapshots, $nb backup snapshots.\n";
4689 95b003ff Origo
#            } elsif ($ni==0 && $nb==0) { # We start from a blank slate
4690
            } elsif ($nb==0) { # We start from a blank slate
4691
                my $snap1 = sprintf "%4d%02d%02d%02d%02d%02d",$year,$mon+1,$mday,$hour,$min,$sec;
4692
                $cmd = qq|$zfscmd snapshot -r $ipath\@SNAPSHOT-$snap1|;
4693
                $res = `$cmd 2>&1`;
4694 04c16f26 hq
                $postreply .= "Status=OK Performing ZFS snapshot from scratch $res $macip\n";
4695
        # Send it to backup by creating new filesystem (created autotically)
4696 95b003ff Origo
                unless ($snaponly || !$zbackupavailable) {
4697
                    if ($macip) {
4698 27512919 Origo
                        $cmd = qq[$zfscmd "send $ipath\@SNAPSHOT-$snap1 | ssh 10.0.0.1 sudo zfs receive $bpath"];
4699 95b003ff Origo
                        $res .= `$cmd 2>&1`;
4700
                        $cmd = qq|zfs set readonly=on $bpath|;
4701
                        $res .= `$cmd 2>&1`;
4702
                        $cmd = qq|zfs mount $bpath|;
4703
                        $res .= `$cmd 2>&1`;
4704
                    } else {
4705
                        $cmd = qq[zfs send -R $ipath\@SNAPSHOT-$snap1 | zfs receive $bpath];
4706
                        $res .= `$cmd 2>&1`;
4707 2a63870a Christian Orellana
                        $cmd = qq|zfs set readonly=on $bpath|;
4708
                        $res .= `$cmd 2>&1`;
4709 95b003ff Origo
                    }
4710
                    $postreply .= "Status=OK Sending complete ZFS snapshot of $macip:$ipath\@$snap1 to $bpath $res\n";
4711
                    $main::syslogit->($user, 'info', "OK Sending complete ZFS snapshot of $macip:$ipath\@$snap1 to $bpath $res");
4712
                    $matches++;
4713
                    $nb++;
4714
                }
4715
                $ni++;
4716 04c16f26 hq
                $postreply .= "Status=OK Synced 0 ZFS snapshots. There are $ni image snapshots, $nb backup snapshots.\n";
4717 95b003ff Origo
            } else {
4718
                $postreply .= "Status=ERROR Unable to sync snapshots.\n";
4719
                $postmsg = "ERROR Unable to sync snapshots";
4720
            }
4721
            my $i=0;
4722
        # Purge image snapshots if asked to
4723
            if ($imageretention && $matches>1) {
4724
                my $rtime;
4725
                if ($imageretention =~ /(\d+)(s|h|d)/) {
4726
                    $rtime = $1;
4727
                    $rtime = $1*60*60 if ($2 eq 'h');
4728
                    $rtime = $1*60*60*24 if ($2 eq 'd');
4729
                    $postreply .= "Status=OK Keeping image snapshots newer than $imageretention out of $ni.\n";
4730
                } elsif ($imageretention =~ /(\d+)$/) {
4731
                    $postreply .= "Status=OK Keeping " . (($imageretention>$ni)?$ni:$imageretention) . " image snapshots out of $ni.\n";
4732
                } else {
4733
                    $imageretention = 0;
4734
                }
4735
                if ($imageretention) {
4736
                    foreach my $isnap (@imagesnaps) {
4737
                        my $purge;
4738
                        if ($rtime) {
4739
                            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)/);
4740
                            my $tdiff = ($current_time - $snaptime);
4741
                            if ( $matches>1 && $tdiff>$rtime )
4742
                                {$purge = 1;}
4743
                            else
4744
                                {last;}
4745
                        } else { # a simple number was specified
4746
#                            if ( $matches>1 && $matches+$matchbase>$imageretention )
4747
                            if ( $matches>1 && $ni>$imageretention )
4748
                                {$purge = 1;}
4749
                            else
4750
                                {last;}
4751
                        }
4752
                        if ($purge) {
4753
                            $cmd = qq|$zfscmd destroy $ipath\@SNAPSHOT-$isnap|;
4754
                            $res = `$cmd 2>&1`;
4755
                            $postreply .= "Status=OK Purging image snapshot $isnap from $ipath.\n";
4756
                            $main::syslogit->($user, 'info', "OK Purging image snapshot $isnap from $ipath");
4757
                            $matches-- if ($i>=$matchbase);
4758
                            $ni--;
4759
                        }
4760
                        $i++;
4761
                    }
4762
                }
4763
            }
4764
            # Purge backup snapshots if asked to
4765
            if ($backupretention && $matches) {
4766
                my $rtime;
4767
                if ($backupretention =~ /(\d+)(s|h|d)/) {
4768
                    $rtime = $1;
4769
                    $rtime = $1*60*60 if ($2 eq 'h');
4770
                    $rtime = $1*60*60*24 if ($2 eq 'd');
4771
                    $postreply .= "Status=OK Keeping backup snapshots newer than $backupretention out of $nb.\n";
4772
                } elsif ($backupretention =~ /(\d+)$/) {
4773
                    $postreply .= "Status=OK Keeping " . (($backupretention>$nb)?$nb:$backupretention) . " backup snapshots out of $nb.\n";
4774
                } else {
4775
                    $backupretention = 0;
4776
                }
4777
                if ($backupretention && $zbackupavailable) {
4778
                    foreach my $bsnap (@backupsnaps) {
4779
                        my $purge;
4780
                        if ($bsnap eq $imagesnaps[$matchbase+$matches-1]) { # We need to keep the last snapshot synced
4781
                            $postreply .= "Status=OK Not purging backup snapshot $matchbase $bsnap.\n";
4782
                            last;
4783
                        } else {
4784
                            if ($rtime) {
4785
                                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)/);
4786
                                my $tdiff = ($current_time - $snaptime);
4787
                                if ( $matches>1 && $tdiff>$rtime )
4788
                                    {$purge = 1;}
4789
                            } else {
4790
                                if ( $nb>$backupretention )
4791
                                    {$purge = 1;}
4792
                            }
4793
                            if ($purge) {
4794
                                $cmd = qq|zfs destroy $bpath\@SNAPSHOT-$bsnap|;
4795
                                $res = `$cmd 2>&1`;
4796
                                $postreply .= "Status=OK Purging backup snapshot $bsnap from $bpath.\n";
4797
                                $main::syslogit->($user, 'info', "OK Purging backup snapshot $bsnap from $bpath");
4798
                                $nb--;
4799
                            } else {
4800
                                last;
4801
                            }
4802
                        }
4803
                    }
4804
                }
4805
            }
4806 27512919 Origo
            $postmsg .= "OK Performing ZFS backup of $bpath. There are $ni image snapshots and $nb backup snapshots. ";
4807 95b003ff Origo
        }
4808 27512919 Origo
        $postreply .= "Status=OK Updating all btimes\n";
4809
        Updateallbtimes();
4810 95b003ff Origo
    } else {
4811
        $postreply .= "Status=ERROR Not allowed\n";
4812
        $postmsg = "ERROR Not allowed";
4813
    }
4814
    $main::updateUI->({tab=>"images", user=>$user, type=>"message", message=>$postmsg});
4815
    return $postreply;
4816
}
4817
4818 2a63870a Christian Orellana
sub Backupfuel {
4819
    my ($image, $action, $obj) = @_;
4820
    if ($help) {
4821
        return <<END
4822
GET:username, dozfs:
4823
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.
4824
END
4825
    }
4826
    my $username = $obj->{'username'} || $user;
4827
    return "Status=Error Not allowed\n" unless ($isadmin || $username eq $user);
4828
4829
    my $remolder = "14D";
4830
    my $stordevs = Liststoragedevices('', 'getstoragedevices');
4831
    my $backupdev = Getbackupdevice('', 'getbackupdevice');
4832
    my $backupdevtype = $stordevs->{$backupdev}->{type};
4833
    foreach my $spool (@spools) {
4834
        my $ppath = $spool->{"path"};
4835
        my $pid = $spool->{"id"};
4836
        if (($spool->{"zfs"} && $backupdevtype eq 'zfs') && !$obj->{'dozfs'}) {
4837
            $postreply .= "Status=OK Skipping fuel on ZFS storage: $ppath/$username/fuel\n";
4838
        } elsif ($pid eq '-1') {
4839
            ;
4840
        } elsif (!$backupdir || !(-d $backupdir)) {
4841
            $postreply .= "Status=OK Backup dir $backupdir does not exist\n";
4842
        } elsif (-d "$ppath/$username/fuel" && !is_folder_empty("$ppath/$username/fuel")) {
4843
            my $srcdir = "$ppath/$username/fuel";
4844
            my $destdir = "$backupdir/$username/fuel/$pid";
4845
4846
            `mkdir -p "$destdir"` unless (-e "$destdir");
4847
            # Do the backup
4848
            my $cmd = qq|/usr/bin/rdiff-backup --print-statistics "$srcdir" "$destdir"|;
4849
            my $res = `$cmd`;
4850
            $cmd = qq|/usr/bin/rdiff-backup --print-statistics --force --remove-older-than $remolder "$destdir"|;
4851
            $res .= `$cmd`;
4852
            if ($res =~ /Errors 0/) {
4853
                my $change = $1 if ($res =~ /TotalDestinationSizeChange \d+ \((.+)\)/);
4854
                $postreply .= "Status=OK Backed up $change, $srcdir -> $destdir\n";
4855
                $main::syslogit->($user, "info", "OK backed up $change, $srcdir -> $destdir") if ($change);
4856
            } else {
4857
                $res =~ s/\n/ /g;
4858
                $postreply .= "Status=Error There was a problem backup up $srcdir -> $destdir: $res\n";
4859
                $main::syslogit->($user, "there was a problem backup up $srcdir -> $destdir");
4860
            }
4861
        } else {
4862
            $postreply .= "Status=OK Skipping empty fuel on: $ppath/$username/fuel\n";
4863
        }
4864
    }
4865
    return $postreply;
4866
}
4867
4868
sub is_folder_empty {
4869
    my $dirname = shift;
4870
    opendir(my $dh, $dirname) or die "Not a directory";
4871
    return scalar(grep { $_ ne "." && $_ ne ".." } readdir($dh)) == 0;
4872
}
4873
4874 95b003ff Origo
sub Backup {
4875
    my ($image, $action, $obj) = @_;
4876
    if ($help) {
4877
        return <<END
4878 2a63870a Christian Orellana
GET:image, skipzfs:
4879 d3805c61 hq
Backs an image up. Set [skipzfs] if ZFS backup is configured, and you want to skip images on ZFS storage.
4880 95b003ff Origo
END
4881
    }
4882 2a63870a Christian Orellana
    my $path = $obj->{path} || $image;
4883 95b003ff Origo
    my $status = $obj->{status};
4884 2a63870a Christian Orellana
    my $skipzfs = $obj->{skipzfs};
4885 95b003ff Origo
    $uistatus = "backingup";
4886
    $uipath = $path;
4887
    my $remolder;
4888
    $remolder = "14D" if ($obj->{bschedule} eq "daily14");;
4889
    $remolder = "7D" if ($obj->{bschedule} eq "daily7");
4890 a2e0bc7e hq
    my $breply = '';
4891 2a63870a Christian Orellana
4892
    my $stordevs = Liststoragedevices('', 'getstoragedevices');
4893
    my $backupdev = Getbackupdevice('', 'getbackupdevice');
4894
    my $backupdevtype = $stordevs->{$backupdev}->{type};
4895
    # Nodes are assumed to alwasy use ZFS
4896
    if ($backupdevtype eq 'zfs' && $skipzfs && ($obj->{regstoragepool} == -1 || $spools[$obj->{regstoragepool}]->{'zfs'})) {
4897
        return "Status=OK Skipping image on ZFS $path\n";
4898
    }
4899 95b003ff Origo
    if ($status eq "snapshotting" || $status eq "unsnapping" || $status eq "reverting" || $status eq "cloning" ||
4900
        $status eq "moving" || $status eq "converting") {
4901 a2e0bc7e hq
        $breply .= "Status=ERROR Problem backing up $obj->{type} image $obj->{name}\n";
4902 95b003ff Origo
    } elsif ($obj->{regstoragepool} == -1) {
4903 a2e0bc7e hq
        my $res = createNodeTask($obj->{mac}, "BACKUP $user $uistatus $status \"$path\" \"$backupdir\" $remolder", $status,  '', $path);
4904 54401133 hq
        if ($res) {
4905 a2e0bc7e hq
            $breply .= "Status=ERROR Suspend serverer befora backing up (image $obj->{name} is not on an LVM partition)\n";
4906 95b003ff Origo
        } else {
4907
            $register{$path}->{'status'} = $uistatus;
4908
            $uistatus = "lbackingup" if ($status eq "active"); # Do lvm snapshot before backing up
4909
            $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
4910 a2e0bc7e hq
            $breply .= "Status=backingup OK backingup image: $obj->{name} (on node)\n";
4911 95b003ff Origo
        }
4912
    } elsif (!$spools[$obj->{regstoragepool}]->{'rdiffenabled'}) {
4913 a2e0bc7e hq
        $breply .= "Status=ERROR Rdiff-backup has not been enabled for this storagepool ($spools[$obj->{regstoragepool}]->{'name'})\n";
4914 95b003ff Origo
    } else {
4915
        if ($spools[$obj->{regstoragepool}]->{'hostpath'} eq "local" && $status eq "active") {
4916
            my $poolpath = $spools[$obj->{regstoragepool}]->{'path'};
4917
            # We only need to worry about taking an LVM snapshot if the image is in active use
4918
            # We also check if the images is actually on an LVM partition
4919
            my $qi = `/bin/cat /proc/mounts | grep "$poolpath"`; # Find the lvm volume mounted on /mnt/images
4920
            ($qi =~ m/\/dev\/mapper\/(\S+)-(\S+) $pool.+/g)[-1]; # Select last match
4921
            my $lvolgroup = $1;
4922
            my $lvol = $2;
4923
            if ($lvolgroup && $lvol) {
4924
                $uistatus = "lbackingup";
4925
            }
4926
        }
4927
        if ($uistatus ne "lbackingup" && $status eq "active") {
4928 a2e0bc7e hq
            $breply .= "Status=ERROR Suspend serverer befora backing up (image $obj->{name} is not on an LVM partition)\n";
4929
        #    $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"});
4930 95b003ff Origo
        } else {
4931
            my $buser;
4932
            my $bname;
4933
            if ($path =~ /.*\/(common|$user)\/(.+)/) {
4934
                $buser = $1;
4935
                $bname = $2;
4936
            }
4937
            if ($buser && $bname) {
4938
                my $dirpath = $spools[$obj->{regstoragepool}]->{'path'};
4939
                #chop $dirpath; # Remove last /
4940
                eval {
4941
                    $register{$path}->{'status'} = $uistatus;
4942
                    my $daemon = Proc::Daemon->new(
4943
                        work_dir => '/usr/local/bin',
4944
                        exec_command => "perl -U steamExec $buser $uistatus $status \"$bname\" \"$dirpath\" \"$backupdir\" $remolder"
4945 a2e0bc7e hq
                    ) or do {$breply .= "Status=ERROR $@\n";};
4946 95b003ff Origo
                    my $pid = $daemon->Init();
4947 a2e0bc7e hq
                    $breply .=  "Status=backingup OK backingup image: $obj->{name}\n";
4948 95b003ff Origo
                    $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $bname");
4949
                    1;
4950 a2e0bc7e hq
                } or do {$breply .= "Status=ERROR $@\n";}
4951 95b003ff Origo
            } else {
4952 a2e0bc7e hq
                $breply .= "Status=ERROR Problem backing up $path\n";
4953 95b003ff Origo
            }
4954
        }
4955
    }
4956 a2e0bc7e hq
    return $breply;
4957 95b003ff Origo
}
4958
4959
sub Restore {
4960
    my ($image, $action, $obj) = @_;
4961
    if ($help) {
4962
        return <<END
4963
GET:image:
4964
Backs an image up.
4965
END
4966
    }
4967
    my $path = $obj->{path};
4968
    my $status = $obj->{status};
4969
    $uistatus = "restoring";
4970 04c16f26 hq
    my($bname, $dirpath, $suffix) = fileparse($path, (".vmdk", ".img", ".vhd", ".vhdx", ".qcow", ".qcow2", ".vdi", ".iso"));
4971 95b003ff Origo
    my $backup = $params{"backup"} || $obj->{backup};
4972
    my $pool = $register{$path}->{'storagepool'};
4973
    $pool = "0" if ($pool == -1);
4974
    my $poolpath = $spools[$pool]->{'path'};
4975
    my $restorefromdir = $backupdir;
4976
    my $inc = $backup;
4977
    my $subdir; # 1 level of subdirs supported
4978 27512919 Origo
    $subdir = $1 if ($dirpath =~ /.+\/$obj->{user}(\/.+)?\//);
4979 95b003ff Origo
4980
    if ($backup =~ /^SNAPSHOT-(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/) { # We are dealing with a zfs restore
4981
        $inc = "$1-$2-$3-$4-$5-$6";
4982
        foreach my $spool (@spools) {
4983
            my $ppath = $spool->{"path"};
4984
            if (-e "$ppath/.zfs/snapshot/$backup/$obj->{user}$subdir/$bname$suffix") {
4985
                $restorefromdir = "$ppath/.zfs/snapshot/$backup";
4986
                last;
4987
            }
4988
        }
4989
    } else {
4990
        if ($backup eq "mirror") {
4991
            my $mir = `/bin/ls "$backupdir/$obj->{user}/$bname$suffix/rdiff-backup-data" | grep current_mirror`;
4992
            if ($mir =~ /current_mirror\.(\S+)\.data/) {
4993
                $inc = $1;
4994
            }
4995
        }
4996
        $inc =~ tr/:T/-/; # qemu-img does not like colons in file names - go figure...
4997
        $inc = substr($inc,0,-6);
4998
    }
4999
    $uipath = "$poolpath/$obj->{user}$subdir/$bname.$inc$suffix";
5000
    my $i;
5001
    if (-e $uipath) {
5002
        $i = 1;
5003
        while (-e "$poolpath/$obj->{user}$subdir/$bname.$inc.$i$suffix") {$i++;}
5004
        $uipath = "$poolpath/$obj->{user}$subdir/$bname.$inc.$i$suffix";
5005
    }
5006
5007
    if (-e $uipath) {
5008
        $postreply .= "Status=ERROR This image is already being restored\n";
5009
    } elsif ($obj->{user} ne $user && !$isadmin) {
5010
        $postreply .= "Status=ERROR No restore privs\n";
5011
    } elsif (!$backup || $backup eq "--") {
5012
        $postreply .= "Status=ERROR No backup selected\n";
5013
    } elsif (overQuotas($obj->{virtualsize})) {
5014
        $postreply .= "Status=ERROR Over quota (". overQuotas($obj->{virtualsize}) . ") restoring: $obj->{name}\n";
5015
    } elsif (overStorage($obj->{ksize}*1024, $pool+0)) {
5016
        $postreply .= "Status=ERROR Out of storage in destination pool restoring: $obj->{name}\n";
5017
    } else {
5018
        my $ug = new Data::UUID;
5019
        my $newuuid = $ug->create_str();
5020
        $register{$uipath} = {
5021
            uuid=>$newuuid,
5022
            status=>"restoring",
5023
            name=>"$obj->{name} ($inc)" . (($i)?" $i":''),
5024
            notes=>$obj->{notes},
5025
            image2=>$obj->{image2},
5026
            managementlink=>$obj->{managementlink},
5027
            upgradelink=>$obj->{upgradelink},
5028
            terminallink=>$obj->{terminallink},
5029
            size=>0,
5030
            realsize=>0,
5031
            virtualsize=>$obj->{virtualsize},
5032
            type=>$obj->{type},
5033
            user=>$user
5034
        };
5035
        eval {
5036
            $register{$path}->{'status'} = $uistatus;
5037
            my $daemon = Proc::Daemon->new(
5038
                work_dir => '/usr/local/bin',
5039
                exec_command => "perl -U steamExec $obj->{user} $uistatus $status \"$path\" \"$restorefromdir\" \"$backup\" \"$uipath\""
5040
            ) or do {$postreply .= "Status=ERROR $@\n";};
5041
            my $pid = $daemon->Init();
5042
            $postreply .=  "Status=$uistatus OK $uistatus $obj->{type} image: $obj->{name} ($inc)". ($console?", $newuuid\n":"\n");
5043
            $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name} ($inc), $uipath, $newuuid: $uuid");
5044
            1;
5045
        } or do {$postreply .= "Status=ERROR $@\n";};
5046
        $main::updateUI->({tab=>"images", user=>$user, type=>"update"});
5047
    }
5048
    return $postreply;
5049
}
5050
5051
sub Master {
5052
    my ($image, $action, $obj) = @_;
5053
    if ($help) {
5054
        return <<END
5055
GET:image:
5056
Converts an image to a master image. Image must not be in use.
5057
END
5058
    }
5059
    my $path = $obj->{path};
5060
    my $status = $register{$path}->{status};
5061
    $path =~ /(.+)\.$obj->{type}$/;
5062
    my $namepath = $1;
5063
    my $uiname;
5064
    if (!$register{$path}) {
5065
        $postreply .= "Status=ERROR Image $path not found\n";
5066
    } elsif ($status ne "unused") {
5067
        $postreply .= "Status=ERROR Only unused images may be mastered\n";
5068 3657de20 Origo
#    } elsif ($namepath =~ /(.+)\.master/ || $register{$path}->{'master'}) {
5069
#        $postreply .= "Status=ERROR Only one level of mastering is supported\n";
5070 95b003ff Origo
    } elsif ($obj->{istoragepool} == -1 || $obj->{regstoragepool} == -1) {
5071
        $postreply .= "Status=ERROR Unable to master $obj->{name} (master images are not supported on node storage)\n";
5072
    } elsif ($obj->{type} eq "qcow2") {
5073
        # Promoting a regular image to master
5074
        # First find an unused path
5075
        if (-e "$namepath.master.$obj->{type}") {
5076
            my $i = 1;
5077
            while ($register{"$namepath.$i.master.$obj->{type}"} || -e "$namepath.$i.master.$obj->{type}") {$i++;};
5078
            $uinewpath = "$namepath.$i.master.$obj->{type}";
5079
        } else {
5080
            $uinewpath = "$namepath.master.$obj->{type}";
5081
        }
5082
5083
        $uipath = $path;
5084
        $uiname = "$obj->{name}";
5085
        eval {
5086
            my $qinfo = `/bin/mv -iv "$path" "$uinewpath"`;
5087
            $register{$path}->{'name'} = $uiname;
5088
            $register{$uinewpath} = $register{$path};
5089
            delete $register{$path};
5090
            $postreply .= "Status=$status Mastered $obj->{type} image: $obj->{name}\n";
5091
            chop $qinfo;
5092
            $main::syslogit->($user, "info", $qinfo);
5093
            1;
5094
        } or do {$postreply .= "Status=ERROR $@\n";};
5095
        sleep 1;
5096
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, newpath=>$uinewpath, status=>$status, name=>$uiname});
5097
    } else {
5098
        $postreply .= "Status=ERROR Only qcow2 images may be mastered\n";
5099
    }
5100
    return $postreply;
5101
}
5102
5103
sub Unmaster {
5104
    my ($image, $action, $obj) = @_;
5105
    if ($help) {
5106
        return <<END
5107
GET:image:
5108
Converts a master image to a regular image. Image must not be in use.
5109
END
5110
    }
5111
    my $path = $obj->{path};
5112
    my $status = $register{$path}->{status};
5113
    $path =~ /(.+)\.$obj->{type}$/;
5114
    my $namepath = $1;
5115
    my $haschildren = 0;
5116
    my $child;
5117
    my $uinewpath;
5118
    my $iname;
5119
    my @regvalues = values %register;
5120
    foreach my $val (@regvalues) {
5121
        if ($val->{'master'} eq $path) {
5122
            $haschildren = 1;
5123
            $child = $val->{'name'};
5124
            last;
5125
        }
5126
    }
5127
    if (!$register{$path}) {
5128
        $postreply .= "Status=ERROR Image $path not found\n";
5129
    } elsif ($haschildren) {
5130
        $postreply .= "Status=Error Cannot unmaster image. This image is used as master by: $child\n";
5131
    } elsif ($status ne "unused" && $status ne "used") {
5132
        $postreply .= "Status=ERROR Only used and unused images may be unmastered\n";
5133
    } elsif (!( ($namepath =~ /(.+)\.master/) || ($obj->{master} && $obj->{master} ne "--")) ) {
5134
        $postreply .= "Status=ERROR You can only unmaster master or child images\n";
5135
    } elsif (($obj->{istoragepool} == -1 || $obj->{regstoragepool} == -1) && $namepath =~ /(.+)\.master/) {
5136
        $postreply .= "Status=ERROR Unable to unmaster $obj->{name} (master images are not supported on node storage)\n";
5137
    } elsif ($obj->{type} eq "qcow2") {
5138
        # Demoting a master to regular image
5139 3657de20 Origo
        if ($action eq 'unmaster' && $namepath =~ /(.+)\.master$/) {
5140 95b003ff Origo
            $namepath = $1;
5141
            $uipath = $path;
5142
            # First find an unused path
5143
            if (-e "$namepath.$obj->{type}") {
5144
                my $i = 1;
5145
                while ($register{"$namepath.$i.$obj->{type}"} || -e "$namepath.$i.$obj->{type}") {$i++;};
5146
                $uinewpath = "$namepath.$i.$obj->{type}";
5147
            } else {
5148
                $uinewpath = "$namepath.$obj->{type}";
5149
            }
5150
5151
            $iname = $obj->{name};
5152
            $iname =~ /(.+)( \(master\))/;
5153
            $iname = $1 if $2;
5154
            eval {
5155
                my $qinfo = `/bin/mv -iv "$path" "$uinewpath"`;
5156
                $register{$path}->{'name'} = $iname;
5157
                $register{$uinewpath} = $register{$path};
5158
                delete $register{$path};
5159
                $postreply .=  "Status=$status Unmastered $obj->{type} image: $obj->{name}\n";
5160
                chomp $qinfo;
5161
                $main::syslogit->($user, "info", $qinfo);
5162
                1;
5163
            } or do {$postreply .= "Status=ERROR $@\n";}
5164
    # Rebasing a child image
5165 3657de20 Origo
        } elsif ($action eq 'rebase' && $obj->{master} && $obj->{master} ne "--") {
5166 95b003ff Origo
            $uistatus = "rebasing";
5167
            $uipath = $path;
5168
            $iname = $obj->{name};
5169
            $iname =~ /(.+)( \(child\d*\))/;
5170
            $iname = $1 if $2;
5171
            my $temppath = "$path.temp";
5172
            $uipath = $path;
5173
            $uimaster = "--";
5174
            my $macip;
5175
5176
            if ($obj->{mac} && $path =~ /\/mnt\/stabile\/node\//) {
5177
                unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
5178
                $macip = $nodereg{$obj->{mac}}->{'ip'};
5179
                untie %nodereg;
5180
            }
5181
            eval {
5182
                my $master = $register{$path}->{'master'};
5183
                my $usedmaster = '';
5184
#                @regvalues = values %register;
5185
                if ($master && $master ne '--') {
5186
                    foreach my $valref (@regvalues) {
5187
                        $usedmaster = 1 if ($valref->{'master'} eq $master && $valref->{'path'} ne $path); # Check if another image is also using this master
5188
                    }
5189
                }
5190 48fcda6b Origo
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$uistatus});
5191 95b003ff Origo
                $register{$path} = {
5192
                    master=>"",
5193
                    name=>"$iname",
5194
                    notes=>$obj->{notes},
5195
                    status=>$uistatus,
5196
                    snap1=>$obj->{snap1},
5197
                    managementlink=>$obj->{managementlink},
5198
                    upgradelink=>$obj->{upgradelink},
5199
                    terminallink=>$obj->{terminallink},
5200
                    image2=>$obj->{image2},
5201
                    storagepool=>$obj->{istoragepool},
5202
                    status=>$uistatus
5203
                };
5204
5205
                if ($macip) {
5206
                    my $esc_localpath = shell_esc_chars($path);
5207
                    my $esc_localpath2 = shell_esc_chars($temppath);
5208
                    $res .= `$sshcmd $macip "/usr/bin/qemu-img convert $esc_localpath -O qcow2 $esc_localpath2"`;
5209
                    $res .= `$sshcmd $macip "if [ -f $esc_localpath2 ]; then /bin/mv -v $esc_localpath2 $esc_localpath; fi"`;
5210
                } else {
5211
                    $res .= `/usr/bin/qemu-img convert -O qcow2 "$path" "$temppath"`;
5212
                    $res .= `if [ -f "$temppath" ]; then /bin/mv -v "$temppath" "$path"; fi`;
5213
                }
5214
                if ($master && !$usedmaster) {
5215
                    $register{$master}->{'status'} = 'unused';
5216
                    $main::syslogit->('info', "Freeing master $master");
5217
                }
5218
                $register{$path}->{'master'} = '';
5219
                $register{$path}->{'status'} = $status;
5220
5221
                $postreply .= "Status=OK $uistatus $obj->{type} image: $obj->{name}\n";
5222 48fcda6b Origo
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, status=>$status});
5223 95b003ff Origo
                $main::syslogit->($user, "info", "$uistatus $obj->{type} image: $obj->{name}: $uuid");
5224
                1;
5225
            } or do {$postreply .= "Status=ERROR $@\n";}
5226
        } else {
5227
            $postreply .= "Status=ERROR Not a master, not a child \"$obj->{name}\"\n";
5228
        }
5229
        sleep 1;
5230
        $main::updateUI->({tab=>"images", user=>$user, type=>"update", uuid=>$obj->{'uuid'}, newpath=>$uinewpath, name=>$iname, status=>$status});
5231
    } else {
5232
        $postreply .= "Status=ERROR Only qcow2 images may be unmastered\n";
5233
    }
5234
    return $postreply;
5235
}
5236
5237
# Save or create new image
5238
sub Save {
5239
    my ($img, $action, $obj) = @_;
5240
    if ($help) {
5241
        return <<END
5242
POST:path, uuid, name, type, virtualsize, storagepool, user:
5243
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.
5244
Depending on your privileges not all changes are permitted. If you save without specifying a uuid or path, a new image is created.
5245
END
5246
    }
5247
    my $path = $obj->{path};
5248
    my $uuid = $obj->{uuid};
5249
    my $status = $obj->{status};
5250
    if ($status eq "new") {
5251
        # Create new image
5252
        my $ug = new Data::UUID;
5253
        if (!$uuid || $uuid eq '--') {
5254
            $uuid = $ug->create_str();
5255
        } else { # Validate
5256
            my $valuuid  = $ug->from_string($uuid);
5257
            if ($ug->to_string($valuuid) eq $uuid) {
5258
                ;
5259
            } else {
5260
                $uuid = $ug->create_str();
5261
            }
5262
        }
5263
        my $newuuid = $uuid;
5264
        my $pooldir = $spools[$obj->{storagepool}]->{'path'};
5265
        my $cmd;
5266
        my $name = $obj->{name};
5267
        $name =~ s/\./_/g; # Remove unwanted chars
5268
        $name =~ s/\//_/g;
5269
        eval {
5270
            my $ipath = "$pooldir/$user/$name.$obj->{type}";
5271
            $obj->{type} = "qcow2" unless ($obj->{type});
5272
            # Find an unused path
5273
            if ($register{$ipath} || -e "$ipath") {
5274
                my $i = 1;
5275
                while ($register{"$pooldir/$user/$name.$i.$obj->{type}"} || -e "$pooldir/$user/$name.$i.$obj->{type}") {$i++;};
5276
                $ipath = "$pooldir/$user/$name.$i.$obj->{type}";
5277
                $name = "$name.$i";
5278
            }
5279
5280
            if ($obj->{type} eq 'qcow2' || $obj->{type} eq 'vmdk') {
5281
                my $size = ($obj->{msize})."M";
5282
                my $format = "qcow2";
5283
                $format = "vmdk" if ($path1 =~ /\.vmdk$/);
5284
                $cmd = qq|/usr/bin/qemu-img create -f $format "$ipath" "$size"|;
5285
            } elsif ($obj->{type} eq 'img') {
5286
                my $size = ($obj->{msize})."M";
5287
                $cmd = qq|/usr/bin/qemu-img create -f raw "$ipath" "$size"|;
5288
            } elsif ($obj->{type} eq 'vdi') {
5289
                my $size = $obj->{msize};
5290
                $cmd = qq|/usr/bin/VBoxManage createhd --filename "$ipath" --size "$size" --format VDI|;
5291
            }
5292
            $obj->{name} = 'New Image' if (!$obj->{name} || $obj->{name} eq '--' || $obj->{name} =~ /^\./ || $obj->{name} =~ /\//);
5293
            if (-e $ipath) {
5294
                $postreply .= "Status=ERROR Image already exists: \"$obj->{name}\" in \"$ipath\”\n";
5295
            } elsif (overQuotas($obj->{ksize}*1024)) {
5296
                $postreply .= "Status=ERROR Over quota (". overQuotas($obj->{ksize}*1024) . ") creating: $obj->{name}\n";
5297 80e0b3f5 hq
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", message=>"Over quota in storage pool $obj->{storagepool}"});
5298 95b003ff Origo
                $main::syslogit->($user, "info", "Over quota in storage pool $obj->{storagepool}, not creating $obj->{type} image $obj->{name}");
5299
            } elsif (overStorage($obj->{ksize}*1024, $obj->{storagepool}+0)) {
5300
                $postreply .= "Status=ERROR Out of storage in destination pool creating: $obj->{name}\n";
5301
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", message=>"Out of storage in storage pool $obj->{storagepool}"});
5302
                $main::syslogit->($user, "info", "Out of storage in storage pool $obj->{storagepool}, not creating $obj->{type} image $obj->{name}");
5303
            } elsif ($obj->{virtualsize} > 10*1024*1024 && $obj->{name} && $obj->{name} ne '--') {
5304
                $register{$ipath} = {
5305
                    uuid=>$newuuid,
5306
                    name=>$obj->{name},
5307
                    user=>$user,
5308
                    notes=>$obj->{notes},
5309
                    type=>$obj->{type},
5310
                    size=>0,
5311
                    realsize=>0,
5312
                    virtualsize=>$obj->{virtualsize},
5313
                    storagepool=>$spools[0]->{'id'},
5314
                    created=>$current_time,
5315
                    managementlink=>$obj->{managementlink},
5316
                    upgradelink=>$obj->{upgradelink},
5317
                    terminallink=>$obj->{terminallink},
5318
                    status=>"creating"
5319
                };
5320
                $uipath = $ipath;
5321
                my $res = `$cmd`;
5322
                $register{$ipath}->{'status'} = 'unused';
5323
5324
                $postreply .= "Status=OK Created $obj->{type} image: $obj->{name}\n";
5325
                $postreply .= "Status=OK uuid: $newuuid\n"; # if ($console || $api);
5326
                $postreply .= "Status=OK path: $ipath\n"; # if ($console || $api);
5327
                sleep 1; # Needed to give updateUI a chance to reload
5328 8d7785ff Origo
                $main::updateUI->({tab=>"images", user=>$user, type=>"update"});
5329
#                $main::updateUI->({tab=>"images", uuid=>$newuuid, user=>$user, type=>"update", name=>$obj->{name}, path=>$obj->{path}});
5330 95b003ff Origo
                $main::syslogit->($user, "info", "Created $obj->{type} image: $obj->{name}: $newuuid");
5331 8d7785ff Origo
                updateBilling("New image: $obj->{name}");
5332 95b003ff Origo
            } else {
5333
                $postreply .= "Status=ERROR Problem creating image: $obj->{name} of size $obj->{virtualsize}\n";
5334
            }
5335
            1;
5336
        } or do {$postreply .= "Status=ERROR $@\n";}
5337
    } else {
5338 d24d9a01 hq
        # Moving images because of owner change or storagepool change
5339 95b003ff Origo
        if ($obj->{user} ne $obj->{reguser} || $obj->{storagepool} ne $obj->{regstoragepool}) {
5340
            $uipath = Move($path, $obj->{user}, $obj->{storagepool}, $obj->{mac});
5341
    # Resize a qcow2 image
5342
        } elsif ($obj->{virtualsize} != $register{$path}->{'virtualsize'} &&
5343
            ($obj->{user} eq $obj->{reguser} || index($privileges,"a")!=-1)) {
5344
            if ($status eq "active" || $status eq "paused") {
5345
                $postreply .= "Status=ERROR Cannot resize active images $path, $status.\n";
5346
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", status=>'ERROR', message=>"ERROR Cannot resize active images"});
5347
            } elsif ($obj->{type} eq "qcow2" || $obj->{type} eq "img") {
5348
                if ($obj->{virtualsize} < $register{$path}->{'virtualsize'}) {
5349
                    $postreply .= "Status=ERROR Only growing of images supported.\n";
5350
                } elsif (overQuotas($obj->{virtualsize}, ($register{$path}->{'storagepool'}==-1))) {
5351
                    $postreply .= "Status=ERROR Over quota (". overQuotas($obj->{virtualsize}, ($register{$path}->{'storagepool'}==-1)) . ") resizing: $obj->{name}\n";
5352
                } elsif (overStorage(
5353
                    $obj->{virtualsize},
5354
                    $register{$path}->{'storagepool'},
5355
                    $register{$path}->{'mac'}
5356
                )) {
5357
                    $postreply .= "Status=ERROR Not enough storage ($obj->{virtualsize}) in destination pool $obj->{storagepool} resizing: $obj->{name}\n";
5358
                } else {
5359
                    $uistatus = "resizing";
5360
                    $uipath = $path;
5361
                    my $mpath = $path;
5362
                    if ($obj->{mac} && $obj->{mac} ne '--') {
5363
                        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
5364
                        $macip = $nodereg{$obj->{mac}}->{'ip'};
5365
                        untie %nodereg;
5366
                    }
5367
                    $mpath = "$macip:$mpath" if ($macip && $macip ne '--');
5368
                    $register{$path}->{'status'} = $uistatus;
5369
                    $register{$path}->{'virtualsize'} = $obj->{virtualsize};
5370
                    my $cmd = qq|steamExec $user $uistatus $status "$mpath" "$obj->{ksize}"|;
5371
                    if ($action eq 'sync_save') { # We wait for result
5372
                        my $res = `$cmd`;
5373
                        $res =~ s/\n/ /g; $res = lc $res;
5374
                        $postreply .= "Status=OK $res\n";
5375
                    } else {
5376
                        my $daemon = Proc::Daemon->new(
5377
                            work_dir => '/usr/local/bin',
5378
                            exec_command => $cmd,
5379
#                            exec_command => "suidperl -U steamExec $user $uistatus $status \"$mpath\" \"$obj->{ksize}\""
5380
                        ) or do {$postreply .= "Status=ERROR $@\n";};
5381
                        my $pid = $daemon->Init();
5382
                    }
5383
                    $postreply .=  "Status=OK $uistatus $obj->{type} image: $obj->{name} ($obj->{ksize}k)\n";
5384
                    $main::syslogit->($user, "info", "$uistatus $obj->{type} image $obj->{name} $uuid $mpath ($obj->{virtualsize})");
5385
                }
5386
            } else {
5387
                $postreply .= "Status=ERROR Can only resize .qcow2 and .img images.\n";
5388
            }
5389
        } else {
5390
            # Regular save
5391
            if ($obj->{user} eq $obj->{reguser} || $isadmin) {
5392
                my $qinfo;
5393
                my $e;
5394
                $obj->{bschedule} = "" if ($obj->{bschedule} eq "--");
5395
                if ($obj->{bschedule}) {
5396
                    # Remove backups
5397 8d7785ff Origo
                    if ($obj->{bschedule} eq "none") {
5398
                        if ($spools[$obj->{regstoragepool}]->{'rdiffenabled'}) {
5399
                            my($bname, $dirpath) = fileparse($path);
5400
                            if ($path =~ /\/($user|common)\/(.+)/) {
5401
                                my $buser = $1;
5402
                                if (-d "$backupdir/$buser/$bname" && $backupdir && $bname && $buser) {
5403
                                    eval {
5404
                                        $qinfo = `/bin/rm -rf "$backupdir/$buser/$bname"`;
5405
                                        1;
5406
                                    } or do {$postreply .= "Status=ERROR $@\n"; $e=1;};
5407
                                    if (!$e) {
5408
                                        $postreply .=  "Status=OK Removed all rdiff backups of $obj->{name}\n";
5409
                                        chomp $qinfo;
5410
                                        $register{$path} = {backupsize=>0};
5411
                                        $main::syslogit->($user, "info", "Removed all backups of $obj->{name}: $path: $qinfo");
5412
                                        $main::updateUI->({
5413
                                            user=>$user,
5414
                                            message=>"Removed all backups of $obj->{name}",
5415
                                            backup=>$path
5416
                                        });
5417
                                        updateBilling("no backup $path");
5418
                                        delete $register{$path}->{'btime'};
5419
                                    }
5420 95b003ff Origo
                                }
5421
                            }
5422
                        }
5423
                        $obj->{bschedule} = "manually";
5424
                        $register{$path}->{'bschedule'} = $obj->{bschedule};
5425
                    }
5426
                }
5427
5428
                $register{$path} = {
5429
                    name=>$obj->{name},
5430
                    user=>$obj->{user},
5431
                    notes=>$obj->{notes},
5432
                    bschedule=>$obj->{bschedule},
5433
                    installable=>$obj->{installable},
5434
                    managementlink=>$obj->{managementlink},
5435
                    upgradelink=>$obj->{upgradelink},
5436
                    terminallink=>$obj->{terminallink},
5437
                    action=>""
5438
                };
5439
                my $domains = $register{$path}->{'domains'};
5440
                if ($status eq 'downloading') {
5441
                    unless (`pgrep $obj->{name}`) { # Check if image is in fact being downloaded
5442
                        $status = 'unused';
5443
                        $register{$path}->{'status'} = $status;
5444
                        unlink ("$path.meta") if (-e "$path.meta");
5445
                    }
5446
                }
5447
                elsif ($status ne 'unused') {
5448
                    my $match;
5449
                    if ($path =~ /\.master\.qcow2$/) {
5450
                        my @regkeys = (tied %register)->select_where("master = '$path'");
5451
                        $match = 2 if (@regkeys);
5452
                    } else {
5453
                        if (!$domreg{$domains}) { # Referenced domain no longer exists
5454
                            ;
5455
                        } else { # Verify if referenced domain still uses image
5456
                            my @imgkeys = ('image', 'image2', 'image3', 'image4');
5457
                            for (my $i=0; $i<4; $i++) {
5458
                                $match = 1 if ($domreg{$domains}->{$imgkeys[$i]} eq $path);
5459
                            }
5460
                        }
5461
                    }
5462
                    unless ($match) {
5463
                        $status = 'unused';
5464
                        $register{$path}->{'status'} = $status;
5465
                    }
5466
                }
5467
                if ($status eq 'unused') {
5468
                    delete $register{$path}->{'domains'};
5469
                    delete $register{$path}->{'domainnames'};
5470
                }
5471
                $uipath = $path;
5472
                $postreply .= "Status=OK Saved $obj->{name} ($uuid)\n";
5473 d3805c61 hq
                $main::updateUI->({tab=>"images", user=>$user, type=>"update", path=>$path, name=>  $obj->{name}, status=>$status});
5474 95b003ff Origo
            } else {
5475
                $postreply .= "Status=ERROR Unable to save $obj->{name}\n";
5476
            }
5477
        }
5478
    }
5479
    if ($postreply) {
5480
        $postmsg = $postreply;
5481
    } else {
5482 3657de20 Origo
        $postreply = to_json(\%{$register{$uipath}}, {pretty=>1}) if ($uipath && $register{$uipath});
5483 95b003ff Origo
        $postreply =~ s/""/"--"/g;
5484
        $postreply =~ s/null/"--"/g;
5485
        $postreply =~ s/"notes" {0,1}: {0,1}"--"/"notes":""/g;
5486
        $postreply =~ s/"installable" {0,1}: {0,1}"(true|false)"/"installable":$1/g;
5487
    }
5488 3657de20 Origo
    return $postreply || "Status=OK Saved $uipath\n";
5489 95b003ff Origo
}
5490
5491
sub Setstoragedevice {
5492
    my ($image, $action, $obj) = @_;
5493
    if ($help) {
5494
        return <<END
5495
GET:device,type:
5496
Changes the device - disk or partition, used for images or backup storage.
5497
[type] is either images or backup.
5498
END
5499
    }
5500
    my $dev = $obj->{device};
5501
    my $force = $obj->{force};
5502
    my $type = 'backup';
5503
    $type = 'images' if ($obj->{type} eq 'images');
5504
    return "Status=Error Not allowed\n" unless ($isadmin);
5505
    my $backupdevice = Getbackupdevice('', 'getbackupdevice');
5506
    my $imagesdevice = Getimagesdevice('', 'getimagesdevice');
5507
    my $devices_obj = from_json(Liststoragedevices('', 'liststoragedevices'));
5508
    my %devices = %$devices_obj;
5509
    my $backupdev = $devices{$backupdevice}->{dev};
5510
    my $imagesdev = $devices{$imagesdevice}->{dev};
5511
    if (!$devices{$dev}) {
5512
        $postreply = "Status=Error You must specify a valid device ($dev)\n";
5513
        return $postreply;
5514
    }
5515
    if (!$force && (($backupdev =~ /$dev/) || ($imagesdev =~ /$dev/))  && $dev !~ /vda/ && $dev !~ /sda/) { # make exception to allow returning to default setup
5516
        $postreply = "Status=Error $dev is already in use as images or backup device\n";
5517
        return $postreply;
5518
    }
5519
    my $stordir = $tenderpathslist[0];
5520
    my $stordevice = $imagesdevice;
5521
    if ($type eq 'backup') {
5522
        $stordir = $backupdir;
5523
        $stordevice = $backupdevice;
5524
    }
5525
    return "Status=Error Storage device not found\n" unless ($stordevice);
5526
    my $mp = $devices{$dev}->{mounted};
5527
    my $newstordir;
5528 e9af6c24 Origo
    # my $oldstordir;
5529 95b003ff Origo
    if ($devices{$dev}->{type} eq 'zfs') {
5530
        my $cmd = qq|zfs list stabile-$type/$type -Ho mountpoint|;
5531
        my $zmp = `$cmd`;
5532
        chomp $zmp;
5533
        if ($zmp =~ /^\//) {
5534
            `zfs mount stabile-$type/$type`;
5535
            $mp = $zmp;
5536
            $newstordir = $mp;
5537
        } else {
5538
            `zfs create stabile-$type/$type`;
5539
            $mp = "/stabile-$type/$type";
5540
            $newstordir = $mp;
5541
        }
5542
    } else {
5543
        $newstordir = (($type eq 'images')?"$mp/images":"$mp/backups");
5544
        $newstordir = $1 if ($newstordir =~ /(.+\/images)\/images$/);
5545
        $newstordir = $1 if ($newstordir =~ /(.+\/backups)\/backups$/);
5546
    }
5547
    if ($mp eq '/') {
5548
        $newstordir = (($type eq 'images')?"/mnt/stabile/images":"/mnt/stabile/backups");
5549
        `umount "$newstordir"`; # in case it's mounted
5550
    }
5551
    `mkdir "$newstordir"` unless (-e $newstordir);
5552
    `chmod 777 "$newstordir"`;
5553
5554
    my $cfg = new Config::Simple("/etc/stabile/config.cfg");
5555
    if ($type eq 'backup') {
5556
        $cfg->param('STORAGE_BACKUPDIR', $newstordir);
5557
        $cfg->save();
5558
    } elsif ($type eq 'images') {
5559 e9af6c24 Origo
5560
    # Handle shared storage config
5561
    #    $oldstordir = $stordir;
5562
        my $i = 0;
5563
        for($i = 0; $i <= $#tenderpathslist; $i++) {
5564
            my $dir = $tenderpathslist[$i];
5565
            last if ($dir eq $newstordir);
5566
        }
5567
        # $tenderpathslist[0] = $newstordir;
5568
        splice(@tenderpathslist, $i,1); # Remove existing entry
5569
        unshift(@tenderpathslist, $newstordir); # Then add the new path
5570
        $cfg->param('STORAGE_POOLS_LOCAL_PATHS', join(',', @tenderpathslist));
5571
5572
        # $tenderlist[0] = 'local';
5573
        splice(@tenderlist, $i,1);
5574
        unshift(@tenderlist, 'local');
5575
        $cfg->param('STORAGE_POOLS_ADDRESS_PATHS', join(',', @tenderlist));
5576
5577
        # $tendernameslist[0] = 'Default';
5578
        splice(@tendernameslist, $i,1);
5579
        unshift(@tendernameslist, 'Default');
5580
5581
        if ($i) { # We've actually changed storage device
5582
            my $oldstorname = $tenderpathslist[1];
5583
            $oldstorname = $1 if ($oldstorname =~ /.*\/(.+)/);
5584
            $tendernameslist[1] = "$oldstorname on $imagesdevice"; # Give the previous default pool a fitting name
5585
5586
            $storagepools = "$storagepools,$i" unless ($storagepools =~ /,\s*$i,?/ || $storagepools =~ /,\s*$i$/ || $storagepools =~ /^$i$/);
5587
            $cfg->param('STORAGE_POOLS_DEFAULTS', $storagepools);
5588
        }
5589
        $cfg->param('STORAGE_POOLS_NAMES', join(',', @tendernameslist));
5590
5591 95b003ff Origo
        $cfg->save();
5592
5593 e9af6c24 Origo
5594
    # Handle node storage configs
5595 95b003ff Origo
        unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities',key=>'identity',CLOBBER=>3}, $Stabile::dbopts)) ) {return "Unable to access id register"};
5596
        # Build hash of known node config files
5597 e9af6c24 Origo
        my @nodeconfigs;
5598
        push @nodeconfigs, "/etc/stabile/nodeconfig.cfg";
5599 95b003ff Origo
        foreach my $valref (values %idreg) {
5600
            my $nodeconfigfile = $valref->{'path'} . "/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
5601
            next if ($nodeconfigs{$nodeconfigfile}); # Node identities may share basedir and node config file
5602
            if (-e $nodeconfigfile) {
5603
                push @nodeconfigs, $nodeconfigfile;
5604
            }
5605
        }
5606
        untie %idreg;
5607
        foreach my $nodeconfig (@nodeconfigs) {
5608
            my $nodecfg = new Config::Simple($nodeconfig);
5609 e9af6c24 Origo
            my @ltenderlist = $nodecfg->param('STORAGE_SERVERS_ADDRESS_PATHS');
5610
            my $ltenders = join(", ", @ltenderlist);
5611
            next if ($ltenders =~ /10\.0\.0\.1:$newstordir$/ || $ltenders =~ /10\.0\.0\.1:$newstordir,/); # This entry already exists
5612
            #my @ltenderlist = split(/,\s*/, $ltenders);
5613
            #$ltenderlist[0] = "10.0.0.1:$newstordir";
5614
            unshift(@ltenderlist, "10.0.0.1:$newstordir");
5615
            $nodecfg->param('STORAGE_SERVERS_ADDRESS_PATHS', join(',', @ltenderlist));
5616
            my @ltenderpathslist = $nodecfg->param('STORAGE_SERVERS_LOCAL_PATHS');
5617
            my $ltenderpaths = join(", ", @ltenderpathslist);
5618
            #my @ltenderpathslist = split(/,\s*/, $ltenderpaths);
5619
            #$ltenderpathslist[0] = $newstordir;
5620
            unshift(@ltenderpathslist, $newstordir);
5621
            $nodecfg->param('STORAGE_SERVERS_LOCAL_PATHS', join(',', @ltenderpathslist));
5622 95b003ff Origo
            $nodecfg->save();
5623
        }
5624
        unless (`grep "$newstordir 10" /etc/exports`) {
5625
            `echo "$newstordir 10.0.0.0/255.255.255.0(sync,no_subtree_check,no_root_squash,rw)" >> /etc/exports`;
5626
            `/usr/sbin/exportfs -r`; #Reexport nfs shares
5627
        }
5628 e9af6c24 Origo
# We no longer undefine storage pools - we add them
5629
#        $oldstordir =~ s/\//\\\//g;
5630
#        `perl -pi -e 's/$oldstordir 10.*\\\n//s;' /etc/exports` if ($oldstordir);
5631
5632 95b003ff Origo
        `mkdir "$newstordir/common"` unless (-e "$newstordir/common");
5633
        `cp "$stordir/ejectcdrom.xml" "$newstordir/ejectcdrom.xml"` unless (-e "$newstordir/ejectcdrom.xml");
5634
        `cp "$stordir/mountvirtio.xml" "$newstordir/mountvirtio.xml"` unless (-e "$newstordir/mountvirtio.xml");
5635
        `cp "$stordir/dummy.qcow2" "$newstordir/dummy.qcow2"` unless (-e "$newstordir/dummy.qcow2");
5636
    }
5637
    Updatedownloads();
5638
5639 27512919 Origo
    # Update /etc/stabile/cgconfig.conf
5640 e9af6c24 Origo
    my $devs = $devices{$dev}->{dev};
5641
    my @pdevs = split(" ", $devs);
5642
    my $majmins;
5643
    foreach my $dev (@pdevs) {
5644
        # It seems that cgroups cannot handle individual partitions for blkio
5645
        my $physdev = $1 if ($dev =~ /(\w+)\d+/);
5646
        if ($physdev && -d "/sys/fs/cgroup" ) {
5647
            my $blkline = `lsblk -l /dev/$physdev`;
5648
            my $majmin = '';
5649
            $majmin = $1 if ($blkline =~ /$physdev +(\d+:\d+)/);
5650
            $postreply .= "Status=OK Setting cgroups block device to $majmin\n";
5651
            if ($majmin) {
5652
                $majmins .= ($majmins)?" $majmin":$majmin;
5653
            }
5654 95b003ff Origo
        }
5655
    }
5656 e9af6c24 Origo
    setCgroupsBlkDevice($majmins) if ($majmins);
5657 95b003ff Origo
5658
    $Stabile::Nodes::console = 1;
5659
    require "$Stabile::basedir/cgi/nodes.cgi";
5660
    $postreply .= Stabile::Nodes::do_reloadall('','reloadall');
5661
5662
    # Update config on stabile.io
5663
    require "$Stabile::basedir/cgi/users.cgi";
5664
    $Stabile::Users::console = 1;
5665
    Stabile::Users::Updateengine('', 'updateengine');
5666
5667
    my $msg = "OK Now using $newstordir for $type on $obj->{device}";
5668
    $main::updateUI->({tab=>'home', user=>$user, type=>'update', message=>$msg});
5669
    $postreply .= "Status=OK Now using $newstordir for $type on $dev\n";
5670
    return $postreply;
5671
}
5672
5673
sub Initializestorage {
5674
    my ($image, $action, $obj) = @_;
5675
    if ($help) {
5676
        return <<END
5677
GET:device,type,fs,activate,force:
5678
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.
5679
[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'.
5680 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).
5681 95b003ff Origo
END
5682
    }
5683
    my $fs = $obj->{fs} || 'zfs';
5684
    my $dev = $obj->{device};
5685
    my $force = $obj->{force};
5686
    my $activate = $obj->{activate};
5687
    my $type = 'backup';
5688
    $type = 'images' if ($obj->{type} eq 'images');
5689
    return "Status=Error Not allowed\n" unless ($isadmin);
5690
    my $backupdevice = Getbackupdevice('', 'getbackupdevice');
5691
    my $imagesdevice = Getimagesdevice('', 'getimagesdevice');
5692
    my $devices_obj = from_json(Liststoragedevices('', 'liststoragedevices'));
5693
    my %devices = %$devices_obj;
5694
    my $backupdev = $devices{$backupdevice}->{dev};
5695
    my $imagesdev = $devices{$imagesdevice}->{dev};
5696
    if (!$dev || !(-e "/dev/$dev")) {
5697
        $postreply = "Status=Error You must specify a valid device\n";
5698
        return $postreply;
5699
    }
5700
    if (($backupdev =~ /$dev/) || ($imagesdev =~ /$dev/)) {
5701
        $postreply = "Status=Error $dev is already in use as images or backup device\n";
5702
        return $postreply;
5703
    }
5704
    my $stordir = "/stabile-$type/$type";
5705
    if ($fs eq 'lvm') {
5706
        if ($type eq 'backup') {
5707
            $stordir = "/mnt/stabile/backups";
5708
        } else {
5709
            $stordir = "/mnt/stabile/images";
5710
        }
5711
    }
5712
    `chmod 666 /dev/zfs` if (-e '/dev/zfs'); # TODO: This should be removed once we upgrade to Bionic and zfs allow is supported
5713
5714
    my $vol = $type . "vol";
5715
    my $mounts = `cat /proc/mounts`;
5716
    my $zpools = `zpool list -v`;
5717
    my $pvs = `pvdisplay -c`;
5718 e9af6c24 Origo
    my $z;
5719 95b003ff Origo
    $postreply = '';
5720
    # Unconfigure existing zfs or lvm if $force and zfs/lvm configured or device is in use by either
5721
    if ($zpools =~ /stabile-$type/ || $mounts =~ /dev\/mapper\/stabile$type/ || $zpools =~ /$dev/ || $pvs =~ /$dev/) {
5722 e9af6c24 Origo
        if ($fs eq 'zfs' || $zpools =~ /$dev/) {
5723
            if ($force) { # ZFS needs to be unconfigured
5724
                my $umount = `LANG=en_US.UTF-8 umount -v "/stabile-$type/$type" 2>&1`;
5725 95b003ff Origo
                unless ($umount =~ /(unmounted|not mounted|no mount point)/) {
5726 e9af6c24 Origo
                    $postreply .= "Status=Error Unable to unmount zfs $type storage on $dev - $umount\n";
5727 95b003ff Origo
                    return $postreply;
5728
                }
5729
                `umount "/stabile-$type"`;
5730
                my $res = `zpool destroy "stabile-$type" 2>&1`;
5731
                chomp $res;
5732
                $postreply .= "Status=OK Unconfigured zfs - $res\n";
5733
            } else {
5734
                $postreply .= "Status=Error ZFS is already configured for $type\n";
5735 e9af6c24 Origo
                $z = 1;
5736
            #    return $postreply;
5737 95b003ff Origo
            }
5738
        }
5739
        if ($fs eq 'lvm' || $pvs =~ /$dev/) {
5740
            if ($force) {
5741
                my $udir = (($type eq 'backup')?"/mnt/stabile/backups":"/mnt/stabile/images");
5742
                my $umount = `umount -v "$udir" 2>&1`;
5743
                unless ($umount =~ /unmounted|not mounted|no mount point/) {
5744
                    $postreply .= "Status=Error Unable to unmount lvm $type storage - $umount\n";
5745
                    return $postreply;
5746
                }
5747
                my $res = `lvremove --yes /dev/stabile$type/$vol  2>&1`;
5748
                chomp $res;
5749
                $res .= `vgremove -f stabile$type 2>&1`;
5750
                chomp $res;
5751
                my $pdev = "/dev/$dev";
5752
                $pdev .= '1' unless ($pdev =~ /1$/);
5753
                $res .= `pvremove $pdev 2>&1`;
5754
                chomp $res;
5755
                $postreply .= "Status=OK Unconfigured lvm - $res\n";
5756
            } else {
5757
                $postreply .= "Status=Error LVM is already configured for $type\n";
5758
                return $postreply;
5759
            }
5760
        }
5761
    }
5762
    # Check if $dev is still in use
5763
    $mounts = `cat /proc/mounts`;
5764
    $zpools = `zpool list -v`;
5765
    $pvs = `pvdisplay -c`;
5766
    if ($mounts =~ /\/dev\/$dev/ || $pvs =~ /$dev/ || $zpools =~ /$dev/) {
5767 e9af6c24 Origo
        $postreply .= "Status=Error $dev is already in use - use force.\n";
5768 95b003ff Origo
        return $postreply;
5769
    }
5770
    # Now format
5771
    my $ispart = 1 if ($dev =~ /[a-zA-Z]+\d+/);
5772
    if ($fs eq 'zfs') { # ZFS was specified
5773
        $postreply = "Status=OK Initializing $dev disk with ZFS for $type...\n";
5774
        if (!$ispart) {
5775
            my $fres = `parted -s /dev/$dev mklabel GPT 2>&1`;
5776
            $postreply .= "Status=OK partitioned $dev: $fres\n";
5777
        }
5778 e9af6c24 Origo
        if ($z) { # zpool already created
5779
            `zpool add stabile-$type /dev/$dev`;
5780
        } else {
5781
            `zpool create stabile-$type /dev/$dev`;
5782
            `zfs create stabile-$type/$type`;
5783
            `zfs set atime=off stabile-$type/$type`;
5784
        }
5785 95b003ff Origo
#        if ($force) {
5786
#            $postreply .= "Status=OK Forcibly removing all files in $stordir to allow ZFS mount\n";
5787
#            `rm -r $stordir/*`;
5788
#        }
5789
#        `zfs set mountpoint=$stordir stabile-$type/$type`;
5790
        $stordir = "/stabile-$type/$type" if (`zfs mount stabile-$type/$type`);
5791
        `/bin/chmod 777 $stordir`;
5792
        $postreply .= "Status=OK Mounted stabile-$type/$type as $type storage on $stordir.\n";
5793
        if ($activate) {
5794
            $postreply .= "Status=OK Setting $type storage device to $dev.\n";
5795
            Setstoragedevice('', 'setstoragedevice', {device=>"stabile-$type", type=>$type});
5796
        }
5797
    } else { # Assume LVM
5798
        $postreply = "Status=OK Initializing $dev with LVM for $type...\n";
5799
        my $part = $dev;
5800
        if (!$ispart) {
5801
            $part = $dev.'1';
5802
            `/sbin/sfdisk -d /dev/$dev > /root/$dev-partition-sectors.save`;
5803
            my $fres = `sfdisk /dev/$dev << EOF\n;\nEOF`;
5804
            $postreply .= "Status=OK partitioned $dev: $fres\n";
5805
        }
5806
        `/sbin/vgcreate -f stabile$type /dev/$part`;
5807
        `/sbin/vgchange -a y stabile$type`;
5808
        my $totalpe =`/sbin/vgdisplay stabile$type | grep "Total PE"`;
5809
        $totalpe =~ /Total PE\s+(\d+)/;
5810
        my $size = $1 -2000;
5811
#        my $size = "10000";
5812
        if ($size <100) {
5813
            $postreply .= "Status=Error Volume is too small to make sense...\n";
5814
            return $postreply;
5815
        }
5816
        my $vol = $type . "vol";
5817
        `/sbin/lvcreate --yes -l $size stabile$type -n $vol`;
5818
#        `/sbin/mkfs.ext4 /dev/stabile$type/$vol`;
5819
        `mkfs.btrfs /dev/stabile$type/$vol`;
5820
        my $mounted = `mount -v /dev/stabile$type/$vol $stordir`;
5821
        `chmod 777 $stordir`;
5822
        if ($mounted) {
5823
            $postreply .= "Status=OK Mounted /dev/stabile$type/$vol as $type storage on $stordir.\n";
5824
        } else {
5825
            $postreply .= "Status=Error Could not mount /dev/stabile$type/$vol as $type storage on $stordir.\n";
5826
        }
5827
        if ($activate){
5828
            Setstoragedevice('', 'setstoragedevice', {device=>"stabile$type-$type".'vol', type=>$type});
5829
        }
5830
    }
5831
    return $postreply;
5832
}
5833
5834
sub setCgroupsBlkDevice {
5835 e9af6c24 Origo
    my @majmins = split(" ", shift);
5836 27512919 Origo
    my $file = "/etc/stabile/cgconfig.conf";
5837
    my %options = (
5838
        blkio.throttle.read_bps_device => $valve_readlimit,
5839
        blkio.throttle.write_bps_device => $valve_writelimit,
5840
        blkio.throttle.read_iops_device => $valve_iopsreadlimit,
5841
        blkio.throttle.write_iops_device => $valve_iopswritelimit
5842
        );
5843
    my @groups = ('stabile', 'stabilevm');
5844 95b003ff Origo
    my @newlines;
5845 27512919 Origo
    foreach my $majmin (@majmins) {
5846
        foreach my $group (@groups) {
5847
            my $mline = qq|group $group {|; push @newlines, $mline;
5848
            my $mline = qq|    blkio {|; push @newlines, $mline;
5849
            foreach my $option (keys %options) {
5850
                my $mline = qq|        $option = "$majmin $options{$option}";|;
5851
                push @newlines, $mline;
5852 e9af6c24 Origo
            }
5853 27512919 Origo
            my $mline = qq|    }|; push @newlines, $mline;
5854
            my $mline = qq|}|; push @newlines, $mline;
5855 95b003ff Origo
        }
5856
    }
5857
    unless (open(FILE, "> $file")) {
5858
        $postreply .= "Status=Error Problem opening $file\n";
5859
        return $postreply;
5860
    }
5861
    print FILE join("\n", @newlines);
5862
    close(FILE);
5863
    return;
5864
}