]>
Commit | Line | Data |
---|---|---|
339e4159 DM |
1 | package PVE::API2::OpenVZ; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use File::Basename; | |
aabf3add | 6 | use File::Path; |
c3163e37 | 7 | use POSIX qw (LONG_MAX); |
339e4159 DM |
8 | |
9 | use PVE::SafeSyslog; | |
aabf3add | 10 | use PVE::Tools qw(extract_param run_command); |
7e79e293 DM |
11 | use PVE::Exception qw(raise raise_param_exc); |
12 | use PVE::INotify; | |
8993f2db DM |
13 | use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file); |
14 | use PVE::AccessControl; | |
339e4159 DM |
15 | use PVE::Storage; |
16 | use PVE::RESTHandler; | |
17 | use PVE::RPCEnvironment; | |
18 | use PVE::OpenVZ; | |
0618d446 | 19 | use PVE::OpenVZMigrate; |
339e4159 DM |
20 | use PVE::JSONSchema qw(get_standard_option); |
21 | ||
22 | use base qw(PVE::RESTHandler); | |
23 | ||
24 | use Data::Dumper; # fixme: remove | |
25 | ||
26 | my $pve_base_ovz_config = <<__EOD; | |
27 | ONBOOT="no" | |
28 | ||
29 | PHYSPAGES="0:256M" | |
30 | SWAPPAGES="0:256M" | |
31 | KMEMSIZE="116M:128M" | |
32 | DCACHESIZE="58M:64M" | |
33 | LOCKEDPAGES="128M" | |
34 | PRIVVMPAGES="unlimited" | |
35 | SHMPAGES="unlimited" | |
36 | NUMPROC="unlimited" | |
37 | VMGUARPAGES="0:unlimited" | |
38 | OOMGUARPAGES="0:unlimited" | |
39 | NUMTCPSOCK="unlimited" | |
40 | NUMFLOCK="unlimited" | |
41 | NUMPTY="unlimited" | |
42 | NUMSIGINFO="unlimited" | |
43 | TCPSNDBUF="unlimited" | |
44 | TCPRCVBUF="unlimited" | |
45 | OTHERSOCKBUF="unlimited" | |
46 | DGRAMRCVBUF="unlimited" | |
47 | NUMOTHERSOCK="unlimited" | |
48 | NUMFILE="unlimited" | |
49 | NUMIPTENT="unlimited" | |
50 | ||
51 | # Disk quota parameters (in form of softlimit:hardlimit) | |
52 | DISKSPACE="unlimited:unlimited" | |
53 | DISKINODES="unlimited:unlimited" | |
54 | QUOTATIME="0" | |
55 | QUOTAUGIDLIMIT="0" | |
56 | ||
57 | # CPU fair scheduler parameter | |
58 | CPUUNITS="1000" | |
59 | CPUS="1" | |
60 | __EOD | |
61 | ||
bc8f054e DM |
62 | my $get_container_storage = sub { |
63 | my ($stcfg, $vmid, $veconf) = @_; | |
64 | ||
65 | my $path = PVE::OpenVZ::get_privatedir($veconf, $vmid); | |
66 | my ($vtype, $volid) = PVE::Storage::path_to_volume_id($stcfg, $path); | |
67 | my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1) if $volid; | |
68 | return wantarray ? ($sid, $volname, $path) : $sid; | |
69 | }; | |
70 | ||
8993f2db DM |
71 | my $check_ct_modify_config_perm = sub { |
72 | my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_; | |
73 | ||
74 | return 1 if $authuser ne 'root@pam'; | |
75 | ||
76 | foreach my $opt (@$key_list) { | |
77 | ||
78 | if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') { | |
79 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']); | |
80 | } elsif ($opt eq 'disk' || $opt eq 'quotatime' || $opt eq 'quotaugidlimit') { | |
81 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']); | |
82 | } elsif ($opt eq 'memory' || $opt eq 'swap') { | |
83 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']); | |
84 | } elsif ($opt eq 'netif' || $opt eq 'ip_address' || $opt eq 'nameserver' || | |
85 | $opt eq 'searchdomain' || $opt eq 'hostname') { | |
86 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']); | |
87 | } else { | |
88 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']); | |
89 | } | |
90 | } | |
91 | ||
92 | return 1; | |
93 | }; | |
94 | ||
339e4159 DM |
95 | __PACKAGE__->register_method({ |
96 | name => 'vmlist', | |
97 | path => '', | |
98 | method => 'GET', | |
99 | description => "OpenVZ container index (per node).", | |
85dc4bf7 DM |
100 | permissions => { |
101 | description => "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.", | |
102 | user => 'all', | |
103 | }, | |
339e4159 DM |
104 | proxyto => 'node', |
105 | protected => 1, # openvz proc files are only readable by root | |
106 | parameters => { | |
107 | additionalProperties => 0, | |
108 | properties => { | |
109 | node => get_standard_option('pve-node'), | |
110 | }, | |
111 | }, | |
112 | returns => { | |
113 | type => 'array', | |
114 | items => { | |
115 | type => "object", | |
116 | properties => {}, | |
117 | }, | |
118 | links => [ { rel => 'child', href => "{vmid}" } ], | |
119 | }, | |
120 | code => sub { | |
121 | my ($param) = @_; | |
122 | ||
85dc4bf7 DM |
123 | my $rpcenv = PVE::RPCEnvironment::get(); |
124 | my $authuser = $rpcenv->get_user(); | |
125 | ||
339e4159 DM |
126 | my $vmstatus = PVE::OpenVZ::vmstatus(); |
127 | ||
85dc4bf7 DM |
128 | my $res = []; |
129 | foreach my $vmid (keys %$vmstatus) { | |
130 | next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1); | |
339e4159 | 131 | |
85dc4bf7 DM |
132 | my $data = $vmstatus->{$vmid}; |
133 | $data->{vmid} = $vmid; | |
134 | push @$res, $data; | |
135 | } | |
136 | ||
137 | return $res; | |
138 | ||
339e4159 DM |
139 | }}); |
140 | ||
aabf3add | 141 | my $restore_openvz = sub { |
9f767883 | 142 | my ($private, $archive, $vmid, $force) = @_; |
aabf3add DM |
143 | |
144 | my $vzconf = PVE::OpenVZ::read_global_vz_config (); | |
145 | my $conffile = PVE::OpenVZ::config_file($vmid); | |
146 | my $cfgdir = dirname($conffile); | |
147 | ||
aabf3add DM |
148 | my $root = $vzconf->{rootdir}; |
149 | $root =~ s/\$VEID/$vmid/; | |
150 | ||
151 | print "you choose to force overwriting VPS config file, private and root directories.\n" if $force; | |
152 | ||
153 | die "unable to create CT $vmid - container already exists\n" | |
154 | if !$force && -f $conffile; | |
155 | ||
156 | die "unable to create CT $vmid - directory '$private' already exists\n" | |
157 | if !$force && -d $private; | |
158 | ||
159 | die "unable to create CT $vmid - directory '$root' already exists\n" | |
160 | if !$force && -d $root; | |
161 | ||
162 | my $conf; | |
163 | ||
164 | eval { | |
9f767883 DM |
165 | if ($force && -f $conffile) { |
166 | my $conf = PVE::OpenVZ::load_config($vmid); | |
167 | ||
2d590018 | 168 | my $oldprivate = PVE::OpenVZ::get_privatedir($conf, $vmid); |
9f767883 DM |
169 | rmtree $oldprivate if -d $oldprivate; |
170 | ||
171 | my $oldroot = $conf->{ve_root} ? $conf->{ve_root}->{value} : $root; | |
172 | rmtree $oldroot if -d $oldroot; | |
173 | }; | |
aabf3add DM |
174 | |
175 | mkpath $private || die "unable to create private dir '$private'"; | |
176 | mkpath $root || die "unable to create private dir '$private'"; | |
177 | ||
178 | my $cmd = ['tar', 'xpf', $archive, '--totals', '--sparse', '-C', $private]; | |
179 | ||
180 | if ($archive eq '-') { | |
181 | print "extracting archive from STDIN\n"; | |
182 | run_command($cmd, input => "<&STDIN"); | |
183 | } else { | |
184 | print "extracting archive '$archive'\n"; | |
185 | run_command($cmd); | |
186 | } | |
187 | ||
188 | my $backup_cfg = "$private/etc/vzdump/vps.conf"; | |
189 | if (-f $backup_cfg) { | |
190 | print "restore configuration to '$conffile'\n"; | |
191 | ||
0618d446 | 192 | my $conf = PVE::Tools::file_get_contents($backup_cfg); |
aabf3add DM |
193 | |
194 | $conf =~ s/VE_ROOT=.*/VE_ROOT=\"$root\"/; | |
195 | $conf =~ s/VE_PRIVATE=.*/VE_PRIVATE=\"$private\"/; | |
196 | $conf =~ s/host_ifname=veth[0-9]+\./host_ifname=veth${vmid}\./g; | |
197 | ||
198 | PVE::Tools::file_set_contents($conffile, $conf); | |
199 | ||
200 | foreach my $s (PVE::OpenVZ::SCRIPT_EXT) { | |
201 | my $tfn = "$cfgdir/${vmid}.$s"; | |
202 | my $sfn = "$private/etc/vzdump/vps.$s"; | |
203 | if (-f $sfn) { | |
204 | my $sc = PVE::Tools::file_get_contents($sfn); | |
205 | PVE::Tools::file_set_contents($tfn, $sc); | |
206 | } | |
207 | } | |
208 | } | |
209 | ||
210 | rmtree "$private/etc/vzdump"; | |
211 | }; | |
212 | ||
213 | my $err = $@; | |
214 | ||
215 | if ($err) { | |
216 | rmtree $private; | |
217 | rmtree $root; | |
218 | unlink $conffile; | |
219 | foreach my $s (PVE::OpenVZ::SCRIPT_EXT) { | |
220 | unlink "$cfgdir/${vmid}.$s"; | |
221 | } | |
222 | die $err; | |
223 | } | |
224 | ||
225 | return $conf; | |
226 | }; | |
227 | ||
228 | # create_vm is also used by vzrestore | |
339e4159 DM |
229 | __PACKAGE__->register_method({ |
230 | name => 'create_vm', | |
231 | path => '', | |
232 | method => 'POST', | |
aabf3add | 233 | description => "Create or restore a container.", |
8993f2db DM |
234 | permissions => { |
235 | description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}, and 'Datastore.AllocateSpace' on the storage.", | |
236 | check => [ 'or', | |
237 | [ 'perm', '/vms/{vmid}', ['VM.Allocate']], | |
238 | [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'], | |
239 | ], | |
240 | }, | |
339e4159 DM |
241 | protected => 1, |
242 | proxyto => 'node', | |
243 | parameters => { | |
244 | additionalProperties => 0, | |
245 | properties => PVE::OpenVZ::json_config_properties({ | |
246 | node => get_standard_option('pve-node'), | |
247 | vmid => get_standard_option('pve-vmid'), | |
248 | ostemplate => { | |
aabf3add | 249 | description => "The OS template or backup file.", |
339e4159 DM |
250 | type => 'string', |
251 | maxLength => 255, | |
252 | }, | |
253 | password => { | |
254 | optional => 1, | |
255 | type => 'string', | |
256 | description => "Sets root password inside container.", | |
257 | }, | |
9f767883 DM |
258 | storage => get_standard_option('pve-storage-id', { |
259 | description => "Target storage.", | |
260 | default => 'local', | |
261 | optional => 1, | |
262 | }), | |
aabf3add DM |
263 | force => { |
264 | optional => 1, | |
265 | type => 'boolean', | |
266 | description => "Allow to overwrite existing container.", | |
267 | }, | |
268 | restore => { | |
269 | optional => 1, | |
270 | type => 'boolean', | |
271 | description => "Mark this as restore task.", | |
272 | }, | |
8993f2db DM |
273 | pool => { |
274 | optional => 1, | |
275 | type => 'string', format => 'pve-poolid', | |
276 | description => "Add the VM to the specified pool.", | |
277 | }, | |
339e4159 DM |
278 | }), |
279 | }, | |
7ca813e6 DM |
280 | returns => { |
281 | type => 'string', | |
282 | }, | |
339e4159 DM |
283 | code => sub { |
284 | my ($param) = @_; | |
285 | ||
7ca813e6 DM |
286 | my $rpcenv = PVE::RPCEnvironment::get(); |
287 | ||
8993f2db | 288 | my $authuser = $rpcenv->get_user(); |
7ca813e6 | 289 | |
339e4159 DM |
290 | my $node = extract_param($param, 'node'); |
291 | ||
339e4159 DM |
292 | my $vmid = extract_param($param, 'vmid'); |
293 | ||
294 | my $password = extract_param($param, 'password'); | |
295 | ||
9f767883 DM |
296 | my $storage = extract_param($param, 'storage') || 'local'; |
297 | ||
8993f2db DM |
298 | my $pool = extract_param($param, 'pool'); |
299 | ||
9f767883 DM |
300 | my $storage_cfg = cfs_read_file("storage.cfg"); |
301 | ||
302 | my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node); | |
303 | ||
304 | raise_param_exc({ storage => "storage '$storage' does not support openvz root directories"}) | |
305 | if !$scfg->{content}->{rootdir}; | |
306 | ||
307 | my $private = PVE::Storage::get_private_dir($storage_cfg, $storage, $vmid); | |
308 | ||
8993f2db DM |
309 | if (defined($pool)) { |
310 | $rpcenv->check_pool_exist($pool); | |
311 | $rpcenv->check_perm_modify($authuser, "/pool/$pool"); | |
312 | } | |
313 | ||
314 | $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']); | |
315 | ||
316 | &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]); | |
317 | ||
9f767883 | 318 | PVE::Storage::activate_storage($storage_cfg, $storage); |
339e4159 DM |
319 | |
320 | my $conf = PVE::OpenVZ::parse_ovz_config("/tmp/openvz/$vmid.conf", $pve_base_ovz_config); | |
321 | ||
8993f2db DM |
322 | my $addVMtoPoolFn = sub { |
323 | my $usercfg = cfs_read_file("user.cfg"); | |
324 | if (my $data = $usercfg->{pools}->{$pool}) { | |
325 | $data->{vms}->{$vmid} = 1; | |
326 | $usercfg->{vms}->{$vmid} = $pool; | |
327 | cfs_write_file("user.cfg", $usercfg); | |
328 | } | |
329 | }; | |
330 | ||
92480040 | 331 | my $ostemplate = extract_param($param, 'ostemplate'); |
339e4159 | 332 | |
92480040 | 333 | my $archive; |
339e4159 | 334 | |
92480040 DM |
335 | if ($ostemplate eq '-') { |
336 | die "pipe requires cli environment\n" | |
337 | if $rpcenv->{type} ne 'cli'; | |
338 | die "pipe can only be used with restore tasks\n" | |
339 | if !$param->{restore}; | |
340 | $archive = '-'; | |
341 | } else { | |
342 | $archive = $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate); | |
343 | die "can't find file '$archive'\n" if ! -f $archive; | |
344 | } | |
339e4159 | 345 | |
92480040 DM |
346 | if (!defined($param->{searchdomain}) && |
347 | !defined($param->{nameserver})) { | |
348 | ||
349 | my $resolv = PVE::INotify::read_file('resolvconf'); | |
aabf3add | 350 | |
92480040 | 351 | $param->{searchdomain} = $resolv->{search} if $resolv->{search}; |
339e4159 | 352 | |
92480040 DM |
353 | my @ns = (); |
354 | push @ns, $resolv->{dns1} if $resolv->{dns1}; | |
355 | push @ns, $resolv->{dns2} if $resolv->{dns2}; | |
356 | push @ns, $resolv->{dns3} if $resolv->{dns3}; | |
4223bcba | 357 | |
92480040 DM |
358 | $param->{nameserver} = join(' ', @ns) if scalar(@ns); |
359 | } | |
4223bcba | 360 | |
cd73c63b DM |
361 | # try to append domain to hostmane |
362 | if ($param->{hostname} && $param->{hostname} !~ m/\./ && | |
363 | $param->{searchdomain}) { | |
364 | ||
365 | $param->{hostname} .= ".$param->{searchdomain}"; | |
366 | } | |
367 | ||
92480040 | 368 | my $basecfg_fn = PVE::OpenVZ::config_file($vmid); |
4223bcba | 369 | |
92480040 DM |
370 | my $check_vmid_usage = sub { |
371 | if ($param->{force}) { | |
372 | die "cant overwrite mounted container\n" | |
373 | if PVE::OpenVZ::check_mounted($conf, $vmid); | |
374 | } else { | |
375 | die "CT $vmid already exists\n" if -f $basecfg_fn; | |
4223bcba | 376 | } |
92480040 DM |
377 | }; |
378 | ||
379 | my $code = sub { | |
380 | ||
381 | &$check_vmid_usage(); # final check after locking | |
4223bcba | 382 | |
3736f16b | 383 | PVE::OpenVZ::update_ovz_config($vmid, $conf, $param); |
339e4159 DM |
384 | |
385 | my $rawconf = PVE::OpenVZ::generate_raw_config($pve_base_ovz_config, $conf); | |
386 | ||
aabf3add | 387 | PVE::Cluster::check_cfs_quorum(); |
339e4159 | 388 | |
92480040 DM |
389 | if ($param->{restore}) { |
390 | &$restore_openvz($private, $archive, $vmid, $param->{force}); | |
0618d446 | 391 | |
92480040 DM |
392 | # is this really needed? |
393 | my $cmd = ['vzctl', '--skiplock', '--quiet', 'set', $vmid, | |
394 | '--applyconfig_map', 'name', '--save']; | |
395 | run_command($cmd); | |
45116ffb | 396 | |
92480040 DM |
397 | # reload config |
398 | $conf = PVE::OpenVZ::load_config($vmid); | |
45116ffb | 399 | |
92480040 DM |
400 | # and initialize quota |
401 | my $disk_quota = $conf->{disk_quota}->{value}; | |
402 | if (!defined($disk_quota) || ($disk_quota != 0)) { | |
403 | $cmd = ['vzctl', '--skiplock', 'quotainit', $vmid]; | |
45116ffb DM |
404 | run_command($cmd); |
405 | } | |
8993f2db | 406 | |
92480040 DM |
407 | } else { |
408 | PVE::Tools::file_set_contents($basecfg_fn, $rawconf); | |
409 | my $cmd = ['vzctl', '--skiplock', 'create', $vmid, | |
410 | '--ostemplate', $archive, '--private', $private]; | |
411 | run_command($cmd); | |
412 | ||
413 | # hack: vzctl '--userpasswd' starts the CT, but we want | |
414 | # to avoid that for create | |
415 | PVE::OpenVZ::set_rootpasswd($private, $password) | |
416 | if defined($password); | |
417 | } | |
9020f201 | 418 | |
92480040 | 419 | PVE::AccessControl::lock_user_config($addVMtoPoolFn, "can't add VM to pool") if $pool; |
7ca813e6 | 420 | }; |
9020f201 | 421 | |
92480040 DM |
422 | my $realcmd = sub { PVE::OpenVZ::lock_container($vmid, 1, $code); }; |
423 | ||
424 | &$check_vmid_usage(); # first check before locking | |
425 | ||
426 | return $rpcenv->fork_worker($param->{restore} ? 'vzrestore' : 'vzcreate', | |
427 | $vmid, $authuser, $realcmd); | |
9020f201 DM |
428 | }}); |
429 | ||
8993f2db DM |
430 | my $vm_config_perm_list = [ |
431 | 'VM.Config.Disk', | |
432 | 'VM.Config.CPU', | |
433 | 'VM.Config.Memory', | |
434 | 'VM.Config.Network', | |
435 | 'VM.Config.Options', | |
436 | ]; | |
437 | ||
9020f201 DM |
438 | __PACKAGE__->register_method({ |
439 | name => 'update_vm', | |
440 | path => '{vmid}/config', | |
441 | method => 'PUT', | |
442 | protected => 1, | |
443 | proxyto => 'node', | |
444 | description => "Set virtual machine options.", | |
8993f2db DM |
445 | permissions => { |
446 | check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1], | |
447 | }, | |
9020f201 DM |
448 | parameters => { |
449 | additionalProperties => 0, | |
450 | properties => PVE::OpenVZ::json_config_properties( | |
451 | { | |
452 | node => get_standard_option('pve-node'), | |
453 | vmid => get_standard_option('pve-vmid'), | |
454 | digest => { | |
455 | type => 'string', | |
456 | description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.', | |
457 | maxLength => 40, | |
458 | optional => 1, | |
459 | } | |
460 | }), | |
461 | }, | |
462 | returns => { type => 'null'}, | |
463 | code => sub { | |
464 | my ($param) = @_; | |
465 | ||
466 | my $rpcenv = PVE::RPCEnvironment::get(); | |
467 | ||
8993f2db | 468 | my $authuser = $rpcenv->get_user(); |
9020f201 DM |
469 | |
470 | my $node = extract_param($param, 'node'); | |
471 | ||
472 | my $vmid = extract_param($param, 'vmid'); | |
339e4159 | 473 | |
9020f201 DM |
474 | my $digest = extract_param($param, 'digest'); |
475 | ||
476 | die "no options specified\n" if !scalar(keys %$param); | |
477 | ||
8993f2db DM |
478 | &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]); |
479 | ||
9020f201 DM |
480 | my $code = sub { |
481 | ||
c3163e37 | 482 | my $conf = PVE::OpenVZ::load_config($vmid); |
9020f201 DM |
483 | die "checksum missmatch (file change by other user?)\n" |
484 | if $digest && $digest ne $conf->{digest}; | |
485 | ||
3736f16b | 486 | my $changes = PVE::OpenVZ::update_ovz_config($vmid, $conf, $param); |
9020f201 DM |
487 | |
488 | return if scalar (@$changes) <= 0; | |
489 | ||
490 | my $cmd = ['vzctl', '--skiplock', 'set', $vmid, @$changes, '--save']; | |
491 | ||
8993f2db | 492 | PVE::Cluster::log_msg('info', $authuser, "update CT $vmid: " . join(' ', @$changes)); |
96e1743e | 493 | |
7e79e293 | 494 | run_command($cmd); |
339e4159 DM |
495 | }; |
496 | ||
92480040 | 497 | PVE::OpenVZ::lock_container($vmid, undef, $code); |
9020f201 DM |
498 | |
499 | return undef; | |
339e4159 DM |
500 | }}); |
501 | ||
07151796 DM |
502 | __PACKAGE__->register_method({ |
503 | name => 'vmdiridx', | |
504 | path => '{vmid}', | |
505 | method => 'GET', | |
506 | proxyto => 'node', | |
507 | description => "Directory index", | |
85dc4bf7 DM |
508 | permissions => { |
509 | user => 'all', | |
510 | }, | |
07151796 DM |
511 | parameters => { |
512 | additionalProperties => 0, | |
513 | properties => { | |
514 | node => get_standard_option('pve-node'), | |
515 | vmid => get_standard_option('pve-vmid'), | |
516 | }, | |
517 | }, | |
518 | returns => { | |
519 | type => 'array', | |
520 | items => { | |
521 | type => "object", | |
522 | properties => { | |
523 | subdir => { type => 'string' }, | |
524 | }, | |
525 | }, | |
526 | links => [ { rel => 'child', href => "{subdir}" } ], | |
527 | }, | |
528 | code => sub { | |
529 | my ($param) = @_; | |
530 | ||
531 | # test if VM exists | |
532 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
533 | ||
534 | my $res = [ | |
535 | { subdir => 'config' }, | |
536 | { subdir => 'status' }, | |
537 | { subdir => 'vncproxy' }, | |
538 | { subdir => 'migrate' }, | |
2d590018 | 539 | { subdir => 'initlog' }, |
07151796 DM |
540 | { subdir => 'rrd' }, |
541 | { subdir => 'rrddata' }, | |
542 | ]; | |
543 | ||
544 | return $res; | |
545 | }}); | |
546 | ||
d9c18330 DM |
547 | __PACKAGE__->register_method({ |
548 | name => 'rrd', | |
549 | path => '{vmid}/rrd', | |
550 | method => 'GET', | |
551 | protected => 1, # fixme: can we avoid that? | |
552 | permissions => { | |
7d020b42 | 553 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], |
d9c18330 DM |
554 | }, |
555 | description => "Read VM RRD statistics (returns PNG)", | |
556 | parameters => { | |
557 | additionalProperties => 0, | |
558 | properties => { | |
559 | node => get_standard_option('pve-node'), | |
560 | vmid => get_standard_option('pve-vmid'), | |
561 | timeframe => { | |
562 | description => "Specify the time frame you are interested in.", | |
563 | type => 'string', | |
564 | enum => [ 'hour', 'day', 'week', 'month', 'year' ], | |
565 | }, | |
566 | ds => { | |
567 | description => "The list of datasources you want to display.", | |
568 | type => 'string', format => 'pve-configid-list', | |
569 | }, | |
570 | cf => { | |
571 | description => "The RRD consolidation function", | |
572 | type => 'string', | |
573 | enum => [ 'AVERAGE', 'MAX' ], | |
574 | optional => 1, | |
575 | }, | |
576 | }, | |
577 | }, | |
578 | returns => { | |
579 | type => "object", | |
580 | properties => { | |
581 | filename => { type => 'string' }, | |
582 | }, | |
583 | }, | |
584 | code => sub { | |
585 | my ($param) = @_; | |
586 | ||
587 | return PVE::Cluster::create_rrd_graph( | |
588 | "pve2-vm/$param->{vmid}", $param->{timeframe}, | |
589 | $param->{ds}, $param->{cf}); | |
590 | ||
591 | }}); | |
592 | ||
593 | __PACKAGE__->register_method({ | |
594 | name => 'rrddata', | |
595 | path => '{vmid}/rrddata', | |
596 | method => 'GET', | |
597 | protected => 1, # fixme: can we avoid that? | |
598 | permissions => { | |
7d020b42 | 599 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], |
d9c18330 DM |
600 | }, |
601 | description => "Read VM RRD statistics", | |
602 | parameters => { | |
603 | additionalProperties => 0, | |
604 | properties => { | |
605 | node => get_standard_option('pve-node'), | |
606 | vmid => get_standard_option('pve-vmid'), | |
607 | timeframe => { | |
608 | description => "Specify the time frame you are interested in.", | |
609 | type => 'string', | |
610 | enum => [ 'hour', 'day', 'week', 'month', 'year' ], | |
611 | }, | |
612 | cf => { | |
613 | description => "The RRD consolidation function", | |
614 | type => 'string', | |
615 | enum => [ 'AVERAGE', 'MAX' ], | |
616 | optional => 1, | |
617 | }, | |
618 | }, | |
619 | }, | |
620 | returns => { | |
621 | type => "array", | |
622 | items => { | |
623 | type => "object", | |
624 | properties => {}, | |
625 | }, | |
626 | }, | |
627 | code => sub { | |
628 | my ($param) = @_; | |
629 | ||
630 | return PVE::Cluster::create_rrd_data( | |
631 | "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf}); | |
632 | }}); | |
633 | ||
2d590018 DM |
634 | __PACKAGE__->register_method({ |
635 | name => 'initlog', | |
636 | path => '{vmid}/initlog', | |
637 | method => 'GET', | |
638 | protected => 1, | |
7b8e4045 | 639 | proxyto => 'node', |
2d590018 | 640 | permissions => { |
7d020b42 | 641 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], |
2d590018 DM |
642 | }, |
643 | description => "Read init log.", | |
644 | parameters => { | |
645 | additionalProperties => 0, | |
646 | properties => { | |
647 | node => get_standard_option('pve-node'), | |
648 | vmid => get_standard_option('pve-vmid'), | |
649 | start => { | |
650 | type => 'integer', | |
651 | minimum => 0, | |
652 | optional => 1, | |
653 | }, | |
654 | limit => { | |
655 | type => 'integer', | |
656 | minimum => 0, | |
657 | optional => 1, | |
658 | }, | |
659 | }, | |
660 | }, | |
661 | returns => { | |
662 | type => 'array', | |
663 | items => { | |
664 | type => "object", | |
665 | properties => { | |
666 | n => { | |
667 | description=> "Line number", | |
668 | type=> 'integer', | |
669 | }, | |
670 | t => { | |
671 | description=> "Line text", | |
672 | type => 'string', | |
673 | } | |
674 | } | |
675 | } | |
676 | }, | |
677 | code => sub { | |
678 | my ($param) = @_; | |
679 | ||
680 | my $rpcenv = PVE::RPCEnvironment::get(); | |
8993f2db | 681 | my $authuser = $rpcenv->get_user(); |
2d590018 DM |
682 | |
683 | my $vmid = $param->{vmid}; | |
684 | ||
685 | my $conf = PVE::OpenVZ::load_config($vmid); | |
686 | ||
687 | my $privatedir = PVE::OpenVZ::get_privatedir($conf, $vmid); | |
688 | ||
689 | my $logfn = "$privatedir/var/log/init.log"; | |
690 | ||
691 | my ($count, $lines) = PVE::Tools::dump_logfile($logfn, $param->{start}, $param->{limit}); | |
692 | ||
e09058af | 693 | $rpcenv->set_result_attrib('total', $count); |
2d590018 DM |
694 | |
695 | return $lines; | |
696 | }}); | |
697 | ||
c3163e37 DM |
698 | __PACKAGE__->register_method({ |
699 | name => 'vm_config', | |
700 | path => '{vmid}/config', | |
701 | method => 'GET', | |
702 | proxyto => 'node', | |
337dca7c | 703 | description => "Get container configuration.", |
85dc4bf7 DM |
704 | permissions => { |
705 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], | |
706 | }, | |
c3163e37 DM |
707 | parameters => { |
708 | additionalProperties => 0, | |
709 | properties => { | |
710 | node => get_standard_option('pve-node'), | |
711 | vmid => get_standard_option('pve-vmid'), | |
712 | }, | |
713 | }, | |
714 | returns => { | |
715 | type => "object", | |
716 | properties => { | |
717 | digest => { | |
718 | type => 'string', | |
719 | description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.', | |
720 | } | |
721 | }, | |
722 | }, | |
723 | code => sub { | |
724 | my ($param) = @_; | |
725 | ||
726 | my $veconf = PVE::OpenVZ::load_config($param->{vmid}); | |
727 | ||
728 | # we only return selected/converted values | |
729 | my $conf = { digest => $veconf->{digest} }; | |
730 | ||
337dca7c DM |
731 | if ($veconf->{ostemplate} && $veconf->{ostemplate}->{value}) { |
732 | $conf->{ostemplate} = $veconf->{ostemplate}->{value}; | |
733 | } | |
734 | ||
2c8e0b15 DM |
735 | my $stcfg = cfs_read_file("storage.cfg"); |
736 | ||
bc8f054e DM |
737 | my ($sid, undef, $path) = &$get_container_storage($stcfg, $param->{vmid}, $veconf); |
738 | $conf->{storage} = $sid || $path; | |
2c8e0b15 | 739 | |
c3163e37 DM |
740 | my $properties = PVE::OpenVZ::json_config_properties(); |
741 | ||
742 | foreach my $k (keys %$properties) { | |
743 | next if $k eq 'memory'; | |
744 | next if $k eq 'swap'; | |
745 | next if $k eq 'disk'; | |
88a6668d DM |
746 | |
747 | next if !$veconf->{$k}; | |
748 | next if !defined($veconf->{$k}->{value}); | |
749 | ||
750 | if ($k eq 'description') { | |
751 | $conf->{$k} = PVE::Tools::decode_text($veconf->{$k}->{value}); | |
752 | } else { | |
753 | $conf->{$k} = $veconf->{$k}->{value}; | |
754 | } | |
c3163e37 DM |
755 | } |
756 | ||
493a4387 | 757 | ($conf->{memory}, $conf->{swap}) = PVE::OpenVZ::ovz_config_extract_mem_swap($veconf, 1024*1024); |
d9f0ffa9 | 758 | |
c3163e37 DM |
759 | my $diskspace = $veconf->{diskspace}->{bar} || LONG_MAX; |
760 | if ($diskspace == LONG_MAX) { | |
761 | $conf->{disk} = 0; | |
762 | } else { | |
d9f0ffa9 | 763 | $conf->{disk} = $diskspace/(1024*1024); |
c3163e37 DM |
764 | } |
765 | return $conf; | |
766 | }}); | |
767 | ||
339e4159 DM |
768 | __PACKAGE__->register_method({ |
769 | name => 'destroy_vm', | |
770 | path => '{vmid}', | |
771 | method => 'DELETE', | |
772 | protected => 1, | |
773 | proxyto => 'node', | |
774 | description => "Destroy the container (also delete all uses files).", | |
85dc4bf7 DM |
775 | permissions => { |
776 | check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']], | |
777 | }, | |
339e4159 DM |
778 | parameters => { |
779 | additionalProperties => 0, | |
780 | properties => { | |
781 | node => get_standard_option('pve-node'), | |
782 | vmid => get_standard_option('pve-vmid'), | |
783 | }, | |
784 | }, | |
a42f6acb DM |
785 | returns => { |
786 | type => 'string', | |
787 | }, | |
339e4159 DM |
788 | code => sub { |
789 | my ($param) = @_; | |
790 | ||
791 | my $rpcenv = PVE::RPCEnvironment::get(); | |
792 | ||
8993f2db | 793 | my $authuser = $rpcenv->get_user(); |
339e4159 DM |
794 | |
795 | my $vmid = $param->{vmid}; | |
796 | ||
07151796 DM |
797 | # test if VM exists |
798 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
799 | ||
a42f6acb DM |
800 | my $realcmd = sub { |
801 | my $cmd = ['vzctl', 'destroy', $vmid ]; | |
339e4159 | 802 | |
7e79e293 | 803 | run_command($cmd); |
a42f6acb | 804 | }; |
339e4159 | 805 | |
8993f2db | 806 | return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd); |
339e4159 DM |
807 | }}); |
808 | ||
b1b121d5 DM |
809 | my $sslcert; |
810 | ||
811 | __PACKAGE__->register_method ({ | |
812 | name => 'vncproxy', | |
813 | path => '{vmid}/vncproxy', | |
814 | method => 'POST', | |
815 | protected => 1, | |
816 | permissions => { | |
7d020b42 | 817 | check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]], |
b1b121d5 DM |
818 | }, |
819 | description => "Creates a TCP VNC proxy connections.", | |
820 | parameters => { | |
821 | additionalProperties => 0, | |
822 | properties => { | |
823 | node => get_standard_option('pve-node'), | |
824 | vmid => get_standard_option('pve-vmid'), | |
825 | }, | |
826 | }, | |
827 | returns => { | |
828 | additionalProperties => 0, | |
829 | properties => { | |
830 | user => { type => 'string' }, | |
831 | ticket => { type => 'string' }, | |
832 | cert => { type => 'string' }, | |
833 | port => { type => 'integer' }, | |
834 | upid => { type => 'string' }, | |
835 | }, | |
836 | }, | |
837 | code => sub { | |
838 | my ($param) = @_; | |
839 | ||
840 | my $rpcenv = PVE::RPCEnvironment::get(); | |
841 | ||
8993f2db | 842 | my $authuser = $rpcenv->get_user(); |
b1b121d5 DM |
843 | |
844 | my $vmid = $param->{vmid}; | |
845 | my $node = $param->{node}; | |
846 | ||
57ebda08 DM |
847 | my $authpath = "/vms/$vmid"; |
848 | ||
8993f2db | 849 | my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath); |
57ebda08 | 850 | |
b1b121d5 DM |
851 | $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192) |
852 | if !$sslcert; | |
853 | ||
854 | my $port = PVE::Tools::next_vnc_port(); | |
855 | ||
856 | my $remip; | |
857 | ||
858 | if ($node ne PVE::INotify::nodename()) { | |
859 | $remip = PVE::Cluster::remote_node_ip($node); | |
860 | } | |
861 | ||
862 | # NOTE: vncterm VNC traffic is already TLS encrypted, | |
863 | # so we select the fastest chipher here (or 'none'?) | |
864 | my $remcmd = $remip ? | |
865 | ['/usr/bin/ssh', '-c', 'blowfish-cbc', '-t', $remip] : []; | |
866 | ||
f9d4fc64 DM |
867 | my $shcmd = [ '/usr/bin/dtach', '-A', |
868 | "/var/run/dtach/vzctlconsole$vmid", | |
869 | '-r', 'winch', '-z', | |
870 | '/usr/sbin/vzctl', 'console', $vmid ]; | |
b1b121d5 DM |
871 | |
872 | my $realcmd = sub { | |
873 | my $upid = shift; | |
874 | ||
875 | syslog ('info', "starting openvz vnc proxy $upid\n"); | |
876 | ||
877 | my $timeout = 10; | |
878 | ||
879 | my $cmd = ['/usr/bin/vncterm', '-rfbport', $port, | |
57ebda08 | 880 | '-timeout', $timeout, '-authpath', $authpath, |
b1b121d5 DM |
881 | '-perm', 'VM.Console', '-c', @$remcmd, @$shcmd]; |
882 | ||
7e79e293 | 883 | run_command($cmd); |
b1b121d5 DM |
884 | |
885 | return; | |
886 | }; | |
887 | ||
8993f2db | 888 | my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd); |
b1b121d5 DM |
889 | |
890 | return { | |
8993f2db | 891 | user => $authuser, |
b1b121d5 DM |
892 | ticket => $ticket, |
893 | port => $port, | |
894 | upid => $upid, | |
895 | cert => $sslcert, | |
896 | }; | |
897 | }}); | |
898 | ||
07151796 DM |
899 | __PACKAGE__->register_method({ |
900 | name => 'vmcmdidx', | |
901 | path => '{vmid}/status', | |
902 | method => 'GET', | |
903 | proxyto => 'node', | |
904 | description => "Directory index", | |
85dc4bf7 DM |
905 | permissions => { |
906 | user => 'all', | |
907 | }, | |
07151796 DM |
908 | parameters => { |
909 | additionalProperties => 0, | |
910 | properties => { | |
911 | node => get_standard_option('pve-node'), | |
912 | vmid => get_standard_option('pve-vmid'), | |
913 | }, | |
914 | }, | |
915 | returns => { | |
916 | type => 'array', | |
917 | items => { | |
918 | type => "object", | |
919 | properties => { | |
920 | subdir => { type => 'string' }, | |
921 | }, | |
922 | }, | |
923 | links => [ { rel => 'child', href => "{subdir}" } ], | |
924 | }, | |
925 | code => sub { | |
926 | my ($param) = @_; | |
927 | ||
928 | # test if VM exists | |
929 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
930 | ||
931 | my $res = [ | |
932 | { subdir => 'current' }, | |
56203bf1 | 933 | { subdir => 'ubc' }, |
07151796 DM |
934 | { subdir => 'start' }, |
935 | { subdir => 'stop' }, | |
936 | ]; | |
937 | ||
938 | return $res; | |
939 | }}); | |
940 | ||
60a452f2 DM |
941 | my $vm_is_ha_managed = sub { |
942 | my ($vmid) = @_; | |
943 | ||
944 | my $cc = PVE::Cluster::cfs_read_file('cluster.conf'); | |
945 | if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $vmid, 1)) { | |
946 | return 1; | |
947 | } | |
948 | return 0; | |
949 | }; | |
950 | ||
07151796 DM |
951 | __PACKAGE__->register_method({ |
952 | name => 'vm_status', | |
953 | path => '{vmid}/status/current', | |
954 | method => 'GET', | |
955 | proxyto => 'node', | |
956 | protected => 1, # openvz /proc entries are only readable by root | |
957 | description => "Get virtual machine status.", | |
85dc4bf7 DM |
958 | permissions => { |
959 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], | |
960 | }, | |
07151796 DM |
961 | parameters => { |
962 | additionalProperties => 0, | |
963 | properties => { | |
964 | node => get_standard_option('pve-node'), | |
965 | vmid => get_standard_option('pve-vmid'), | |
966 | }, | |
967 | }, | |
968 | returns => { type => 'object' }, | |
969 | code => sub { | |
970 | my ($param) = @_; | |
971 | ||
972 | # test if VM exists | |
973 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
974 | ||
975 | my $vmstatus = PVE::OpenVZ::vmstatus($param->{vmid}); | |
58bd7194 | 976 | my $status = $vmstatus->{$param->{vmid}}; |
07151796 | 977 | |
60a452f2 | 978 | $status->{ha} = &$vm_is_ha_managed($param->{vmid}); |
58bd7194 DM |
979 | |
980 | return $status; | |
07151796 DM |
981 | }}); |
982 | ||
56203bf1 DM |
983 | __PACKAGE__->register_method({ |
984 | name => 'vm_user_beancounters', | |
985 | path => '{vmid}/status/ubc', | |
986 | method => 'GET', | |
987 | proxyto => 'node', | |
988 | protected => 1, # openvz /proc entries are only readable by root | |
989 | description => "Get container user_beancounters.", | |
85dc4bf7 DM |
990 | permissions => { |
991 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], | |
992 | }, | |
56203bf1 DM |
993 | parameters => { |
994 | additionalProperties => 0, | |
995 | properties => { | |
996 | node => get_standard_option('pve-node'), | |
997 | vmid => get_standard_option('pve-vmid'), | |
998 | }, | |
999 | }, | |
1000 | returns => { | |
1001 | type => 'array', | |
1002 | items => { | |
1003 | type => "object", | |
1004 | properties => { | |
1005 | id => { type => 'string' }, | |
1006 | held => { type => 'number' }, | |
1007 | maxheld => { type => 'number' }, | |
1008 | bar => { type => 'number' }, | |
1009 | lim => { type => 'number' }, | |
1010 | failcnt => { type => 'number' }, | |
1011 | }, | |
1012 | }, | |
1013 | }, | |
1014 | code => sub { | |
1015 | my ($param) = @_; | |
1016 | ||
1017 | # test if VM exists | |
1018 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
1019 | ||
9056e748 DM |
1020 | my $ubchash = PVE::OpenVZ::read_user_beancounters(); |
1021 | my $ubc = $ubchash->{$param->{vmid}} || {}; | |
1022 | delete $ubc->{failcntsum}; | |
56203bf1 DM |
1023 | |
1024 | return PVE::RESTHandler::hash_to_array($ubc, 'id'); | |
1025 | }}); | |
1026 | ||
07151796 DM |
1027 | __PACKAGE__->register_method({ |
1028 | name => 'vm_start', | |
1029 | path => '{vmid}/status/start', | |
1030 | method => 'POST', | |
1031 | protected => 1, | |
1032 | proxyto => 'node', | |
1033 | description => "Start the container.", | |
85dc4bf7 DM |
1034 | permissions => { |
1035 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
1036 | }, | |
07151796 DM |
1037 | parameters => { |
1038 | additionalProperties => 0, | |
1039 | properties => { | |
1040 | node => get_standard_option('pve-node'), | |
1041 | vmid => get_standard_option('pve-vmid'), | |
1042 | }, | |
1043 | }, | |
1044 | returns => { | |
1045 | type => 'string', | |
1046 | }, | |
1047 | code => sub { | |
1048 | my ($param) = @_; | |
1049 | ||
1050 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1051 | ||
8993f2db | 1052 | my $authuser = $rpcenv->get_user(); |
07151796 DM |
1053 | |
1054 | my $node = extract_param($param, 'node'); | |
1055 | ||
1056 | my $vmid = extract_param($param, 'vmid'); | |
1057 | ||
51ed1415 DM |
1058 | die "CT $vmid already running\n" if PVE::OpenVZ::check_running($vmid); |
1059 | ||
60a452f2 DM |
1060 | if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { |
1061 | ||
1062 | my $hacmd = sub { | |
1063 | my $upid = shift; | |
1064 | ||
1065 | my $service = "pvevm:$vmid"; | |
1066 | ||
1067 | my $cmd = ['clusvcadm', '-e', $service, '-m', $node]; | |
1068 | ||
1069 | print "Executing HA start for CT $vmid\n"; | |
1070 | ||
1071 | PVE::Tools::run_command($cmd); | |
1072 | ||
1073 | return; | |
1074 | }; | |
1075 | ||
1076 | return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd); | |
1077 | ||
1078 | } else { | |
1079 | ||
1080 | my $realcmd = sub { | |
1081 | my $upid = shift; | |
07151796 | 1082 | |
60a452f2 | 1083 | syslog('info', "starting CT $vmid: $upid\n"); |
07151796 | 1084 | |
bc8f054e DM |
1085 | my $veconf = PVE::OpenVZ::load_config($vmid); |
1086 | my $stcfg = cfs_read_file("storage.cfg"); | |
1087 | if (my $sid = &$get_container_storage($stcfg, $vmid, $veconf)) { | |
1088 | PVE::Storage::activate_storage($stcfg, $sid); | |
1089 | } | |
1090 | ||
60a452f2 | 1091 | my $cmd = ['vzctl', 'start', $vmid]; |
07151796 | 1092 | |
60a452f2 | 1093 | run_command($cmd); |
7e79e293 | 1094 | |
60a452f2 DM |
1095 | return; |
1096 | }; | |
07151796 | 1097 | |
60a452f2 DM |
1098 | return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd); |
1099 | } | |
07151796 DM |
1100 | }}); |
1101 | ||
1102 | __PACKAGE__->register_method({ | |
1103 | name => 'vm_stop', | |
1104 | path => '{vmid}/status/stop', | |
1105 | method => 'POST', | |
1106 | protected => 1, | |
1107 | proxyto => 'node', | |
1108 | description => "Stop the container.", | |
85dc4bf7 DM |
1109 | permissions => { |
1110 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
1111 | }, | |
07151796 DM |
1112 | parameters => { |
1113 | additionalProperties => 0, | |
1114 | properties => { | |
1115 | node => get_standard_option('pve-node'), | |
1116 | vmid => get_standard_option('pve-vmid'), | |
51ed1415 DM |
1117 | }, |
1118 | }, | |
1119 | returns => { | |
1120 | type => 'string', | |
1121 | }, | |
1122 | code => sub { | |
1123 | my ($param) = @_; | |
1124 | ||
1125 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1126 | ||
8993f2db | 1127 | my $authuser = $rpcenv->get_user(); |
51ed1415 DM |
1128 | |
1129 | my $node = extract_param($param, 'node'); | |
1130 | ||
1131 | my $vmid = extract_param($param, 'vmid'); | |
1132 | ||
1133 | die "CT $vmid not running\n" if !PVE::OpenVZ::check_running($vmid); | |
1134 | ||
60a452f2 | 1135 | if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { |
51ed1415 | 1136 | |
60a452f2 DM |
1137 | my $hacmd = sub { |
1138 | my $upid = shift; | |
51ed1415 | 1139 | |
60a452f2 | 1140 | my $service = "pvevm:$vmid"; |
51ed1415 | 1141 | |
60a452f2 | 1142 | my $cmd = ['clusvcadm', '-d', $service]; |
51ed1415 | 1143 | |
60a452f2 DM |
1144 | print "Executing HA stop for CT $vmid\n"; |
1145 | ||
1146 | PVE::Tools::run_command($cmd); | |
1147 | ||
1148 | return; | |
1149 | }; | |
1150 | ||
1151 | return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd); | |
1152 | ||
1153 | } else { | |
1154 | ||
1155 | my $realcmd = sub { | |
1156 | my $upid = shift; | |
1157 | ||
1158 | syslog('info', "stoping CT $vmid: $upid\n"); | |
1159 | ||
1160 | my $cmd = ['vzctl', 'stop', $vmid, '--fast']; | |
1161 | run_command($cmd); | |
1162 | ||
1163 | return; | |
1164 | }; | |
1165 | ||
1166 | return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd); | |
1167 | } | |
51ed1415 DM |
1168 | }}); |
1169 | ||
8710f280 DM |
1170 | __PACKAGE__->register_method({ |
1171 | name => 'vm_mount', | |
1172 | path => '{vmid}/status/mount', | |
1173 | method => 'POST', | |
1174 | protected => 1, | |
1175 | proxyto => 'node', | |
1176 | description => "Mounts container private area.", | |
1177 | permissions => { | |
1178 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
1179 | }, | |
1180 | parameters => { | |
1181 | additionalProperties => 0, | |
1182 | properties => { | |
1183 | node => get_standard_option('pve-node'), | |
1184 | vmid => get_standard_option('pve-vmid'), | |
1185 | }, | |
1186 | }, | |
1187 | returns => { | |
1188 | type => 'string', | |
1189 | }, | |
1190 | code => sub { | |
1191 | my ($param) = @_; | |
1192 | ||
1193 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1194 | ||
1195 | my $authuser = $rpcenv->get_user(); | |
1196 | ||
1197 | my $node = extract_param($param, 'node'); | |
1198 | ||
1199 | my $vmid = extract_param($param, 'vmid'); | |
1200 | ||
1201 | die "CT $vmid is running\n" if PVE::OpenVZ::check_running($vmid); | |
1202 | ||
1203 | my $realcmd = sub { | |
1204 | my $upid = shift; | |
1205 | ||
1206 | syslog('info', "mount CT $vmid: $upid\n"); | |
1207 | ||
1208 | my $cmd = ['vzctl', 'mount', $vmid]; | |
1209 | ||
1210 | run_command($cmd); | |
1211 | ||
1212 | return; | |
1213 | }; | |
1214 | ||
1215 | return $rpcenv->fork_worker('vzmount', $vmid, $authuser, $realcmd); | |
1216 | }}); | |
1217 | ||
1218 | __PACKAGE__->register_method({ | |
1219 | name => 'vm_umount', | |
1220 | path => '{vmid}/status/umount', | |
1221 | method => 'POST', | |
1222 | protected => 1, | |
1223 | proxyto => 'node', | |
1224 | description => "Unmounts container private area.", | |
1225 | permissions => { | |
1226 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
1227 | }, | |
1228 | parameters => { | |
1229 | additionalProperties => 0, | |
1230 | properties => { | |
1231 | node => get_standard_option('pve-node'), | |
1232 | vmid => get_standard_option('pve-vmid'), | |
1233 | }, | |
1234 | }, | |
1235 | returns => { | |
1236 | type => 'string', | |
1237 | }, | |
1238 | code => sub { | |
1239 | my ($param) = @_; | |
1240 | ||
1241 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1242 | ||
1243 | my $authuser = $rpcenv->get_user(); | |
1244 | ||
1245 | my $node = extract_param($param, 'node'); | |
1246 | ||
1247 | my $vmid = extract_param($param, 'vmid'); | |
1248 | ||
1249 | die "CT $vmid is running\n" if PVE::OpenVZ::check_running($vmid); | |
1250 | ||
1251 | my $realcmd = sub { | |
1252 | my $upid = shift; | |
1253 | ||
1254 | syslog('info', "umount CT $vmid: $upid\n"); | |
1255 | ||
1256 | my $cmd = ['vzctl', 'umount', $vmid]; | |
1257 | ||
1258 | run_command($cmd); | |
1259 | ||
1260 | return; | |
1261 | }; | |
1262 | ||
1263 | return $rpcenv->fork_worker('vzumount', $vmid, $authuser, $realcmd); | |
1264 | }}); | |
1265 | ||
51ed1415 DM |
1266 | __PACKAGE__->register_method({ |
1267 | name => 'vm_shutdown', | |
1268 | path => '{vmid}/status/shutdown', | |
1269 | method => 'POST', | |
1270 | protected => 1, | |
1271 | proxyto => 'node', | |
1272 | description => "Shutdown the container.", | |
85dc4bf7 DM |
1273 | permissions => { |
1274 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
1275 | }, | |
51ed1415 DM |
1276 | parameters => { |
1277 | additionalProperties => 0, | |
1278 | properties => { | |
1279 | node => get_standard_option('pve-node'), | |
1280 | vmid => get_standard_option('pve-vmid'), | |
1281 | timeout => { | |
1282 | description => "Wait maximal timeout seconds.", | |
1283 | type => 'integer', | |
1284 | minimum => 0, | |
1285 | optional => 1, | |
1286 | default => 60, | |
1287 | }, | |
1288 | forceStop => { | |
1289 | description => "Make sure the Container stops.", | |
07151796 | 1290 | type => 'boolean', |
07151796 | 1291 | optional => 1, |
51ed1415 | 1292 | default => 0, |
07151796 DM |
1293 | } |
1294 | }, | |
1295 | }, | |
1296 | returns => { | |
1297 | type => 'string', | |
1298 | }, | |
1299 | code => sub { | |
1300 | my ($param) = @_; | |
1301 | ||
1302 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1303 | ||
8993f2db | 1304 | my $authuser = $rpcenv->get_user(); |
07151796 DM |
1305 | |
1306 | my $node = extract_param($param, 'node'); | |
1307 | ||
1308 | my $vmid = extract_param($param, 'vmid'); | |
1309 | ||
51ed1415 DM |
1310 | my $timeout = extract_param($param, 'timeout'); |
1311 | ||
1312 | die "CT $vmid not running\n" if !PVE::OpenVZ::check_running($vmid); | |
1313 | ||
07151796 DM |
1314 | my $realcmd = sub { |
1315 | my $upid = shift; | |
1316 | ||
51ed1415 | 1317 | syslog('info', "shutdown CT $vmid: $upid\n"); |
07151796 DM |
1318 | |
1319 | my $cmd = ['vzctl', 'stop', $vmid]; | |
1320 | ||
51ed1415 DM |
1321 | $timeout = 60 if !defined($timeout); |
1322 | ||
1323 | eval { run_command($cmd, timeout => $timeout); }; | |
1324 | my $err = $@; | |
1325 | return if !$err; | |
1326 | ||
1327 | die $err if !$param->{forceStop}; | |
1328 | ||
1329 | warn "shutdown failed - forcing stop now\n"; | |
1330 | ||
1331 | push @$cmd, '--fast'; | |
7e79e293 | 1332 | run_command($cmd); |
07151796 DM |
1333 | |
1334 | return; | |
1335 | }; | |
1336 | ||
8993f2db | 1337 | my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd); |
07151796 DM |
1338 | |
1339 | return $upid; | |
1340 | }}); | |
1341 | ||
7e79e293 DM |
1342 | __PACKAGE__->register_method({ |
1343 | name => 'migrate_vm', | |
1344 | path => '{vmid}/migrate', | |
1345 | method => 'POST', | |
1346 | protected => 1, | |
1347 | proxyto => 'node', | |
1348 | description => "Migrate the container to another node. Creates a new migration task.", | |
85dc4bf7 DM |
1349 | permissions => { |
1350 | check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]], | |
1351 | }, | |
7e79e293 DM |
1352 | parameters => { |
1353 | additionalProperties => 0, | |
1354 | properties => { | |
1355 | node => get_standard_option('pve-node'), | |
1356 | vmid => get_standard_option('pve-vmid'), | |
1357 | target => get_standard_option('pve-node', { description => "Target node." }), | |
1358 | online => { | |
1359 | type => 'boolean', | |
1360 | description => "Use online/live migration.", | |
1361 | optional => 1, | |
1362 | }, | |
1363 | }, | |
1364 | }, | |
1365 | returns => { | |
1366 | type => 'string', | |
1367 | description => "the task ID.", | |
1368 | }, | |
1369 | code => sub { | |
1370 | my ($param) = @_; | |
1371 | ||
1372 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1373 | ||
8993f2db | 1374 | my $authuser = $rpcenv->get_user(); |
7e79e293 DM |
1375 | |
1376 | my $target = extract_param($param, 'target'); | |
1377 | ||
1378 | my $localnode = PVE::INotify::nodename(); | |
1379 | raise_param_exc({ target => "target is local node."}) if $target eq $localnode; | |
1380 | ||
1381 | PVE::Cluster::check_cfs_quorum(); | |
1382 | ||
1383 | PVE::Cluster::check_node_exists($target); | |
1384 | ||
1385 | my $targetip = PVE::Cluster::remote_node_ip($target); | |
1386 | ||
1387 | my $vmid = extract_param($param, 'vmid'); | |
1388 | ||
1389 | # test if VM exists | |
1390 | PVE::OpenVZ::load_config($vmid); | |
1391 | ||
1392 | # try to detect errors early | |
1393 | if (PVE::OpenVZ::check_running($vmid)) { | |
1394 | die "cant migrate running container without --online\n" | |
1395 | if !$param->{online}; | |
1396 | } | |
1397 | ||
60a452f2 | 1398 | if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { |
45116ffb | 1399 | |
60a452f2 DM |
1400 | my $hacmd = sub { |
1401 | my $upid = shift; | |
45116ffb | 1402 | |
60a452f2 | 1403 | my $service = "pvevm:$vmid"; |
7e79e293 | 1404 | |
60a452f2 | 1405 | my $cmd = ['clusvcadm', '-M', $service, '-m', $target]; |
7e79e293 | 1406 | |
60a452f2 DM |
1407 | print "Executing HA migrate for CT $vmid to node $target\n"; |
1408 | ||
1409 | PVE::Tools::run_command($cmd); | |
1410 | ||
1411 | return; | |
1412 | }; | |
1413 | ||
1414 | return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd); | |
1415 | ||
1416 | } else { | |
1417 | ||
1418 | my $realcmd = sub { | |
1419 | my $upid = shift; | |
1420 | ||
1421 | PVE::OpenVZMigrate->migrate($target, $targetip, $vmid, $param); | |
1422 | ||
1423 | return; | |
1424 | }; | |
1425 | ||
1426 | return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd); | |
1427 | } | |
7e79e293 DM |
1428 | }}); |
1429 | ||
339e4159 | 1430 | 1; |