From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 6BF4F60E3A for ; Mon, 19 Oct 2020 21:02:57 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 60CAD2F1AD for ; Mon, 19 Oct 2020 21:02:27 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id CC3D62F174 for ; Mon, 19 Oct 2020 21:02:24 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 9DA5E45E02 for ; Mon, 19 Oct 2020 21:02:24 +0200 (CEST) From: Stoiko Ivanov To: pmg-devel@lists.proxmox.com Date: Mon, 19 Oct 2020 21:02:05 +0200 Message-Id: <20201019190209.11495-9-s.ivanov@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201019190209.11495-1-s.ivanov@proxmox.com> References: <20201019190209.11495-1-s.ivanov@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.044 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pmg-devel] [RFC pmg-api 08/12] PBSTools: add systemd-timer helpers X-BeenThere: pmg-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Mail Gateway development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 19 Oct 2020 19:02:57 -0000 add helper methods to create systemd-timer units, which run backups to a PBS-Remote regularly. Unit-file handling taken from pve-storage/PVE/API2/Disks/Directory.pm Signed-off-by: Stoiko Ivanov --- debian/pmg-pbsbackup@.service | 6 ++ debian/rules | 1 + src/Makefile | 2 +- src/PMG/PBSTools.pm | 165 ++++++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 debian/pmg-pbsbackup@.service diff --git a/debian/pmg-pbsbackup@.service b/debian/pmg-pbsbackup@.service new file mode 100644 index 0000000..37aa23b --- /dev/null +++ b/debian/pmg-pbsbackup@.service @@ -0,0 +1,6 @@ +[Unit] +Description=Backup to PBS remote %I + +[Service] +Type=oneshot +ExecStart=/usr/bin/pmgbackup pbsjob run %I diff --git a/debian/rules b/debian/rules index bab4d98..5a2cf7a 100755 --- a/debian/rules +++ b/debian/rules @@ -20,6 +20,7 @@ override_dh_installinit: dh_systemd_enable --name=pmgspamreport pmgspamreport.service dh_systemd_enable --name=pmgreport pmgreport.service dh_systemd_enable --name=pmgsync pmgsync.service + dh_systemd_enable --no-enable --name=pmg-pbsbackup@ pmg-pbsbackup@.service override_dh_systemd_start: dh_systemd_start pmg-hourly.timer pmg-daily.timer pmgspamreport.timer pmgreport.timer diff --git a/src/Makefile b/src/Makefile index 7f9726b..a460048 100644 --- a/src/Makefile +++ b/src/Makefile @@ -15,7 +15,7 @@ CRONSCRIPTS = pmg-hourly pmg-daily CLI_CLASSES = $(addprefix PMG/CLI/, $(addsuffix .pm, ${CLITOOLS})) SERVICE_CLASSES = $(addprefix PMG/Service/, $(addsuffix .pm, ${SERVICES})) -SERVICE_UNITS = $(addprefix debian/, $(addsuffix .service, ${SERVICES})) +SERVICE_UNITS = $(addprefix debian/, $(addsuffix .service, ${SERVICES})) debian/pmg-pbsbackup@.service TIMER_UNITS = $(addprefix debian/, $(addsuffix .timer, ${CRONSCRIPTS} pmgspamreport pmgreport)) CLI_BINARIES = $(addprefix bin/, ${CLITOOLS} ${CLISCRIPTS} ${CRONSCRIPTS}) diff --git a/src/PMG/PBSTools.pm b/src/PMG/PBSTools.pm index 9efb2ce..fcef74a 100644 --- a/src/PMG/PBSTools.pm +++ b/src/PMG/PBSTools.pm @@ -298,4 +298,169 @@ sub status { return ($total, $free, $used, $active); } +# systemd timer +my $read_ini = sub { + my ($filename) = @_; + + my $content = file_get_contents($filename); + my @lines = split /\n/, $content; + + my $result = {}; + my $section; + + foreach my $line (@lines) { + $line = trim($line); + if ($line =~ m/^\[([^\]]+)\]/) { + $section = $1; + if (!defined($result->{$section})) { + $result->{$section} = {}; + } + } elsif ($line =~ m/^(.*?)=(.*)$/) { + my ($key, $val) = ($1, $2); + if (!$section) { + warn "key value pair found without section, skipping\n"; + next; + } + + if ($result->{$section}->{$key}) { + # make duplicate properties to arrays to keep the order + my $prop = $result->{$section}->{$key}; + if (ref($prop) eq 'ARRAY') { + push @$prop, $val; + } else { + $result->{$section}->{$key} = [$prop, $val]; + } + } else { + $result->{$section}->{$key} = $val; + } + } + # ignore everything else + } + + return $result; +}; + +my $write_ini = sub { + my ($ini, $filename) = @_; + + my $content = ""; + + foreach my $sname (sort keys %$ini) { + my $section = $ini->{$sname}; + + $content .= "[$sname]\n"; + + foreach my $pname (sort keys %$section) { + my $prop = $section->{$pname}; + + if (!ref($prop)) { + $content .= "$pname=$prop\n"; + } elsif (ref($prop) eq 'ARRAY') { + foreach my $val (@$prop) { + $content .= "$pname=$val\n"; + } + } else { + die "invalid property '$pname'\n"; + } + } + $content .= "\n"; + } + + file_set_contents($filename, $content); +}; + +sub get_schedules { + my ($param) = @_; + + my $result = []; + + my $systemd_dir = '/etc/systemd/system'; + + dir_glob_foreach($systemd_dir, '^pmg-pbsbackup@.+\.timer$', sub { + my ($filename) = @_; + my $remote; + if ($filename =~ /^pmg-pbsbackup\@(.+)\.timer$/) { + $remote = PVE::Systemd::unescape_unit($1); + } else { + die 'Unrecognized timer name!\n'; + } + + my $unitfile = "$systemd_dir/$filename"; + my $unit = $read_ini->($unitfile); + + push @$result, { + unitfile => $unitfile, + remote => $remote, + schedule => $unit->{'Timer'}->{'OnCalendar'}, + delay => $unit->{'Timer'}->{'RandomizedDelaySec'}, + }; + }); + + return $result; + +} + +sub create_schedule { + my ($remote, $schedule, $delay) = @_; + + my $unit_name = 'pmg-pbsbackup@' . PVE::Systemd::escape_unit($remote); + #my $service_unit = $unit_name . '.service'; + my $timer_unit = $unit_name . '.timer'; + my $timer_unit_path = "/etc/systemd/system/$timer_unit"; + + # create systemd timer + run_command(['systemd-analyze', 'calendar', $schedule], errmsg => "Invalid schedule specification", outfunc => sub {}); + run_command(['systemd-analyze', 'timespan', $delay], errmsg => "Invalid delay specification", outfunc => sub {}); + my $timer = { + 'Unit' => { + 'Description' => "Timer for PBS Backup to remote $remote", + }, + 'Timer' => { + 'OnCalendar' => $schedule, + 'RandomizedDelaySec' => $delay, + }, + 'Install' => { + 'WantedBy' => 'timers.target', + }, + }; + + eval { + $write_ini->($timer, $timer_unit_path); + run_command(['systemctl', 'daemon-reload']); + run_command(['systemctl', 'enable', $timer_unit]); + run_command(['systemctl', 'start', $timer_unit]); + + }; + if (my $err = $@) { + die "Creating backup schedule for $remote failed: $err\n"; + } + + return; +} + +sub delete_schedule { + my ($remote) = @_; + + my $schedules = get_schedules(); + + die "Schedule for $remote not found!\n" if !grep {$_->{remote} eq $remote} @$schedules; + + my $unit_name = 'pmg-pbsbackup@' . PVE::Systemd::escape_unit($remote); + my $service_unit = $unit_name . '.service'; + my $timer_unit = $unit_name . '.timer'; + my $timer_unit_path = "/etc/systemd/system/$timer_unit"; + + eval { + run_command(['systemctl', 'disable', $timer_unit]); + unlink($timer_unit_path) || die "delete '$timer_unit_path' failed - $!\n"; + run_command(['systemctl', 'daemon-reload']); + + }; + if (my $err = $@) { + die "Removing backup schedule for $remote failed: $err\n"; + } + + return; +} + 1; -- 2.20.1