PVE/Storage/PBSPlugin.pm: start new proxmox backup server plugin

This commit is contained in:
Dietmar Maurer
2020-02-19 13:55:27 +01:00
committed by Thomas Lamprecht
parent 75815bf556
commit 271fe39460
4 changed files with 362 additions and 1 deletions

View File

@ -136,7 +136,7 @@ __PACKAGE__->register_method ({
my $password;
# always extract pw, else it gets written to the www-data readable scfg
if (my $tmp_pw = extract_param($param, 'password')) {
if ($type eq 'cifs' && $param->{username}) {
if (($type eq 'pbs') || ($type eq 'cifs' && $param->{username})) {
$password = $tmp_pw;
} else {
warn "ignore password parameter\n";

View File

@ -36,6 +36,7 @@ use PVE::Storage::GlusterfsPlugin;
use PVE::Storage::ZFSPoolPlugin;
use PVE::Storage::ZFSPlugin;
use PVE::Storage::DRBDPlugin;
use PVE::Storage::PBSPlugin;
# Storage API version. Icrement it on changes in storage API interface.
use constant APIVER => 3;
@ -58,6 +59,7 @@ PVE::Storage::GlusterfsPlugin->register();
PVE::Storage::ZFSPoolPlugin->register();
PVE::Storage::ZFSPlugin->register();
PVE::Storage::DRBDPlugin->register();
PVE::Storage::PBSPlugin->register();
# load third-party plugins
if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) {

View File

@ -12,6 +12,7 @@ SOURCES= \
ZFSPoolPlugin.pm \
ZFSPlugin.pm \
DRBDPlugin.pm \
PBSPlugin.pm \
LvmThinPlugin.pm
.PHONY: install

358
PVE/Storage/PBSPlugin.pm Normal file
View File

@ -0,0 +1,358 @@
package PVE::Storage::PBSPlugin;
# Plugin to access Proxmox Backup Server
use strict;
use warnings;
use POSIX qw(strftime);
use IO::File;
use HTTP::Request;
use LWP::UserAgent;
use JSON;
use Data::Dumper; # fixme: remove
use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
use PVE::Storage::Plugin;
use PVE::JSONSchema qw(get_standard_option);
use base qw(PVE::Storage::Plugin);
# Configuration
sub type {
return 'pbs';
}
sub plugindata {
return {
content => [ {backup => 1, none => 1}, { backup => 1 }],
};
}
sub properties {
return {
datastore => {
description => "Proxmox backup server datastore name.",
type => 'string',
},
# openssl s_client -connect <host>:8007 2>&1 |openssl x509 -fingerprint -sha256
fingerprint => get_standard_option('fingerprint-sha256'),
};
}
sub options {
return {
server => { fixed => 1 },
datastore => { fixed => 1 },
nodes => { optional => 1},
disable => { optional => 1},
content => { optional => 1},
username => { optional => 1 },
password => { optional => 1},
maxfiles => { optional => 1 },
fingerprint => { optional => 1 },
};
}
# Helpers
sub pbs_password_file_name {
my ($scfg, $storeid) = @_;
return "/etc/pve/priv/${storeid}.pw";
}
sub pbs_set_password {
my ($scfg, $storeid, $password) = @_;
my $pwfile = pbs_password_file_name($scfg, $storeid);
PVE::Tools::file_set_contents($pwfile, "$password\n");
}
sub pbs_delete_password {
my ($scfg, $storeid) = @_;
my $pwfile = pbs_password_file_name($scfg, $storeid);
unlink $pwfile;
}
sub pbs_get_password {
my ($scfg, $storeid) = @_;
my $pwfile = pbs_password_file_name($scfg, $storeid);
return PVE::Tools::file_read_firstline($pwfile);
}
sub run_raw_client_cmd {
my ($scfg, $storeid, $client_cmd, $param, %opts) = @_;
my $server = $scfg->{server};
my $datastore = $scfg->{datastore};
my $username = $scfg->{username} // 'root@pam';
my $userns_cmd = delete $opts{userns_cmd};
my $cmd = [];
push @$cmd, @$userns_cmd if defined($userns_cmd);
push @$cmd, "/usr/bin/proxmox-backup-client", $client_cmd;
push @$cmd, @$param if defined($param);
push @$cmd, "--repository", "$username\@$server:$datastore";
local $ENV{PBS_PASSWORD} = pbs_get_password($scfg, $storeid);
local $ENV{PBS_FINGERPRINT} = $scfg->{fingerprint};
if (my $logfunc = $opts{logfunc}) {
$logfunc->("run bps command: " . join(' ', @$cmd));
}
run_command($cmd, %opts);
}
sub run_client_cmd {
my ($scfg, $storeid, $client_cmd, $param, $no_output) = @_;
my $json_str = '';
my $outfunc = sub {
my $line = shift;
$json_str .= "$line\n";
};
$param = [] if !defined($param);
$param = [ $param ] if !ref($param);
$param = [@$param, '--output-format=json'] if !$no_output;
run_raw_client_cmd($scfg, $storeid, $client_cmd, $param,
outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
return undef if $no_output;
my $res = decode_json($json_str);
return $res;
}
# Storage implementation
sub on_add_hook {
my ($class, $storeid, $scfg, %param) = @_;
if (my $password = $param{password}) {
pbs_set_password($scfg, $storeid, $password);
}
}
sub on_delete_hook {
my ($class, $storeid, $scfg) = @_;
pbs_delete_password($scfg, $storeid);
}
sub parse_volname {
my ($class, $volname) = @_;
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)$!) {
my $btype = $1;
my $bid = $2;
my $btime = $3;
my $format = "pbs-$btype";
my $name = "$btype/$bid/$btime";
if ($bid =~ m/^\d+$/) {
return ('backup', $name, $bid, undef, undef, undef, $format);
} else {
return ('backup', $name, undef, undef, undef, undef, $format);
}
}
die "unable to parse PBS volume name '$volname'\n";
}
sub path {
my ($class, $scfg, $volname, $storeid, $snapname) = @_;
die "volume snapshot is not possible on pbs storage"
if defined($snapname);
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
my $server = $scfg->{server};
my $datastore = $scfg->{datastore};
my $username = $scfg->{username} // 'root@pam';
# artifical url - we currently do not use that anywhere
my $path = "pbs://$username\@$server:$datastore/$name";
return ($path, $vmid, $vtype);
}
sub create_base {
my ($class, $storeid, $scfg, $volname) = @_;
die "can't create base images in pbs storage\n";
}
sub clone_image {
my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
die "can't clone images in pbs storage\n";
}
sub alloc_image {
my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
die "can't allocate space in pbs storage\n";
}
sub free_image {
my ($class, $storeid, $scfg, $volname, $isBase) = @_;
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
run_client_cmd($scfg, $storeid, "forget", [ $name ], 1);
}
sub list_images {
my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
my $res = [];
return $res;
}
sub list_volumes {
my ($class, $storeid, $scfg, $vmid, $content_types) = @_;
my $res = [];
return $res if !grep { $_ eq 'backup' } @$content_types;
my $data = run_client_cmd($scfg, $storeid, "snapshots");
foreach my $item (@$data) {
my $btype = $item->{"backup-type"};
my $bid = $item->{"backup-id"};
my $btime = $item->{"backup-time"};
my $size = $item->{size} // 1;
next if !($btype eq 'vm' || $btype eq 'ct');
next if $bid !~ m/^\d+$/;
$btime = strftime("%FT%TZ", gmtime($btime));
my $volname = "backup/${btype}/${bid}/${btime}";
my $volid = "$storeid:$volname";
my $info = { volid => $volid , format => "pbs-$btype", size => $size, content => 'backup', vmid => int($bid) };
push @$res, $info;
}
return $res;
}
sub status {
my ($class, $storeid, $scfg, $cache) = @_;
my $total = 0;
my $free = 0;
my $used = 0;
my $active = 0;
eval {
my $res = run_client_cmd($scfg, $storeid, "status");
$active = 1;
$total = $res->{total};
$used = $res->{used};
$free = $res->{avail};
};
if (my $err = $@) {
warn $err;
}
return ($total, $free, $used, $active);
}
sub activate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
return 1;
}
sub deactivate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
return 1;
}
sub activate_volume {
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
die "volume snapshot is not possible on pbs device" if $snapname;
return 1;
}
sub deactivate_volume {
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
die "volume snapshot is not possible on pbs device" if $snapname;
return 1;
}
sub volume_size_info {
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
my ($vtype, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
my $data = run_client_cmd($scfg, $storeid, "files", [ $name ]);
my $size = 0;
foreach my $info (@$data) {
$size += $info->{size} if $info->{size};
}
my $used = $size;
return wantarray ? ($size, $format, $used, undef) : $size;
}
sub volume_resize {
my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
die "volume resize is not possible on pbs device";
}
sub volume_snapshot {
my ($class, $scfg, $storeid, $volname, $snap) = @_;
die "volume snapshot is not possible on pbs device";
}
sub volume_snapshot_rollback {
my ($class, $scfg, $storeid, $volname, $snap) = @_;
die "volume snapshot rollback is not possible on pbs device";
}
sub volume_snapshot_delete {
my ($class, $scfg, $storeid, $volname, $snap) = @_;
die "volume snapshot delete is not possible on pbs device";
}
sub volume_has_feature {
my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
return undef;
}
1;