]>
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); |
aabf3add | 8 | use Cwd 'abs_path'; |
339e4159 DM |
9 | |
10 | use PVE::SafeSyslog; | |
aabf3add | 11 | use PVE::Tools qw(extract_param run_command); |
7e79e293 DM |
12 | use PVE::Exception qw(raise raise_param_exc); |
13 | use PVE::INotify; | |
339e4159 DM |
14 | use PVE::Cluster qw(cfs_lock_file cfs_read_file); |
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 | ||
339e4159 DM |
62 | __PACKAGE__->register_method({ |
63 | name => 'vmlist', | |
64 | path => '', | |
65 | method => 'GET', | |
66 | description => "OpenVZ container index (per node).", | |
67 | proxyto => 'node', | |
68 | protected => 1, # openvz proc files are only readable by root | |
69 | parameters => { | |
70 | additionalProperties => 0, | |
71 | properties => { | |
72 | node => get_standard_option('pve-node'), | |
73 | }, | |
74 | }, | |
75 | returns => { | |
76 | type => 'array', | |
77 | items => { | |
78 | type => "object", | |
79 | properties => {}, | |
80 | }, | |
81 | links => [ { rel => 'child', href => "{vmid}" } ], | |
82 | }, | |
83 | code => sub { | |
84 | my ($param) = @_; | |
85 | ||
86 | my $vmstatus = PVE::OpenVZ::vmstatus(); | |
87 | ||
88 | return PVE::RESTHandler::hash_to_array($vmstatus, 'vmid'); | |
89 | ||
90 | }}); | |
91 | ||
aabf3add | 92 | my $restore_openvz = sub { |
9f767883 | 93 | my ($private, $archive, $vmid, $force) = @_; |
aabf3add DM |
94 | |
95 | my $vzconf = PVE::OpenVZ::read_global_vz_config (); | |
96 | my $conffile = PVE::OpenVZ::config_file($vmid); | |
97 | my $cfgdir = dirname($conffile); | |
98 | ||
aabf3add DM |
99 | my $root = $vzconf->{rootdir}; |
100 | $root =~ s/\$VEID/$vmid/; | |
101 | ||
102 | print "you choose to force overwriting VPS config file, private and root directories.\n" if $force; | |
103 | ||
104 | die "unable to create CT $vmid - container already exists\n" | |
105 | if !$force && -f $conffile; | |
106 | ||
107 | die "unable to create CT $vmid - directory '$private' already exists\n" | |
108 | if !$force && -d $private; | |
109 | ||
110 | die "unable to create CT $vmid - directory '$root' already exists\n" | |
111 | if !$force && -d $root; | |
112 | ||
113 | my $conf; | |
114 | ||
115 | eval { | |
9f767883 DM |
116 | if ($force && -f $conffile) { |
117 | my $conf = PVE::OpenVZ::load_config($vmid); | |
118 | ||
2d590018 | 119 | my $oldprivate = PVE::OpenVZ::get_privatedir($conf, $vmid); |
9f767883 DM |
120 | rmtree $oldprivate if -d $oldprivate; |
121 | ||
122 | my $oldroot = $conf->{ve_root} ? $conf->{ve_root}->{value} : $root; | |
123 | rmtree $oldroot if -d $oldroot; | |
124 | }; | |
aabf3add DM |
125 | |
126 | mkpath $private || die "unable to create private dir '$private'"; | |
127 | mkpath $root || die "unable to create private dir '$private'"; | |
128 | ||
129 | my $cmd = ['tar', 'xpf', $archive, '--totals', '--sparse', '-C', $private]; | |
130 | ||
131 | if ($archive eq '-') { | |
132 | print "extracting archive from STDIN\n"; | |
133 | run_command($cmd, input => "<&STDIN"); | |
134 | } else { | |
135 | print "extracting archive '$archive'\n"; | |
136 | run_command($cmd); | |
137 | } | |
138 | ||
139 | my $backup_cfg = "$private/etc/vzdump/vps.conf"; | |
140 | if (-f $backup_cfg) { | |
141 | print "restore configuration to '$conffile'\n"; | |
142 | ||
0618d446 | 143 | my $conf = PVE::Tools::file_get_contents($backup_cfg); |
aabf3add DM |
144 | |
145 | $conf =~ s/VE_ROOT=.*/VE_ROOT=\"$root\"/; | |
146 | $conf =~ s/VE_PRIVATE=.*/VE_PRIVATE=\"$private\"/; | |
147 | $conf =~ s/host_ifname=veth[0-9]+\./host_ifname=veth${vmid}\./g; | |
148 | ||
149 | PVE::Tools::file_set_contents($conffile, $conf); | |
150 | ||
151 | foreach my $s (PVE::OpenVZ::SCRIPT_EXT) { | |
152 | my $tfn = "$cfgdir/${vmid}.$s"; | |
153 | my $sfn = "$private/etc/vzdump/vps.$s"; | |
154 | if (-f $sfn) { | |
155 | my $sc = PVE::Tools::file_get_contents($sfn); | |
156 | PVE::Tools::file_set_contents($tfn, $sc); | |
157 | } | |
158 | } | |
159 | } | |
160 | ||
161 | rmtree "$private/etc/vzdump"; | |
162 | }; | |
163 | ||
164 | my $err = $@; | |
165 | ||
166 | if ($err) { | |
167 | rmtree $private; | |
168 | rmtree $root; | |
169 | unlink $conffile; | |
170 | foreach my $s (PVE::OpenVZ::SCRIPT_EXT) { | |
171 | unlink "$cfgdir/${vmid}.$s"; | |
172 | } | |
173 | die $err; | |
174 | } | |
175 | ||
176 | return $conf; | |
177 | }; | |
178 | ||
179 | # create_vm is also used by vzrestore | |
339e4159 DM |
180 | __PACKAGE__->register_method({ |
181 | name => 'create_vm', | |
182 | path => '', | |
183 | method => 'POST', | |
aabf3add | 184 | description => "Create or restore a container.", |
339e4159 DM |
185 | protected => 1, |
186 | proxyto => 'node', | |
187 | parameters => { | |
188 | additionalProperties => 0, | |
189 | properties => PVE::OpenVZ::json_config_properties({ | |
190 | node => get_standard_option('pve-node'), | |
191 | vmid => get_standard_option('pve-vmid'), | |
192 | ostemplate => { | |
aabf3add | 193 | description => "The OS template or backup file.", |
339e4159 DM |
194 | type => 'string', |
195 | maxLength => 255, | |
196 | }, | |
197 | password => { | |
198 | optional => 1, | |
199 | type => 'string', | |
200 | description => "Sets root password inside container.", | |
201 | }, | |
9f767883 DM |
202 | storage => get_standard_option('pve-storage-id', { |
203 | description => "Target storage.", | |
204 | default => 'local', | |
205 | optional => 1, | |
206 | }), | |
aabf3add DM |
207 | force => { |
208 | optional => 1, | |
209 | type => 'boolean', | |
210 | description => "Allow to overwrite existing container.", | |
211 | }, | |
212 | restore => { | |
213 | optional => 1, | |
214 | type => 'boolean', | |
215 | description => "Mark this as restore task.", | |
216 | }, | |
339e4159 DM |
217 | }), |
218 | }, | |
7ca813e6 DM |
219 | returns => { |
220 | type => 'string', | |
221 | }, | |
339e4159 DM |
222 | code => sub { |
223 | my ($param) = @_; | |
224 | ||
7ca813e6 DM |
225 | my $rpcenv = PVE::RPCEnvironment::get(); |
226 | ||
227 | my $user = $rpcenv->get_user(); | |
228 | ||
339e4159 DM |
229 | my $node = extract_param($param, 'node'); |
230 | ||
339e4159 DM |
231 | my $vmid = extract_param($param, 'vmid'); |
232 | ||
233 | my $password = extract_param($param, 'password'); | |
234 | ||
9f767883 DM |
235 | my $storage = extract_param($param, 'storage') || 'local'; |
236 | ||
237 | my $storage_cfg = cfs_read_file("storage.cfg"); | |
238 | ||
239 | my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node); | |
240 | ||
241 | raise_param_exc({ storage => "storage '$storage' does not support openvz root directories"}) | |
242 | if !$scfg->{content}->{rootdir}; | |
243 | ||
244 | my $private = PVE::Storage::get_private_dir($storage_cfg, $storage, $vmid); | |
245 | ||
246 | PVE::Storage::activate_storage($storage_cfg, $storage); | |
339e4159 DM |
247 | |
248 | my $conf = PVE::OpenVZ::parse_ovz_config("/tmp/openvz/$vmid.conf", $pve_base_ovz_config); | |
249 | ||
250 | my $code = sub { | |
251 | ||
07151796 | 252 | my $basecfg_fn = PVE::OpenVZ::config_file($vmid); |
339e4159 | 253 | |
aabf3add | 254 | if ($param->{force}) { |
0618d446 | 255 | die "cant overwrite mounted container\n" if PVE::OpenVZ::check_mounted($conf, $vmid); |
aabf3add | 256 | } else { |
51ed1415 | 257 | die "CT $vmid already exists\n" if -f $basecfg_fn; |
aabf3add | 258 | } |
339e4159 DM |
259 | |
260 | my $ostemplate = extract_param($param, 'ostemplate'); | |
261 | ||
aabf3add DM |
262 | my $archive; |
263 | ||
264 | if ($ostemplate eq '-') { | |
265 | die "pipe requires cli environment\n" | |
266 | if $rpcenv->{type} ne 'cli'; | |
3736f16b DM |
267 | die "pipe can only be used with restore tasks\n" |
268 | if !$param->{restore}; | |
aabf3add DM |
269 | $archive = '-'; |
270 | } else { | |
271 | if (PVE::Storage::parse_volume_id($ostemplate, 1)) { | |
9f767883 | 272 | $archive = PVE::Storage::path($storage_cfg, $ostemplate); |
aabf3add | 273 | } else { |
89cd4028 DM |
274 | raise_param_exc({ archive => "Only root can pass arbitrary paths." }) |
275 | if $user ne 'root@pam'; | |
276 | ||
aabf3add DM |
277 | $archive = abs_path($ostemplate); |
278 | } | |
279 | die "can't find file '$archive'\n" if ! -f $archive; | |
339e4159 DM |
280 | } |
281 | ||
4223bcba DM |
282 | if (!defined($param->{searchdomain}) && |
283 | !defined($param->{nameserver})) { | |
284 | ||
285 | my $resolv = PVE::INotify::read_file('resolvconf'); | |
286 | ||
287 | $param->{searchdomain} = $resolv->{search} if $resolv->{search}; | |
288 | ||
289 | my @ns = (); | |
290 | push @ns, $resolv->{dns1} if $resolv->{dns1}; | |
291 | push @ns, $resolv->{dns2} if $resolv->{dns2}; | |
292 | push @ns, $resolv->{dns3} if $resolv->{dns3}; | |
293 | ||
294 | $param->{nameserver} = join(' ', @ns) if scalar(@ns); | |
295 | } | |
296 | ||
3736f16b DM |
297 | PVE::OpenVZ::update_ovz_config($vmid, $conf, $param); |
298 | if (!$param->{restore}) { | |
299 | $conf->{ostemplate}->{value} = $archive; | |
300 | $conf->{ostemplate}->{value} =~ s|^.*/||; | |
301 | $conf->{ostemplate}->{value} =~ s/\.tar\.(gz|bz2)$//; | |
9f767883 | 302 | $conf->{ve_private}->{value} = $private; |
3736f16b | 303 | } |
339e4159 DM |
304 | |
305 | my $rawconf = PVE::OpenVZ::generate_raw_config($pve_base_ovz_config, $conf); | |
306 | ||
aabf3add | 307 | PVE::Cluster::check_cfs_quorum(); |
339e4159 | 308 | |
aabf3add | 309 | my $realcmd = sub { |
9f767883 | 310 | &$restore_openvz($private, $archive, $vmid, $param->{force}); |
339e4159 | 311 | |
aabf3add DM |
312 | PVE::Tools::file_set_contents($basecfg_fn, $rawconf) |
313 | if !$param->{restore}; | |
339e4159 | 314 | |
7ca813e6 DM |
315 | # hack: vzctl '--userpasswd' starts the CT, but we want |
316 | # to avoid that for create | |
9f767883 | 317 | PVE::OpenVZ::set_rootpasswd($private, $password) if defined($password); |
0618d446 DM |
318 | |
319 | # is this really needed? | |
320 | my $cmd = ['vzctl', '--skiplock', '--quiet', 'set', $vmid, | |
321 | '--applyconfig_map', 'name', '--save']; | |
322 | run_command($cmd); | |
45116ffb DM |
323 | |
324 | # reload config | |
325 | my $conf = PVE::OpenVZ::load_config($vmid); | |
326 | ||
327 | # and initialize quota | |
328 | my $disk_quota = $conf->{disk_quota}->{value}; | |
329 | if (!defined($disk_quota) || ($disk_quota != 0)) { | |
330 | my $cmd = ['vzctl', '--skiplock', 'quotainit', $vmid]; | |
331 | run_command($cmd); | |
332 | } | |
7ca813e6 | 333 | }; |
9020f201 | 334 | |
aabf3add DM |
335 | return $rpcenv->fork_worker($param->{restore} ? 'vzrestore' : 'vzcreate', |
336 | $vmid, $user, $realcmd); | |
7ca813e6 | 337 | }; |
9020f201 | 338 | |
7ca813e6 | 339 | return PVE::OpenVZ::lock_container($vmid, $code); |
9020f201 DM |
340 | }}); |
341 | ||
342 | __PACKAGE__->register_method({ | |
343 | name => 'update_vm', | |
344 | path => '{vmid}/config', | |
345 | method => 'PUT', | |
346 | protected => 1, | |
347 | proxyto => 'node', | |
348 | description => "Set virtual machine options.", | |
349 | parameters => { | |
350 | additionalProperties => 0, | |
351 | properties => PVE::OpenVZ::json_config_properties( | |
352 | { | |
353 | node => get_standard_option('pve-node'), | |
354 | vmid => get_standard_option('pve-vmid'), | |
355 | digest => { | |
356 | type => 'string', | |
357 | description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.', | |
358 | maxLength => 40, | |
359 | optional => 1, | |
360 | } | |
361 | }), | |
362 | }, | |
363 | returns => { type => 'null'}, | |
364 | code => sub { | |
365 | my ($param) = @_; | |
366 | ||
367 | my $rpcenv = PVE::RPCEnvironment::get(); | |
368 | ||
369 | my $user = $rpcenv->get_user(); | |
370 | ||
371 | my $node = extract_param($param, 'node'); | |
372 | ||
373 | my $vmid = extract_param($param, 'vmid'); | |
339e4159 | 374 | |
9020f201 DM |
375 | my $digest = extract_param($param, 'digest'); |
376 | ||
377 | die "no options specified\n" if !scalar(keys %$param); | |
378 | ||
379 | my $code = sub { | |
380 | ||
c3163e37 | 381 | my $conf = PVE::OpenVZ::load_config($vmid); |
9020f201 DM |
382 | die "checksum missmatch (file change by other user?)\n" |
383 | if $digest && $digest ne $conf->{digest}; | |
384 | ||
3736f16b | 385 | my $changes = PVE::OpenVZ::update_ovz_config($vmid, $conf, $param); |
9020f201 DM |
386 | |
387 | return if scalar (@$changes) <= 0; | |
388 | ||
389 | my $cmd = ['vzctl', '--skiplock', 'set', $vmid, @$changes, '--save']; | |
390 | ||
96e1743e DM |
391 | PVE::Cluster::log_msg('info', $user, "update CT $vmid: " . join(' ', @$changes)); |
392 | ||
7e79e293 | 393 | run_command($cmd); |
339e4159 DM |
394 | }; |
395 | ||
396 | PVE::OpenVZ::lock_container($vmid, $code); | |
9020f201 DM |
397 | |
398 | return undef; | |
339e4159 DM |
399 | }}); |
400 | ||
07151796 DM |
401 | __PACKAGE__->register_method({ |
402 | name => 'vmdiridx', | |
403 | path => '{vmid}', | |
404 | method => 'GET', | |
405 | proxyto => 'node', | |
406 | description => "Directory index", | |
407 | parameters => { | |
408 | additionalProperties => 0, | |
409 | properties => { | |
410 | node => get_standard_option('pve-node'), | |
411 | vmid => get_standard_option('pve-vmid'), | |
412 | }, | |
413 | }, | |
414 | returns => { | |
415 | type => 'array', | |
416 | items => { | |
417 | type => "object", | |
418 | properties => { | |
419 | subdir => { type => 'string' }, | |
420 | }, | |
421 | }, | |
422 | links => [ { rel => 'child', href => "{subdir}" } ], | |
423 | }, | |
424 | code => sub { | |
425 | my ($param) = @_; | |
426 | ||
427 | # test if VM exists | |
428 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
429 | ||
430 | my $res = [ | |
431 | { subdir => 'config' }, | |
432 | { subdir => 'status' }, | |
433 | { subdir => 'vncproxy' }, | |
434 | { subdir => 'migrate' }, | |
2d590018 | 435 | { subdir => 'initlog' }, |
07151796 DM |
436 | { subdir => 'rrd' }, |
437 | { subdir => 'rrddata' }, | |
438 | ]; | |
439 | ||
440 | return $res; | |
441 | }}); | |
442 | ||
d9c18330 DM |
443 | __PACKAGE__->register_method({ |
444 | name => 'rrd', | |
445 | path => '{vmid}/rrd', | |
446 | method => 'GET', | |
447 | protected => 1, # fixme: can we avoid that? | |
448 | permissions => { | |
449 | path => '/vms/{vmid}', | |
450 | privs => [ 'VM.Audit' ], | |
451 | }, | |
452 | description => "Read VM RRD statistics (returns PNG)", | |
453 | parameters => { | |
454 | additionalProperties => 0, | |
455 | properties => { | |
456 | node => get_standard_option('pve-node'), | |
457 | vmid => get_standard_option('pve-vmid'), | |
458 | timeframe => { | |
459 | description => "Specify the time frame you are interested in.", | |
460 | type => 'string', | |
461 | enum => [ 'hour', 'day', 'week', 'month', 'year' ], | |
462 | }, | |
463 | ds => { | |
464 | description => "The list of datasources you want to display.", | |
465 | type => 'string', format => 'pve-configid-list', | |
466 | }, | |
467 | cf => { | |
468 | description => "The RRD consolidation function", | |
469 | type => 'string', | |
470 | enum => [ 'AVERAGE', 'MAX' ], | |
471 | optional => 1, | |
472 | }, | |
473 | }, | |
474 | }, | |
475 | returns => { | |
476 | type => "object", | |
477 | properties => { | |
478 | filename => { type => 'string' }, | |
479 | }, | |
480 | }, | |
481 | code => sub { | |
482 | my ($param) = @_; | |
483 | ||
484 | return PVE::Cluster::create_rrd_graph( | |
485 | "pve2-vm/$param->{vmid}", $param->{timeframe}, | |
486 | $param->{ds}, $param->{cf}); | |
487 | ||
488 | }}); | |
489 | ||
490 | __PACKAGE__->register_method({ | |
491 | name => 'rrddata', | |
492 | path => '{vmid}/rrddata', | |
493 | method => 'GET', | |
494 | protected => 1, # fixme: can we avoid that? | |
495 | permissions => { | |
496 | path => '/vms/{vmid}', | |
497 | privs => [ 'VM.Audit' ], | |
498 | }, | |
499 | description => "Read VM RRD statistics", | |
500 | parameters => { | |
501 | additionalProperties => 0, | |
502 | properties => { | |
503 | node => get_standard_option('pve-node'), | |
504 | vmid => get_standard_option('pve-vmid'), | |
505 | timeframe => { | |
506 | description => "Specify the time frame you are interested in.", | |
507 | type => 'string', | |
508 | enum => [ 'hour', 'day', 'week', 'month', 'year' ], | |
509 | }, | |
510 | cf => { | |
511 | description => "The RRD consolidation function", | |
512 | type => 'string', | |
513 | enum => [ 'AVERAGE', 'MAX' ], | |
514 | optional => 1, | |
515 | }, | |
516 | }, | |
517 | }, | |
518 | returns => { | |
519 | type => "array", | |
520 | items => { | |
521 | type => "object", | |
522 | properties => {}, | |
523 | }, | |
524 | }, | |
525 | code => sub { | |
526 | my ($param) = @_; | |
527 | ||
528 | return PVE::Cluster::create_rrd_data( | |
529 | "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf}); | |
530 | }}); | |
531 | ||
2d590018 DM |
532 | __PACKAGE__->register_method({ |
533 | name => 'initlog', | |
534 | path => '{vmid}/initlog', | |
535 | method => 'GET', | |
536 | protected => 1, | |
7b8e4045 | 537 | proxyto => 'node', |
2d590018 DM |
538 | permissions => { |
539 | path => '/vms/{vmid}', | |
540 | privs => [ 'VM.Audit' ], | |
541 | }, | |
542 | description => "Read init log.", | |
543 | parameters => { | |
544 | additionalProperties => 0, | |
545 | properties => { | |
546 | node => get_standard_option('pve-node'), | |
547 | vmid => get_standard_option('pve-vmid'), | |
548 | start => { | |
549 | type => 'integer', | |
550 | minimum => 0, | |
551 | optional => 1, | |
552 | }, | |
553 | limit => { | |
554 | type => 'integer', | |
555 | minimum => 0, | |
556 | optional => 1, | |
557 | }, | |
558 | }, | |
559 | }, | |
560 | returns => { | |
561 | type => 'array', | |
562 | items => { | |
563 | type => "object", | |
564 | properties => { | |
565 | n => { | |
566 | description=> "Line number", | |
567 | type=> 'integer', | |
568 | }, | |
569 | t => { | |
570 | description=> "Line text", | |
571 | type => 'string', | |
572 | } | |
573 | } | |
574 | } | |
575 | }, | |
576 | code => sub { | |
577 | my ($param) = @_; | |
578 | ||
579 | my $rpcenv = PVE::RPCEnvironment::get(); | |
580 | my $user = $rpcenv->get_user(); | |
581 | ||
582 | my $vmid = $param->{vmid}; | |
583 | ||
584 | my $conf = PVE::OpenVZ::load_config($vmid); | |
585 | ||
586 | my $privatedir = PVE::OpenVZ::get_privatedir($conf, $vmid); | |
587 | ||
588 | my $logfn = "$privatedir/var/log/init.log"; | |
589 | ||
590 | my ($count, $lines) = PVE::Tools::dump_logfile($logfn, $param->{start}, $param->{limit}); | |
591 | ||
592 | $rpcenv->set_result_count($count); | |
593 | ||
594 | return $lines; | |
595 | }}); | |
596 | ||
c3163e37 DM |
597 | __PACKAGE__->register_method({ |
598 | name => 'vm_config', | |
599 | path => '{vmid}/config', | |
600 | method => 'GET', | |
601 | proxyto => 'node', | |
337dca7c | 602 | description => "Get container configuration.", |
c3163e37 DM |
603 | parameters => { |
604 | additionalProperties => 0, | |
605 | properties => { | |
606 | node => get_standard_option('pve-node'), | |
607 | vmid => get_standard_option('pve-vmid'), | |
608 | }, | |
609 | }, | |
610 | returns => { | |
611 | type => "object", | |
612 | properties => { | |
613 | digest => { | |
614 | type => 'string', | |
615 | description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.', | |
616 | } | |
617 | }, | |
618 | }, | |
619 | code => sub { | |
620 | my ($param) = @_; | |
621 | ||
622 | my $veconf = PVE::OpenVZ::load_config($param->{vmid}); | |
623 | ||
624 | # we only return selected/converted values | |
625 | my $conf = { digest => $veconf->{digest} }; | |
626 | ||
337dca7c DM |
627 | if ($veconf->{ostemplate} && $veconf->{ostemplate}->{value}) { |
628 | $conf->{ostemplate} = $veconf->{ostemplate}->{value}; | |
629 | } | |
630 | ||
2c8e0b15 DM |
631 | my $stcfg = cfs_read_file("storage.cfg"); |
632 | ||
0618d446 DM |
633 | if ($veconf->{ve_private} && $conf->{ve_private}->{value}) { |
634 | my $path = PVE::OpenVZ::get_privatedir($veconf, $param->{vmid}); | |
2c8e0b15 DM |
635 | my ($vtype, $volid) = PVE::Storage::path_to_volume_id($stcfg, $path); |
636 | my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1) if $volid; | |
637 | $conf->{storage} = $sid || $path; | |
638 | } | |
639 | ||
c3163e37 DM |
640 | my $properties = PVE::OpenVZ::json_config_properties(); |
641 | ||
642 | foreach my $k (keys %$properties) { | |
643 | next if $k eq 'memory'; | |
644 | next if $k eq 'swap'; | |
645 | next if $k eq 'disk'; | |
88a6668d DM |
646 | |
647 | next if !$veconf->{$k}; | |
648 | next if !defined($veconf->{$k}->{value}); | |
649 | ||
650 | if ($k eq 'description') { | |
651 | $conf->{$k} = PVE::Tools::decode_text($veconf->{$k}->{value}); | |
652 | } else { | |
653 | $conf->{$k} = $veconf->{$k}->{value}; | |
654 | } | |
c3163e37 DM |
655 | } |
656 | ||
657 | $conf->{memory} = $veconf->{physpages}->{lim} ? | |
d9f0ffa9 | 658 | int(($veconf->{physpages}->{lim} * 4)/ 1024) : 512; |
c3163e37 | 659 | $conf->{swap} = $veconf->{swappages}->{lim} ? |
d9f0ffa9 DM |
660 | int(($veconf->{swappages}->{lim} * 4)/1024) : 0; |
661 | ||
c3163e37 DM |
662 | my $diskspace = $veconf->{diskspace}->{bar} || LONG_MAX; |
663 | if ($diskspace == LONG_MAX) { | |
664 | $conf->{disk} = 0; | |
665 | } else { | |
d9f0ffa9 | 666 | $conf->{disk} = $diskspace/(1024*1024); |
c3163e37 DM |
667 | } |
668 | return $conf; | |
669 | }}); | |
670 | ||
339e4159 DM |
671 | __PACKAGE__->register_method({ |
672 | name => 'destroy_vm', | |
673 | path => '{vmid}', | |
674 | method => 'DELETE', | |
675 | protected => 1, | |
676 | proxyto => 'node', | |
677 | description => "Destroy the container (also delete all uses files).", | |
678 | parameters => { | |
679 | additionalProperties => 0, | |
680 | properties => { | |
681 | node => get_standard_option('pve-node'), | |
682 | vmid => get_standard_option('pve-vmid'), | |
683 | }, | |
684 | }, | |
a42f6acb DM |
685 | returns => { |
686 | type => 'string', | |
687 | }, | |
339e4159 DM |
688 | code => sub { |
689 | my ($param) = @_; | |
690 | ||
691 | my $rpcenv = PVE::RPCEnvironment::get(); | |
692 | ||
693 | my $user = $rpcenv->get_user(); | |
694 | ||
695 | my $vmid = $param->{vmid}; | |
696 | ||
07151796 DM |
697 | # test if VM exists |
698 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
699 | ||
a42f6acb DM |
700 | my $realcmd = sub { |
701 | my $cmd = ['vzctl', 'destroy', $vmid ]; | |
339e4159 | 702 | |
7e79e293 | 703 | run_command($cmd); |
a42f6acb | 704 | }; |
339e4159 | 705 | |
a42f6acb | 706 | return $rpcenv->fork_worker('vzdestroy', $vmid, $user, $realcmd); |
339e4159 DM |
707 | }}); |
708 | ||
b1b121d5 DM |
709 | my $sslcert; |
710 | ||
711 | __PACKAGE__->register_method ({ | |
712 | name => 'vncproxy', | |
713 | path => '{vmid}/vncproxy', | |
714 | method => 'POST', | |
715 | protected => 1, | |
716 | permissions => { | |
717 | path => '/vms/{vmid}', | |
718 | privs => [ 'VM.Console' ], | |
719 | }, | |
720 | description => "Creates a TCP VNC proxy connections.", | |
721 | parameters => { | |
722 | additionalProperties => 0, | |
723 | properties => { | |
724 | node => get_standard_option('pve-node'), | |
725 | vmid => get_standard_option('pve-vmid'), | |
726 | }, | |
727 | }, | |
728 | returns => { | |
729 | additionalProperties => 0, | |
730 | properties => { | |
731 | user => { type => 'string' }, | |
732 | ticket => { type => 'string' }, | |
733 | cert => { type => 'string' }, | |
734 | port => { type => 'integer' }, | |
735 | upid => { type => 'string' }, | |
736 | }, | |
737 | }, | |
738 | code => sub { | |
739 | my ($param) = @_; | |
740 | ||
741 | my $rpcenv = PVE::RPCEnvironment::get(); | |
742 | ||
743 | my $user = $rpcenv->get_user(); | |
744 | my $ticket = PVE::AccessControl::assemble_ticket($user); | |
745 | ||
746 | my $vmid = $param->{vmid}; | |
747 | my $node = $param->{node}; | |
748 | ||
749 | $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192) | |
750 | if !$sslcert; | |
751 | ||
752 | my $port = PVE::Tools::next_vnc_port(); | |
753 | ||
754 | my $remip; | |
755 | ||
756 | if ($node ne PVE::INotify::nodename()) { | |
757 | $remip = PVE::Cluster::remote_node_ip($node); | |
758 | } | |
759 | ||
760 | # NOTE: vncterm VNC traffic is already TLS encrypted, | |
761 | # so we select the fastest chipher here (or 'none'?) | |
762 | my $remcmd = $remip ? | |
763 | ['/usr/bin/ssh', '-c', 'blowfish-cbc', '-t', $remip] : []; | |
764 | ||
765 | my $shcmd = [ '/usr/sbin/vzctl', 'enter', $vmid ]; | |
766 | ||
767 | my $realcmd = sub { | |
768 | my $upid = shift; | |
769 | ||
770 | syslog ('info', "starting openvz vnc proxy $upid\n"); | |
771 | ||
772 | my $timeout = 10; | |
773 | ||
774 | my $cmd = ['/usr/bin/vncterm', '-rfbport', $port, | |
775 | '-timeout', $timeout, '-authpath', "/vms/$vmid", | |
776 | '-perm', 'VM.Console', '-c', @$remcmd, @$shcmd]; | |
777 | ||
7e79e293 | 778 | run_command($cmd); |
b1b121d5 DM |
779 | |
780 | return; | |
781 | }; | |
782 | ||
783 | my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $user, $realcmd); | |
784 | ||
785 | return { | |
786 | user => $user, | |
787 | ticket => $ticket, | |
788 | port => $port, | |
789 | upid => $upid, | |
790 | cert => $sslcert, | |
791 | }; | |
792 | }}); | |
793 | ||
07151796 DM |
794 | __PACKAGE__->register_method({ |
795 | name => 'vmcmdidx', | |
796 | path => '{vmid}/status', | |
797 | method => 'GET', | |
798 | proxyto => 'node', | |
799 | description => "Directory index", | |
800 | parameters => { | |
801 | additionalProperties => 0, | |
802 | properties => { | |
803 | node => get_standard_option('pve-node'), | |
804 | vmid => get_standard_option('pve-vmid'), | |
805 | }, | |
806 | }, | |
807 | returns => { | |
808 | type => 'array', | |
809 | items => { | |
810 | type => "object", | |
811 | properties => { | |
812 | subdir => { type => 'string' }, | |
813 | }, | |
814 | }, | |
815 | links => [ { rel => 'child', href => "{subdir}" } ], | |
816 | }, | |
817 | code => sub { | |
818 | my ($param) = @_; | |
819 | ||
820 | # test if VM exists | |
821 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
822 | ||
823 | my $res = [ | |
824 | { subdir => 'current' }, | |
56203bf1 | 825 | { subdir => 'ubc' }, |
07151796 DM |
826 | { subdir => 'start' }, |
827 | { subdir => 'stop' }, | |
828 | ]; | |
829 | ||
830 | return $res; | |
831 | }}); | |
832 | ||
833 | __PACKAGE__->register_method({ | |
834 | name => 'vm_status', | |
835 | path => '{vmid}/status/current', | |
836 | method => 'GET', | |
837 | proxyto => 'node', | |
838 | protected => 1, # openvz /proc entries are only readable by root | |
839 | description => "Get virtual machine status.", | |
840 | parameters => { | |
841 | additionalProperties => 0, | |
842 | properties => { | |
843 | node => get_standard_option('pve-node'), | |
844 | vmid => get_standard_option('pve-vmid'), | |
845 | }, | |
846 | }, | |
847 | returns => { type => 'object' }, | |
848 | code => sub { | |
849 | my ($param) = @_; | |
850 | ||
851 | # test if VM exists | |
852 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
853 | ||
854 | my $vmstatus = PVE::OpenVZ::vmstatus($param->{vmid}); | |
855 | ||
856 | return $vmstatus->{$param->{vmid}}; | |
857 | }}); | |
858 | ||
56203bf1 DM |
859 | __PACKAGE__->register_method({ |
860 | name => 'vm_user_beancounters', | |
861 | path => '{vmid}/status/ubc', | |
862 | method => 'GET', | |
863 | proxyto => 'node', | |
864 | protected => 1, # openvz /proc entries are only readable by root | |
865 | description => "Get container user_beancounters.", | |
866 | parameters => { | |
867 | additionalProperties => 0, | |
868 | properties => { | |
869 | node => get_standard_option('pve-node'), | |
870 | vmid => get_standard_option('pve-vmid'), | |
871 | }, | |
872 | }, | |
873 | returns => { | |
874 | type => 'array', | |
875 | items => { | |
876 | type => "object", | |
877 | properties => { | |
878 | id => { type => 'string' }, | |
879 | held => { type => 'number' }, | |
880 | maxheld => { type => 'number' }, | |
881 | bar => { type => 'number' }, | |
882 | lim => { type => 'number' }, | |
883 | failcnt => { type => 'number' }, | |
884 | }, | |
885 | }, | |
886 | }, | |
887 | code => sub { | |
888 | my ($param) = @_; | |
889 | ||
890 | # test if VM exists | |
891 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
892 | ||
9056e748 DM |
893 | my $ubchash = PVE::OpenVZ::read_user_beancounters(); |
894 | my $ubc = $ubchash->{$param->{vmid}} || {}; | |
895 | delete $ubc->{failcntsum}; | |
56203bf1 DM |
896 | |
897 | return PVE::RESTHandler::hash_to_array($ubc, 'id'); | |
898 | }}); | |
899 | ||
07151796 DM |
900 | __PACKAGE__->register_method({ |
901 | name => 'vm_start', | |
902 | path => '{vmid}/status/start', | |
903 | method => 'POST', | |
904 | protected => 1, | |
905 | proxyto => 'node', | |
906 | description => "Start the container.", | |
907 | parameters => { | |
908 | additionalProperties => 0, | |
909 | properties => { | |
910 | node => get_standard_option('pve-node'), | |
911 | vmid => get_standard_option('pve-vmid'), | |
912 | }, | |
913 | }, | |
914 | returns => { | |
915 | type => 'string', | |
916 | }, | |
917 | code => sub { | |
918 | my ($param) = @_; | |
919 | ||
920 | my $rpcenv = PVE::RPCEnvironment::get(); | |
921 | ||
922 | my $user = $rpcenv->get_user(); | |
923 | ||
924 | my $node = extract_param($param, 'node'); | |
925 | ||
926 | my $vmid = extract_param($param, 'vmid'); | |
927 | ||
51ed1415 DM |
928 | die "CT $vmid already running\n" if PVE::OpenVZ::check_running($vmid); |
929 | ||
07151796 DM |
930 | my $realcmd = sub { |
931 | my $upid = shift; | |
932 | ||
51ed1415 | 933 | syslog('info', "starting CT $vmid: $upid\n"); |
07151796 DM |
934 | |
935 | my $cmd = ['vzctl', 'start', $vmid]; | |
936 | ||
7e79e293 DM |
937 | run_command($cmd); |
938 | ||
939 | return; | |
07151796 DM |
940 | }; |
941 | ||
c661ad50 | 942 | return $rpcenv->fork_worker('vzstart', $vmid, $user, $realcmd); |
07151796 DM |
943 | }}); |
944 | ||
945 | __PACKAGE__->register_method({ | |
946 | name => 'vm_stop', | |
947 | path => '{vmid}/status/stop', | |
948 | method => 'POST', | |
949 | protected => 1, | |
950 | proxyto => 'node', | |
951 | description => "Stop the container.", | |
952 | parameters => { | |
953 | additionalProperties => 0, | |
954 | properties => { | |
955 | node => get_standard_option('pve-node'), | |
956 | vmid => get_standard_option('pve-vmid'), | |
51ed1415 DM |
957 | }, |
958 | }, | |
959 | returns => { | |
960 | type => 'string', | |
961 | }, | |
962 | code => sub { | |
963 | my ($param) = @_; | |
964 | ||
965 | my $rpcenv = PVE::RPCEnvironment::get(); | |
966 | ||
967 | my $user = $rpcenv->get_user(); | |
968 | ||
969 | my $node = extract_param($param, 'node'); | |
970 | ||
971 | my $vmid = extract_param($param, 'vmid'); | |
972 | ||
973 | die "CT $vmid not running\n" if !PVE::OpenVZ::check_running($vmid); | |
974 | ||
975 | my $realcmd = sub { | |
976 | my $upid = shift; | |
977 | ||
978 | syslog('info', "stoping CT $vmid: $upid\n"); | |
979 | ||
980 | my $cmd = ['vzctl', 'stop', $vmid, '--fast']; | |
981 | run_command($cmd); | |
982 | ||
983 | return; | |
984 | }; | |
985 | ||
986 | my $upid = $rpcenv->fork_worker('vzstop', $vmid, $user, $realcmd); | |
987 | ||
988 | return $upid; | |
989 | }}); | |
990 | ||
991 | __PACKAGE__->register_method({ | |
992 | name => 'vm_shutdown', | |
993 | path => '{vmid}/status/shutdown', | |
994 | method => 'POST', | |
995 | protected => 1, | |
996 | proxyto => 'node', | |
997 | description => "Shutdown the container.", | |
998 | parameters => { | |
999 | additionalProperties => 0, | |
1000 | properties => { | |
1001 | node => get_standard_option('pve-node'), | |
1002 | vmid => get_standard_option('pve-vmid'), | |
1003 | timeout => { | |
1004 | description => "Wait maximal timeout seconds.", | |
1005 | type => 'integer', | |
1006 | minimum => 0, | |
1007 | optional => 1, | |
1008 | default => 60, | |
1009 | }, | |
1010 | forceStop => { | |
1011 | description => "Make sure the Container stops.", | |
07151796 | 1012 | type => 'boolean', |
07151796 | 1013 | optional => 1, |
51ed1415 | 1014 | default => 0, |
07151796 DM |
1015 | } |
1016 | }, | |
1017 | }, | |
1018 | returns => { | |
1019 | type => 'string', | |
1020 | }, | |
1021 | code => sub { | |
1022 | my ($param) = @_; | |
1023 | ||
1024 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1025 | ||
1026 | my $user = $rpcenv->get_user(); | |
1027 | ||
1028 | my $node = extract_param($param, 'node'); | |
1029 | ||
1030 | my $vmid = extract_param($param, 'vmid'); | |
1031 | ||
51ed1415 DM |
1032 | my $timeout = extract_param($param, 'timeout'); |
1033 | ||
1034 | die "CT $vmid not running\n" if !PVE::OpenVZ::check_running($vmid); | |
1035 | ||
07151796 DM |
1036 | my $realcmd = sub { |
1037 | my $upid = shift; | |
1038 | ||
51ed1415 | 1039 | syslog('info', "shutdown CT $vmid: $upid\n"); |
07151796 DM |
1040 | |
1041 | my $cmd = ['vzctl', 'stop', $vmid]; | |
1042 | ||
51ed1415 DM |
1043 | $timeout = 60 if !defined($timeout); |
1044 | ||
1045 | eval { run_command($cmd, timeout => $timeout); }; | |
1046 | my $err = $@; | |
1047 | return if !$err; | |
1048 | ||
1049 | die $err if !$param->{forceStop}; | |
1050 | ||
1051 | warn "shutdown failed - forcing stop now\n"; | |
1052 | ||
1053 | push @$cmd, '--fast'; | |
7e79e293 | 1054 | run_command($cmd); |
07151796 DM |
1055 | |
1056 | return; | |
1057 | }; | |
1058 | ||
51ed1415 | 1059 | my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $user, $realcmd); |
07151796 DM |
1060 | |
1061 | return $upid; | |
1062 | }}); | |
1063 | ||
7e79e293 DM |
1064 | __PACKAGE__->register_method({ |
1065 | name => 'migrate_vm', | |
1066 | path => '{vmid}/migrate', | |
1067 | method => 'POST', | |
1068 | protected => 1, | |
1069 | proxyto => 'node', | |
1070 | description => "Migrate the container to another node. Creates a new migration task.", | |
1071 | parameters => { | |
1072 | additionalProperties => 0, | |
1073 | properties => { | |
1074 | node => get_standard_option('pve-node'), | |
1075 | vmid => get_standard_option('pve-vmid'), | |
1076 | target => get_standard_option('pve-node', { description => "Target node." }), | |
1077 | online => { | |
1078 | type => 'boolean', | |
1079 | description => "Use online/live migration.", | |
1080 | optional => 1, | |
1081 | }, | |
1082 | }, | |
1083 | }, | |
1084 | returns => { | |
1085 | type => 'string', | |
1086 | description => "the task ID.", | |
1087 | }, | |
1088 | code => sub { | |
1089 | my ($param) = @_; | |
1090 | ||
1091 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1092 | ||
1093 | my $user = $rpcenv->get_user(); | |
1094 | ||
1095 | my $target = extract_param($param, 'target'); | |
1096 | ||
1097 | my $localnode = PVE::INotify::nodename(); | |
1098 | raise_param_exc({ target => "target is local node."}) if $target eq $localnode; | |
1099 | ||
1100 | PVE::Cluster::check_cfs_quorum(); | |
1101 | ||
1102 | PVE::Cluster::check_node_exists($target); | |
1103 | ||
1104 | my $targetip = PVE::Cluster::remote_node_ip($target); | |
1105 | ||
1106 | my $vmid = extract_param($param, 'vmid'); | |
1107 | ||
1108 | # test if VM exists | |
1109 | PVE::OpenVZ::load_config($vmid); | |
1110 | ||
1111 | # try to detect errors early | |
1112 | if (PVE::OpenVZ::check_running($vmid)) { | |
1113 | die "cant migrate running container without --online\n" | |
1114 | if !$param->{online}; | |
1115 | } | |
1116 | ||
1117 | my $realcmd = sub { | |
1118 | my $upid = shift; | |
45116ffb DM |
1119 | |
1120 | PVE::OpenVZMigrate->migrate($target, $targetip, $vmid, $param); | |
1121 | ||
7e79e293 DM |
1122 | return; |
1123 | }; | |
1124 | ||
1125 | my $upid = $rpcenv->fork_worker('vzmigrate', $vmid, $user, $realcmd); | |
1126 | ||
1127 | return $upid; | |
1128 | }}); | |
1129 | ||
339e4159 | 1130 | 1; |