]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/PBSPlugin.pm
drbd: comment that the builtin plugin is depreacated
[pve-storage.git] / PVE / Storage / PBSPlugin.pm
CommitLineData
271fe394
DM
1package PVE::Storage::PBSPlugin;
2
3# Plugin to access Proxmox Backup Server
4
5use strict;
6use warnings;
4133e6e2 7
76bb5feb 8use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);
76bb5feb 9use IO::File;
271fe394 10use JSON;
76bb5feb 11use POSIX qw(strftime ENOENT);
271fe394 12
2f9eb6dc 13use PVE::APIClient::LWP;
271fe394 14use PVE::JSONSchema qw(get_standard_option);
4133e6e2 15use PVE::Network;
53003cb5 16use PVE::PBSClient;
4133e6e2
TL
17use PVE::Storage::Plugin;
18use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach $IPV6RE);
271fe394
DM
19
20use base qw(PVE::Storage::Plugin);
21
22# Configuration
23
24sub type {
25 return 'pbs';
26}
27
28sub plugindata {
29 return {
30 content => [ {backup => 1, none => 1}, { backup => 1 }],
31 };
32}
33
34sub properties {
35 return {
36 datastore => {
4133e6e2 37 description => "Proxmox Backup Server datastore name.",
271fe394
DM
38 type => 'string',
39 },
40 # openssl s_client -connect <host>:8007 2>&1 |openssl x509 -fingerprint -sha256
41 fingerprint => get_standard_option('fingerprint-sha256'),
ce2e2733 42 'encryption-key' => {
1aeb322b 43 description => "Encryption key. Use 'autogen' to generate one automatically without passphrase.",
76bb5feb
WB
44 type => 'string',
45 },
4133e6e2
TL
46 port => {
47 description => "For non default port.",
48 type => 'integer',
49 minimum => 1,
50 maximum => 65535,
51 default => 8007,
2f9eb6dc 52 },
271fe394
DM
53 };
54}
55
56sub options {
57 return {
58 server => { fixed => 1 },
59 datastore => { fixed => 1 },
4133e6e2 60 port => { optional => 1 },
271fe394
DM
61 nodes => { optional => 1},
62 disable => { optional => 1},
63 content => { optional => 1},
64 username => { optional => 1 },
ce2e2733
TL
65 password => { optional => 1 },
66 'encryption-key' => { optional => 1 },
271fe394 67 maxfiles => { optional => 1 },
3353698f 68 'prune-backups' => { optional => 1 },
271fe394
DM
69 fingerprint => { optional => 1 },
70 };
71}
72
73# Helpers
74
75sub pbs_password_file_name {
76 my ($scfg, $storeid) = @_;
77
462537a2 78 return "/etc/pve/priv/storage/${storeid}.pw";
271fe394
DM
79}
80
81sub pbs_set_password {
82 my ($scfg, $storeid, $password) = @_;
83
84 my $pwfile = pbs_password_file_name($scfg, $storeid);
9e34813f 85 mkdir "/etc/pve/priv/storage";
271fe394
DM
86
87 PVE::Tools::file_set_contents($pwfile, "$password\n");
88}
89
90sub pbs_delete_password {
91 my ($scfg, $storeid) = @_;
92
93 my $pwfile = pbs_password_file_name($scfg, $storeid);
94
95 unlink $pwfile;
96}
97
98sub pbs_get_password {
99 my ($scfg, $storeid) = @_;
100
101 my $pwfile = pbs_password_file_name($scfg, $storeid);
102
103 return PVE::Tools::file_read_firstline($pwfile);
104}
105
76bb5feb
WB
106sub pbs_encryption_key_file_name {
107 my ($scfg, $storeid) = @_;
108
109 return "/etc/pve/priv/storage/${storeid}.enc";
110}
111
112sub pbs_set_encryption_key {
113 my ($scfg, $storeid, $key) = @_;
114
115 my $pwfile = pbs_encryption_key_file_name($scfg, $storeid);
116 mkdir "/etc/pve/priv/storage";
117
118 PVE::Tools::file_set_contents($pwfile, "$key\n");
119}
120
121sub pbs_delete_encryption_key {
122 my ($scfg, $storeid) = @_;
123
124 my $pwfile = pbs_encryption_key_file_name($scfg, $storeid);
125
4ef17e1f
TL
126 if (!unlink $pwfile) {
127 return if $! == ENOENT;
128 die "failed to delete encryption key! $!\n";
129 }
18cf6c9f 130 delete $scfg->{'encryption-key'};
76bb5feb
WB
131}
132
133sub pbs_get_encryption_key {
134 my ($scfg, $storeid) = @_;
135
136 my $pwfile = pbs_encryption_key_file_name($scfg, $storeid);
137
138 return PVE::Tools::file_get_contents($pwfile);
139}
140
141# Returns a file handle if there is an encryption key, or `undef` if there is not. Dies on error.
142sub pbs_open_encryption_key {
143 my ($scfg, $storeid) = @_;
144
145 my $encryption_key_file = pbs_encryption_key_file_name($scfg, $storeid);
146
147 my $keyfd;
148 if (!open($keyfd, '<', $encryption_key_file)) {
149 return undef if $! == ENOENT;
150 die "failed to open encryption key: $encryption_key_file: $!\n";
151 }
152
153 return $keyfd;
154}
155
8602fd56
FE
156sub print_volid {
157 my ($storeid, $btype, $bid, $btime) = @_;
158
159 my $time_str = strftime("%FT%TZ", gmtime($btime));
160 my $volname = "backup/${btype}/${bid}/${time_str}";
161
162 return "${storeid}:${volname}";
163}
271fe394 164
02cc5e10
WB
165my $USE_CRYPT_PARAMS = {
166 backup => 1,
167 restore => 1,
168 'upload-log' => 1,
169};
170
76bb5feb 171my sub do_raw_client_cmd {
02cc5e10
WB
172 my ($scfg, $storeid, $client_cmd, $param, %opts) = @_;
173
174 my $use_crypto = $USE_CRYPT_PARAMS->{$client_cmd};
271fe394 175
1574a590
TL
176 my $client_exe = '/usr/bin/proxmox-backup-client';
177 die "executable not found '$client_exe'! Proxmox backup client not installed?\n"
178 if ! -x $client_exe;
179
53003cb5 180 my $repo = PVE::PBSClient::get_repository($scfg);
271fe394
DM
181
182 my $userns_cmd = delete $opts{userns_cmd};
183
184 my $cmd = [];
185
186 push @$cmd, @$userns_cmd if defined($userns_cmd);
187
1574a590 188 push @$cmd, $client_exe, $client_cmd;
271fe394 189
76bb5feb
WB
190 # This must live in the top scope to not get closed before the `run_command`
191 my $keyfd;
02cc5e10 192 if ($use_crypto) {
76bb5feb
WB
193 if (defined($keyfd = pbs_open_encryption_key($scfg, $storeid))) {
194 my $flags = fcntl($keyfd, F_GETFD, 0)
195 // die "failed to get file descriptor flags: $!\n";
196 fcntl($keyfd, F_SETFD, $flags & ~FD_CLOEXEC)
197 or die "failed to remove FD_CLOEXEC from encryption key file descriptor\n";
198 push @$cmd, '--crypt-mode=encrypt', '--keyfd='.fileno($keyfd);
199 } else {
200 push @$cmd, '--crypt-mode=none';
201 }
202 }
203
271fe394
DM
204 push @$cmd, @$param if defined($param);
205
53003cb5 206 push @$cmd, "--repository", $repo;
271fe394
DM
207
208 local $ENV{PBS_PASSWORD} = pbs_get_password($scfg, $storeid);
209
210 local $ENV{PBS_FINGERPRINT} = $scfg->{fingerprint};
211
8b4c2a7e
DM
212 # no ascii-art on task logs
213 local $ENV{PROXMOX_OUTPUT_NO_BORDER} = 1;
214 local $ENV{PROXMOX_OUTPUT_NO_HEADER} = 1;
215
271fe394 216 if (my $logfunc = $opts{logfunc}) {
e6d1edcb 217 $logfunc->("run: " . join(' ', @$cmd));
271fe394
DM
218 }
219
220 run_command($cmd, %opts);
221}
222
76bb5feb
WB
223# FIXME: External perl code should NOT have access to this.
224#
225# There should be separate functions to
226# - make backups
227# - restore backups
228# - restore files
229# with a sane API
02cc5e10 230sub run_raw_client_cmd {
76bb5feb 231 my ($scfg, $storeid, $client_cmd, $param, %opts) = @_;
02cc5e10 232 return do_raw_client_cmd($scfg, $storeid, $client_cmd, $param, %opts);
76bb5feb
WB
233}
234
271fe394
DM
235sub run_client_cmd {
236 my ($scfg, $storeid, $client_cmd, $param, $no_output) = @_;
237
238 my $json_str = '';
fee2ece3 239 my $outfunc = sub { $json_str .= "$_[0]\n" };
271fe394
DM
240
241 $param = [] if !defined($param);
242 $param = [ $param ] if !ref($param);
243
244 $param = [@$param, '--output-format=json'] if !$no_output;
245
02cc5e10 246 do_raw_client_cmd($scfg, $storeid, $client_cmd, $param,
76bb5feb 247 outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
271fe394
DM
248
249 return undef if $no_output;
250
251 my $res = decode_json($json_str);
252
253 return $res;
254}
255
256# Storage implementation
257
c855ac15
DM
258sub extract_vzdump_config {
259 my ($class, $scfg, $volname, $storeid) = @_;
260
261 my ($vtype, $name, $vmid, undef, undef, undef, $format) = $class->parse_volname($volname);
262
263 my $config = '';
fee2ece3 264 my $outfunc = sub { $config .= "$_[0]\n" };
c855ac15
DM
265
266 my $config_name;
267 if ($format eq 'pbs-vm') {
268 $config_name = 'qemu-server.conf';
269 } elsif ($format eq 'pbs-ct') {
270 $config_name = 'pct.conf';
271 } else {
272 die "unable to extract configuration for backup format '$format'\n";
273 }
274
02cc5e10 275 do_raw_client_cmd($scfg, $storeid, 'restore', [ $name, $config_name, '-' ],
76bb5feb 276 outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
c855ac15
DM
277
278 return $config;
279}
280
8f26b391
FE
281sub prune_backups {
282 my ($class, $scfg, $storeid, $keep, $vmid, $type, $dryrun, $logfunc) = @_;
283
284 $logfunc //= sub { print "$_[1]\n" };
285
286 my $backups = $class->list_volumes($storeid, $scfg, $vmid, ['backup']);
287
288 $type = 'vm' if defined($type) && $type eq 'qemu';
289 $type = 'ct' if defined($type) && $type eq 'lxc';
290
291 my $backup_groups = {};
292 foreach my $backup (@{$backups}) {
293 (my $backup_type = $backup->{format}) =~ s/^pbs-//;
294
295 next if defined($type) && $backup_type ne $type;
296
297 my $backup_group = "$backup_type/$backup->{vmid}";
298 $backup_groups->{$backup_group} = 1;
299 }
300
301 my @param;
1b87f013
FE
302
303 my $keep_all = delete $keep->{'keep-all'};
304
305 if (!$keep_all) {
306 foreach my $opt (keys %{$keep}) {
307 next if $keep->{$opt} == 0;
308 push @param, "--$opt";
309 push @param, "$keep->{$opt}";
310 }
311 } else { # no need to pass anything to PBS
312 $keep = { 'keep-all' => 1 };
8f26b391
FE
313 }
314
315 push @param, '--dry-run' if $dryrun;
316
317 my $prune_list = [];
318 my $failed;
319
320 foreach my $backup_group (keys %{$backup_groups}) {
321 $logfunc->('info', "running 'proxmox-backup-client prune' for '$backup_group'")
322 if !$dryrun;
323 eval {
324 my $res = run_client_cmd($scfg, $storeid, 'prune', [ $backup_group, @param ]);
325
326 foreach my $backup (@{$res}) {
327 die "result from proxmox-backup-client is not as expected\n"
328 if !defined($backup->{'backup-time'})
329 || !defined($backup->{'backup-type'})
330 || !defined($backup->{'backup-id'})
331 || !defined($backup->{'keep'});
332
333 my $ctime = $backup->{'backup-time'};
334 my $type = $backup->{'backup-type'};
335 my $vmid = $backup->{'backup-id'};
336 my $volid = print_volid($storeid, $type, $vmid, $ctime);
337
338 push @{$prune_list}, {
339 ctime => $ctime,
340 mark => $backup->{keep} ? 'keep' : 'remove',
341 type => $type eq 'vm' ? 'qemu' : 'lxc',
342 vmid => $vmid,
343 volid => $volid,
344 };
345 }
346 };
347 if (my $err = $@) {
348 $logfunc->('err', "prune '$backup_group': $err\n");
349 $failed = 1;
350 }
351 }
352 die "error pruning backups - check log\n" if $failed;
353
354 return $prune_list;
355}
356
1aeb322b
TL
357my $autogen_encryption_key = sub {
358 my ($scfg, $storeid) = @_;
359 my $encfile = pbs_encryption_key_file_name($scfg, $storeid);
478609d3
TL
360 if (-f $encfile) {
361 rename $encfile, "$encfile.old";
362 }
4558cb6e
TL
363 my $cmd = ['proxmox-backup-client', 'key', 'create', '--kdf', 'none', $encfile];
364 run_command($cmd, errmsg => 'failed to create encryption key');
365 return PVE::Tools::file_get_contents($encfile);
1aeb322b
TL
366};
367
271fe394
DM
368sub on_add_hook {
369 my ($class, $storeid, $scfg, %param) = @_;
370
0b6b98d1
TL
371 my $res = {};
372
76bb5feb
WB
373 if (defined(my $password = $param{password})) {
374 pbs_set_password($scfg, $storeid, $password);
b494636a
DM
375 } else {
376 pbs_delete_password($scfg, $storeid);
377 }
76bb5feb 378
ce2e2733 379 if (defined(my $encryption_key = $param{'encryption-key'})) {
d2c47b38 380 my $decoded_key;
1aeb322b 381 if ($encryption_key eq 'autogen') {
0b6b98d1 382 $res->{'encryption-key'} = $autogen_encryption_key->($scfg, $storeid);
3cc2eb73 383 $decoded_key = decode_json($res->{'encryption-key'});
1aeb322b 384 } else {
d2c47b38
TL
385 $decoded_key = eval { decode_json($encryption_key) };
386 if ($@ || !exists($decoded_key->{data})) {
387 die "Value does not seems like a valid, JSON formatted encryption key!\n";
388 }
1aeb322b 389 pbs_set_encryption_key($scfg, $storeid, $encryption_key);
0b6b98d1 390 $res->{'encryption-key'} = $encryption_key;
1aeb322b 391 }
3cc2eb73 392 $scfg->{'encryption-key'} = $decoded_key->{fingerprint} || 1;
76bb5feb
WB
393 } else {
394 pbs_delete_encryption_key($scfg, $storeid);
395 }
0b6b98d1
TL
396
397 return $res;
b494636a
DM
398}
399
400sub on_update_hook {
401 my ($class, $storeid, $scfg, %param) = @_;
402
0b6b98d1
TL
403 my $res = {};
404
76bb5feb
WB
405 if (exists($param{password})) {
406 if (defined($param{password})) {
407 pbs_set_password($scfg, $storeid, $param{password});
408 } else {
409 pbs_delete_password($scfg, $storeid);
410 }
411 }
b494636a 412
ce2e2733
TL
413 if (exists($param{'encryption-key'})) {
414 if (defined(my $encryption_key = delete($param{'encryption-key'}))) {
d2c47b38 415 my $decoded_key;
1aeb322b 416 if ($encryption_key eq 'autogen') {
0b6b98d1 417 $res->{'encryption-key'} = $autogen_encryption_key->($scfg, $storeid);
3cc2eb73 418 $decoded_key = decode_json($res->{'encryption-key'});
1aeb322b 419 } else {
d2c47b38
TL
420 $decoded_key = eval { decode_json($encryption_key) };
421 if ($@ || !exists($decoded_key->{data})) {
422 die "Value does not seems like a valid, JSON formatted encryption key!\n";
423 }
1aeb322b 424 pbs_set_encryption_key($scfg, $storeid, $encryption_key);
0b6b98d1 425 $res->{'encryption-key'} = $encryption_key;
1aeb322b 426 }
3cc2eb73 427 $scfg->{'encryption-key'} = $decoded_key->{fingerprint} || 1;
76bb5feb
WB
428 } else {
429 pbs_delete_encryption_key($scfg, $storeid);
430 }
271fe394 431 }
0b6b98d1
TL
432
433 return $res;
271fe394
DM
434}
435
436sub on_delete_hook {
437 my ($class, $storeid, $scfg) = @_;
438
439 pbs_delete_password($scfg, $storeid);
76bb5feb 440 pbs_delete_encryption_key($scfg, $storeid);
f3ccd0ef
FE
441
442 return;
271fe394
DM
443}
444
445sub parse_volname {
446 my ($class, $volname) = @_;
447
448 if ($volname =~ m!^backup/([^\s_]+)/([^\s_]+)/([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z)$!) {
449 my $btype = $1;
450 my $bid = $2;
451 my $btime = $3;
452 my $format = "pbs-$btype";
453
454 my $name = "$btype/$bid/$btime";
455
456 if ($bid =~ m/^\d+$/) {
457 return ('backup', $name, $bid, undef, undef, undef, $format);
458 } else {
459 return ('backup', $name, undef, undef, undef, undef, $format);
460 }
461 }
462
463 die "unable to parse PBS volume name '$volname'\n";
464}
465
466sub path {
467 my ($class, $scfg, $volname, $storeid, $snapname) = @_;
468
469 die "volume snapshot is not possible on pbs storage"
470 if defined($snapname);
471
472 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
473
53003cb5 474 my $repo = PVE::PBSClient::get_repository($scfg);
271fe394
DM
475
476 # artifical url - we currently do not use that anywhere
53003cb5 477 my $path = "pbs://$repo/$name";
271fe394
DM
478
479 return ($path, $vmid, $vtype);
480}
481
482sub create_base {
483 my ($class, $storeid, $scfg, $volname) = @_;
484
485 die "can't create base images in pbs storage\n";
486}
487
488sub clone_image {
489 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
490
491 die "can't clone images in pbs storage\n";
492}
493
494sub alloc_image {
495 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
496
497 die "can't allocate space in pbs storage\n";
498}
499
500sub free_image {
501 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
502
503 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
504
505 run_client_cmd($scfg, $storeid, "forget", [ $name ], 1);
506}
507
508
509sub list_images {
510 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
511
512 my $res = [];
513
514 return $res;
515}
516
878fe017
TL
517my sub snapshot_files_encrypted {
518 my ($files) = @_;
519 return 0 if !$files;
520
521 my $any;
522 my $all = 1;
523 for my $file (@$files) {
524 my $fn = $file->{filename};
525 next if $fn eq 'client.log.blob' || $fn eq 'index.json.blob';
526
527 my $crypt = $file->{'crypt-mode'};
528
529 $all = 0 if !$crypt || $crypt ne 'encrypt';
530 $any ||= $crypt eq 'encrypt';
531 }
532 return $any && $all;
533}
534
271fe394
DM
535sub list_volumes {
536 my ($class, $storeid, $scfg, $vmid, $content_types) = @_;
537
538 my $res = [];
539
540 return $res if !grep { $_ eq 'backup' } @$content_types;
541
542 my $data = run_client_cmd($scfg, $storeid, "snapshots");
543
544 foreach my $item (@$data) {
545 my $btype = $item->{"backup-type"};
546 my $bid = $item->{"backup-id"};
545e127e 547 my $epoch = $item->{"backup-time"};
271fe394
DM
548 my $size = $item->{size} // 1;
549
550 next if !($btype eq 'vm' || $btype eq 'ct');
551 next if $bid !~ m/^\d+$/;
ddf7fdaa 552 next if defined($vmid) && $bid ne $vmid;
271fe394 553
8602fd56 554 my $volid = print_volid($storeid, $btype, $bid, $epoch);
271fe394 555
545e127e 556 my $info = {
c05b1a8c
TL
557 volid => $volid,
558 format => "pbs-$btype",
559 size => $size,
560 content => 'backup',
561 vmid => int($bid),
562 ctime => $epoch,
545e127e 563 };
271fe394 564
9778e5c2 565 $info->{verification} = $item->{verification} if defined($item->{verification});
6fef456c 566 $info->{notes} = $item->{comment} if defined($item->{comment});
878fe017
TL
567 if (defined($item->{fingerprint})) {
568 $info->{encrypted} = $item->{fingerprint};
569 } elsif (snapshot_files_encrypted($item->{files})) {
570 $info->{encrypted} = '1';
571 }
9778e5c2 572
271fe394
DM
573 push @$res, $info;
574 }
575
576 return $res;
577}
578
579sub status {
580 my ($class, $storeid, $scfg, $cache) = @_;
581
582 my $total = 0;
583 my $free = 0;
584 my $used = 0;
585 my $active = 0;
586
587 eval {
588 my $res = run_client_cmd($scfg, $storeid, "status");
589
590 $active = 1;
591 $total = $res->{total};
592 $used = $res->{used};
f155c912
TL
593 $free = $res->{avail};
594 };
271fe394
DM
595 if (my $err = $@) {
596 warn $err;
597 }
598
599 return ($total, $free, $used, $active);
600}
601
2f9eb6dc
TL
602# TODO: use a client with native rust/proxmox-backup bindings to profit from
603# API schema checks and types
604my sub pbs_api_connect {
605 my ($scfg, $password) = @_;
606
607 my $params = {};
608
609 my $user = $scfg->{username} // 'root@pam';
610
611 if (my $tokenid = PVE::AccessControl::pve_verify_tokenid($user, 1)) {
ab90c3b1 612 $params->{apitoken} = "PBSAPIToken=${tokenid}:${password}";
2f9eb6dc
TL
613 } else {
614 $params->{password} = $password;
615 $params->{username} = $user;
616 }
617
618 if (my $fp = $scfg->{fingerprint}) {
619 $params->{cached_fingerprints}->{uc($fp)} = 1;
620 }
621
622 my $conn = PVE::APIClient::LWP->new(
623 %$params,
624 host => $scfg->{server},
625 port => $scfg->{port} // 8007,
626 timeout => 7, # cope with a 401 (3s api delay) and high latency
627 cookie_name => 'PBSAuthCookie',
628 );
629
630 return $conn;
631}
632
8b62ac6a
TL
633# can also be used for not (yet) added storages, pass $scfg with
634# {
635# server
636# user
637# port (optional default to 8007)
638# fingerprint (optional for trusted certs)
639# }
640sub scan_datastores {
641 my ($scfg, $password) = @_;
642
643 my $conn = pbs_api_connect($scfg, $password);
644
645 my $response = eval { $conn->get('/api2/json/admin/datastore', {}) };
646 die "error fetching datastores - $@" if $@;
647
648 return $response;
649}
650
271fe394
DM
651sub activate_storage {
652 my ($class, $storeid, $scfg, $cache) = @_;
bb0a0f96 653
2cd10f58 654 my $password = pbs_get_password($scfg, $storeid);
bb0a0f96 655
2cd10f58
TL
656 my $datastores = eval { scan_datastores($scfg, $password) };
657 die "$storeid: $@" if $@;
658
659 my $datastore = $scfg->{datastore};
660
661 for my $ds (@$datastores) {
662 if ($ds->{store} eq $datastore) {
663 return 1;
664 }
665 }
666
667 die "$storeid: Cannot find datastore '$datastore', check permissions and existance!\n";
271fe394
DM
668}
669
670sub deactivate_storage {
671 my ($class, $storeid, $scfg, $cache) = @_;
672 return 1;
673}
674
675sub activate_volume {
676 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
677
678 die "volume snapshot is not possible on pbs device" if $snapname;
679
680 return 1;
681}
682
683sub deactivate_volume {
684 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
685
686 die "volume snapshot is not possible on pbs device" if $snapname;
687
688 return 1;
689}
690
45e93e6d
DC
691sub get_volume_notes {
692 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
693
694 my (undef, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
695
696 my $data = run_client_cmd($scfg, $storeid, "snapshot", [ "notes", "show", $name ]);
697
698 return $data->{notes};
699}
700
701sub update_volume_notes {
702 my ($class, $scfg, $storeid, $volname, $notes, $timeout) = @_;
703
704 my (undef, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
705
706 run_client_cmd($scfg, $storeid, "snapshot", [ "notes", "update", $name, $notes ], 1);
707
708 return undef;
709}
710
271fe394
DM
711sub volume_size_info {
712 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
713
714 my ($vtype, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
715
716 my $data = run_client_cmd($scfg, $storeid, "files", [ $name ]);
717
718 my $size = 0;
719 foreach my $info (@$data) {
720 $size += $info->{size} if $info->{size};
721 }
722
723 my $used = $size;
724
725 return wantarray ? ($size, $format, $used, undef) : $size;
726}
727
728sub volume_resize {
729 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
730 die "volume resize is not possible on pbs device";
731}
732
733sub volume_snapshot {
734 my ($class, $scfg, $storeid, $volname, $snap) = @_;
735 die "volume snapshot is not possible on pbs device";
736}
737
738sub volume_snapshot_rollback {
739 my ($class, $scfg, $storeid, $volname, $snap) = @_;
740 die "volume snapshot rollback is not possible on pbs device";
741}
742
743sub volume_snapshot_delete {
744 my ($class, $scfg, $storeid, $volname, $snap) = @_;
745 die "volume snapshot delete is not possible on pbs device";
746}
747
748sub volume_has_feature {
749 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
750
751 return undef;
752}
753
7541;