sub free_image {
my ($class, $storeid, $scfg, $volname, $isBase, $format) = @_;
+ die "cannot remove protected volume '$volname' on '$storeid'\n"
+ if $class->get_volume_attribute($scfg, $storeid, $volname, 'protected');
+
my $path = $class->filesystem_path($scfg, $volname);
if ($isBase) {
return wantarray ? ($size, $format, $used, $parent, $st->ctime) : $size;
}
+# FIXME remove on the next APIAGE reset.
+# Deprecated, use get_volume_attribute instead.
sub get_volume_notes {
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
die "volume notes are not supported for $class";
}
+# FIXME remove on the next APIAGE reset.
+# Deprecated, use update_volume_attribute instead.
sub update_volume_notes {
my ($class, $scfg, $storeid, $volname, $notes, $timeout) = @_;
die "volume notes are not supported for $class";
}
+# Returns undef if the attribute is not supported for the volume.
+# Should die if there is an error fetching the attribute.
+# Possible attributes:
+# notes - user-provided comments/notes.
+# protected - not to be removed by free_image, and for backups, ignored when pruning.
+sub get_volume_attribute {
+ my ($class, $scfg, $storeid, $volname, $attribute) = @_;
+
+ if ($attribute eq 'notes') {
+ my $notes = eval { $class->get_volume_notes($scfg, $storeid, $volname); };
+ if (my $err = $@) {
+ return if $err =~ m/^volume notes are not supported/;
+ die $err;
+ }
+ return $notes;
+ }
+
+ return;
+}
+
+# Dies if the attribute is not supported for the volume.
+sub update_volume_attribute {
+ my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
+
+ if ($attribute eq 'notes') {
+ $class->update_volume_notes($scfg, $storeid, $volname, $value);
+ }
+
+ die "attribute '$attribute' is not supported for storage type '$scfg->{type}'\n";
+}
+
sub volume_size_info {
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
my $path = $class->filesystem_path($scfg, $volname);
return undef;
}
+# Asserts that a rollback to $snap on $volname is possible.
+# If certain snapshots are preventing the rollback and $blockers is an array
+# reference, the snapshot names can be pushed onto $blockers prior to dying.
sub volume_rollback_is_possible {
- my ($class, $scfg, $storeid, $volname, $snap) = @_;
+ my ($class, $scfg, $storeid, $volname, $snap, $blockers) = @_;
return 1;
}
snap => {qcow2 => 1} },
sparseinit => { base => {qcow2 => 1, raw => 1, vmdk => 1},
current => {qcow2 => 1, raw => 1, vmdk => 1} },
+ rename => { current => {qcow2 => 1, raw => 1, vmdk => 1} },
};
# clone_image creates a qcow2 volume
defined($opts->{valid_target_formats}) &&
!(grep { $_ eq 'qcow2' } @{$opts->{valid_target_formats}});
+ return 0 if $feature eq 'rename' && $class->can('api') && $class->api() < 10;
+
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) =
$class->parse_volname($volname);
$info->{notes} = $notes if defined($notes);
}
+ $info->{protected} = 1 if -e PVE::Storage::protection_file_path($original);
} elsif ($tt eq 'snippets') {
$info = {
return $res;
};
+# If attributes are set on a volume, they should be included in the result.
+# See get_volume_attribute for a list of possible attributes.
sub list_volumes {
my ($class, $storeid, $scfg, $vmid, $content_types) = @_;
return ($res->{total}, $res->{avail}, $res->{used}, 1);
}
-sub volume_snapshot_list {
+# Returns a hash with the snapshot names as keys and the following data:
+# id - Unique id to distinguish different snapshots even if the have the same name.
+# timestamp - Creation time of the snapshot (seconds since epoch).
+# Returns an empty hash if the volume does not exist.
+sub volume_snapshot_info {
my ($class, $scfg, $storeid, $volname) = @_;
- # implement in subclass
- die "Volume_snapshot_list is not implemented for $class";
-
- # return an empty array if dataset does not exist.
+ die "volume_snapshot_info is not implemented for $class";
}
sub activate_storage {
push @{$backup_groups->{$group}}, $prune_entry;
} else {
# ignore backups that don't use the standard naming scheme
- $prune_entry->{mark} = 'protected';
+ $prune_entry->{mark} = 'renamed';
}
+ $prune_entry->{mark} = 'protected' if $backup->{protected};
+
push @{$prune_list}, $prune_entry;
}
return ();
}
+sub rename_volume {
+ my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_;
+ die "not implemented in storage plugin '$class'\n" if $class->can('api') && $class->api() < 10;
+ die "no path found\n" if !$scfg->{path};
+
+ my (
+ undef,
+ $source_image,
+ $source_vmid,
+ $base_name,
+ $base_vmid,
+ undef,
+ $format
+ ) = $class->parse_volname($source_volname);
+
+ $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format, 1)
+ if !$target_volname;
+
+ my $basedir = $class->get_subdir($scfg, 'images');
+
+ mkpath "${basedir}/${target_vmid}";
+
+ my $old_path = "${basedir}/${source_vmid}/${source_image}";
+ my $new_path = "${basedir}/${target_vmid}/${target_volname}";
+
+ die "target volume '${target_volname}' already exists\n" if -e $new_path;
+
+ my $base = $base_name ? "${base_vmid}/${base_name}/" : '';
+
+ rename($old_path, $new_path) ||
+ die "rename '$old_path' to '$new_path' failed - $!\n";
+
+ return "${storeid}:${base}${target_vmid}/${target_volname}";
+}
+
1;