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
;
14 use PVE
::RPCEnvironment
;
18 use PVE
::JSONSchema
qw(get_standard_option);
19 use base
qw(PVE::RESTHandler);
21 use Data
::Dumper
; # fixme: remove
23 my $check_ct_modify_config_perm = sub {
24 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
26 return 1 if $authuser ne 'root@pam';
28 foreach my $opt (@$key_list) {
30 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
31 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
32 } elsif ($opt eq 'disk') {
33 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
34 } elsif ($opt eq 'memory' || $opt eq 'swap') {
35 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
36 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
37 $opt eq 'searchdomain' || $opt eq 'hostname') {
38 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
40 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
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 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
96 description
=> "The name of the snapshot.",
97 type
=> 'string', format
=> 'pve-configid',
101 __PACKAGE__-
>register_method({
105 description
=> "LXC container index (per node).",
107 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
111 protected
=> 1, # /proc files are only readable by root
113 additionalProperties
=> 0,
115 node
=> get_standard_option
('pve-node'),
124 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
129 my $rpcenv = PVE
::RPCEnvironment
::get
();
130 my $authuser = $rpcenv->get_user();
132 my $vmstatus = PVE
::LXC
::vmstatus
();
135 foreach my $vmid (keys %$vmstatus) {
136 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
138 my $data = $vmstatus->{$vmid};
139 $data->{vmid
} = $vmid;
147 __PACKAGE__-
>register_method({
151 description
=> "Create or restore a container.",
153 user
=> 'all', # check inside
154 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
155 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
156 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
161 additionalProperties
=> 0,
162 properties
=> PVE
::LXC
::json_config_properties_no_rootfs
({
163 node
=> get_standard_option
('pve-node'),
164 vmid
=> get_standard_option
('pve-vmid'),
166 description
=> "The OS template or backup file.",
173 description
=> "Sets root password inside container.",
176 storage
=> get_standard_option
('pve-storage-id', {
177 description
=> "Target storage.",
184 description
=> "Amount of disk space for the VM in GB. A zero indicates no limits.",
191 description
=> "Allow to overwrite existing container.",
196 description
=> "Mark this as restore task.",
200 type
=> 'string', format
=> 'pve-poolid',
201 description
=> "Add the VM to the specified pool.",
211 my $rpcenv = PVE
::RPCEnvironment
::get
();
213 my $authuser = $rpcenv->get_user();
215 my $node = extract_param
($param, 'node');
217 my $vmid = extract_param
($param, 'vmid');
219 my $basecfg_fn = PVE
::LXC
::config_file
($vmid);
221 my $same_container_exists = -f
$basecfg_fn;
223 my $restore = extract_param
($param, 'restore');
226 # fixme: limit allowed parameters
230 my $force = extract_param
($param, 'force');
232 if (!($same_container_exists && $restore && $force)) {
233 PVE
::Cluster
::check_vmid_unused
($vmid);
236 my $password = extract_param
($param, 'password');
238 my $disksize = extract_param
($param, 'size');
240 my $storage = extract_param
($param, 'storage') // 'local';
242 my $storage_cfg = cfs_read_file
("storage.cfg");
244 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $storage, $node);
246 raise_param_exc
({ storage
=> "storage '$storage' does not support container root directories"})
247 if !($scfg->{content
}->{images
} || $scfg->{content
}->{rootdir
});
249 my $pool = extract_param
($param, 'pool');
251 if (defined($pool)) {
252 $rpcenv->check_pool_exist($pool);
253 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
256 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
258 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
260 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
262 } elsif ($restore && $force && $same_container_exists &&
263 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
264 # OK: user has VM.Backup permissions, and want to restore an existing VM
269 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
271 PVE
::Storage
::activate_storage
($storage_cfg, $storage);
273 my $ostemplate = extract_param
($param, 'ostemplate');
277 if ($ostemplate eq '-') {
278 die "pipe requires cli environment\n"
279 if $rpcenv->{type
} ne 'cli';
280 die "pipe can only be used with restore tasks\n"
282 die "pipe requires --size parameter\n"
283 if !defined($disksize);
286 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
287 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
292 PVE
::LXC
::update_pct_config
($vmid, $conf, 0, $param);
294 my $check_vmid_usage = sub {
296 die "cant overwrite running container\n"
297 if PVE
::LXC
::check_running
($vmid);
299 PVE
::Cluster
::check_vmid_unused
($vmid);
304 &$check_vmid_usage(); # final check after locking
306 PVE
::Cluster
::check_cfs_quorum
();
311 if (!defined($disksize)) {
313 (undef, $disksize) = PVE
::LXCCreate
::recover_config
($archive);
314 die "unable to detect disk size - please specify with --size\n"
320 $volid = &$alloc_rootfs($storage_cfg, $storage, $disksize, $vmid);
322 PVE
::LXCCreate
::create_rootfs
($storage_cfg, $storage, $volid, $vmid, $conf,
323 $archive, $password, $restore);
325 $conf->{rootfs
} = PVE
::LXC
::print_ct_mountpoint
({volume
=> $volid, size
=> $disksize });
328 $conf->{hostname
} ||= "CT$vmid";
329 $conf->{memory
} ||= 512;
330 $conf->{swap
} //= 512;
332 PVE
::LXC
::create_config
($vmid, $conf);
335 eval { PVE
::Storage
::vdisk_free
($storage_cfg, $volid) if $volid; };
337 PVE
::LXC
::destroy_config
($vmid);
340 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
343 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
345 &$check_vmid_usage(); # first check before locking
347 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
348 $vmid, $authuser, $realcmd);
352 my $vm_config_perm_list = [
360 __PACKAGE__-
>register_method({
362 path
=> '{vmid}/config',
366 description
=> "Set container options.",
368 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
371 additionalProperties
=> 0,
372 properties
=> PVE
::LXC
::json_config_properties
(
374 node
=> get_standard_option
('pve-node'),
375 vmid
=> get_standard_option
('pve-vmid'),
377 type
=> 'string', format
=> 'pve-configid-list',
378 description
=> "A list of settings you want to delete.",
383 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
389 returns
=> { type
=> 'null'},
393 my $rpcenv = PVE
::RPCEnvironment
::get
();
395 my $authuser = $rpcenv->get_user();
397 my $node = extract_param
($param, 'node');
399 my $vmid = extract_param
($param, 'vmid');
401 my $digest = extract_param
($param, 'digest');
403 die "no options specified\n" if !scalar(keys %$param);
405 my $delete_str = extract_param
($param, 'delete');
406 my @delete = PVE
::Tools
::split_list
($delete_str);
408 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
410 foreach my $opt (@delete) {
411 raise_param_exc
({ delete => "you can't use '-$opt' and " .
412 "-delete $opt' at the same time" })
413 if defined($param->{$opt});
415 if (!PVE
::LXC
::option_exists
($opt)) {
416 raise_param_exc
({ delete => "unknown option '$opt'" });
420 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
422 my $storage_cfg = cfs_read_file
("storage.cfg");
426 my $conf = PVE
::LXC
::load_config
($vmid);
427 PVE
::LXC
::check_lock
($conf);
429 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
431 my $running = PVE
::LXC
::check_running
($vmid);
433 PVE
::LXC
::update_pct_config
($vmid, $conf, $running, $param, \
@delete);
435 PVE
::LXC
::write_config
($vmid, $conf);
436 PVE
::LXC
::update_lxc_config
($storage_cfg, $vmid, $conf);
439 PVE
::LXC
::lock_container
($vmid, undef, $code);
444 __PACKAGE__-
>register_method ({
445 subclass
=> "PVE::API2::Firewall::CT",
446 path
=> '{vmid}/firewall',
449 __PACKAGE__-
>register_method({
454 description
=> "Directory index",
459 additionalProperties
=> 0,
461 node
=> get_standard_option
('pve-node'),
462 vmid
=> get_standard_option
('pve-vmid'),
470 subdir
=> { type
=> 'string' },
473 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
479 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
482 { subdir
=> 'config' },
483 { subdir
=> 'status' },
484 { subdir
=> 'vncproxy' },
485 { subdir
=> 'vncwebsocket' },
486 { subdir
=> 'spiceproxy' },
487 { subdir
=> 'migrate' },
488 # { subdir => 'initlog' },
490 { subdir
=> 'rrddata' },
491 { subdir
=> 'firewall' },
492 { subdir
=> 'snapshot' },
498 __PACKAGE__-
>register_method({
500 path
=> '{vmid}/rrd',
502 protected
=> 1, # fixme: can we avoid that?
504 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
506 description
=> "Read VM RRD statistics (returns PNG)",
508 additionalProperties
=> 0,
510 node
=> get_standard_option
('pve-node'),
511 vmid
=> get_standard_option
('pve-vmid'),
513 description
=> "Specify the time frame you are interested in.",
515 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
518 description
=> "The list of datasources you want to display.",
519 type
=> 'string', format
=> 'pve-configid-list',
522 description
=> "The RRD consolidation function",
524 enum
=> [ 'AVERAGE', 'MAX' ],
532 filename
=> { type
=> 'string' },
538 return PVE
::Cluster
::create_rrd_graph
(
539 "pve2-vm/$param->{vmid}", $param->{timeframe
},
540 $param->{ds
}, $param->{cf
});
544 __PACKAGE__-
>register_method({
546 path
=> '{vmid}/rrddata',
548 protected
=> 1, # fixme: can we avoid that?
550 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
552 description
=> "Read VM RRD statistics",
554 additionalProperties
=> 0,
556 node
=> get_standard_option
('pve-node'),
557 vmid
=> get_standard_option
('pve-vmid'),
559 description
=> "Specify the time frame you are interested in.",
561 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
564 description
=> "The RRD consolidation function",
566 enum
=> [ 'AVERAGE', 'MAX' ],
581 return PVE
::Cluster
::create_rrd_data
(
582 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
586 __PACKAGE__-
>register_method({
588 path
=> '{vmid}/config',
591 description
=> "Get container configuration.",
593 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
596 additionalProperties
=> 0,
598 node
=> get_standard_option
('pve-node'),
599 vmid
=> get_standard_option
('pve-vmid'),
607 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
614 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
616 delete $conf->{snapshots
};
622 __PACKAGE__-
>register_method({
623 name
=> 'destroy_vm',
628 description
=> "Destroy the container (also delete all uses files).",
630 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
633 additionalProperties
=> 0,
635 node
=> get_standard_option
('pve-node'),
636 vmid
=> get_standard_option
('pve-vmid'),
645 my $rpcenv = PVE
::RPCEnvironment
::get
();
647 my $authuser = $rpcenv->get_user();
649 my $vmid = $param->{vmid
};
651 # test if container exists
652 my $conf = PVE
::LXC
::load_config
($vmid);
654 my $storage_cfg = cfs_read_file
("storage.cfg");
657 # reload config after lock
658 $conf = PVE
::LXC
::load_config
($vmid);
659 PVE
::LXC
::check_lock
($conf);
661 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
662 PVE
::AccessControl
::remove_vm_access
($vmid);
665 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
667 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
672 __PACKAGE__-
>register_method ({
674 path
=> '{vmid}/vncproxy',
678 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
680 description
=> "Creates a TCP VNC proxy connections.",
682 additionalProperties
=> 0,
684 node
=> get_standard_option
('pve-node'),
685 vmid
=> get_standard_option
('pve-vmid'),
689 description
=> "use websocket instead of standard VNC.",
694 additionalProperties
=> 0,
696 user
=> { type
=> 'string' },
697 ticket
=> { type
=> 'string' },
698 cert
=> { type
=> 'string' },
699 port
=> { type
=> 'integer' },
700 upid
=> { type
=> 'string' },
706 my $rpcenv = PVE
::RPCEnvironment
::get
();
708 my $authuser = $rpcenv->get_user();
710 my $vmid = $param->{vmid
};
711 my $node = $param->{node
};
713 my $authpath = "/vms/$vmid";
715 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
717 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
720 my ($remip, $family);
722 if ($node ne PVE
::INotify
::nodename
()) {
723 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
725 $family = PVE
::Tools
::get_host_address_family
($node);
728 my $port = PVE
::Tools
::next_vnc_port
($family);
730 # NOTE: vncterm VNC traffic is already TLS encrypted,
731 # so we select the fastest chipher here (or 'none'?)
732 my $remcmd = $remip ?
733 ['/usr/bin/ssh', '-t', $remip] : [];
735 my $conf = PVE
::LXC
::load_config
($vmid);
736 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
738 my $shcmd = [ '/usr/bin/dtach', '-A',
739 "/var/run/dtach/vzctlconsole$vmid",
740 '-r', 'winch', '-z', @$concmd];
745 syslog
('info', "starting lxc vnc proxy $upid\n");
749 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
750 '-timeout', $timeout, '-authpath', $authpath,
751 '-perm', 'VM.Console'];
753 if ($param->{websocket
}) {
754 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
755 push @$cmd, '-notls', '-listen', 'localhost';
758 push @$cmd, '-c', @$remcmd, @$shcmd;
765 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
767 PVE
::Tools
::wait_for_vnc_port
($port);
778 __PACKAGE__-
>register_method({
779 name
=> 'vncwebsocket',
780 path
=> '{vmid}/vncwebsocket',
783 description
=> "You also need to pass a valid ticket (vncticket).",
784 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
786 description
=> "Opens a weksocket for VNC traffic.",
788 additionalProperties
=> 0,
790 node
=> get_standard_option
('pve-node'),
791 vmid
=> get_standard_option
('pve-vmid'),
793 description
=> "Ticket from previous call to vncproxy.",
798 description
=> "Port number returned by previous vncproxy call.",
808 port
=> { type
=> 'string' },
814 my $rpcenv = PVE
::RPCEnvironment
::get
();
816 my $authuser = $rpcenv->get_user();
818 my $authpath = "/vms/$param->{vmid}";
820 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
822 my $port = $param->{port
};
824 return { port
=> $port };
827 __PACKAGE__-
>register_method ({
828 name
=> 'spiceproxy',
829 path
=> '{vmid}/spiceproxy',
834 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
836 description
=> "Returns a SPICE configuration to connect to the CT.",
838 additionalProperties
=> 0,
840 node
=> get_standard_option
('pve-node'),
841 vmid
=> get_standard_option
('pve-vmid'),
842 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
845 returns
=> get_standard_option
('remote-viewer-config'),
849 my $vmid = $param->{vmid
};
850 my $node = $param->{node
};
851 my $proxy = $param->{proxy
};
853 my $authpath = "/vms/$vmid";
854 my $permissions = 'VM.Console';
856 my $conf = PVE
::LXC
::load_config
($vmid);
857 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
859 my $shcmd = ['/usr/bin/dtach', '-A',
860 "/var/run/dtach/vzctlconsole$vmid",
861 '-r', 'winch', '-z', @$concmd];
863 my $title = "CT $vmid";
865 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
868 __PACKAGE__-
>register_method({
870 path
=> '{vmid}/status',
873 description
=> "Directory index",
878 additionalProperties
=> 0,
880 node
=> get_standard_option
('pve-node'),
881 vmid
=> get_standard_option
('pve-vmid'),
889 subdir
=> { type
=> 'string' },
892 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
898 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
901 { subdir
=> 'current' },
902 { subdir
=> 'start' },
903 { subdir
=> 'stop' },
904 { subdir
=> 'shutdown' },
905 { subdir
=> 'migrate' },
911 __PACKAGE__-
>register_method({
913 path
=> '{vmid}/status/current',
916 protected
=> 1, # openvz /proc entries are only readable by root
917 description
=> "Get virtual machine status.",
919 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
922 additionalProperties
=> 0,
924 node
=> get_standard_option
('pve-node'),
925 vmid
=> get_standard_option
('pve-vmid'),
928 returns
=> { type
=> 'object' },
933 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
935 my $vmstatus = PVE
::LXC
::vmstatus
($param->{vmid
});
936 my $status = $vmstatus->{$param->{vmid
}};
938 $status->{ha
} = PVE
::HA
::Config
::vm_is_ha_managed
($param->{vmid
}) ?
1 : 0;
943 __PACKAGE__-
>register_method({
945 path
=> '{vmid}/status/start',
949 description
=> "Start the container.",
951 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
954 additionalProperties
=> 0,
956 node
=> get_standard_option
('pve-node'),
957 vmid
=> get_standard_option
('pve-vmid'),
966 my $rpcenv = PVE
::RPCEnvironment
::get
();
968 my $authuser = $rpcenv->get_user();
970 my $node = extract_param
($param, 'node');
972 my $vmid = extract_param
($param, 'vmid');
974 die "CT $vmid already running\n" if PVE
::LXC
::check_running
($vmid);
976 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
981 my $service = "ct:$vmid";
983 my $cmd = ['ha-manager', 'enable', $service];
985 print "Executing HA start for CT $vmid\n";
987 PVE
::Tools
::run_command
($cmd);
992 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
999 syslog
('info', "starting CT $vmid: $upid\n");
1001 my $conf = PVE
::LXC
::load_config
($vmid);
1003 die "you can't start a CT if it's a template\n"
1004 if PVE
::LXC
::is_template
($conf);
1006 my $storage_cfg = cfs_read_file
("storage.cfg");
1008 PVE
::LXC
::foreach_mountpoint
($conf, sub {
1009 my ($ms, $mountpoint) = @_;
1011 my $volid = $mountpoint->{volume
};
1012 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1014 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1015 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1016 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1017 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1019 if($ms ne 'rootfs' && $format ne 'subvol' && ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs')) {
1020 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1021 PVE
::Tools
::run_command
(['losetup', '--find', '--show', $path]);
1025 PVE
::LXC
::update_lxc_config
($storage_cfg, $vmid, $conf);
1027 my $cmd = ['lxc-start', '-n', $vmid];
1034 return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd);
1038 __PACKAGE__-
>register_method({
1040 path
=> '{vmid}/status/stop',
1044 description
=> "Stop the container.",
1046 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1049 additionalProperties
=> 0,
1051 node
=> get_standard_option
('pve-node'),
1052 vmid
=> get_standard_option
('pve-vmid'),
1061 my $rpcenv = PVE
::RPCEnvironment
::get
();
1063 my $authuser = $rpcenv->get_user();
1065 my $node = extract_param
($param, 'node');
1067 my $vmid = extract_param
($param, 'vmid');
1069 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1071 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1076 my $service = "ct:$vmid";
1078 my $cmd = ['ha-manager', 'disable', $service];
1080 print "Executing HA stop for CT $vmid\n";
1082 PVE
::Tools
::run_command
($cmd);
1087 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1094 syslog
('info', "stoping CT $vmid: $upid\n");
1096 my $conf = PVE
::LXC
::load_config
($vmid);
1098 my $storage_cfg = PVE
::Storage
::config
();
1100 my $cmd = ['lxc-stop', '-n', $vmid, '--kill'];
1104 PVE
::LXC
::vm_stop_cleanup
($storage_cfg, $vmid, $conf);
1109 return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd);
1113 __PACKAGE__-
>register_method({
1114 name
=> 'vm_shutdown',
1115 path
=> '{vmid}/status/shutdown',
1119 description
=> "Shutdown the container.",
1121 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1124 additionalProperties
=> 0,
1126 node
=> get_standard_option
('pve-node'),
1127 vmid
=> get_standard_option
('pve-vmid'),
1129 description
=> "Wait maximal timeout seconds.",
1136 description
=> "Make sure the Container stops.",
1149 my $rpcenv = PVE
::RPCEnvironment
::get
();
1151 my $authuser = $rpcenv->get_user();
1153 my $node = extract_param
($param, 'node');
1155 my $vmid = extract_param
($param, 'vmid');
1157 my $timeout = extract_param
($param, 'timeout');
1159 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1164 syslog
('info', "shutdown CT $vmid: $upid\n");
1166 my $cmd = ['lxc-stop', '-n', $vmid];
1168 $timeout = 60 if !defined($timeout);
1170 my $conf = PVE
::LXC
::load_config
($vmid);
1172 my $storage_cfg = PVE
::Storage
::config
();
1174 push @$cmd, '--timeout', $timeout;
1176 eval { run_command
($cmd, timeout
=> $timeout+5); };
1178 if ($err && $param->{forceStop
}) {
1180 warn "shutdown failed - forcing stop now\n";
1182 push @$cmd, '--kill';
1187 PVE
::LXC
::vm_stop_cleanup
($storage_cfg, $vmid, $conf);
1194 my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd);
1199 __PACKAGE__-
>register_method({
1200 name
=> 'vm_suspend',
1201 path
=> '{vmid}/status/suspend',
1205 description
=> "Suspend the container.",
1207 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1210 additionalProperties
=> 0,
1212 node
=> get_standard_option
('pve-node'),
1213 vmid
=> get_standard_option
('pve-vmid'),
1222 my $rpcenv = PVE
::RPCEnvironment
::get
();
1224 my $authuser = $rpcenv->get_user();
1226 my $node = extract_param
($param, 'node');
1228 my $vmid = extract_param
($param, 'vmid');
1230 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1235 syslog
('info', "suspend CT $vmid: $upid\n");
1237 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-s', '-D', '/var/liv/vz/dump'];
1244 my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd);
1249 __PACKAGE__-
>register_method({
1250 name
=> 'vm_resume',
1251 path
=> '{vmid}/status/resume',
1255 description
=> "Resume the container.",
1257 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1260 additionalProperties
=> 0,
1262 node
=> get_standard_option
('pve-node'),
1263 vmid
=> get_standard_option
('pve-vmid'),
1272 my $rpcenv = PVE
::RPCEnvironment
::get
();
1274 my $authuser = $rpcenv->get_user();
1276 my $node = extract_param
($param, 'node');
1278 my $vmid = extract_param
($param, 'vmid');
1280 die "CT $vmid already running\n" if PVE
::LXC
::check_running
($vmid);
1285 syslog
('info', "resume CT $vmid: $upid\n");
1287 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-r', '--foreground',
1288 '-D', '/var/liv/vz/dump'];
1295 my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd);
1300 __PACKAGE__-
>register_method({
1301 name
=> 'migrate_vm',
1302 path
=> '{vmid}/migrate',
1306 description
=> "Migrate the container to another node. Creates a new migration task.",
1308 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1311 additionalProperties
=> 0,
1313 node
=> get_standard_option
('pve-node'),
1314 vmid
=> get_standard_option
('pve-vmid'),
1315 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1318 description
=> "Use online/live migration.",
1325 description
=> "the task ID.",
1330 my $rpcenv = PVE
::RPCEnvironment
::get
();
1332 my $authuser = $rpcenv->get_user();
1334 my $target = extract_param
($param, 'target');
1336 my $localnode = PVE
::INotify
::nodename
();
1337 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1339 PVE
::Cluster
::check_cfs_quorum
();
1341 PVE
::Cluster
::check_node_exists
($target);
1343 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1345 my $vmid = extract_param
($param, 'vmid');
1348 PVE
::LXC
::load_config
($vmid);
1350 # try to detect errors early
1351 if (PVE
::LXC
::check_running
($vmid)) {
1352 die "cant migrate running container without --online\n"
1353 if !$param->{online
};
1356 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1361 my $service = "ct:$vmid";
1363 my $cmd = ['ha-manager', 'migrate', $service, $target];
1365 print "Executing HA migrate for CT $vmid to node $target\n";
1367 PVE
::Tools
::run_command
($cmd);
1372 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1379 # fixme: implement lxc container migration
1380 die "lxc container migration not implemented\n";
1385 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
1389 __PACKAGE__-
>register_method({
1391 path
=> '{vmid}/snapshot',
1395 description
=> "Snapshot a container.",
1397 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1400 additionalProperties
=> 0,
1402 node
=> get_standard_option
('pve-node'),
1403 vmid
=> get_standard_option
('pve-vmid'),
1404 snapname
=> get_standard_option
('pve-lxc-snapshot-name'),
1407 # type => 'boolean',
1408 # description => "Save the vmstate",
1413 description
=> "A textual description or comment.",
1419 description
=> "the task ID.",
1424 my $rpcenv = PVE
::RPCEnvironment
::get
();
1426 my $authuser = $rpcenv->get_user();
1428 my $node = extract_param
($param, 'node');
1430 my $vmid = extract_param
($param, 'vmid');
1432 my $snapname = extract_param
($param, 'snapname');
1434 die "unable to use snapshot name 'current' (reserved name)\n"
1435 if $snapname eq 'current';
1438 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot container $vmid: $snapname");
1439 PVE
::LXC
::snapshot_create
($vmid, $snapname, $param->{description
});
1442 return $rpcenv->fork_worker('pctsnapshot', $vmid, $authuser, $realcmd);
1445 __PACKAGE__-
>register_method({
1446 name
=> 'delsnapshot',
1447 path
=> '{vmid}/snapshot/{snapname}',
1451 description
=> "Delete a LXC snapshot.",
1453 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1456 additionalProperties
=> 0,
1458 node
=> get_standard_option
('pve-node'),
1459 vmid
=> get_standard_option
('pve-vmid'),
1460 snapname
=> get_standard_option
('pve-lxc-snapshot-name'),
1464 description
=> "For removal from config file, even if removing disk snapshots fails.",
1470 description
=> "the task ID.",
1475 my $rpcenv = PVE
::RPCEnvironment
::get
();
1477 my $authuser = $rpcenv->get_user();
1479 my $node = extract_param
($param, 'node');
1481 my $vmid = extract_param
($param, 'vmid');
1483 my $snapname = extract_param
($param, 'snapname');
1486 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
1487 PVE
::LXC
::snapshot_delete
($vmid, $snapname, $param->{force
});
1490 return $rpcenv->fork_worker('lxcdelsnapshot', $vmid, $authuser, $realcmd);
1493 __PACKAGE__-
>register_method({
1495 path
=> '{vmid}/snapshot/{snapname}/rollback',
1499 description
=> "Rollback LXC state to specified snapshot.",
1501 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1504 additionalProperties
=> 0,
1506 node
=> get_standard_option
('pve-node'),
1507 vmid
=> get_standard_option
('pve-vmid'),
1508 snapname
=> get_standard_option
('pve-lxc-snapshot-name'),
1513 description
=> "the task ID.",
1518 my $rpcenv = PVE
::RPCEnvironment
::get
();
1520 my $authuser = $rpcenv->get_user();
1522 my $node = extract_param
($param, 'node');
1524 my $vmid = extract_param
($param, 'vmid');
1526 my $snapname = extract_param
($param, 'snapname');
1529 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot LXC $vmid: $snapname");
1530 PVE
::LXC
::snapshot_rollback
($vmid, $snapname);
1533 return $rpcenv->fork_worker('lxcrollback', $vmid, $authuser, $realcmd);
1536 __PACKAGE__-
>register_method({
1537 name
=> 'snapshot_list',
1538 path
=> '{vmid}/snapshot',
1540 description
=> "List all snapshots.",
1542 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1545 protected
=> 1, # lxc pid files are only readable by root
1547 additionalProperties
=> 0,
1549 vmid
=> get_standard_option
('pve-vmid'),
1550 node
=> get_standard_option
('pve-node'),
1559 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1564 my $vmid = $param->{vmid
};
1566 my $conf = PVE
::LXC
::load_config
($vmid);
1567 my $snaphash = $conf->{snapshots
} || {};
1571 foreach my $name (keys %$snaphash) {
1572 my $d = $snaphash->{$name};
1575 snaptime
=> $d->{snaptime
} || 0,
1576 description
=> $d->{description
} || '',
1578 $item->{parent
} = $d->{parent
} if defined($d->{parent
});
1579 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
1583 my $running = PVE
::LXC
::check_running
($vmid) ?
1 : 0;
1584 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
1585 $current->{parent
} = $conf->{parent
} if defined($conf->{parent
});
1587 push @$res, $current;
1592 __PACKAGE__-
>register_method({
1593 name
=> 'snapshot_cmd_idx',
1594 path
=> '{vmid}/snapshot/{snapname}',
1601 additionalProperties
=> 0,
1603 vmid
=> get_standard_option
('pve-vmid'),
1604 node
=> get_standard_option
('pve-node'),
1605 snapname
=> get_standard_option
('pve-lxc-snapshot-name'),
1614 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
1621 push @$res, { cmd
=> 'rollback' };
1622 push @$res, { cmd
=> 'config' };
1627 __PACKAGE__-
>register_method({
1628 name
=> 'update_snapshot_config',
1629 path
=> '{vmid}/snapshot/{snapname}/config',
1633 description
=> "Update snapshot metadata.",
1635 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1638 additionalProperties
=> 0,
1640 node
=> get_standard_option
('pve-node'),
1641 vmid
=> get_standard_option
('pve-vmid'),
1642 snapname
=> get_standard_option
('pve-lxc-snapshot-name'),
1646 description
=> "A textual description or comment.",
1650 returns
=> { type
=> 'null' },
1654 my $rpcenv = PVE
::RPCEnvironment
::get
();
1656 my $authuser = $rpcenv->get_user();
1658 my $vmid = extract_param
($param, 'vmid');
1660 my $snapname = extract_param
($param, 'snapname');
1662 return undef if !defined($param->{description
});
1664 my $updatefn = sub {
1666 my $conf = PVE
::LXC
::load_config
($vmid);
1667 PVE
::LXC
::check_lock
($conf);
1669 my $snap = $conf->{snapshots
}->{$snapname};
1671 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1673 $snap->{description
} = $param->{description
} if defined($param->{description
});
1675 PVE
::LXC
::write_config
($vmid, $conf, 1);
1678 PVE
::LXC
::lock_container
($vmid, 10, $updatefn);
1683 __PACKAGE__-
>register_method({
1684 name
=> 'get_snapshot_config',
1685 path
=> '{vmid}/snapshot/{snapname}/config',
1688 description
=> "Get snapshot configuration",
1690 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1693 additionalProperties
=> 0,
1695 node
=> get_standard_option
('pve-node'),
1696 vmid
=> get_standard_option
('pve-vmid'),
1697 snapname
=> get_standard_option
('pve-lxc-snapshot-name'),
1700 returns
=> { type
=> "object" },
1704 my $rpcenv = PVE
::RPCEnvironment
::get
();
1706 my $authuser = $rpcenv->get_user();
1708 my $vmid = extract_param
($param, 'vmid');
1710 my $snapname = extract_param
($param, 'snapname');
1712 my $conf = PVE
::LXC
::load_config
($vmid);
1714 my $snap = $conf->{snapshots
}->{$snapname};
1716 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1718 delete $snap->{lxc
};
1723 __PACKAGE__-
>register_method({
1724 name
=> 'vm_feature',
1725 path
=> '{vmid}/feature',
1729 description
=> "Check if feature for virtual machine is available.",
1731 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1734 additionalProperties
=> 0,
1736 node
=> get_standard_option
('pve-node'),
1737 vmid
=> get_standard_option
('pve-vmid'),
1739 description
=> "Feature to check.",
1741 enum
=> [ 'snapshot' ],
1743 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1751 hasFeature
=> { type
=> 'boolean' },
1754 #items => { type => 'string' },
1761 my $node = extract_param
($param, 'node');
1763 my $vmid = extract_param
($param, 'vmid');
1765 my $snapname = extract_param
($param, 'snapname');
1767 my $feature = extract_param
($param, 'feature');
1769 my $conf = PVE
::LXC
::load_config
($vmid);
1772 my $snap = $conf->{snapshots
}->{$snapname};
1773 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1776 my $storage_cfg = PVE
::Storage
::config
();
1777 #Maybe include later
1778 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1779 my $hasFeature = PVE
::LXC
::has_feature
($feature, $conf, $storage_cfg, $snapname);
1782 hasFeature
=> $hasFeature,
1783 #nodes => [ keys %$nodelist ],
1787 __PACKAGE__-
>register_method({
1789 path
=> '{vmid}/template',
1793 description
=> "Create a Template.",
1795 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1796 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1799 additionalProperties
=> 0,
1801 node
=> get_standard_option
('pve-node'),
1802 vmid
=> get_standard_option
('pve-vmid'),
1805 returns
=> { type
=> 'null'},
1809 my $rpcenv = PVE
::RPCEnvironment
::get
();
1811 my $authuser = $rpcenv->get_user();
1813 my $node = extract_param
($param, 'node');
1815 my $vmid = extract_param
($param, 'vmid');
1817 my $updatefn = sub {
1819 my $conf = PVE
::LXC
::load_config
($vmid);
1820 PVE
::LXC
::check_lock
($conf);
1822 die "unable to create template, because CT contains snapshots\n"
1823 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1825 die "you can't convert a template to a template\n"
1826 if PVE
::LXC
::is_template
($conf);
1828 die "you can't convert a CT to template if the CT is running\n"
1829 if PVE
::LXC
::check_running
($vmid);
1832 PVE
::LXC
::template_create
($vmid, $conf);
1835 $conf->{template
} = 1;
1837 PVE
::LXC
::write_config
($vmid, $conf);
1838 # and remove lxc config
1839 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
1841 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1844 PVE
::LXC
::lock_container
($vmid, undef, $updatefn);