1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc);
10 use PVE
::Cluster
qw(cfs_read_file);
11 use PVE
::AccessControl
;
15 use PVE
::RPCEnvironment
;
18 use PVE
::LXC
::Migrate
;
19 use PVE
::API2
::LXC
::Config
;
20 use PVE
::API2
::LXC
::Status
;
21 use PVE
::API2
::LXC
::Snapshot
;
22 use PVE
::JSONSchema
qw(get_standard_option);
23 use base
qw(PVE::RESTHandler);
26 if (!$ENV{PVE_GENERATING_DOCS
}) {
27 require PVE
::HA
::Env
::PVE2
;
28 import PVE
::HA
::Env
::PVE2
;
29 require PVE
::HA
::Config
;
30 import PVE
::HA
::Config
;
34 __PACKAGE__-
>register_method ({
35 subclass
=> "PVE::API2::LXC::Config",
36 path
=> '{vmid}/config',
39 __PACKAGE__-
>register_method ({
40 subclass
=> "PVE::API2::LXC::Status",
41 path
=> '{vmid}/status',
44 __PACKAGE__-
>register_method ({
45 subclass
=> "PVE::API2::LXC::Snapshot",
46 path
=> '{vmid}/snapshot',
49 __PACKAGE__-
>register_method ({
50 subclass
=> "PVE::API2::Firewall::CT",
51 path
=> '{vmid}/firewall',
54 __PACKAGE__-
>register_method({
58 description
=> "LXC container index (per node).",
60 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
64 protected
=> 1, # /proc files are only readable by root
66 additionalProperties
=> 0,
68 node
=> get_standard_option
('pve-node'),
77 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
82 my $rpcenv = PVE
::RPCEnvironment
::get
();
83 my $authuser = $rpcenv->get_user();
85 my $vmstatus = PVE
::LXC
::vmstatus
();
88 foreach my $vmid (keys %$vmstatus) {
89 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
91 my $data = $vmstatus->{$vmid};
92 $data->{vmid
} = $vmid;
100 __PACKAGE__-
>register_method({
104 description
=> "Create or restore a container.",
106 user
=> 'all', # check inside
107 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
108 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
109 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
114 additionalProperties
=> 0,
115 properties
=> PVE
::LXC
::Config-
>json_config_properties({
116 node
=> get_standard_option
('pve-node'),
117 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
119 description
=> "The OS template or backup file.",
122 completion
=> \
&PVE
::LXC
::complete_os_templates
,
127 description
=> "Sets root password inside container.",
130 storage
=> get_standard_option
('pve-storage-id', {
131 description
=> "Default Storage.",
134 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
139 description
=> "Allow to overwrite existing container.",
144 description
=> "Mark this as restore task.",
148 type
=> 'string', format
=> 'pve-poolid',
149 description
=> "Add the VM to the specified pool.",
151 'ignore-unpack-errors' => {
154 description
=> "Ignore errors when extracting the template.",
156 'ssh-public-keys' => {
159 description
=> "Setup public SSH keys (one key per line, " .
170 my $rpcenv = PVE
::RPCEnvironment
::get
();
172 my $authuser = $rpcenv->get_user();
174 my $node = extract_param
($param, 'node');
176 my $vmid = extract_param
($param, 'vmid');
178 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
180 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
182 my $same_container_exists = -f
$basecfg_fn;
184 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
185 my $unprivileged = extract_param
($param, 'unprivileged');
187 my $restore = extract_param
($param, 'restore');
190 # fixme: limit allowed parameters
194 my $force = extract_param
($param, 'force');
196 if (!($same_container_exists && $restore && $force)) {
197 PVE
::Cluster
::check_vmid_unused
($vmid);
199 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
200 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
203 my $password = extract_param
($param, 'password');
205 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
206 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
208 my $pool = extract_param
($param, 'pool');
210 if (defined($pool)) {
211 $rpcenv->check_pool_exist($pool);
212 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
215 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
217 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
219 } elsif ($restore && $force && $same_container_exists &&
220 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
221 # OK: user has VM.Backup permissions, and want to restore an existing VM
226 my $ostemplate = extract_param
($param, 'ostemplate');
227 my $storage = extract_param
($param, 'storage') // 'local';
229 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
231 my $storage_cfg = cfs_read_file
("storage.cfg");
236 if ($ostemplate eq '-') {
237 die "pipe requires cli environment\n"
238 if $rpcenv->{type
} ne 'cli';
239 die "pipe can only be used with restore tasks\n"
242 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
244 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
245 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
248 my $check_and_activate_storage = sub {
251 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
253 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
254 if !$scfg->{content
}->{rootdir
};
256 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
258 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
263 my $no_disk_param = {};
265 my $storage_only_mode = 1;
266 foreach my $opt (keys %$param) {
267 my $value = $param->{$opt};
268 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
269 # allow to use simple numbers (add default storage in that case)
270 if ($value =~ m/^\d+(\.\d+)?$/) {
271 $mp_param->{$opt} = "$storage:$value";
273 $mp_param->{$opt} = $value;
275 $storage_only_mode = 0;
276 } elsif ($opt =~ m/^unused\d+$/) {
277 warn "ignoring '$opt', cannot create/restore with unused volume\n";
278 delete $param->{$opt};
280 $no_disk_param->{$opt} = $value;
284 die "mount points configured, but 'rootfs' not set - aborting\n"
285 if !$storage_only_mode && !defined($mp_param->{rootfs
});
287 # check storage access, activate storage
288 my $delayed_mp_param = {};
289 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
290 my ($ms, $mountpoint) = @_;
292 my $volid = $mountpoint->{volume
};
293 my $mp = $mountpoint->{mp
};
295 if ($mountpoint->{type
} ne 'volume') { # bind or device
296 die "Only root can pass arbitrary filesystem paths.\n"
297 if $authuser ne 'root@pam';
299 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
300 &$check_and_activate_storage($sid);
304 # check/activate default storage
305 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
307 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
309 $conf->{unprivileged
} = 1 if $unprivileged;
311 my $check_vmid_usage = sub {
313 die "can't overwrite running container\n"
314 if PVE
::LXC
::check_running
($vmid);
316 PVE
::Cluster
::check_vmid_unused
($vmid);
321 &$check_vmid_usage(); # final check after locking
324 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
326 die "container exists" if !$restore; # just to be sure
327 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
330 # try to create empty config on local node, we have an flock
331 PVE
::LXC
::Config-
>write_config($vmid, {});
334 # another node was faster, abort
335 die "Could not reserve ID $vmid, already taken\n" if $@;
338 PVE
::Cluster
::check_cfs_quorum
();
342 if ($storage_only_mode) {
344 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
345 die "rootfs configuration could not be recovered, please check and specify manually!\n"
346 if !defined($mp_param->{rootfs
});
347 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
348 my ($ms, $mountpoint) = @_;
349 my $type = $mountpoint->{type
};
350 if ($type eq 'volume') {
351 die "unable to detect disk size - please specify $ms (size)\n"
352 if !defined($mountpoint->{size
});
353 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
354 delete $mountpoint->{size
};
355 $mountpoint->{volume
} = "$storage:$disksize";
356 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
358 my $type = $mountpoint->{type
};
359 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
360 if ($ms eq 'rootfs');
361 die "restoring '$ms' to $type mount is only possible for root\n"
362 if $authuser ne 'root@pam';
364 if ($mountpoint->{backup
}) {
365 warn "WARNING - unsupported configuration!\n";
366 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
367 warn "mount point configuration will be restored after archive extraction!\n";
368 warn "contained files will be restored to wrong directory!\n";
370 delete $mp_param->{$ms}; # actually delay bind/dev mps
371 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
375 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
379 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
381 if (defined($old_conf)) {
382 # destroy old container volumes
383 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
387 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
388 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors);
391 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
393 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
394 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
395 $lxc_setup->post_create_hook($password, $ssh_keys);
399 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
400 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
403 $conf->{hostname
} ||= "CT$vmid";
404 $conf->{memory
} ||= 512;
405 $conf->{swap
} //= 512;
406 foreach my $mp (keys %$delayed_mp_param) {
407 $conf->{$mp} = $delayed_mp_param->{$mp};
409 PVE
::LXC
::Config-
>write_config($vmid, $conf);
412 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
413 PVE
::LXC
::destroy_config
($vmid);
416 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
419 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
421 &$check_vmid_usage(); # first check before locking
423 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
424 $vmid, $authuser, $realcmd);
428 __PACKAGE__-
>register_method({
433 description
=> "Directory index",
438 additionalProperties
=> 0,
440 node
=> get_standard_option
('pve-node'),
441 vmid
=> get_standard_option
('pve-vmid'),
449 subdir
=> { type
=> 'string' },
452 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
458 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
461 { subdir
=> 'config' },
462 { subdir
=> 'status' },
463 { subdir
=> 'vncproxy' },
464 { subdir
=> 'vncwebsocket' },
465 { subdir
=> 'spiceproxy' },
466 { subdir
=> 'migrate' },
467 { subdir
=> 'clone' },
468 # { subdir => 'initlog' },
470 { subdir
=> 'rrddata' },
471 { subdir
=> 'firewall' },
472 { subdir
=> 'snapshot' },
473 { subdir
=> 'resize' },
480 __PACKAGE__-
>register_method({
482 path
=> '{vmid}/rrd',
484 protected
=> 1, # fixme: can we avoid that?
486 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
488 description
=> "Read VM RRD statistics (returns PNG)",
490 additionalProperties
=> 0,
492 node
=> get_standard_option
('pve-node'),
493 vmid
=> get_standard_option
('pve-vmid'),
495 description
=> "Specify the time frame you are interested in.",
497 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
500 description
=> "The list of datasources you want to display.",
501 type
=> 'string', format
=> 'pve-configid-list',
504 description
=> "The RRD consolidation function",
506 enum
=> [ 'AVERAGE', 'MAX' ],
514 filename
=> { type
=> 'string' },
520 return PVE
::Cluster
::create_rrd_graph
(
521 "pve2-vm/$param->{vmid}", $param->{timeframe
},
522 $param->{ds
}, $param->{cf
});
526 __PACKAGE__-
>register_method({
528 path
=> '{vmid}/rrddata',
530 protected
=> 1, # fixme: can we avoid that?
532 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
534 description
=> "Read VM RRD statistics",
536 additionalProperties
=> 0,
538 node
=> get_standard_option
('pve-node'),
539 vmid
=> get_standard_option
('pve-vmid'),
541 description
=> "Specify the time frame you are interested in.",
543 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
546 description
=> "The RRD consolidation function",
548 enum
=> [ 'AVERAGE', 'MAX' ],
563 return PVE
::Cluster
::create_rrd_data
(
564 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
567 __PACKAGE__-
>register_method({
568 name
=> 'destroy_vm',
573 description
=> "Destroy the container (also delete all uses files).",
575 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
578 additionalProperties
=> 0,
580 node
=> get_standard_option
('pve-node'),
581 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
590 my $rpcenv = PVE
::RPCEnvironment
::get
();
592 my $authuser = $rpcenv->get_user();
594 my $vmid = $param->{vmid
};
596 # test if container exists
597 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
599 my $storage_cfg = cfs_read_file
("storage.cfg");
601 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
603 die "unable to remove CT $vmid - used in HA resources\n"
604 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
606 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
608 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
611 # reload config after lock
612 $conf = PVE
::LXC
::Config-
>load_config($vmid);
613 PVE
::LXC
::Config-
>check_lock($conf);
615 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
617 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
618 PVE
::AccessControl
::remove_vm_access
($vmid);
619 PVE
::Firewall
::remove_vmfw_conf
($vmid);
622 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
624 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
629 __PACKAGE__-
>register_method ({
631 path
=> '{vmid}/vncproxy',
635 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
637 description
=> "Creates a TCP VNC proxy connections.",
639 additionalProperties
=> 0,
641 node
=> get_standard_option
('pve-node'),
642 vmid
=> get_standard_option
('pve-vmid'),
646 description
=> "use websocket instead of standard VNC.",
651 additionalProperties
=> 0,
653 user
=> { type
=> 'string' },
654 ticket
=> { type
=> 'string' },
655 cert
=> { type
=> 'string' },
656 port
=> { type
=> 'integer' },
657 upid
=> { type
=> 'string' },
663 my $rpcenv = PVE
::RPCEnvironment
::get
();
665 my $authuser = $rpcenv->get_user();
667 my $vmid = $param->{vmid
};
668 my $node = $param->{node
};
670 my $authpath = "/vms/$vmid";
672 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
674 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
677 my ($remip, $family);
679 if ($node ne PVE
::INotify
::nodename
()) {
680 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
682 $family = PVE
::Tools
::get_host_address_family
($node);
685 my $port = PVE
::Tools
::next_vnc_port
($family);
687 # NOTE: vncterm VNC traffic is already TLS encrypted,
688 # so we select the fastest chipher here (or 'none'?)
689 my $remcmd = $remip ?
690 ['/usr/bin/ssh', '-t', $remip] : [];
692 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
693 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
695 my $shcmd = [ '/usr/bin/dtach', '-A',
696 "/var/run/dtach/vzctlconsole$vmid",
697 '-r', 'winch', '-z', @$concmd];
702 syslog
('info', "starting lxc vnc proxy $upid\n");
706 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
707 '-timeout', $timeout, '-authpath', $authpath,
708 '-perm', 'VM.Console'];
710 if ($param->{websocket
}) {
711 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
712 push @$cmd, '-notls', '-listen', 'localhost';
715 push @$cmd, '-c', @$remcmd, @$shcmd;
717 run_command
($cmd, keeplocale
=> 1);
722 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
724 PVE
::Tools
::wait_for_vnc_port
($port);
735 __PACKAGE__-
>register_method({
736 name
=> 'vncwebsocket',
737 path
=> '{vmid}/vncwebsocket',
740 description
=> "You also need to pass a valid ticket (vncticket).",
741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
743 description
=> "Opens a weksocket for VNC traffic.",
745 additionalProperties
=> 0,
747 node
=> get_standard_option
('pve-node'),
748 vmid
=> get_standard_option
('pve-vmid'),
750 description
=> "Ticket from previous call to vncproxy.",
755 description
=> "Port number returned by previous vncproxy call.",
765 port
=> { type
=> 'string' },
771 my $rpcenv = PVE
::RPCEnvironment
::get
();
773 my $authuser = $rpcenv->get_user();
775 my $authpath = "/vms/$param->{vmid}";
777 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
779 my $port = $param->{port
};
781 return { port
=> $port };
784 __PACKAGE__-
>register_method ({
785 name
=> 'spiceproxy',
786 path
=> '{vmid}/spiceproxy',
791 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
793 description
=> "Returns a SPICE configuration to connect to the CT.",
795 additionalProperties
=> 0,
797 node
=> get_standard_option
('pve-node'),
798 vmid
=> get_standard_option
('pve-vmid'),
799 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
802 returns
=> get_standard_option
('remote-viewer-config'),
806 my $vmid = $param->{vmid
};
807 my $node = $param->{node
};
808 my $proxy = $param->{proxy
};
810 my $authpath = "/vms/$vmid";
811 my $permissions = 'VM.Console';
813 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
815 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
817 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
819 my $shcmd = ['/usr/bin/dtach', '-A',
820 "/var/run/dtach/vzctlconsole$vmid",
821 '-r', 'winch', '-z', @$concmd];
823 my $title = "CT $vmid";
825 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
829 __PACKAGE__-
>register_method({
830 name
=> 'migrate_vm',
831 path
=> '{vmid}/migrate',
835 description
=> "Migrate the container to another node. Creates a new migration task.",
837 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
840 additionalProperties
=> 0,
842 node
=> get_standard_option
('pve-node'),
843 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
844 target
=> get_standard_option
('pve-node', {
845 description
=> "Target node.",
846 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
850 description
=> "Use online/live migration.",
855 description
=> "Use restart migration",
860 description
=> "Timeout in seconds for shutdown for restart migration",
866 description
=> "Force migration despite local bind / device" .
867 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
874 description
=> "the task ID.",
879 my $rpcenv = PVE
::RPCEnvironment
::get
();
881 my $authuser = $rpcenv->get_user();
883 my $target = extract_param
($param, 'target');
885 my $localnode = PVE
::INotify
::nodename
();
886 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
888 PVE
::Cluster
::check_cfs_quorum
();
890 PVE
::Cluster
::check_node_exists
($target);
892 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
894 my $vmid = extract_param
($param, 'vmid');
897 PVE
::LXC
::Config-
>load_config($vmid);
899 # try to detect errors early
900 if (PVE
::LXC
::check_running
($vmid)) {
901 die "can't migrate running container without --online or --restart\n"
902 if !$param->{online
} && !$param->{restart
};
905 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
910 my $service = "ct:$vmid";
912 my $cmd = ['ha-manager', 'migrate', $service, $target];
914 print "Executing HA migrate for CT $vmid to node $target\n";
916 PVE
::Tools
::run_command
($cmd);
921 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
928 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
933 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
937 __PACKAGE__-
>register_method({
938 name
=> 'vm_feature',
939 path
=> '{vmid}/feature',
943 description
=> "Check if feature for virtual machine is available.",
945 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
948 additionalProperties
=> 0,
950 node
=> get_standard_option
('pve-node'),
951 vmid
=> get_standard_option
('pve-vmid'),
953 description
=> "Feature to check.",
955 enum
=> [ 'snapshot' ],
957 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
965 hasFeature
=> { type
=> 'boolean' },
968 #items => { type => 'string' },
975 my $node = extract_param
($param, 'node');
977 my $vmid = extract_param
($param, 'vmid');
979 my $snapname = extract_param
($param, 'snapname');
981 my $feature = extract_param
($param, 'feature');
983 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
986 my $snap = $conf->{snapshots
}->{$snapname};
987 die "snapshot '$snapname' does not exist\n" if !defined($snap);
990 my $storage_cfg = PVE
::Storage
::config
();
992 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
993 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
996 hasFeature
=> $hasFeature,
997 #nodes => [ keys %$nodelist ],
1001 __PACKAGE__-
>register_method({
1003 path
=> '{vmid}/template',
1007 description
=> "Create a Template.",
1009 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1010 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1013 additionalProperties
=> 0,
1015 node
=> get_standard_option
('pve-node'),
1016 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1019 description
=> "The template feature is experimental, set this " .
1020 "flag if you know what you are doing.",
1025 returns
=> { type
=> 'null'},
1029 my $rpcenv = PVE
::RPCEnvironment
::get
();
1031 my $authuser = $rpcenv->get_user();
1033 my $node = extract_param
($param, 'node');
1035 my $vmid = extract_param
($param, 'vmid');
1037 my $updatefn = sub {
1039 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1040 PVE
::LXC
::Config-
>check_lock($conf);
1042 die "unable to create template, because CT contains snapshots\n"
1043 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1045 die "you can't convert a template to a template\n"
1046 if PVE
::LXC
::Config-
>is_template($conf);
1048 die "you can't convert a CT to template if the CT is running\n"
1049 if PVE
::LXC
::check_running
($vmid);
1052 PVE
::LXC
::template_create
($vmid, $conf);
1055 $conf->{template
} = 1;
1057 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1058 # and remove lxc config
1059 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1061 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1064 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1069 __PACKAGE__-
>register_method({
1071 path
=> '{vmid}/clone',
1075 description
=> "Create a container clone/copy",
1077 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1078 "and 'VM.Allocate' permissions " .
1079 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1080 "'Datastore.AllocateSpace' on any used storage.",
1083 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1085 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1086 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1091 additionalProperties
=> 0,
1093 node
=> get_standard_option
('pve-node'),
1094 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1095 newid
=> get_standard_option
('pve-vmid', {
1096 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1097 description
=> 'VMID for the clone.' }),
1100 type
=> 'string', format
=> 'dns-name',
1101 description
=> "Set a hostname for the new CT.",
1106 description
=> "Description for the new CT.",
1110 type
=> 'string', format
=> 'pve-poolid',
1111 description
=> "Add the new CT to the specified pool.",
1113 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1116 storage
=> get_standard_option
('pve-storage-id', {
1117 description
=> "Target storage for full clone.",
1124 description
=> "Create a full copy of all disk. This is always done when " .
1125 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1130 description
=> "The clone feature is experimental, set this " .
1131 "flag if you know what you are doing.",
1134 # target => get_standard_option('pve-node', {
1135 # description => "Target node. Only allowed if the original VM is on shared storage.",
1146 my $rpcenv = PVE
::RPCEnvironment
::get
();
1148 my $authuser = $rpcenv->get_user();
1150 my $node = extract_param
($param, 'node');
1152 my $vmid = extract_param
($param, 'vmid');
1154 my $newid = extract_param
($param, 'newid');
1156 my $pool = extract_param
($param, 'pool');
1158 if (defined($pool)) {
1159 $rpcenv->check_pool_exist($pool);
1162 my $snapname = extract_param
($param, 'snapname');
1164 my $storage = extract_param
($param, 'storage');
1166 my $localnode = PVE
::INotify
::nodename
();
1168 my $storecfg = PVE
::Storage
::config
();
1171 # check if storage is enabled on local node
1172 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1175 PVE
::Cluster
::check_cfs_quorum
();
1177 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1181 # do all tests after lock
1182 # we also try to do all tests before we fork the worker
1183 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1185 PVE
::LXC
::Config-
>check_lock($conf);
1187 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1189 die "unexpected state change\n" if $verify_running != $running;
1191 die "snapshot '$snapname' does not exist\n"
1192 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1194 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1196 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1197 die "unable to create CT $newid: config file already exists\n"
1200 my $newconf = { lock => 'clone' };
1201 my $mountpoints = {};
1205 foreach my $opt (keys %$oldconf) {
1206 my $value = $oldconf->{$opt};
1208 # no need to copy unused images, because VMID(owner) changes anyways
1209 next if $opt =~ m/^unused\d+$/;
1211 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1212 my $mp = $opt eq 'rootfs' ?
1213 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1214 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1216 if ($mp->{type
} eq 'volume') {
1217 my $volid = $mp->{volume
};
1218 if ($param->{full
}) {
1219 die "fixme: full clone not implemented";
1221 die "Full clone feature for '$volid' is not available\n"
1222 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1223 $fullclone->{$opt} = 1;
1225 # not full means clone instead of copy
1226 die "Linked clone feature for '$volid' is not available\n"
1227 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1230 $mountpoints->{$opt} = $mp;
1231 push @$vollist, $volid;
1234 # TODO: allow bind mounts?
1235 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1239 # copy everything else
1240 $newconf->{$opt} = $value;
1244 delete $newconf->{template
};
1245 if ($param->{hostname
}) {
1246 $newconf->{hostname
} = $param->{hostname
};
1249 if ($param->{description
}) {
1250 $newconf->{description
} = $param->{description
};
1253 # create empty/temp config - this fails if CT already exists on other node
1254 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1259 my $newvollist = [];
1262 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1264 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1266 foreach my $opt (keys %$mountpoints) {
1267 my $mp = $mountpoints->{$opt};
1268 my $volid = $mp->{volume
};
1270 if ($fullclone->{$opt}) {
1271 die "fixme: full clone not implemented\n";
1273 print "create linked clone of mount point $opt ($volid)\n";
1274 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1275 push @$newvollist, $newvolid;
1276 $mp->{volume
} = $newvolid;
1278 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1279 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1283 delete $newconf->{lock};
1284 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1286 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1291 sleep 1; # some storage like rbd need to wait before release volume - really?
1293 foreach my $volid (@$newvollist) {
1294 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1297 die "clone failed: $err";
1303 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1305 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1309 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1313 __PACKAGE__-
>register_method({
1314 name
=> 'resize_vm',
1315 path
=> '{vmid}/resize',
1319 description
=> "Resize a container mount point.",
1321 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1324 additionalProperties
=> 0,
1326 node
=> get_standard_option
('pve-node'),
1327 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1330 description
=> "The disk you want to resize.",
1331 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1335 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1336 description
=> "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
1340 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1348 description
=> "the task ID.",
1353 my $rpcenv = PVE
::RPCEnvironment
::get
();
1355 my $authuser = $rpcenv->get_user();
1357 my $node = extract_param
($param, 'node');
1359 my $vmid = extract_param
($param, 'vmid');
1361 my $digest = extract_param
($param, 'digest');
1363 my $sizestr = extract_param
($param, 'size');
1364 my $ext = ($sizestr =~ s/^\+//);
1365 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1366 die "invalid size string" if !defined($newsize);
1368 die "no options specified\n" if !scalar(keys %$param);
1370 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1372 my $storage_cfg = cfs_read_file
("storage.cfg");
1376 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1377 PVE
::LXC
::Config-
>check_lock($conf);
1379 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1381 my $running = PVE
::LXC
::check_running
($vmid);
1383 my $disk = $param->{disk
};
1384 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1385 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1387 my $volid = $mp->{volume
};
1389 my (undef, undef, $owner, undef, undef, undef, $format) =
1390 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1392 die "can't resize mount point owned by another container ($owner)"
1395 die "can't resize volume: $disk if snapshot exists\n"
1396 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1398 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1400 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1402 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1404 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1405 $newsize += $size if $ext;
1406 $newsize = int($newsize);
1408 die "unable to shrink disk size\n" if $newsize < $size;
1410 return if $size == $newsize;
1412 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1414 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1415 # we pass 0 here (parameter only makes sense for qemu)
1416 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1418 $mp->{size
} = $newsize;
1419 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1421 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1423 if ($format eq 'raw') {
1424 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1428 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1429 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1430 die "internal error: CT running but mount point not attached to a loop device"
1432 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1434 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1435 # to be visible to it in its namespace.
1436 # To not interfere with the rest of the system we unshare the current mount namespace,
1437 # mount over /tmp and then run resize2fs.
1439 # interestingly we don't need to e2fsck on mounted systems...
1440 my $quoted = PVE
::Tools
::shellquote
($path);
1441 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1443 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1445 warn "Failed to update the container's filesystem: $@\n" if $@;
1448 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1449 PVE
::Tools
::run_command
(['resize2fs', $path]);
1451 warn "Failed to update the container's filesystem: $@\n" if $@;
1456 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1459 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;