]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/API2/Backup.pm
ui: fix align mode of two column container
[pve-manager.git] / PVE / API2 / Backup.pm
index b1ad6afc7c662ea611b4465ce36dedd236b6631e..8814032382ea3729d7050d957250135f76581fb6 100644 (file)
@@ -15,7 +15,9 @@ use PVE::Storage;
 use PVE::Exception qw(raise_param_exc);
 use PVE::VZDump;
 use PVE::VZDump::Common;
+use PVE::VZDump::JobBase;
 use PVE::Jobs; # for VZDump Jobs
+use Proxmox::RS::CalendarEvent;
 
 use base qw(PVE::RESTHandler);
 
@@ -38,14 +40,60 @@ my $vzdump_job_id_prop = {
     maxLength => 50
 };
 
-my $assert_param_permission = sub {
-    my ($param, $user) = @_;
+# NOTE: also used by the vzdump API call.
+sub assert_param_permission_common {
+    my ($rpcenv, $user, $param, $is_delete) = @_;
     return if $user eq 'root@pam'; # always OK
 
     for my $key (qw(tmpdir dumpdir script)) {
        raise_param_exc({ $key => "Only root may set this option."}) if exists $param->{$key};
     }
-};
+
+    if (grep { defined($param->{$_}) } qw(bwlimit ionice performance)) {
+       $rpcenv->check($user, "/", [ 'Sys.Modify' ]);
+    }
+
+    if ($param->{fleecing} && !$is_delete) {
+       my $fleecing = PVE::VZDump::parse_fleecing($param) // {};
+       $rpcenv->check($user, "/storage/$fleecing->{storage}", [ 'Datastore.AllocateSpace' ])
+           if $fleecing->{storage};
+    }
+}
+
+my sub assert_param_permission_create {
+    my ($rpcenv, $user, $param) = @_;
+    return if $user eq 'root@pam'; # always OK
+
+    assert_param_permission_common($rpcenv, $user, $param);
+
+    if (my $storeid = PVE::VZDump::get_storage_param($param)) {
+       $rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]);
+    }
+}
+
+my sub assert_param_permission_update {
+    my ($rpcenv, $user, $update, $delete, $current) = @_;
+    return if $user eq 'root@pam'; # always OK
+
+    assert_param_permission_common($rpcenv, $user, $update);
+    assert_param_permission_common($rpcenv, $user, $delete, 1);
+
+    if ($update->{storage}) {
+       $rpcenv->check($user, "/storage/$update->{storage}", [ 'Datastore.Allocate' ])
+    } elsif ($delete->{storage}) {
+       $rpcenv->check($user, "/storage/local", [ 'Datastore.Allocate' ]);
+    }
+
+    return if !$current; # early check done
+
+    if ($current->{dumpdir}) {
+       die "only root\@pam may edit jobs with a 'dumpdir' option.";
+    } else {
+       if (my $storeid = PVE::VZDump::get_storage_param($current)) {
+           $rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]);
+       }
+    }
+}
 
 my $convert_to_schedule = sub {
     my ($job) = @_;
@@ -61,13 +109,14 @@ my $convert_to_schedule = sub {
 };
 
 my $schedule_param_check = sub {
-    my ($param) = @_;
+    my ($param, $required) = @_;
     if (defined($param->{schedule})) {
        if (defined($param->{starttime})) {
            raise_param_exc({ starttime => "'starttime' and 'schedule' cannot both be set" });
        }
     } elsif (!defined($param->{starttime})) {
-       raise_param_exc({ schedule => "neither 'starttime' nor 'schedule' were set" });
+       raise_param_exc({ schedule => "neither 'starttime' nor 'schedule' were set" })
+           if $required;
     } else {
        $param->{schedule} = $convert_to_schedule->($param);
     }
@@ -117,6 +166,20 @@ __PACKAGE__->register_method({
        foreach my $jobid (sort { $order->{$a} <=> $order->{$b} } keys %$jobs) {
            my $job = $jobs->{$jobid};
            next if $job->{type} ne 'vzdump';
+
+           if (my $schedule = $job->{schedule}) {
+               # vzdump jobs are cluster wide, there maybe was no local run
+               # so simply calculate from now
+               my $last_run = time();
+               my $calspec = Proxmox::RS::CalendarEvent->new($schedule);
+               my $next_run = $calspec->compute_next_event($last_run);
+               $job->{'next-run'} = $next_run if defined($next_run);
+           }
+
+           # FIXME remove in PVE 8.0?
+           # backwards compat: before moving the job registry to pve-common, id was auto-injected
+           $job->{id} = $jobid;
+
            push @$res, $job;
        }
 
@@ -168,6 +231,13 @@ __PACKAGE__->register_method({
                description => "Enable or disable the job.",
                default => '1',
            },
+           'repeat-missed' => {
+               optional => 1,
+               type => 'boolean',
+               description => "If true, the job will be run as soon as possible if it was missed".
+                   " while the scheduler was not running.",
+               default => 0,
+           },
            comment => {
                optional => 1,
                type => 'string',
@@ -183,14 +253,14 @@ __PACKAGE__->register_method({
        my $rpcenv = PVE::RPCEnvironment::get();
        my $user = $rpcenv->get_user();
 
-       $assert_param_permission->($param, $user);
+       assert_param_permission_create($rpcenv, $user, $param);
 
        if (my $pool = $param->{pool}) {
            $rpcenv->check_pool_exist($pool);
            $rpcenv->check($user, "/pool/$pool", ['VM.Backup']);
        }
 
-       $schedule_param_check->($param);
+       $schedule_param_check->($param, 1);
 
        $param->{enabled} = 1 if !defined($param->{enabled});
 
@@ -204,12 +274,11 @@ __PACKAGE__->register_method({
                if $data->{ids}->{$id};
 
            PVE::VZDump::verify_vzdump_parameters($param, 1);
-           my $plugin = PVE::Jobs::Plugin->lookup('vzdump');
-           my $opts = $plugin->check_config($id, $param, 1, 1);
+           my $opts = PVE::VZDump::JobBase->check_config($id, $param, 1, 1);
 
            $data->{ids}->{$id} = $opts;
 
-           PVE::Jobs::create_job($id, 'vzdump');
+           PVE::Jobs::create_job($id, 'vzdump', $opts);
 
            cfs_write_file('jobs.cfg', $data);
        });
@@ -254,7 +323,12 @@ __PACKAGE__->register_method({
 
        my $jobs_data = cfs_read_file('jobs.cfg');
        my $job = $jobs_data->{ids}->{$param->{id}};
-       return $job if $job && $job->{type} eq 'vzdump';
+       if ($job && $job->{type} eq 'vzdump') {
+           # FIXME remove in PVE 8.0?
+           # backwards compat: before moving the job registry to pve-common, id was auto-injected
+           $job->{id} = $param->{id};
+           return $job;
+       }
 
        raise_param_exc({ id => "No such job '$param->{id}'" });
 
@@ -369,6 +443,13 @@ __PACKAGE__->register_method({
                description => "Enable or disable the job.",
                default => '1',
            },
+           'repeat-missed' => {
+               optional => 1,
+               type => 'boolean',
+               description => "If true, the job will be run as soon as possible if it was missed".
+                   " while the scheduler was not running.",
+               default => 0,
+           },
            comment => {
                optional => 1,
                type => 'string',
@@ -384,7 +465,11 @@ __PACKAGE__->register_method({
        my $rpcenv = PVE::RPCEnvironment::get();
        my $user = $rpcenv->get_user();
 
-       $assert_param_permission->($param, $user);
+       my $id = extract_param($param, 'id');
+       my $delete = extract_param($param, 'delete');
+       $delete = { map { $_ => 1 } PVE::Tools::split_list($delete) } if $delete;
+
+       assert_param_permission_update($rpcenv, $user, $param, $delete);
 
        if (my $pool = $param->{pool}) {
            $rpcenv->check_pool_exist($pool);
@@ -393,23 +478,16 @@ __PACKAGE__->register_method({
 
        $schedule_param_check->($param);
 
-       my $id = extract_param($param, 'id');
-       my $delete = extract_param($param, 'delete');
-       if ($delete) {
-           $delete = [PVE::Tools::split_list($delete)];
-       }
-
        my $update_job = sub {
            my $data = cfs_read_file('vzdump.cron');
            my $jobs_data = cfs_read_file('jobs.cfg');
 
            my $jobs = $data->{jobs} || [];
 
-           die "no options specified\n" if !scalar(keys %$param);
+           die "no options specified\n" if !scalar(keys $param->%*) && !scalar(keys $delete->%*);
 
            PVE::VZDump::verify_vzdump_parameters($param);
-           my $plugin = PVE::Jobs::Plugin->lookup('vzdump');
-           my $opts = $plugin->check_config($id, $param, 0, 1);
+           my $opts = PVE::VZDump::JobBase->check_config($id, $param, 0, 1);
 
            # try to find it in old vzdump.cron and convert it to a job
            my ($idx) = grep { $jobs->[$_]->{id} eq $id } (0 .. scalar(@$jobs) - 1);
@@ -428,19 +506,21 @@ __PACKAGE__->register_method({
                die "no such vzdump job\n" if !$job || $job->{type} ne 'vzdump';
            }
 
-           foreach my $k (@$delete) {
-               if (!PVE::VZDump::option_exists($k) && $k ne 'comment') {
+           assert_param_permission_update($rpcenv, $user, $param, $delete, $job);
+
+           my $deletable = {
+               comment => 1,
+               'repeat-missed' => 1,
+           };
+
+           for my $k (keys $delete->%*) {
+               if (!PVE::VZDump::option_exists($k) && !$deletable->{$k}) {
                    raise_param_exc({ delete => "unknown option '$k'" });
                }
 
                delete $job->{$k};
            }
 
-           my $schedule_updated = 0;
-           if ($param->{schedule} ne $job->{schedule}) {
-               $schedule_updated = 1;
-           }
-
            foreach my $k (keys %$param) {
                $job->{$k} = $param->{$k};
            }
@@ -462,14 +542,13 @@ __PACKAGE__->register_method({
 
            PVE::VZDump::verify_vzdump_parameters($job, 1);
 
-           if ($schedule_updated) {
-               PVE::Jobs::updated_job_schedule($id, 'vzdump');
-           }
-
            if (defined($idx)) {
                cfs_write_file('vzdump.cron', $data);
            }
            cfs_write_file('jobs.cfg', $jobs_data);
+
+           PVE::Jobs::detect_changed_runtime_props($id, 'vzdump', $job);
+
            return;
        };
        cfs_lock_file('vzdump.cron', undef, sub {