use PVE::INotify;
use PVE::RPCEnvironment;
use PVE::Storage;
+use PVE::Tools qw(extract_param);
use PVE::API2::Storage::Config;
use PVE::API2::Storage::Content;
+use PVE::API2::Storage::PruneBackups;
use PVE::API2::Storage::Status;
use PVE::JSONSchema qw(get_standard_option);
use PVE::PTY;
return PVE::PTY::read_password("Enter Password: ");
},
});
+
+ my $enc_key_map = {
+ name => 'encryption-key',
+ desc => 'a file containing an encryption key, or the special value "autogen"',
+ func => sub {
+ my ($value) = @_;
+ return $value if $value eq 'autogen';
+ return PVE::Tools::file_get_contents($value);
+ }
+ };
+
+
my $mapping = {
'cifsscan' => [ $password_map ],
- 'create' => [ $password_map ],
+ 'create' => [ $password_map, $enc_key_map ],
+ 'update' => [ $password_map, $enc_key_map ],
};
return $mapping->{$name};
}
PVE::RPCEnvironment->setup_default_cli_env();
}
+__PACKAGE__->register_method ({
+ name => 'apiinfo',
+ path => 'apiinfo',
+ method => 'GET',
+ description => "Returns APIVER and APIAGE.",
+ parameters => {
+ additionalProperties => 0,
+ properties => {},
+ },
+ returns => {
+ type => 'object',
+ properties => {
+ apiver => { type => 'integer' },
+ apiage => { type => 'integer' },
+ },
+ },
+ code => sub {
+ return {
+ apiver => PVE::Storage::APIVER,
+ apiage => PVE::Storage::APIAGE,
+ };
+ }
+});
+
__PACKAGE__->register_method ({
name => 'path',
path => 'path',
my $print_content = sub {
my ($list) = @_;
- my $maxlenname = 0;
+ my ($maxlenname, $maxsize) = (0, 0);
foreach my $info (@$list) {
-
my $volid = $info->{volid};
my $sidlen = length ($volid);
$maxlenname = $sidlen if $sidlen > $maxlenname;
+ $maxsize = $info->{size} if ($info->{size} // 0) > $maxsize;
}
+ my $sizemaxdigits = length($maxsize);
+
+ my $basefmt = "%-${maxlenname}s %-7s %-9s %${sizemaxdigits}s";
+ printf "$basefmt %s\n", "Volid", "Format", "Type", "Size", "VMID";
foreach my $info (@$list) {
next if !$info->{vmid};
my $volid = $info->{volid};
- printf "%-${maxlenname}s %5s %10d %d\n", $volid,
- $info->{format}, $info->{size}, $info->{vmid};
+ printf "$basefmt %d\n", $volid, $info->{format}, $info->{content}, $info->{size}, $info->{vmid};
}
foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) {
next if $info->{vmid};
my $volid = $info->{volid};
- printf "%-${maxlenname}s %5s %10d\n", $volid,
- $info->{format}, $info->{size};
+ printf "$basefmt\n", $volid, $info->{format}, $info->{content}, $info->{size};
}
};
name => 'export',
path => 'export',
method => 'GET',
- description => "Export a volume.",
+ description => "Used internally to export a volume.",
protected => 1,
parameters => {
additionalProperties => 0,
name => 'import',
path => 'import',
method => 'PUT',
- description => "Import a volume.",
+ description => "Used internally to import a volume.",
protected => 1,
parameters => {
additionalProperties => 0,
enum => $KNOWN_EXPORT_FORMATS,
},
filename => {
- description => "Source file name",
+ description => "Source file name. For '-' stdin is used, the " .
+ "tcp://<IP-or-CIDR> format allows to use a TCP connection as input. " .
+ "Else, the file is treated as common file.",
type => 'string',
},
base => {
maxLength => 80,
optional => 1,
},
+ 'allow-rename' => {
+ description => "Choose a new volume ID if the requested " .
+ "volume ID already exists, instead of throwing an error.",
+ type => 'boolean',
+ optional => 1,
+ default => 0,
+ },
},
},
- returns => { type => 'null' },
+ returns => { type => 'string' },
code => sub {
my ($param) = @_;
my $infh;
if ($filename eq '-') {
$infh = \*STDIN;
+ } elsif ($filename =~ m!^tcp://(([^/]+)(/\d+)?)$!) {
+ my ($cidr, $ip, $subnet) = ($1, $2, $3);
+ if ($subnet) { # got real CIDR notation, not just IP
+ my $ips = PVE::Network::get_local_ip_from_cidr($cidr);
+ die "Unable to get any local IP address in network '$cidr'\n"
+ if scalar(@$ips) < 1;
+ die "Got multiple local IP address in network '$cidr'\n"
+ if scalar(@$ips) > 1;
+
+ $ip = $ips->[0];
+ }
+ my $family = PVE::Tools::get_host_address_family($ip);
+ my $port = PVE::Tools::next_migrate_port($family, $ip);
+
+ my $sock_params = {
+ Listen => 1,
+ ReuseAddr => 1,
+ Proto => &Socket::IPPROTO_TCP,
+ GetAddrInfoFlags => 0,
+ LocalAddr => $ip,
+ LocalPort => $port,
+ };
+ my $socket = IO::Socket::IP->new(%$sock_params)
+ or die "failed to open socket: $!\n";
+
+ print "$ip\n$port\n"; # tell remote where to connect
+ *STDOUT->flush();
+
+ my $prev_alarm = alarm 0;
+ local $SIG{ALRM} = sub { die "timed out waiting for client\n" };
+ alarm 30;
+ my $client = $socket->accept; # Wait for a client
+ alarm $prev_alarm;
+ close($socket);
+
+ $infh = \*$client;
} else {
sysopen($infh, $filename, O_RDONLY)
or die "open($filename): $!\n";
my $cfg = PVE::Storage::config();
my $volume = $param->{volume};
my $delete = $param->{'delete-snapshot'};
- PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
- $param->{base}, $param->{'with-snapshots'});
- PVE::Storage::volume_snapshot_delete($cfg, $volume, $delete)
+ my $imported_volid = PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
+ $param->{base}, $param->{'with-snapshots'}, $param->{'allow-rename'});
+ PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete)
if defined($delete);
- return;
+ return $imported_volid;
}
});
my $res = PVE::Storage::scan_nfs($server);
my $data = [];
- foreach my $k (keys %$res) {
+ foreach my $k (sort keys %$res) {
push @$data, { path => $k, options => $res->{$k} };
}
return $data;
my $res = PVE::Storage::scan_cifs($server, $username, $password, $domain);
my $data = [];
- foreach my $k (keys %$res) {
- next if $k =~ m/NT_STATUS_/;
+ foreach my $k (sort keys %$res) {
push @$data, { share => $k, description => $res->{$k} };
}
type => 'array',
items => {
type => "object",
- properties => {
+ properties => {
volname => {
description => "The volume name.",
type => 'string',
my $res = PVE::Storage::scan_nfs($server);
my $data = [];
- foreach my $path (keys %$res) {
+ foreach my $path (sort keys %$res) {
if ($path =~ m!^/([^\s/]+)$!) {
push @$data, { volname => $1 };
}
my $res = PVE::Storage::scan_iscsi($param->{portal});
my $data = [];
- foreach my $k (keys %$res) {
+ foreach my $k (sort keys %$res) {
push @$data, { target => $k, portal => join(',', @{$res->{$k}}) };
}
return PVE::Storage::scan_zfs();
}});
+__PACKAGE__->register_method ({
+ name => 'prunebackups',
+ path => 'prunebackups',
+ method => 'GET',
+ description => "Prune backups. Only those using the standard naming scheme are considered. " .
+ "If no keep options are specified, those from the storage configuration are used.",
+ protected => 1,
+ proxyto => 'node',
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ 'dry-run' => {
+ description => "Only show what would be pruned, don't delete anything.",
+ type => 'boolean',
+ optional => 1,
+ },
+ node => get_standard_option('pve-node'),
+ storage => get_standard_option('pve-storage-id', {
+ completion => \&PVE::Storage::complete_storage_enabled,
+ }),
+ %{$PVE::Storage::Plugin::prune_backups_format},
+ type => {
+ description => "Either 'qemu' or 'lxc'. Only consider backups for guests of this type.",
+ type => 'string',
+ optional => 1,
+ enum => ['qemu', 'lxc'],
+ },
+ vmid => get_standard_option('pve-vmid', {
+ description => "Only consider backups for this guest.",
+ optional => 1,
+ completion => \&PVE::Cluster::complete_vmid,
+ }),
+ },
+ },
+ returns => {
+ type => 'object',
+ properties => {
+ dryrun => {
+ description => 'If it was a dry run or not. The list will only be defined in that case.',
+ type => 'boolean',
+ },
+ list => {
+ type => 'array',
+ items => {
+ type => 'object',
+ properties => {
+ volid => {
+ description => "Backup volume ID.",
+ type => 'string',
+ },
+ 'ctime' => {
+ description => "Creation time of the backup (seconds since the UNIX epoch).",
+ type => 'integer',
+ },
+ 'mark' => {
+ description => "Whether the backup would be kept or removed. For backups that don't " .
+ "use the standard naming scheme, it's 'protected'.",
+ type => 'string',
+ },
+ type => {
+ description => "One of 'qemu', 'lxc', 'openvz' or 'unknown'.",
+ type => 'string',
+ },
+ 'vmid' => {
+ description => "The VM the backup belongs to.",
+ type => 'integer',
+ optional => 1,
+ },
+ },
+ },
+ },
+ },
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $dryrun = extract_param($param, 'dry-run') ? 1 : 0;
+
+ my $keep_opts;
+ foreach my $keep (keys %{$PVE::Storage::Plugin::prune_backups_format}) {
+ $keep_opts->{$keep} = extract_param($param, $keep) if defined($param->{$keep});
+ }
+ $param->{'prune-backups'} = PVE::JSONSchema::print_property_string(
+ $keep_opts, $PVE::Storage::Plugin::prune_backups_format) if $keep_opts;
+
+ my $list = [];
+ if ($dryrun) {
+ $list = PVE::API2::Storage::PruneBackups->dryrun($param);
+ } else {
+ PVE::API2::Storage::PruneBackups->delete($param);
+ }
+
+ return {
+ dryrun => $dryrun,
+ list => $list,
+ };
+ }});
+
our $cmddef = {
add => [ "PVE::API2::Storage::Config", 'create', ['type', 'storage'] ],
set => [ "PVE::API2::Storage::Config", 'update', ['storage'] ],
}],
free => [ "PVE::API2::Storage::Content", 'delete', ['volume'],
{ node => $nodename } ],
- nfsscan => [ __PACKAGE__, 'nfsscan', ['server'],
- { node => $nodename }, sub {
- my $res = shift;
-
- my $maxlen = 0;
- foreach my $rec (@$res) {
- my $len = length ($rec->{path});
- $maxlen = $len if $len > $maxlen;
- }
- foreach my $rec (@$res) {
- printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options};
- }
- }],
- cifsscan => [ __PACKAGE__, 'cifsscan', ['server'],
- { node => $nodename }, sub {
- my $res = shift;
-
- my $maxlen = 0;
- foreach my $rec (@$res) {
- my $len = length ($rec->{share});
- $maxlen = $len if $len > $maxlen;
- }
- foreach my $rec (@$res) {
- printf "%-${maxlen}s %s\n", $rec->{share}, $rec->{description};
- }
- }],
- glusterfsscan => [ __PACKAGE__, 'glusterfsscan', ['server'],
- { node => $nodename }, sub {
- my $res = shift;
-
- foreach my $rec (@$res) {
- printf "%s\n", $rec->{volname};
- }
- }],
- iscsiscan => [ __PACKAGE__, 'iscsiscan', ['portal'],
- { node => $nodename }, sub {
- my $res = shift;
-
- my $maxlen = 0;
- foreach my $rec (@$res) {
- my $len = length ($rec->{target});
- $maxlen = $len if $len > $maxlen;
- }
- foreach my $rec (@$res) {
- printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal};
- }
- }],
- lvmscan => [ __PACKAGE__, 'lvmscan', [],
- { node => $nodename }, sub {
- my $res = shift;
- foreach my $rec (@$res) {
- printf "$rec->{vg}\n";
- }
- }],
- lvmthinscan => [ __PACKAGE__, 'lvmthinscan', ['vg'],
- { node => $nodename }, sub {
- my $res = shift;
- foreach my $rec (@$res) {
- printf "$rec->{lv}\n";
- }
- }],
- zfsscan => [ __PACKAGE__, 'zfsscan', [],
- { node => $nodename }, sub {
- my $res = shift;
-
- foreach my $rec (@$res) {
- printf "$rec->{pool}\n";
- }
- }],
+ scan => {
+ nfs => [ __PACKAGE__, 'nfsscan', ['server'], { node => $nodename }, sub {
+ my $res = shift;
+
+ my $maxlen = 0;
+ foreach my $rec (@$res) {
+ my $len = length ($rec->{path});
+ $maxlen = $len if $len > $maxlen;
+ }
+ foreach my $rec (@$res) {
+ printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options};
+ }
+ }],
+ cifs => [ __PACKAGE__, 'cifsscan', ['server'], { node => $nodename }, sub {
+ my $res = shift;
+
+ my $maxlen = 0;
+ foreach my $rec (@$res) {
+ my $len = length ($rec->{share});
+ $maxlen = $len if $len > $maxlen;
+ }
+ foreach my $rec (@$res) {
+ printf "%-${maxlen}s %s\n", $rec->{share}, $rec->{description};
+ }
+ }],
+ glusterfs => [ __PACKAGE__, 'glusterfsscan', ['server'], { node => $nodename }, sub {
+ my $res = shift;
+
+ foreach my $rec (@$res) {
+ printf "%s\n", $rec->{volname};
+ }
+ }],
+ iscsi => [ __PACKAGE__, 'iscsiscan', ['portal'], { node => $nodename }, sub {
+ my $res = shift;
+
+ my $maxlen = 0;
+ foreach my $rec (@$res) {
+ my $len = length ($rec->{target});
+ $maxlen = $len if $len > $maxlen;
+ }
+ foreach my $rec (@$res) {
+ printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal};
+ }
+ }],
+ lvm => [ __PACKAGE__, 'lvmscan', [], { node => $nodename }, sub {
+ my $res = shift;
+ foreach my $rec (@$res) {
+ printf "$rec->{vg}\n";
+ }
+ }],
+ lvmthin => [ __PACKAGE__, 'lvmthinscan', ['vg'], { node => $nodename }, sub {
+ my $res = shift;
+ foreach my $rec (@$res) {
+ printf "$rec->{lv}\n";
+ }
+ }],
+ zfs => [ __PACKAGE__, 'zfsscan', [], { node => $nodename }, sub {
+ my $res = shift;
+
+ foreach my $rec (@$res) {
+ printf "$rec->{pool}\n";
+ }
+ }],
+ },
+ nfsscan => { alias => 'scan nfs' },
+ cifsscan => { alias => 'scan cifs' },
+ glusterfsscan => { alias => 'scan glusterfs' },
+ iscsiscan => { alias => 'scan iscsi' },
+ lvmscan => { alias => 'scan lvm' },
+ lvmthinscan => { alias => 'scan lvmthin' },
+ zfsscan => { alias => 'scan zfs' },
path => [ __PACKAGE__, 'path', ['volume']],
extractconfig => [__PACKAGE__, 'extractconfig', ['volume']],
export => [ __PACKAGE__, 'export', ['volume', 'format', 'filename']],
- import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename']],
+ import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename'], {}, sub {
+ my $volid = shift;
+ print PVE::Storage::volume_imported_message($volid);
+ }],
+ apiinfo => [ __PACKAGE__, 'apiinfo', [], {}, sub {
+ my $res = shift;
+
+ print "APIVER $res->{apiver}\n";
+ print "APIAGE $res->{apiage}\n";
+ }],
+ 'prune-backups' => [ __PACKAGE__, 'prunebackups', ['storage'], { node => $nodename }, sub {
+ my $res = shift;
+
+ my ($dryrun, $list) = ($res->{dryrun}, $res->{list});
+
+ return if !$dryrun;
+
+ if (!scalar(@{$list})) {
+ print "No backups found\n";
+ return;
+ }
+
+ print "NOTE: this is only a preview and might not be what a subsequent\n" .
+ "prune call does if backups are removed/added in the meantime.\n\n";
+
+ my @sorted = sort {
+ my $vmcmp = PVE::Tools::safe_compare($a->{vmid}, $b->{vmid}, sub { $_[0] <=> $_[1] });
+ return $vmcmp if $vmcmp ne 0;
+ return $a->{ctime} <=> $b->{ctime};
+ } @{$list};
+
+ my $maxlen = 0;
+ foreach my $backup (@sorted) {
+ my $volid = $backup->{volid};
+ $maxlen = length($volid) if length($volid) > $maxlen;
+ }
+ $maxlen+=1;
+
+ printf("%-${maxlen}s %15s %10s\n", 'Backup', 'Backup-ID', 'Prune-Mark');
+ foreach my $backup (@sorted) {
+ my $type = $backup->{type};
+ my $vmid = $backup->{vmid};
+ my $backup_id = defined($vmid) ? "$type/$vmid" : "$type";
+ printf("%-${maxlen}s %15s %10s\n", $backup->{volid}, $backup_id, $backup->{mark});
+ }
+ }],
};
1;