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
::API2
::LXC
::Config
;
19 use PVE
::API2
::LXC
::Status
;
20 use PVE
::API2
::LXC
::Snapshot
;
22 use PVE
::JSONSchema
qw(get_standard_option);
23 use base
qw(PVE::RESTHandler);
25 use Data
::Dumper
; # fixme: remove
27 __PACKAGE__-
>register_method ({
28 subclass
=> "PVE::API2::LXC::Config",
29 path
=> '{vmid}/config',
32 __PACKAGE__-
>register_method ({
33 subclass
=> "PVE::API2::LXC::Status",
34 path
=> '{vmid}/status',
37 __PACKAGE__-
>register_method ({
38 subclass
=> "PVE::API2::LXC::Snapshot",
39 path
=> '{vmid}/snapshot',
42 __PACKAGE__-
>register_method ({
43 subclass
=> "PVE::API2::Firewall::CT",
44 path
=> '{vmid}/firewall',
47 my $alloc_rootfs = sub {
48 my ($storage_conf, $storage, $disk_size_gb, $vmid) = @_;
52 my $size = 4*1024*1024; # defaults to 4G
54 $size = int($disk_size_gb*1024) * 1024 if defined($disk_size_gb);
57 my $scfg = PVE
::Storage
::storage_config
($storage_conf, $storage);
58 # fixme: use better naming ct-$vmid-disk-X.raw?
60 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
62 $volid = PVE
::Storage
::vdisk_alloc
($storage_conf, $storage, $vmid, 'raw',
65 $volid = PVE
::Storage
::vdisk_alloc
($storage_conf, $storage, $vmid, 'subvol',
68 } elsif ($scfg->{type
} eq 'zfspool') {
70 $volid = PVE
::Storage
::vdisk_alloc
($storage_conf, $storage, $vmid, 'subvol',
72 } elsif ($scfg->{type
} eq 'drbd') {
74 $volid = PVE
::Storage
::vdisk_alloc
($storage_conf, $storage, $vmid, 'raw', undef, $size);
76 } elsif ($scfg->{type
} eq 'rbd') {
78 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
79 $volid = PVE
::Storage
::vdisk_alloc
($storage_conf, $storage, $vmid, 'raw', undef, $size);
82 die "unable to create containers on storage type '$scfg->{type}'\n";
87 eval { PVE
::Storage
::vdisk_free
($storage_conf, $volid) if $volid; };
95 __PACKAGE__-
>register_method({
99 description
=> "LXC container index (per node).",
101 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
105 protected
=> 1, # /proc files are only readable by root
107 additionalProperties
=> 0,
109 node
=> get_standard_option
('pve-node'),
118 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
123 my $rpcenv = PVE
::RPCEnvironment
::get
();
124 my $authuser = $rpcenv->get_user();
126 my $vmstatus = PVE
::LXC
::vmstatus
();
129 foreach my $vmid (keys %$vmstatus) {
130 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
132 my $data = $vmstatus->{$vmid};
133 $data->{vmid
} = $vmid;
141 __PACKAGE__-
>register_method({
145 description
=> "Create or restore a container.",
147 user
=> 'all', # check inside
148 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
149 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
150 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
155 additionalProperties
=> 0,
156 properties
=> PVE
::LXC
::json_config_properties_no_rootfs
({
157 node
=> get_standard_option
('pve-node'),
158 vmid
=> get_standard_option
('pve-vmid'),
160 description
=> "The OS template or backup file.",
167 description
=> "Sets root password inside container.",
170 storage
=> get_standard_option
('pve-storage-id', {
171 description
=> "Target storage.",
178 description
=> "Amount of disk space for the VM in GB. A zero indicates no limits.",
185 description
=> "Allow to overwrite existing container.",
190 description
=> "Mark this as restore task.",
194 type
=> 'string', format
=> 'pve-poolid',
195 description
=> "Add the VM to the specified pool.",
205 my $rpcenv = PVE
::RPCEnvironment
::get
();
207 my $authuser = $rpcenv->get_user();
209 my $node = extract_param
($param, 'node');
211 my $vmid = extract_param
($param, 'vmid');
213 my $basecfg_fn = PVE
::LXC
::config_file
($vmid);
215 my $same_container_exists = -f
$basecfg_fn;
217 my $restore = extract_param
($param, 'restore');
220 # fixme: limit allowed parameters
224 my $force = extract_param
($param, 'force');
226 if (!($same_container_exists && $restore && $force)) {
227 PVE
::Cluster
::check_vmid_unused
($vmid);
230 my $password = extract_param
($param, 'password');
232 my $disksize = extract_param
($param, 'size');
234 my $storage = extract_param
($param, 'storage') // 'local';
236 my $storage_cfg = cfs_read_file
("storage.cfg");
238 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $storage, $node);
240 raise_param_exc
({ storage
=> "storage '$storage' does not support container root directories"})
241 if !($scfg->{content
}->{images
} || $scfg->{content
}->{rootdir
});
243 my $pool = extract_param
($param, 'pool');
245 if (defined($pool)) {
246 $rpcenv->check_pool_exist($pool);
247 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
250 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
252 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
254 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
256 } elsif ($restore && $force && $same_container_exists &&
257 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
258 # OK: user has VM.Backup permissions, and want to restore an existing VM
263 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
265 PVE
::Storage
::activate_storage
($storage_cfg, $storage);
267 my $ostemplate = extract_param
($param, 'ostemplate');
271 if ($ostemplate eq '-') {
272 die "pipe requires cli environment\n"
273 if $rpcenv->{type
} ne 'cli';
274 die "pipe can only be used with restore tasks\n"
276 die "pipe requires --size parameter\n"
277 if !defined($disksize);
280 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
281 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
286 PVE
::LXC
::update_pct_config
($vmid, $conf, 0, $param);
288 my $check_vmid_usage = sub {
290 die "cant overwrite running container\n"
291 if PVE
::LXC
::check_running
($vmid);
293 PVE
::Cluster
::check_vmid_unused
($vmid);
298 &$check_vmid_usage(); # final check after locking
300 PVE
::Cluster
::check_cfs_quorum
();
305 if (!defined($disksize)) {
307 (undef, $disksize) = PVE
::LXC
::Create
::recover_config
($archive);
308 die "unable to detect disk size - please specify with --size\n"
314 $volid = &$alloc_rootfs($storage_cfg, $storage, $disksize, $vmid);
316 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $storage, $volid, $vmid, $conf,
317 $archive, $password, $restore);
319 $conf->{rootfs
} = PVE
::LXC
::print_ct_mountpoint
({volume
=> $volid, size
=> $disksize });
322 $conf->{hostname
} ||= "CT$vmid";
323 $conf->{memory
} ||= 512;
324 $conf->{swap
} //= 512;
326 PVE
::LXC
::create_config
($vmid, $conf);
329 eval { PVE
::Storage
::vdisk_free
($storage_cfg, $volid) if $volid; };
331 PVE
::LXC
::destroy_config
($vmid);
334 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
337 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
339 &$check_vmid_usage(); # first check before locking
341 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
342 $vmid, $authuser, $realcmd);
346 __PACKAGE__-
>register_method({
351 description
=> "Directory index",
356 additionalProperties
=> 0,
358 node
=> get_standard_option
('pve-node'),
359 vmid
=> get_standard_option
('pve-vmid'),
367 subdir
=> { type
=> 'string' },
370 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
376 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
379 { subdir
=> 'config' },
380 { subdir
=> 'status' },
381 { subdir
=> 'vncproxy' },
382 { subdir
=> 'vncwebsocket' },
383 { subdir
=> 'spiceproxy' },
384 { subdir
=> 'migrate' },
385 # { subdir => 'initlog' },
387 { subdir
=> 'rrddata' },
388 { subdir
=> 'firewall' },
389 { subdir
=> 'snapshot' },
395 __PACKAGE__-
>register_method({
397 path
=> '{vmid}/rrd',
399 protected
=> 1, # fixme: can we avoid that?
401 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
403 description
=> "Read VM RRD statistics (returns PNG)",
405 additionalProperties
=> 0,
407 node
=> get_standard_option
('pve-node'),
408 vmid
=> get_standard_option
('pve-vmid'),
410 description
=> "Specify the time frame you are interested in.",
412 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
415 description
=> "The list of datasources you want to display.",
416 type
=> 'string', format
=> 'pve-configid-list',
419 description
=> "The RRD consolidation function",
421 enum
=> [ 'AVERAGE', 'MAX' ],
429 filename
=> { type
=> 'string' },
435 return PVE
::Cluster
::create_rrd_graph
(
436 "pve2-vm/$param->{vmid}", $param->{timeframe
},
437 $param->{ds
}, $param->{cf
});
441 __PACKAGE__-
>register_method({
443 path
=> '{vmid}/rrddata',
445 protected
=> 1, # fixme: can we avoid that?
447 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
449 description
=> "Read VM RRD statistics",
451 additionalProperties
=> 0,
453 node
=> get_standard_option
('pve-node'),
454 vmid
=> get_standard_option
('pve-vmid'),
456 description
=> "Specify the time frame you are interested in.",
458 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
461 description
=> "The RRD consolidation function",
463 enum
=> [ 'AVERAGE', 'MAX' ],
478 return PVE
::Cluster
::create_rrd_data
(
479 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
482 __PACKAGE__-
>register_method({
483 name
=> 'destroy_vm',
488 description
=> "Destroy the container (also delete all uses files).",
490 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
493 additionalProperties
=> 0,
495 node
=> get_standard_option
('pve-node'),
496 vmid
=> get_standard_option
('pve-vmid'),
505 my $rpcenv = PVE
::RPCEnvironment
::get
();
507 my $authuser = $rpcenv->get_user();
509 my $vmid = $param->{vmid
};
511 # test if container exists
512 my $conf = PVE
::LXC
::load_config
($vmid);
514 my $storage_cfg = cfs_read_file
("storage.cfg");
517 # reload config after lock
518 $conf = PVE
::LXC
::load_config
($vmid);
519 PVE
::LXC
::check_lock
($conf);
521 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
522 PVE
::AccessControl
::remove_vm_access
($vmid);
523 PVE
::Firewall
::remove_vmfw_conf
($vmid);
526 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
528 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
533 __PACKAGE__-
>register_method ({
535 path
=> '{vmid}/vncproxy',
539 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
541 description
=> "Creates a TCP VNC proxy connections.",
543 additionalProperties
=> 0,
545 node
=> get_standard_option
('pve-node'),
546 vmid
=> get_standard_option
('pve-vmid'),
550 description
=> "use websocket instead of standard VNC.",
555 additionalProperties
=> 0,
557 user
=> { type
=> 'string' },
558 ticket
=> { type
=> 'string' },
559 cert
=> { type
=> 'string' },
560 port
=> { type
=> 'integer' },
561 upid
=> { type
=> 'string' },
567 my $rpcenv = PVE
::RPCEnvironment
::get
();
569 my $authuser = $rpcenv->get_user();
571 my $vmid = $param->{vmid
};
572 my $node = $param->{node
};
574 my $authpath = "/vms/$vmid";
576 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
578 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
581 my ($remip, $family);
583 if ($node ne PVE
::INotify
::nodename
()) {
584 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
586 $family = PVE
::Tools
::get_host_address_family
($node);
589 my $port = PVE
::Tools
::next_vnc_port
($family);
591 # NOTE: vncterm VNC traffic is already TLS encrypted,
592 # so we select the fastest chipher here (or 'none'?)
593 my $remcmd = $remip ?
594 ['/usr/bin/ssh', '-t', $remip] : [];
596 my $conf = PVE
::LXC
::load_config
($vmid, $node);
597 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
599 my $shcmd = [ '/usr/bin/dtach', '-A',
600 "/var/run/dtach/vzctlconsole$vmid",
601 '-r', 'winch', '-z', @$concmd];
606 syslog
('info', "starting lxc vnc proxy $upid\n");
610 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
611 '-timeout', $timeout, '-authpath', $authpath,
612 '-perm', 'VM.Console'];
614 if ($param->{websocket
}) {
615 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
616 push @$cmd, '-notls', '-listen', 'localhost';
619 push @$cmd, '-c', @$remcmd, @$shcmd;
626 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
628 PVE
::Tools
::wait_for_vnc_port
($port);
639 __PACKAGE__-
>register_method({
640 name
=> 'vncwebsocket',
641 path
=> '{vmid}/vncwebsocket',
644 description
=> "You also need to pass a valid ticket (vncticket).",
645 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
647 description
=> "Opens a weksocket for VNC traffic.",
649 additionalProperties
=> 0,
651 node
=> get_standard_option
('pve-node'),
652 vmid
=> get_standard_option
('pve-vmid'),
654 description
=> "Ticket from previous call to vncproxy.",
659 description
=> "Port number returned by previous vncproxy call.",
669 port
=> { type
=> 'string' },
675 my $rpcenv = PVE
::RPCEnvironment
::get
();
677 my $authuser = $rpcenv->get_user();
679 my $authpath = "/vms/$param->{vmid}";
681 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
683 my $port = $param->{port
};
685 return { port
=> $port };
688 __PACKAGE__-
>register_method ({
689 name
=> 'spiceproxy',
690 path
=> '{vmid}/spiceproxy',
695 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
697 description
=> "Returns a SPICE configuration to connect to the CT.",
699 additionalProperties
=> 0,
701 node
=> get_standard_option
('pve-node'),
702 vmid
=> get_standard_option
('pve-vmid'),
703 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
706 returns
=> get_standard_option
('remote-viewer-config'),
710 my $vmid = $param->{vmid
};
711 my $node = $param->{node
};
712 my $proxy = $param->{proxy
};
714 my $authpath = "/vms/$vmid";
715 my $permissions = 'VM.Console';
717 my $conf = PVE
::LXC
::load_config
($vmid);
718 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
720 my $shcmd = ['/usr/bin/dtach', '-A',
721 "/var/run/dtach/vzctlconsole$vmid",
722 '-r', 'winch', '-z', @$concmd];
724 my $title = "CT $vmid";
726 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
730 __PACKAGE__-
>register_method({
731 name
=> 'migrate_vm',
732 path
=> '{vmid}/migrate',
736 description
=> "Migrate the container to another node. Creates a new migration task.",
738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
741 additionalProperties
=> 0,
743 node
=> get_standard_option
('pve-node'),
744 vmid
=> get_standard_option
('pve-vmid'),
745 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
748 description
=> "Use online/live migration.",
755 description
=> "the task ID.",
760 my $rpcenv = PVE
::RPCEnvironment
::get
();
762 my $authuser = $rpcenv->get_user();
764 my $target = extract_param
($param, 'target');
766 my $localnode = PVE
::INotify
::nodename
();
767 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
769 PVE
::Cluster
::check_cfs_quorum
();
771 PVE
::Cluster
::check_node_exists
($target);
773 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
775 my $vmid = extract_param
($param, 'vmid');
778 PVE
::LXC
::load_config
($vmid);
780 # try to detect errors early
781 if (PVE
::LXC
::check_running
($vmid)) {
782 die "cant migrate running container without --online\n"
783 if !$param->{online
};
786 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
791 my $service = "ct:$vmid";
793 my $cmd = ['ha-manager', 'migrate', $service, $target];
795 print "Executing HA migrate for CT $vmid to node $target\n";
797 PVE
::Tools
::run_command
($cmd);
802 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
809 # fixme: implement lxc container migration
810 die "lxc container migration not implemented\n";
815 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
819 __PACKAGE__-
>register_method({
820 name
=> 'vm_feature',
821 path
=> '{vmid}/feature',
825 description
=> "Check if feature for virtual machine is available.",
827 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
830 additionalProperties
=> 0,
832 node
=> get_standard_option
('pve-node'),
833 vmid
=> get_standard_option
('pve-vmid'),
835 description
=> "Feature to check.",
837 enum
=> [ 'snapshot' ],
839 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
847 hasFeature
=> { type
=> 'boolean' },
850 #items => { type => 'string' },
857 my $node = extract_param
($param, 'node');
859 my $vmid = extract_param
($param, 'vmid');
861 my $snapname = extract_param
($param, 'snapname');
863 my $feature = extract_param
($param, 'feature');
865 my $conf = PVE
::LXC
::load_config
($vmid);
868 my $snap = $conf->{snapshots
}->{$snapname};
869 die "snapshot '$snapname' does not exist\n" if !defined($snap);
872 my $storage_cfg = PVE
::Storage
::config
();
874 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
875 my $hasFeature = PVE
::LXC
::has_feature
($feature, $conf, $storage_cfg, $snapname);
878 hasFeature
=> $hasFeature,
879 #nodes => [ keys %$nodelist ],
883 __PACKAGE__-
>register_method({
885 path
=> '{vmid}/template',
889 description
=> "Create a Template.",
891 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
892 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
895 additionalProperties
=> 0,
897 node
=> get_standard_option
('pve-node'),
898 vmid
=> get_standard_option
('pve-vmid'),
901 returns
=> { type
=> 'null'},
905 my $rpcenv = PVE
::RPCEnvironment
::get
();
907 my $authuser = $rpcenv->get_user();
909 my $node = extract_param
($param, 'node');
911 my $vmid = extract_param
($param, 'vmid');
915 my $conf = PVE
::LXC
::load_config
($vmid);
916 PVE
::LXC
::check_lock
($conf);
918 die "unable to create template, because CT contains snapshots\n"
919 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
921 die "you can't convert a template to a template\n"
922 if PVE
::LXC
::is_template
($conf);
924 die "you can't convert a CT to template if the CT is running\n"
925 if PVE
::LXC
::check_running
($vmid);
928 PVE
::LXC
::template_create
($vmid, $conf);
931 $conf->{template
} = 1;
933 PVE
::LXC
::write_config
($vmid, $conf);
934 # and remove lxc config
935 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
937 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
940 PVE
::LXC
::lock_container
($vmid, undef, $updatefn);