1 package PVE
::API2
::Storage
::Content
;
11 use PVE
::Exception
qw(raise_param_exc);
12 use PVE
::RPCEnvironment
;
14 use PVE
::JSONSchema
qw(get_standard_option);
17 use base
qw(PVE::RESTHandler);
19 __PACKAGE__-
>register_method ({
23 description
=> "List storage content.",
25 check
=> ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any
=> 1],
30 additionalProperties
=> 0,
32 node
=> get_standard_option
('pve-node'),
33 storage
=> get_standard_option
('pve-storage-id', {
34 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
37 description
=> "Only list content of this type.",
38 type
=> 'string', format
=> 'pve-storage-content',
40 completion
=> \
&PVE
::Storage
::complete_content_type
,
42 vmid
=> get_standard_option
('pve-vmid', {
43 description
=> "Only list images for this VM",
45 completion
=> \
&PVE
::Cluster
::complete_vmid
,
55 description
=> "Volume identifier.",
59 description
=> "Associated Owner VMID.",
64 description
=> "Volume identifier of parent (for linked cloned).",
69 description
=> "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
73 description
=> "Volume size in bytes.",
78 description
=> "Used space. Please note that most storage plugins " .
79 "do not report anything useful here.",
85 description
=> "Creation time (seconds since the UNIX Epoch).",
91 description
=> "Optional notes. If they contain multiple lines, only the first one is returned here.",
96 description
=> "If whole backup is encrypted, value is the fingerprint or '1' "
97 ." if encrypted. Only useful for the Proxmox Backup Server storage type.",
102 description
=> "Last backup verification result, only useful for PBS storages.",
106 description
=> "Last backup verification state.",
110 description
=> "Last backup verification UPID.",
117 description
=> "Protection status. Currently only supported for backups.",
123 links
=> [ { rel
=> 'child', href
=> "{volid}" } ],
128 my $rpcenv = PVE
::RPCEnvironment
::get
();
130 my $authuser = $rpcenv->get_user();
132 my $storeid = $param->{storage
};
134 my $cfg = PVE
::Storage
::config
();
136 my $vollist = PVE
::Storage
::volume_list
($cfg, $storeid, $param->{vmid
}, $param->{content
});
139 foreach my $item (@$vollist) {
140 eval { PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $cfg, undef, $item->{volid
}); };
142 $item->{vmid
} = int($item->{vmid
}) if defined($item->{vmid
});
143 $item->{size
} = int($item->{size
}) if defined($item->{size
});
144 $item->{used
} = int($item->{used
}) if defined($item->{used
});
151 __PACKAGE__-
>register_method ({
155 description
=> "Allocate disk images.",
157 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
162 additionalProperties
=> 0,
164 node
=> get_standard_option
('pve-node'),
165 storage
=> get_standard_option
('pve-storage-id', {
166 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
169 description
=> "The name of the file to create.",
172 vmid
=> get_standard_option
('pve-vmid', {
173 description
=> "Specify owner VM",
174 completion
=> \
&PVE
::Cluster
::complete_vmid
,
177 description
=> "Size in kilobyte (1024 bytes). Optional suffixes 'M' (megabyte, 1024K) and 'G' (gigabyte, 1024M)",
179 pattern
=> '\d+[MG]?',
183 enum
=> ['raw', 'qcow2', 'subvol'],
190 description
=> "Volume identifier",
196 my $storeid = $param->{storage
};
197 my $name = $param->{filename
};
198 my $sizestr = $param->{size
};
201 if ($sizestr =~ m/^\d+$/) {
203 } elsif ($sizestr =~ m/^(\d+)M$/) {
205 } elsif ($sizestr =~ m/^(\d+)G$/) {
206 $size = $1 * 1024 * 1024;
208 raise_param_exc
({ size
=> "unable to parse size '$sizestr'" });
211 # extract FORMAT from name
212 if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
215 raise_param_exc
({ format
=> "different storage formats ($param->{format} != $fmt)" })
216 if $param->{format
} && $param->{format
} ne $fmt;
218 $param->{format
} = $fmt;
221 my $cfg = PVE
::Storage
::config
();
223 my $volid = PVE
::Storage
::vdisk_alloc
($cfg, $storeid, $param->{vmid
},
230 # we allow to pass volume names (without storage prefix) if the storage
231 # is specified as separate parameter.
232 my $real_volume_id = sub {
233 my ($storeid, $volume) = @_;
237 if ($volume =~ m/:/) {
239 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volume);
240 die "storage ID mismatch ($sid != $storeid)\n"
241 if $storeid && $sid ne $storeid;
245 raise_param_exc
({ volume
=> $@ }) if $@;
248 raise_param_exc
({ volume
=> "no storage specified - incomplete volume ID" })
251 $volid = "$storeid:$volume";
254 return wantarray ?
($volid, $storeid) : $volid;
257 __PACKAGE__-
>register_method ({
261 description
=> "Get volume attributes",
263 description
=> "You need read access for the volume.",
269 additionalProperties
=> 0,
271 node
=> get_standard_option
('pve-node'),
272 storage
=> get_standard_option
('pve-storage-id', { optional
=> 1 }),
274 description
=> "Volume identifier",
283 description
=> "The Path",
287 description
=> "Volume size in bytes.",
292 description
=> "Used space. Please note that most storage plugins " .
293 "do not report anything useful here.",
298 description
=> "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
302 description
=> "Optional notes.",
307 description
=> "Protection status. Currently only supported for backups.",
316 my $rpcenv = PVE
::RPCEnvironment
::get
();
317 my $authuser = $rpcenv->get_user();
319 my ($volid, $storeid) = &$real_volume_id($param->{storage
}, $param->{volume
});
321 my $cfg = PVE
::Storage
::config
();
323 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $cfg, undef, $volid);
325 my $path = PVE
::Storage
::path
($cfg, $volid);
326 my ($size, $format, $used, $parent) = PVE
::Storage
::volume_size_info
($cfg, $volid);
327 die "volume_size_info on '$volid' failed\n" if !($format && $size);
331 size
=> int($size), # cast to integer in case it was changed to a string previously
336 for my $attribute (qw(notes protected)) {
337 # keep going if fetching an optional attribute fails
339 my $value = PVE
::Storage
::get_volume_attribute
($cfg, $volid, $attribute);
340 $entry->{$attribute} = $value if defined($value);
348 __PACKAGE__-
>register_method ({
349 name
=> 'updateattributes',
352 description
=> "Update volume attributes",
354 description
=> "You need read access for the volume.",
360 additionalProperties
=> 0,
362 node
=> get_standard_option
('pve-node'),
363 storage
=> get_standard_option
('pve-storage-id', { optional
=> 1 }),
365 description
=> "Volume identifier",
369 description
=> "The new notes.",
374 description
=> "Protection status. Currently only supported for backups.",
380 returns
=> { type
=> 'null' },
384 my $rpcenv = PVE
::RPCEnvironment
::get
();
385 my $authuser = $rpcenv->get_user();
387 my ($volid, $storeid) = &$real_volume_id($param->{storage
}, $param->{volume
});
389 my $cfg = PVE
::Storage
::config
();
391 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $cfg, undef, $volid);
393 for my $attr (qw(notes protected)) {
394 if (exists $param->{$attr}) {
395 PVE
::Storage
::update_volume_attribute
($cfg, $volid, $attr, $param->{$attr});
402 __PACKAGE__-
>register_method ({
406 description
=> "Delete volume",
408 description
=> "You need 'Datastore.Allocate' privilege on the storage (or 'Datastore.AllocateSpace' for backup volumes if you have VM.Backup privilege on the VM).",
414 additionalProperties
=> 0,
416 node
=> get_standard_option
('pve-node'),
417 storage
=> get_standard_option
('pve-storage-id', {
419 completion
=> \
&PVE
::Storage
::complete_storage
,
422 description
=> "Volume identifier",
424 completion
=> \
&PVE
::Storage
::complete_volume
,
428 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
435 returns
=> { type
=> 'string', optional
=> 1, },
439 my $rpcenv = PVE
::RPCEnvironment
::get
();
440 my $authuser = $rpcenv->get_user();
442 my $cfg = PVE
::Storage
::config
();
444 my ($volid, $storeid) = &$real_volume_id($param->{storage
}, $param->{volume
});
446 my ($path, $ownervm, $vtype) = PVE
::Storage
::path
($cfg, $volid);
447 if ($vtype eq 'backup' && $ownervm) {
448 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
449 $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
451 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
455 PVE
::Storage
::vdisk_free
($cfg, $volid);
456 print "Removed volume '$volid'\n";
457 if ($vtype eq 'backup'
458 && $path =~ /(.*\/vzdump-\w
+-\d
+-\d
{4}_\d
{2}_\d
{2}-\d
{2}_\d
{2}_\d
{2})[^\
/]+$/) {
459 # Remove log file #318 and notes file #3972 if they still exist
460 PVE
::Storage
::archive_auxiliaries_remove
($path);
464 my $id = (defined $ownervm ?
"$ownervm@" : '') . $storeid;
465 my $upid = $rpcenv->fork_worker('imgdel', $id, $authuser, $worker);
466 my $background_delay = $param->{delay
};
467 if ($background_delay) {
468 my $end_time = time() + $background_delay;
469 my $currently_deleting; # not necessarily true, e.g. sequential api call from cli
471 my $task = PVE
::Tools
::upid_decode
($upid);
472 $currently_deleting = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
473 sleep 1 if $currently_deleting;
474 } while (time() < $end_time && $currently_deleting);
476 if (!$currently_deleting) {
477 my $status = PVE
::Tools
::upid_read_status
($upid);
479 return undef if !PVE
::Tools
::upid_status_is_error
($status);
486 __PACKAGE__-
>register_method ({
490 description
=> "Copy a volume. This is experimental code - do not use.",
494 additionalProperties
=> 0,
496 node
=> get_standard_option
('pve-node'),
497 storage
=> get_standard_option
('pve-storage-id', { optional
=> 1}),
499 description
=> "Source volume identifier",
503 description
=> "Target volume identifier",
506 target_node
=> get_standard_option
('pve-node', {
507 description
=> "Target node. Default is local node.",
518 my $rpcenv = PVE
::RPCEnvironment
::get
();
520 my $user = $rpcenv->get_user();
522 my $target_node = $param->{target_node
} || PVE
::INotify
::nodename
();
524 # cd /nodes/localhost/storage/local/content
525 # pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
526 # pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
528 my $src_volid = &$real_volume_id($param->{storage
}, $param->{volume
});
529 my $dst_volid = &$real_volume_id($param->{storage
}, $param->{target
});
531 print "DEBUG: COPY $src_volid TO $dst_volid\n";
533 my $cfg = PVE
::Storage
::config
();
535 # do all parameter checks first
537 # then do all short running task (to raise errors before we go to background)
539 # then start the worker task
543 print "DEBUG: starting worker $upid\n";
545 my ($target_sid, $target_volname) = PVE
::Storage
::parse_volume_id
($dst_volid);
546 #my $target_ip = PVE::Cluster::remote_node_ip($target_node);
548 # you need to get this working (fails currently, because storage_migrate() uses
549 # ssh to connect to local host (which is not needed
550 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($target_node);
551 PVE
::Storage
::storage_migrate
($cfg, $src_volid, $sshinfo, $target_sid, {'target_volname' => $target_volname});
553 print "DEBUG: end worker $upid\n";
557 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);