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
;
23 use PVE
::JSONSchema
qw(get_standard_option);
24 use base
qw(PVE::RESTHandler);
26 use Data
::Dumper
; # fixme: remove
28 __PACKAGE__-
>register_method ({
29 subclass
=> "PVE::API2::LXC::Config",
30 path
=> '{vmid}/config',
33 __PACKAGE__-
>register_method ({
34 subclass
=> "PVE::API2::LXC::Status",
35 path
=> '{vmid}/status',
38 __PACKAGE__-
>register_method ({
39 subclass
=> "PVE::API2::LXC::Snapshot",
40 path
=> '{vmid}/snapshot',
43 __PACKAGE__-
>register_method ({
44 subclass
=> "PVE::API2::Firewall::CT",
45 path
=> '{vmid}/firewall',
48 __PACKAGE__-
>register_method({
52 description
=> "LXC container index (per node).",
54 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
58 protected
=> 1, # /proc files are only readable by root
60 additionalProperties
=> 0,
62 node
=> get_standard_option
('pve-node'),
71 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
76 my $rpcenv = PVE
::RPCEnvironment
::get
();
77 my $authuser = $rpcenv->get_user();
79 my $vmstatus = PVE
::LXC
::vmstatus
();
82 foreach my $vmid (keys %$vmstatus) {
83 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
85 my $data = $vmstatus->{$vmid};
86 $data->{vmid
} = $vmid;
94 __PACKAGE__-
>register_method({
98 description
=> "Create or restore a container.",
100 user
=> 'all', # check inside
101 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
102 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
103 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
108 additionalProperties
=> 0,
109 properties
=> PVE
::LXC
::json_config_properties
({
110 node
=> get_standard_option
('pve-node'),
111 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
113 description
=> "The OS template or backup file.",
116 completion
=> \
&PVE
::LXC
::complete_os_templates
,
121 description
=> "Sets root password inside container.",
124 storage
=> get_standard_option
('pve-storage-id', {
125 description
=> "Default Storage.",
132 description
=> "Allow to overwrite existing container.",
137 description
=> "Mark this as restore task.",
141 type
=> 'string', format
=> 'pve-poolid',
142 description
=> "Add the VM to the specified pool.",
152 my $rpcenv = PVE
::RPCEnvironment
::get
();
154 my $authuser = $rpcenv->get_user();
156 my $node = extract_param
($param, 'node');
158 my $vmid = extract_param
($param, 'vmid');
160 my $basecfg_fn = PVE
::LXC
::config_file
($vmid);
162 my $same_container_exists = -f
$basecfg_fn;
164 my $restore = extract_param
($param, 'restore');
167 # fixme: limit allowed parameters
171 my $force = extract_param
($param, 'force');
173 if (!($same_container_exists && $restore && $force)) {
174 PVE
::Cluster
::check_vmid_unused
($vmid);
176 my $conf = PVE
::LXC
::load_config
($vmid);
177 PVE
::LXC
::check_protection
($conf, "unable to restore CT $vmid");
180 my $password = extract_param
($param, 'password');
182 my $storage = extract_param
($param, 'storage') // 'local';
184 my $storage_cfg = cfs_read_file
("storage.cfg");
186 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $storage, $node);
188 raise_param_exc
({ storage
=> "storage '$storage' does not support container root directories"})
189 if !($scfg->{content
}->{images
} || $scfg->{content
}->{rootdir
});
191 my $pool = extract_param
($param, 'pool');
193 if (defined($pool)) {
194 $rpcenv->check_pool_exist($pool);
195 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
198 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
200 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
202 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
204 } elsif ($restore && $force && $same_container_exists &&
205 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
206 # OK: user has VM.Backup permissions, and want to restore an existing VM
211 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
213 PVE
::Storage
::activate_storage
($storage_cfg, $storage);
215 my $ostemplate = extract_param
($param, 'ostemplate');
219 if ($ostemplate eq '-') {
220 die "pipe requires cli environment\n"
221 if $rpcenv->{type
} ne 'cli';
222 die "pipe can only be used with restore tasks\n"
225 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
227 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
228 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
233 my $no_disk_param = {};
234 foreach my $opt (keys %$param) {
235 my $value = $param->{$opt};
236 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
237 # allow to use simple numbers (add default storage in that case)
238 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
240 $no_disk_param->{$opt} = $value;
243 PVE
::LXC
::update_pct_config
($vmid, $conf, 0, $no_disk_param);
245 my $check_vmid_usage = sub {
247 die "can't overwrite running container\n"
248 if PVE
::LXC
::check_running
($vmid);
250 PVE
::Cluster
::check_vmid_unused
($vmid);
255 &$check_vmid_usage(); # final check after locking
257 PVE
::Cluster
::check_cfs_quorum
();
261 if (!defined($param->{rootfs
})) {
263 my (undef, $disksize) = PVE
::LXC
::Create
::recover_config
($archive);
264 $disksize /= 1024 * 1024 * 1024; # create_disks expects GB as unit size
265 die "unable to detect disk size - please specify rootfs (size)\n"
267 $param->{rootfs
} = "$storage:$disksize";
269 $param->{rootfs
} = "$storage:4"; # defaults to 4GB
273 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $param, $conf);
275 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $vmid, $conf, $archive, $password, $restore);
277 $conf->{hostname
} ||= "CT$vmid";
278 $conf->{memory
} ||= 512;
279 $conf->{swap
} //= 512;
280 PVE
::LXC
::create_config
($vmid, $conf);
283 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
284 PVE
::LXC
::destroy_config
($vmid);
287 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
290 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
292 &$check_vmid_usage(); # first check before locking
294 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
295 $vmid, $authuser, $realcmd);
299 __PACKAGE__-
>register_method({
304 description
=> "Directory index",
309 additionalProperties
=> 0,
311 node
=> get_standard_option
('pve-node'),
312 vmid
=> get_standard_option
('pve-vmid'),
320 subdir
=> { type
=> 'string' },
323 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
329 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
332 { subdir
=> 'config' },
333 { subdir
=> 'status' },
334 { subdir
=> 'vncproxy' },
335 { subdir
=> 'vncwebsocket' },
336 { subdir
=> 'spiceproxy' },
337 { subdir
=> 'migrate' },
338 # { subdir => 'initlog' },
340 { subdir
=> 'rrddata' },
341 { subdir
=> 'firewall' },
342 { subdir
=> 'snapshot' },
348 __PACKAGE__-
>register_method({
350 path
=> '{vmid}/rrd',
352 protected
=> 1, # fixme: can we avoid that?
354 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
356 description
=> "Read VM RRD statistics (returns PNG)",
358 additionalProperties
=> 0,
360 node
=> get_standard_option
('pve-node'),
361 vmid
=> get_standard_option
('pve-vmid'),
363 description
=> "Specify the time frame you are interested in.",
365 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
368 description
=> "The list of datasources you want to display.",
369 type
=> 'string', format
=> 'pve-configid-list',
372 description
=> "The RRD consolidation function",
374 enum
=> [ 'AVERAGE', 'MAX' ],
382 filename
=> { type
=> 'string' },
388 return PVE
::Cluster
::create_rrd_graph
(
389 "pve2-vm/$param->{vmid}", $param->{timeframe
},
390 $param->{ds
}, $param->{cf
});
394 __PACKAGE__-
>register_method({
396 path
=> '{vmid}/rrddata',
398 protected
=> 1, # fixme: can we avoid that?
400 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
402 description
=> "Read VM RRD statistics",
404 additionalProperties
=> 0,
406 node
=> get_standard_option
('pve-node'),
407 vmid
=> get_standard_option
('pve-vmid'),
409 description
=> "Specify the time frame you are interested in.",
411 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
414 description
=> "The RRD consolidation function",
416 enum
=> [ 'AVERAGE', 'MAX' ],
431 return PVE
::Cluster
::create_rrd_data
(
432 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
435 __PACKAGE__-
>register_method({
436 name
=> 'destroy_vm',
441 description
=> "Destroy the container (also delete all uses files).",
443 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
446 additionalProperties
=> 0,
448 node
=> get_standard_option
('pve-node'),
449 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
458 my $rpcenv = PVE
::RPCEnvironment
::get
();
460 my $authuser = $rpcenv->get_user();
462 my $vmid = $param->{vmid
};
464 # test if container exists
465 my $conf = PVE
::LXC
::load_config
($vmid);
467 my $storage_cfg = cfs_read_file
("storage.cfg");
469 PVE
::LXC
::check_protection
($conf, "can't remove CT $vmid");
471 die "unable to remove CT $vmid - used in HA resources\n"
472 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
475 # reload config after lock
476 $conf = PVE
::LXC
::load_config
($vmid);
477 PVE
::LXC
::check_lock
($conf);
479 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
480 PVE
::AccessControl
::remove_vm_access
($vmid);
481 PVE
::Firewall
::remove_vmfw_conf
($vmid);
484 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
486 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
491 __PACKAGE__-
>register_method ({
493 path
=> '{vmid}/vncproxy',
497 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
499 description
=> "Creates a TCP VNC proxy connections.",
501 additionalProperties
=> 0,
503 node
=> get_standard_option
('pve-node'),
504 vmid
=> get_standard_option
('pve-vmid'),
508 description
=> "use websocket instead of standard VNC.",
513 additionalProperties
=> 0,
515 user
=> { type
=> 'string' },
516 ticket
=> { type
=> 'string' },
517 cert
=> { type
=> 'string' },
518 port
=> { type
=> 'integer' },
519 upid
=> { type
=> 'string' },
525 my $rpcenv = PVE
::RPCEnvironment
::get
();
527 my $authuser = $rpcenv->get_user();
529 my $vmid = $param->{vmid
};
530 my $node = $param->{node
};
532 my $authpath = "/vms/$vmid";
534 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
536 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
539 my ($remip, $family);
541 if ($node ne PVE
::INotify
::nodename
()) {
542 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
544 $family = PVE
::Tools
::get_host_address_family
($node);
547 my $port = PVE
::Tools
::next_vnc_port
($family);
549 # NOTE: vncterm VNC traffic is already TLS encrypted,
550 # so we select the fastest chipher here (or 'none'?)
551 my $remcmd = $remip ?
552 ['/usr/bin/ssh', '-t', $remip] : [];
554 my $conf = PVE
::LXC
::load_config
($vmid, $node);
555 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
557 my $shcmd = [ '/usr/bin/dtach', '-A',
558 "/var/run/dtach/vzctlconsole$vmid",
559 '-r', 'winch', '-z', @$concmd];
564 syslog
('info', "starting lxc vnc proxy $upid\n");
568 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
569 '-timeout', $timeout, '-authpath', $authpath,
570 '-perm', 'VM.Console'];
572 if ($param->{websocket
}) {
573 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
574 push @$cmd, '-notls', '-listen', 'localhost';
577 push @$cmd, '-c', @$remcmd, @$shcmd;
584 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
586 PVE
::Tools
::wait_for_vnc_port
($port);
597 __PACKAGE__-
>register_method({
598 name
=> 'vncwebsocket',
599 path
=> '{vmid}/vncwebsocket',
602 description
=> "You also need to pass a valid ticket (vncticket).",
603 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
605 description
=> "Opens a weksocket for VNC traffic.",
607 additionalProperties
=> 0,
609 node
=> get_standard_option
('pve-node'),
610 vmid
=> get_standard_option
('pve-vmid'),
612 description
=> "Ticket from previous call to vncproxy.",
617 description
=> "Port number returned by previous vncproxy call.",
627 port
=> { type
=> 'string' },
633 my $rpcenv = PVE
::RPCEnvironment
::get
();
635 my $authuser = $rpcenv->get_user();
637 my $authpath = "/vms/$param->{vmid}";
639 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
641 my $port = $param->{port
};
643 return { port
=> $port };
646 __PACKAGE__-
>register_method ({
647 name
=> 'spiceproxy',
648 path
=> '{vmid}/spiceproxy',
653 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
655 description
=> "Returns a SPICE configuration to connect to the CT.",
657 additionalProperties
=> 0,
659 node
=> get_standard_option
('pve-node'),
660 vmid
=> get_standard_option
('pve-vmid'),
661 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
664 returns
=> get_standard_option
('remote-viewer-config'),
668 my $vmid = $param->{vmid
};
669 my $node = $param->{node
};
670 my $proxy = $param->{proxy
};
672 my $authpath = "/vms/$vmid";
673 my $permissions = 'VM.Console';
675 my $conf = PVE
::LXC
::load_config
($vmid);
677 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
679 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
681 my $shcmd = ['/usr/bin/dtach', '-A',
682 "/var/run/dtach/vzctlconsole$vmid",
683 '-r', 'winch', '-z', @$concmd];
685 my $title = "CT $vmid";
687 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
691 __PACKAGE__-
>register_method({
692 name
=> 'migrate_vm',
693 path
=> '{vmid}/migrate',
697 description
=> "Migrate the container to another node. Creates a new migration task.",
699 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
702 additionalProperties
=> 0,
704 node
=> get_standard_option
('pve-node'),
705 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
706 target
=> get_standard_option
('pve-node', {
707 description
=> "Target node.",
708 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
712 description
=> "Use online/live migration.",
719 description
=> "the task ID.",
724 my $rpcenv = PVE
::RPCEnvironment
::get
();
726 my $authuser = $rpcenv->get_user();
728 my $target = extract_param
($param, 'target');
730 my $localnode = PVE
::INotify
::nodename
();
731 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
733 PVE
::Cluster
::check_cfs_quorum
();
735 PVE
::Cluster
::check_node_exists
($target);
737 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
739 my $vmid = extract_param
($param, 'vmid');
742 PVE
::LXC
::load_config
($vmid);
744 # try to detect errors early
745 if (PVE
::LXC
::check_running
($vmid)) {
746 die "can't migrate running container without --online\n"
747 if !$param->{online
};
750 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
755 my $service = "ct:$vmid";
757 my $cmd = ['ha-manager', 'migrate', $service, $target];
759 print "Executing HA migrate for CT $vmid to node $target\n";
761 PVE
::Tools
::run_command
($cmd);
766 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
773 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
778 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
782 __PACKAGE__-
>register_method({
783 name
=> 'vm_feature',
784 path
=> '{vmid}/feature',
788 description
=> "Check if feature for virtual machine is available.",
790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
793 additionalProperties
=> 0,
795 node
=> get_standard_option
('pve-node'),
796 vmid
=> get_standard_option
('pve-vmid'),
798 description
=> "Feature to check.",
800 enum
=> [ 'snapshot' ],
802 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
810 hasFeature
=> { type
=> 'boolean' },
813 #items => { type => 'string' },
820 my $node = extract_param
($param, 'node');
822 my $vmid = extract_param
($param, 'vmid');
824 my $snapname = extract_param
($param, 'snapname');
826 my $feature = extract_param
($param, 'feature');
828 my $conf = PVE
::LXC
::load_config
($vmid);
831 my $snap = $conf->{snapshots
}->{$snapname};
832 die "snapshot '$snapname' does not exist\n" if !defined($snap);
835 my $storage_cfg = PVE
::Storage
::config
();
837 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
838 my $hasFeature = PVE
::LXC
::has_feature
($feature, $conf, $storage_cfg, $snapname);
841 hasFeature
=> $hasFeature,
842 #nodes => [ keys %$nodelist ],
846 __PACKAGE__-
>register_method({
848 path
=> '{vmid}/template',
852 description
=> "Create a Template.",
854 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
855 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
858 additionalProperties
=> 0,
860 node
=> get_standard_option
('pve-node'),
861 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
864 returns
=> { type
=> 'null'},
868 my $rpcenv = PVE
::RPCEnvironment
::get
();
870 my $authuser = $rpcenv->get_user();
872 my $node = extract_param
($param, 'node');
874 my $vmid = extract_param
($param, 'vmid');
878 my $conf = PVE
::LXC
::load_config
($vmid);
879 PVE
::LXC
::check_lock
($conf);
881 die "unable to create template, because CT contains snapshots\n"
882 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
884 die "you can't convert a template to a template\n"
885 if PVE
::LXC
::is_template
($conf);
887 die "you can't convert a CT to template if the CT is running\n"
888 if PVE
::LXC
::check_running
($vmid);
891 PVE
::LXC
::template_create
($vmid, $conf);
894 $conf->{template
} = 1;
896 PVE
::LXC
::write_config
($vmid, $conf);
897 # and remove lxc config
898 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
900 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
903 PVE
::LXC
::lock_container
($vmid, undef, $updatefn);