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
::ReplicationTools
;
23 use PVE
::JSONSchema
qw(get_standard_option);
24 use base
qw(PVE::RESTHandler);
27 if (!$ENV{PVE_GENERATING_DOCS
}) {
28 require PVE
::HA
::Env
::PVE2
;
29 import PVE
::HA
::Env
::PVE2
;
30 require PVE
::HA
::Config
;
31 import PVE
::HA
::Config
;
35 __PACKAGE__-
>register_method ({
36 subclass
=> "PVE::API2::LXC::Config",
37 path
=> '{vmid}/config',
40 __PACKAGE__-
>register_method ({
41 subclass
=> "PVE::API2::LXC::Status",
42 path
=> '{vmid}/status',
45 __PACKAGE__-
>register_method ({
46 subclass
=> "PVE::API2::LXC::Snapshot",
47 path
=> '{vmid}/snapshot',
50 __PACKAGE__-
>register_method ({
51 subclass
=> "PVE::API2::Firewall::CT",
52 path
=> '{vmid}/firewall',
55 __PACKAGE__-
>register_method({
59 description
=> "LXC container index (per node).",
61 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
65 protected
=> 1, # /proc files are only readable by root
67 additionalProperties
=> 0,
69 node
=> get_standard_option
('pve-node'),
78 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
83 my $rpcenv = PVE
::RPCEnvironment
::get
();
84 my $authuser = $rpcenv->get_user();
86 my $vmstatus = PVE
::LXC
::vmstatus
();
89 foreach my $vmid (keys %$vmstatus) {
90 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
92 my $data = $vmstatus->{$vmid};
93 $data->{vmid
} = $vmid;
101 __PACKAGE__-
>register_method({
105 description
=> "Create or restore a container.",
107 user
=> 'all', # check inside
108 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
109 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
110 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
115 additionalProperties
=> 0,
116 properties
=> PVE
::LXC
::Config-
>json_config_properties({
117 node
=> get_standard_option
('pve-node'),
118 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
120 description
=> "The OS template or backup file.",
123 completion
=> \
&PVE
::LXC
::complete_os_templates
,
128 description
=> "Sets root password inside container.",
131 storage
=> get_standard_option
('pve-storage-id', {
132 description
=> "Default Storage.",
135 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
140 description
=> "Allow to overwrite existing container.",
145 description
=> "Mark this as restore task.",
149 type
=> 'string', format
=> 'pve-poolid',
150 description
=> "Add the VM to the specified pool.",
152 'ignore-unpack-errors' => {
155 description
=> "Ignore errors when extracting the template.",
157 'ssh-public-keys' => {
160 description
=> "Setup public SSH keys (one key per line, " .
171 my $rpcenv = PVE
::RPCEnvironment
::get
();
173 my $authuser = $rpcenv->get_user();
175 my $node = extract_param
($param, 'node');
177 my $vmid = extract_param
($param, 'vmid');
179 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
181 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
183 my $same_container_exists = -f
$basecfg_fn;
185 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
186 my $unprivileged = extract_param
($param, 'unprivileged');
188 my $restore = extract_param
($param, 'restore');
191 # fixme: limit allowed parameters
195 my $force = extract_param
($param, 'force');
197 if (!($same_container_exists && $restore && $force)) {
198 PVE
::Cluster
::check_vmid_unused
($vmid);
200 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
201 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
204 my $password = extract_param
($param, 'password');
206 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
207 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
209 my $pool = extract_param
($param, 'pool');
211 if (defined($pool)) {
212 $rpcenv->check_pool_exist($pool);
213 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
216 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
218 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
220 } elsif ($restore && $force && $same_container_exists &&
221 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
222 # OK: user has VM.Backup permissions, and want to restore an existing VM
227 my $ostemplate = extract_param
($param, 'ostemplate');
228 my $storage = extract_param
($param, 'storage') // 'local';
230 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
232 my $storage_cfg = cfs_read_file
("storage.cfg");
237 if ($ostemplate eq '-') {
238 die "pipe requires cli environment\n"
239 if $rpcenv->{type
} ne 'cli';
240 die "pipe can only be used with restore tasks\n"
243 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
245 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
246 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
249 my $check_and_activate_storage = sub {
252 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
254 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
255 if !$scfg->{content
}->{rootdir
};
257 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
259 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
264 my $no_disk_param = {};
266 my $storage_only_mode = 1;
267 foreach my $opt (keys %$param) {
268 my $value = $param->{$opt};
269 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
270 # allow to use simple numbers (add default storage in that case)
271 if ($value =~ m/^\d+(\.\d+)?$/) {
272 $mp_param->{$opt} = "$storage:$value";
274 $mp_param->{$opt} = $value;
276 $storage_only_mode = 0;
277 } elsif ($opt =~ m/^unused\d+$/) {
278 warn "ignoring '$opt', cannot create/restore with unused volume\n";
279 delete $param->{$opt};
281 $no_disk_param->{$opt} = $value;
285 die "mount points configured, but 'rootfs' not set - aborting\n"
286 if !$storage_only_mode && !defined($mp_param->{rootfs
});
288 # check storage access, activate storage
289 my $delayed_mp_param = {};
290 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
291 my ($ms, $mountpoint) = @_;
293 my $volid = $mountpoint->{volume
};
294 my $mp = $mountpoint->{mp
};
296 if ($mountpoint->{type
} ne 'volume') { # bind or device
297 die "Only root can pass arbitrary filesystem paths.\n"
298 if $authuser ne 'root@pam';
300 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
301 &$check_and_activate_storage($sid);
305 # check/activate default storage
306 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
308 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
310 $conf->{unprivileged
} = 1 if $unprivileged;
312 my $check_vmid_usage = sub {
314 die "can't overwrite running container\n"
315 if PVE
::LXC
::check_running
($vmid);
317 PVE
::Cluster
::check_vmid_unused
($vmid);
322 &$check_vmid_usage(); # final check after locking
325 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
327 die "container exists" if !$restore; # just to be sure
328 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
331 # try to create empty config on local node, we have an flock
332 PVE
::LXC
::Config-
>write_config($vmid, {});
335 # another node was faster, abort
336 die "Could not reserve ID $vmid, already taken\n" if $@;
339 PVE
::Cluster
::check_cfs_quorum
();
343 if ($storage_only_mode) {
345 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
346 die "rootfs configuration could not be recovered, please check and specify manually!\n"
347 if !defined($mp_param->{rootfs
});
348 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
349 my ($ms, $mountpoint) = @_;
350 my $type = $mountpoint->{type
};
351 if ($type eq 'volume') {
352 die "unable to detect disk size - please specify $ms (size)\n"
353 if !defined($mountpoint->{size
});
354 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
355 delete $mountpoint->{size
};
356 $mountpoint->{volume
} = "$storage:$disksize";
357 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
359 my $type = $mountpoint->{type
};
360 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
361 if ($ms eq 'rootfs');
362 die "restoring '$ms' to $type mount is only possible for root\n"
363 if $authuser ne 'root@pam';
365 if ($mountpoint->{backup
}) {
366 warn "WARNING - unsupported configuration!\n";
367 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
368 warn "mount point configuration will be restored after archive extraction!\n";
369 warn "contained files will be restored to wrong directory!\n";
371 delete $mp_param->{$ms}; # actually delay bind/dev mps
372 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
376 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
380 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
382 if (defined($old_conf)) {
383 # destroy old container volumes
384 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
388 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
389 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors);
392 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
394 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
395 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
396 $lxc_setup->post_create_hook($password, $ssh_keys);
400 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
401 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
404 $conf->{hostname
} ||= "CT$vmid";
405 $conf->{memory
} ||= 512;
406 $conf->{swap
} //= 512;
407 foreach my $mp (keys %$delayed_mp_param) {
408 $conf->{$mp} = $delayed_mp_param->{$mp};
410 PVE
::LXC
::Config-
>write_config($vmid, $conf);
413 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
414 PVE
::LXC
::destroy_config
($vmid);
417 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
420 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
422 &$check_vmid_usage(); # first check before locking
424 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
425 $vmid, $authuser, $realcmd);
429 __PACKAGE__-
>register_method({
434 description
=> "Directory index",
439 additionalProperties
=> 0,
441 node
=> get_standard_option
('pve-node'),
442 vmid
=> get_standard_option
('pve-vmid'),
450 subdir
=> { type
=> 'string' },
453 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
459 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
462 { subdir
=> 'config' },
463 { subdir
=> 'status' },
464 { subdir
=> 'vncproxy' },
465 { subdir
=> 'vncwebsocket' },
466 { subdir
=> 'spiceproxy' },
467 { subdir
=> 'migrate' },
468 { subdir
=> 'clone' },
469 # { subdir => 'initlog' },
471 { subdir
=> 'rrddata' },
472 { subdir
=> 'firewall' },
473 { subdir
=> 'snapshot' },
474 { subdir
=> 'resize' },
481 __PACKAGE__-
>register_method({
483 path
=> '{vmid}/rrd',
485 protected
=> 1, # fixme: can we avoid that?
487 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
489 description
=> "Read VM RRD statistics (returns PNG)",
491 additionalProperties
=> 0,
493 node
=> get_standard_option
('pve-node'),
494 vmid
=> get_standard_option
('pve-vmid'),
496 description
=> "Specify the time frame you are interested in.",
498 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
501 description
=> "The list of datasources you want to display.",
502 type
=> 'string', format
=> 'pve-configid-list',
505 description
=> "The RRD consolidation function",
507 enum
=> [ 'AVERAGE', 'MAX' ],
515 filename
=> { type
=> 'string' },
521 return PVE
::Cluster
::create_rrd_graph
(
522 "pve2-vm/$param->{vmid}", $param->{timeframe
},
523 $param->{ds
}, $param->{cf
});
527 __PACKAGE__-
>register_method({
529 path
=> '{vmid}/rrddata',
531 protected
=> 1, # fixme: can we avoid that?
533 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
535 description
=> "Read VM RRD statistics",
537 additionalProperties
=> 0,
539 node
=> get_standard_option
('pve-node'),
540 vmid
=> get_standard_option
('pve-vmid'),
542 description
=> "Specify the time frame you are interested in.",
544 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
547 description
=> "The RRD consolidation function",
549 enum
=> [ 'AVERAGE', 'MAX' ],
564 return PVE
::Cluster
::create_rrd_data
(
565 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
568 __PACKAGE__-
>register_method({
569 name
=> 'destroy_vm',
574 description
=> "Destroy the container (also delete all uses files).",
576 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
579 additionalProperties
=> 0,
581 node
=> get_standard_option
('pve-node'),
582 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
591 my $rpcenv = PVE
::RPCEnvironment
::get
();
593 my $authuser = $rpcenv->get_user();
595 my $vmid = $param->{vmid
};
597 # test if container exists
598 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
600 my $storage_cfg = cfs_read_file
("storage.cfg");
602 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
604 die "unable to remove CT $vmid - used in HA resources\n"
605 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
607 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
609 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
612 # reload config after lock
613 $conf = PVE
::LXC
::Config-
>load_config($vmid);
614 PVE
::LXC
::Config-
>check_lock($conf);
616 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
618 # return without error if vm has no replica job
619 PVE
::ReplicationTools
::destroy_replica
($vmid);
621 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
622 PVE
::AccessControl
::remove_vm_access
($vmid);
623 PVE
::Firewall
::remove_vmfw_conf
($vmid);
626 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
628 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
633 __PACKAGE__-
>register_method ({
635 path
=> '{vmid}/vncproxy',
639 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
641 description
=> "Creates a TCP VNC proxy connections.",
643 additionalProperties
=> 0,
645 node
=> get_standard_option
('pve-node'),
646 vmid
=> get_standard_option
('pve-vmid'),
650 description
=> "use websocket instead of standard VNC.",
655 additionalProperties
=> 0,
657 user
=> { type
=> 'string' },
658 ticket
=> { type
=> 'string' },
659 cert
=> { type
=> 'string' },
660 port
=> { type
=> 'integer' },
661 upid
=> { type
=> 'string' },
667 my $rpcenv = PVE
::RPCEnvironment
::get
();
669 my $authuser = $rpcenv->get_user();
671 my $vmid = $param->{vmid
};
672 my $node = $param->{node
};
674 my $authpath = "/vms/$vmid";
676 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
678 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
681 my ($remip, $family);
683 if ($node ne PVE
::INotify
::nodename
()) {
684 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
686 $family = PVE
::Tools
::get_host_address_family
($node);
689 my $port = PVE
::Tools
::next_vnc_port
($family);
691 # NOTE: vncterm VNC traffic is already TLS encrypted,
692 # so we select the fastest chipher here (or 'none'?)
693 my $remcmd = $remip ?
694 ['/usr/bin/ssh', '-t', $remip] : [];
696 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
697 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
699 my $shcmd = [ '/usr/bin/dtach', '-A',
700 "/var/run/dtach/vzctlconsole$vmid",
701 '-r', 'winch', '-z', @$concmd];
706 syslog
('info', "starting lxc vnc proxy $upid\n");
710 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
711 '-timeout', $timeout, '-authpath', $authpath,
712 '-perm', 'VM.Console'];
714 if ($param->{websocket
}) {
715 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
716 push @$cmd, '-notls', '-listen', 'localhost';
719 push @$cmd, '-c', @$remcmd, @$shcmd;
721 run_command
($cmd, keeplocale
=> 1);
726 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
728 PVE
::Tools
::wait_for_vnc_port
($port);
739 __PACKAGE__-
>register_method({
740 name
=> 'vncwebsocket',
741 path
=> '{vmid}/vncwebsocket',
744 description
=> "You also need to pass a valid ticket (vncticket).",
745 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
747 description
=> "Opens a weksocket for VNC traffic.",
749 additionalProperties
=> 0,
751 node
=> get_standard_option
('pve-node'),
752 vmid
=> get_standard_option
('pve-vmid'),
754 description
=> "Ticket from previous call to vncproxy.",
759 description
=> "Port number returned by previous vncproxy call.",
769 port
=> { type
=> 'string' },
775 my $rpcenv = PVE
::RPCEnvironment
::get
();
777 my $authuser = $rpcenv->get_user();
779 my $authpath = "/vms/$param->{vmid}";
781 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
783 my $port = $param->{port
};
785 return { port
=> $port };
788 __PACKAGE__-
>register_method ({
789 name
=> 'spiceproxy',
790 path
=> '{vmid}/spiceproxy',
795 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
797 description
=> "Returns a SPICE configuration to connect to the CT.",
799 additionalProperties
=> 0,
801 node
=> get_standard_option
('pve-node'),
802 vmid
=> get_standard_option
('pve-vmid'),
803 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
806 returns
=> get_standard_option
('remote-viewer-config'),
810 my $vmid = $param->{vmid
};
811 my $node = $param->{node
};
812 my $proxy = $param->{proxy
};
814 my $authpath = "/vms/$vmid";
815 my $permissions = 'VM.Console';
817 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
819 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
821 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
823 my $shcmd = ['/usr/bin/dtach', '-A',
824 "/var/run/dtach/vzctlconsole$vmid",
825 '-r', 'winch', '-z', @$concmd];
827 my $title = "CT $vmid";
829 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
833 __PACKAGE__-
>register_method({
834 name
=> 'migrate_vm',
835 path
=> '{vmid}/migrate',
839 description
=> "Migrate the container to another node. Creates a new migration task.",
841 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
844 additionalProperties
=> 0,
846 node
=> get_standard_option
('pve-node'),
847 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
848 target
=> get_standard_option
('pve-node', {
849 description
=> "Target node.",
850 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
854 description
=> "Use online/live migration.",
859 description
=> "Use restart migration",
864 description
=> "Timeout in seconds for shutdown for restart migration",
870 description
=> "Force migration despite local bind / device" .
871 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
878 description
=> "the task ID.",
883 my $rpcenv = PVE
::RPCEnvironment
::get
();
885 my $authuser = $rpcenv->get_user();
887 my $target = extract_param
($param, 'target');
889 my $localnode = PVE
::INotify
::nodename
();
890 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
892 PVE
::Cluster
::check_cfs_quorum
();
894 PVE
::Cluster
::check_node_exists
($target);
896 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
898 my $vmid = extract_param
($param, 'vmid');
901 PVE
::LXC
::Config-
>load_config($vmid);
903 # try to detect errors early
904 if (PVE
::LXC
::check_running
($vmid)) {
905 die "can't migrate running container without --online or --restart\n"
906 if !$param->{online
} && !$param->{restart
};
909 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
914 my $service = "ct:$vmid";
916 my $cmd = ['ha-manager', 'migrate', $service, $target];
918 print "Executing HA migrate for CT $vmid to node $target\n";
920 PVE
::Tools
::run_command
($cmd);
925 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
932 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
937 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
941 __PACKAGE__-
>register_method({
942 name
=> 'vm_feature',
943 path
=> '{vmid}/feature',
947 description
=> "Check if feature for virtual machine is available.",
949 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
952 additionalProperties
=> 0,
954 node
=> get_standard_option
('pve-node'),
955 vmid
=> get_standard_option
('pve-vmid'),
957 description
=> "Feature to check.",
959 enum
=> [ 'snapshot' ],
961 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
969 hasFeature
=> { type
=> 'boolean' },
972 #items => { type => 'string' },
979 my $node = extract_param
($param, 'node');
981 my $vmid = extract_param
($param, 'vmid');
983 my $snapname = extract_param
($param, 'snapname');
985 my $feature = extract_param
($param, 'feature');
987 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
990 my $snap = $conf->{snapshots
}->{$snapname};
991 die "snapshot '$snapname' does not exist\n" if !defined($snap);
994 my $storage_cfg = PVE
::Storage
::config
();
996 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
997 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1000 hasFeature
=> $hasFeature,
1001 #nodes => [ keys %$nodelist ],
1005 __PACKAGE__-
>register_method({
1007 path
=> '{vmid}/template',
1011 description
=> "Create a Template.",
1013 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1014 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1017 additionalProperties
=> 0,
1019 node
=> get_standard_option
('pve-node'),
1020 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1023 description
=> "The template feature is experimental, set this " .
1024 "flag if you know what you are doing.",
1029 returns
=> { type
=> 'null'},
1033 my $rpcenv = PVE
::RPCEnvironment
::get
();
1035 my $authuser = $rpcenv->get_user();
1037 my $node = extract_param
($param, 'node');
1039 my $vmid = extract_param
($param, 'vmid');
1041 my $updatefn = sub {
1043 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1044 PVE
::LXC
::Config-
>check_lock($conf);
1046 die "unable to create template, because CT contains snapshots\n"
1047 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1049 die "you can't convert a template to a template\n"
1050 if PVE
::LXC
::Config-
>is_template($conf);
1052 die "you can't convert a CT to template if the CT is running\n"
1053 if PVE
::LXC
::check_running
($vmid);
1056 PVE
::LXC
::template_create
($vmid, $conf);
1059 $conf->{template
} = 1;
1061 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1062 # and remove lxc config
1063 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1065 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1068 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1073 __PACKAGE__-
>register_method({
1075 path
=> '{vmid}/clone',
1079 description
=> "Create a container clone/copy",
1081 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1082 "and 'VM.Allocate' permissions " .
1083 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1084 "'Datastore.AllocateSpace' on any used storage.",
1087 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1089 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1090 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1095 additionalProperties
=> 0,
1097 node
=> get_standard_option
('pve-node'),
1098 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1099 newid
=> get_standard_option
('pve-vmid', {
1100 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1101 description
=> 'VMID for the clone.' }),
1104 type
=> 'string', format
=> 'dns-name',
1105 description
=> "Set a hostname for the new CT.",
1110 description
=> "Description for the new CT.",
1114 type
=> 'string', format
=> 'pve-poolid',
1115 description
=> "Add the new CT to the specified pool.",
1117 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1120 storage
=> get_standard_option
('pve-storage-id', {
1121 description
=> "Target storage for full clone.",
1128 description
=> "Create a full copy of all disk. This is always done when " .
1129 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1134 description
=> "The clone feature is experimental, set this " .
1135 "flag if you know what you are doing.",
1138 # target => get_standard_option('pve-node', {
1139 # description => "Target node. Only allowed if the original VM is on shared storage.",
1150 my $rpcenv = PVE
::RPCEnvironment
::get
();
1152 my $authuser = $rpcenv->get_user();
1154 my $node = extract_param
($param, 'node');
1156 my $vmid = extract_param
($param, 'vmid');
1158 my $newid = extract_param
($param, 'newid');
1160 my $pool = extract_param
($param, 'pool');
1162 if (defined($pool)) {
1163 $rpcenv->check_pool_exist($pool);
1166 my $snapname = extract_param
($param, 'snapname');
1168 my $storage = extract_param
($param, 'storage');
1170 my $localnode = PVE
::INotify
::nodename
();
1172 my $storecfg = PVE
::Storage
::config
();
1175 # check if storage is enabled on local node
1176 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1179 PVE
::Cluster
::check_cfs_quorum
();
1181 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1185 # do all tests after lock
1186 # we also try to do all tests before we fork the worker
1187 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1189 PVE
::LXC
::Config-
>check_lock($conf);
1191 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1193 die "unexpected state change\n" if $verify_running != $running;
1195 die "snapshot '$snapname' does not exist\n"
1196 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1198 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1200 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1201 die "unable to create CT $newid: config file already exists\n"
1204 my $newconf = { lock => 'clone' };
1205 my $mountpoints = {};
1209 foreach my $opt (keys %$oldconf) {
1210 my $value = $oldconf->{$opt};
1212 # no need to copy unused images, because VMID(owner) changes anyways
1213 next if $opt =~ m/^unused\d+$/;
1215 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1216 my $mp = $opt eq 'rootfs' ?
1217 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1218 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1220 if ($mp->{type
} eq 'volume') {
1221 my $volid = $mp->{volume
};
1222 if ($param->{full
}) {
1223 die "fixme: full clone not implemented";
1225 die "Full clone feature for '$volid' is not available\n"
1226 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1227 $fullclone->{$opt} = 1;
1229 # not full means clone instead of copy
1230 die "Linked clone feature for '$volid' is not available\n"
1231 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1234 $mountpoints->{$opt} = $mp;
1235 push @$vollist, $volid;
1238 # TODO: allow bind mounts?
1239 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1243 # copy everything else
1244 $newconf->{$opt} = $value;
1248 delete $newconf->{template
};
1249 if ($param->{hostname
}) {
1250 $newconf->{hostname
} = $param->{hostname
};
1253 if ($param->{description
}) {
1254 $newconf->{description
} = $param->{description
};
1257 # create empty/temp config - this fails if CT already exists on other node
1258 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1263 my $newvollist = [];
1266 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1268 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1270 foreach my $opt (keys %$mountpoints) {
1271 my $mp = $mountpoints->{$opt};
1272 my $volid = $mp->{volume
};
1274 if ($fullclone->{$opt}) {
1275 die "fixme: full clone not implemented\n";
1277 print "create linked clone of mount point $opt ($volid)\n";
1278 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1279 push @$newvollist, $newvolid;
1280 $mp->{volume
} = $newvolid;
1282 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1283 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1287 delete $newconf->{lock};
1288 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1290 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1295 sleep 1; # some storage like rbd need to wait before release volume - really?
1297 foreach my $volid (@$newvollist) {
1298 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1301 die "clone failed: $err";
1307 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1309 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1313 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1317 __PACKAGE__-
>register_method({
1318 name
=> 'resize_vm',
1319 path
=> '{vmid}/resize',
1323 description
=> "Resize a container mount point.",
1325 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1328 additionalProperties
=> 0,
1330 node
=> get_standard_option
('pve-node'),
1331 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1334 description
=> "The disk you want to resize.",
1335 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1339 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1340 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.",
1344 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1352 description
=> "the task ID.",
1357 my $rpcenv = PVE
::RPCEnvironment
::get
();
1359 my $authuser = $rpcenv->get_user();
1361 my $node = extract_param
($param, 'node');
1363 my $vmid = extract_param
($param, 'vmid');
1365 my $digest = extract_param
($param, 'digest');
1367 my $sizestr = extract_param
($param, 'size');
1368 my $ext = ($sizestr =~ s/^\+//);
1369 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1370 die "invalid size string" if !defined($newsize);
1372 die "no options specified\n" if !scalar(keys %$param);
1374 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1376 my $storage_cfg = cfs_read_file
("storage.cfg");
1380 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1381 PVE
::LXC
::Config-
>check_lock($conf);
1383 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1385 my $running = PVE
::LXC
::check_running
($vmid);
1387 my $disk = $param->{disk
};
1388 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1389 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1391 my $volid = $mp->{volume
};
1393 my (undef, undef, $owner, undef, undef, undef, $format) =
1394 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1396 die "can't resize mount point owned by another container ($owner)"
1399 die "can't resize volume: $disk if snapshot exists\n"
1400 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1402 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1404 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1406 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1408 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1409 $newsize += $size if $ext;
1410 $newsize = int($newsize);
1412 die "unable to shrink disk size\n" if $newsize < $size;
1414 return if $size == $newsize;
1416 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1418 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1419 # we pass 0 here (parameter only makes sense for qemu)
1420 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1422 $mp->{size
} = $newsize;
1423 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1425 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1427 if ($format eq 'raw') {
1428 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1432 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1433 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1434 die "internal error: CT running but mount point not attached to a loop device"
1436 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1438 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1439 # to be visible to it in its namespace.
1440 # To not interfere with the rest of the system we unshare the current mount namespace,
1441 # mount over /tmp and then run resize2fs.
1443 # interestingly we don't need to e2fsck on mounted systems...
1444 my $quoted = PVE
::Tools
::shellquote
($path);
1445 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1447 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1449 warn "Failed to update the container's filesystem: $@\n" if $@;
1452 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1453 PVE
::Tools
::run_command
(['resize2fs', $path]);
1455 warn "Failed to update the container's filesystem: $@\n" if $@;
1460 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1463 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;