Tuxis Misc

view proxmox/pmrb @ 12:43c3e457cac3

Relogin after each backup
author Mark Schouten <mark@tuxis.nl>
date Thu, 04 Jun 2015 14:56:51 +0200
parents 280adbc93f5b
children
line source
1 #!/usr/bin/perl -w
3 use constant {
4 TIMEZONE => 'Europe/Amsterdam'
5 };
7 use strict;
8 use Getopt::Long;
9 use PVE::API2Client;
10 use PVE::AccessControl;
11 use PVE::INotify;
12 use JSON;
13 use DateTime;
15 use Data::Dumper;
17 sub usage {
18 my $error = shift;
20 if (defined($error)) {
21 print "ERROR: $error\n\n\n";
22 }
24 print "
25 This script will provide you with rotating backups of your VMs. It will make
26 sure to create backups if the oldest backup is too old. However, it will not
27 start new backups if it is too late to avoid performanceissues with your
28 storage.
30 Options:
31 pmrb [--dry-run] [--verbose] [--notafter <HH:MM>] [--maxage <weeks>] [--storage <pool>] [--email <emailaddress>]
32 --dry-run Don't actually run the backup. Just show which machines would be backed up
33 --compress Enable compression of the images
34 --verbose Add some extra output
35 --notafter Do not start a backup after HH:MM
36 --maxage Run new backups if the last VM-backup is older than <weeks> weeks
37 --storage Don't autodetect any storagepool that allows backups, use <pool>
38 --email Send an email to <emailaddress> for each backup
39 --help This help
41 ";
43 if(defined($error)) {
44 exit(1);
45 }
46 }
48 my (%todo, $dryrun, $verbose, $notafter, $storagepool, $email, $help, $compress);
49 my $maxage = 1;
50 my $timelimit = DateTime->now(time_zone => TIMEZONE);
52 GetOptions (
53 "dry-run" => \$dryrun,
54 "verbose" => \$verbose,
55 "compress" => \$compress,
56 "help" => \$help,
57 "notafter=s" => \$notafter,
58 "maxage=s" => \$maxage,
59 "email=s" => \$email,
60 "storage=s" => \$storagepool ) or die(usage());
62 if (defined($help)) {
63 usage();
64 exit(0);
65 }
66 if (defined($notafter) && $notafter !~ m/^([0-1][0-9]|2[0-4]):[0-5][0-9]$/) {
67 die(usage("Incorrect value for notafter '".$notafter."'. Should be HH:MM"));
68 } elsif (defined($notafter) && $notafter ne "") {
69 my ($hour, $minute) = split(':', $notafter);
70 $timelimit->set(hour => $hour);
71 $timelimit->set(minute => $minute);
72 }
74 if (defined($maxage) && $maxage !~ m/^[0-9]+$/) {
75 die(usage("Incorrect value for maxage '".$maxage."'. Should be a number."));
76 }
78 if (defined($storagepool) && $storagepool !~ m/^[a-z][-a-z0-9_]+$/i) {
79 die(usage("Incorrect value for storage '".$storagepool."'. Should be an existing storagepool in Proxmox."));
80 }
82 my $hostname = PVE::INotify::read_file("hostname");
83 my $oldest = DateTime->now(time_zone => TIMEZONE)->subtract(weeks => $maxage);
85 # normally you use username/password,
86 # but we can simply create a ticket and CRSF token if we are root
87 my $ticket = PVE::AccessControl::assemble_ticket('root@pam');
88 my $csrftoken = PVE::AccessControl::assemble_csrf_prevention_token('root@pam');
90 my $conn = PVE::API2Client->new(
91 ticket => $ticket,
92 csrftoken => $csrftoken,
93 );
95 sub get_vms() {
96 my @ret;
98 if (defined($verbose)) {
99 print "Looking for VMs on node ".$hostname."\n";
100 }
101 my $res = $conn->get("api2/json/nodes/$hostname/qemu");
102 foreach my $vm (@{$res->{'data'}}) {
103 my $vmid = $vm->{'vmid'};
104 if (get_vm_state($vmid) eq "running") {
105 push @ret, $vmid;
106 }
107 }
109 return @ret;
110 }
112 sub get_backup_storage() {
113 my $pool;
115 my $res = $conn->get("api2/json/storage");
116 foreach my $spool (@{$res->{'data'}}) {
117 if (defined($storagepool)) {
118 if ($spool->{'storage'} eq $storagepool) {
119 if (defined($verbose)) {
120 print "Selecting storage from pool $storagepool, as dictated";
121 }
122 return {'name' => $spool->{'storage'},
123 'path' => $spool->{'path'}."/dump"};
124 }
125 } else {
126 if ($spool->{'content'} eq "backup") {
127 if (defined($verbose)) {
128 print "Autodetected storage from pool ".$spool->{'storage'}."\n";
129 }
130 return {'name' => $spool->{'storage'},
131 'path' => $spool->{'path'}."/dump"};
132 }
133 }
134 }
136 if (defined($storagepool)) {
137 usage("Could not find storagepool ".$storagepool." which you dictated");
138 }
139 return undef;
140 }
142 sub get_latest_backup($$) {
143 my $path = shift;
144 my $vmid = shift;
146 opendir(DIR, $path);
147 foreach (grep { /^vzdump-qemu-$vmid-.*\.vma(\.lzo)?/ } readdir(DIR) ) {
148 if (m#vzdump-qemu-$vmid-([0-9]{4})_([0-9]{2})_([0-9]{2})-([0-9]{2})_([0-9]{2})_([0-9]{2})\.vma(\.lzo)?#) {
149 closedir(DIR);
150 return DateTime->new(
151 year => $1,
152 month => $2,
153 day => $3,
154 hour => $4,
155 minute => $5,
156 second => $6,
157 time_zone => TIMEZONE);
158 }
159 }
161 return 'None';
162 }
164 sub get_vm_state($) {
165 my $vmid = shift;
167 my $res = $conn->get("api2/json/nodes/$hostname/qemu/$vmid/status/current");
168 return $res->{'data'}->{'status'};
169 }
171 sub run_backup($$) {
172 my $path = shift;
173 my $vmid = shift;
175 my @command;
177 if (get_vm_state($vmid) eq "running") {
178 @command = ('vzdump', $vmid, '--quiet', '1', '--mode', 'snapshot', '--storage', $path);
179 }
181 if (defined($compress)) {
182 push(@command, ('--compress', 'lzo'));
183 }
185 push(@command, ('--storage', $path));
187 if (defined($email)) {
188 push(@command, '--mailto');
189 push(@command, $email);
190 }
191 if (defined($dryrun)) {
192 print "DRY RUN: ".join(' ', @command)."\n";
193 } else {
194 print "Running backup for $vmid\n";
195 system(@command);
196 }
197 }
199 my $backuppool = get_backup_storage();
201 if (!defined($backuppool)) {
202 print "Cannot find a pool where we would look for backups\n";
203 exit 1;
204 }
206 my @vms = get_vms();
207 foreach my $vm (@vms) {
208 my $latest = get_latest_backup($backuppool->{'path'}, $vm);
209 if ($latest eq 'None') {
210 if (defined($verbose)) { print "Scheduling first backup for $vm\n"; }
211 $todo{$vm} = DateTime->from_epoch(epoch => 0);
212 next;
213 }
214 if (DateTime->compare($latest, $oldest) < 0) {
215 if (defined($verbose)) { print "Scheduling new backup for $vm\n"; }
216 $todo{$vm} = $latest;
217 next;
218 }
219 if (defined($verbose)) { print "$vm is current enough. Skipping\n"; }
220 }
222 foreach my $vm (sort { $todo{$a} <=> $todo{$b} } keys %todo) {
223 my $now = DateTime->now(time_zone => TIMEZONE);
224 if (defined($notafter) && DateTime->compare($timelimit, $now) < 0) {
225 print "Exiting. We're too late\n";
226 exit(0);
227 }
228 run_backup($backuppool->{'name'}, $vm);
229 $conn->update_ticket(PVE::AccessControl::assemble_ticket('root@pam'));
230 }