]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Nodes.pm
plug VZDump into our API
[pve-manager.git] / PVE / API2 / Nodes.pm
1 package PVE::API2::Nodes::Nodeinfo;
2
3 use strict;
4 use warnings;
5 use POSIX;
6 use Filesys::Df;
7 use Time::Local qw(timegm_nocheck);
8 use PVE::pvecfg;
9 use PVE::Tools;
10 use PVE::ProcFSTools;
11 use PVE::SafeSyslog;
12 use PVE::Cluster;
13 use PVE::INotify;
14 use PVE::RESTHandler;
15 use PVE::RPCEnvironment;
16 use PVE::JSONSchema qw(get_standard_option);
17 use PVE::AccessControl;
18 use PVE::OpenVZ;
19 use PVE::API2::Services;
20 use PVE::API2::Network;
21 use PVE::API2::Tasks;
22 use PVE::API2::Storage::Scan;
23 use PVE::API2::Storage::Status;
24 use PVE::API2::Qemu;
25 use PVE::API2::OpenVZ;
26 use PVE::API2::VZDump;
27 use JSON;
28
29 use base qw(PVE::RESTHandler);
30
31 __PACKAGE__->register_method ({
32 subclass => "PVE::API2::Qemu",
33 path => 'qemu',
34 });
35
36 __PACKAGE__->register_method ({
37 subclass => "PVE::API2::OpenVZ",
38 path => 'openvz',
39 });
40
41 __PACKAGE__->register_method ({
42 subclass => "PVE::API2::VZDump",
43 path => 'vzdump',
44 });
45
46 __PACKAGE__->register_method ({
47 subclass => "PVE::API2::Services",
48 path => 'services',
49 });
50
51 __PACKAGE__->register_method ({
52 subclass => "PVE::API2::Network",
53 path => 'network',
54 });
55
56 __PACKAGE__->register_method ({
57 subclass => "PVE::API2::Tasks",
58 path => 'tasks',
59 });
60
61 __PACKAGE__->register_method ({
62 subclass => "PVE::API2::Storage::Scan",
63 path => 'scan',
64 });
65
66 __PACKAGE__->register_method ({
67 subclass => "PVE::API2::Storage::Status",
68 path => 'storage',
69 });
70
71 __PACKAGE__->register_method ({
72 name => 'index',
73 path => '',
74 method => 'GET',
75 permissions => { user => 'all' },
76 description => "Node index.",
77 parameters => {
78 additionalProperties => 0,
79 properties => {
80 node => get_standard_option('pve-node'),
81 },
82 },
83 returns => {
84 type => 'array',
85 items => {
86 type => "object",
87 properties => {},
88 },
89 links => [ { rel => 'child', href => "{name}" } ],
90 },
91 code => sub {
92 my ($param) = @_;
93
94 my $result = [
95 { name => 'syslog' },
96 { name => 'status' },
97 { name => 'tasks' },
98 { name => 'rrd' }, # fixme: remove?
99 { name => 'rrddata' },# fixme: remove?
100 { name => 'vncshell' },
101 { name => 'time' },
102 { name => 'dns' },
103 { name => 'services' },
104 { name => 'scan' },
105 { name => 'storage' },
106 { name => 'upload' },
107 { name => 'qemu' },
108 { name => 'openvz' },
109 { name => 'vzdump' },
110 { name => 'ubcfailcnt' },
111 { name => 'network' },
112 { name => 'network_changes' },
113 ];
114
115 return $result;
116 }});
117
118 __PACKAGE__->register_method({
119 name => 'beancounters_failcnt',
120 path => 'ubcfailcnt',
121 permissions => {
122 path => '/nodes/{node}',
123 privs => [ 'Sys.Audit' ],
124 },
125 method => 'GET',
126 proxyto => 'node',
127 protected => 1, # openvz /proc entries are only readable by root
128 description => "Get user_beancounters failcnt for all active containers.",
129 parameters => {
130 additionalProperties => 0,
131 properties => {
132 node => get_standard_option('pve-node'),
133 },
134 },
135 returns => {
136 type => 'array',
137 items => {
138 type => "object",
139 properties => {
140 id => { type => 'string' },
141 failcnt => { type => 'number' },
142 },
143 },
144 },
145 code => sub {
146 my ($param) = @_;
147
148 my $ubchash = PVE::OpenVZ::read_user_beancounters();
149
150 my $res = [];
151 foreach my $vmid (keys %$ubchash) {
152 next if !$vmid;
153 push @$res, { id => $vmid, failcnt => $ubchash->{$vmid}->{failcntsum} };
154
155 }
156 return $res;
157 }});
158
159 __PACKAGE__->register_method({
160 name => 'network_changes',
161 path => 'network_changes',
162 method => 'GET',
163 permissions => {
164 path => '/nodes/{node}',
165 privs => [ 'Sys.Audit' ],
166 },
167 description => "Get network configuration changes (diff) since last boot.",
168 proxyto => 'node',
169 parameters => {
170 additionalProperties => 0,
171 properties => {
172 node => get_standard_option('pve-node'),
173 },
174 },
175 returns => { type => "string" },
176 code => sub {
177 my ($param) = @_;
178
179 my $res = PVE::INotify::read_file('interfaces', 1);
180
181 return $res->{changes} || '';
182 }});
183
184 __PACKAGE__->register_method({
185 name => 'revert_network_changes',
186 path => 'network_changes',
187 method => 'DELETE',
188 permissions => {
189 path => '/nodes/{node}',
190 privs => [ 'Sys.Modify' ],
191 },
192 protected => 1,
193 description => "Revert network configuration changes.",
194 proxyto => 'node',
195 parameters => {
196 additionalProperties => 0,
197 properties => {
198 node => get_standard_option('pve-node'),
199 },
200 },
201 returns => { type => "null" },
202 code => sub {
203 my ($param) = @_;
204
205 unlink "/etc/network/interfaces.new";
206
207 return undef;
208 }});
209
210 __PACKAGE__->register_method({
211 name => 'status',
212 path => 'status',
213 method => 'GET',
214 permissions => {
215 path => '/nodes/{node}',
216 privs => [ 'Sys.Audit' ],
217 },
218 description => "Read node status",
219 proxyto => 'node',
220 parameters => {
221 additionalProperties => 0,
222 properties => {
223 node => get_standard_option('pve-node'),
224 },
225 },
226 returns => {
227 type => "object",
228 properties => {
229
230 },
231 },
232 code => sub {
233 my ($param) = @_;
234
235 my $res = {
236 uptime => 0,
237 idle => 0,
238 };
239
240 my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime();
241 $res->{uptime} = $uptime;
242
243 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
244 $res->{loadavg} = [ $avg1, $avg5, $avg15];
245
246 my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
247
248 $res->{kversion} = "$sysname $release $version";
249
250 $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo();
251
252 my $stat = PVE::ProcFSTools::read_proc_stat();
253 $res->{cpu} = $stat->{cpu};
254 $res->{wait} = $stat->{wait};
255
256 my $meminfo = PVE::ProcFSTools::read_meminfo();
257 $res->{memory} = {
258 free => $meminfo->{memfree},
259 total => $meminfo->{memtotal},
260 used => $meminfo->{memused},
261 };
262 $res->{swap} = {
263 free => $meminfo->{swapfree},
264 total => $meminfo->{swaptotal},
265 used => $meminfo->{swapused},
266 };
267
268 $res->{pveversion} = PVE::pvecfg::package() . "/" .
269 PVE::pvecfg::version() . "/" .
270 PVE::pvecfg::repoid();
271
272 my $dinfo = df('/', 1); # output is bytes
273
274 $res->{rootfs} = {
275 total => $dinfo->{blocks},
276 avail => $dinfo->{bavail},
277 used => $dinfo->{used},
278 free => $dinfo->{bavail} - $dinfo->{used},
279 };
280
281 return $res;
282 }});
283
284 __PACKAGE__->register_method({
285 name => 'node_cmd',
286 path => 'status',
287 method => 'POST',
288 permissions => {
289 path => '/nodes/{node}',
290 privs => [ 'Sys.PowerMgmt' ],
291 },
292 protected => 1,
293 description => "Reboot or shutdown a node.",
294 proxyto => 'node',
295 parameters => {
296 additionalProperties => 0,
297 properties => {
298 node => get_standard_option('pve-node'),
299 command => {
300 description => "Specify the command.",
301 type => 'string',
302 enum => [qw(reboot shutdown)],
303 },
304 },
305 },
306 returns => { type => "null" },
307 code => sub {
308 my ($param) = @_;
309
310 if ($param->{command} eq 'reboot') {
311 system ("(sleep 2;/sbin/reboot)&");
312 } elsif ($param->{command} eq 'shutdown') {
313 system ("(sleep 2;/sbin/poweroff)&");
314 }
315
316 return undef;
317 }});
318
319
320 __PACKAGE__->register_method({
321 name => 'rrd',
322 path => 'rrd',
323 method => 'GET',
324 protected => 1, # fixme: can we avoid that?
325 permissions => {
326 path => '/nodes/{node}',
327 privs => [ 'Sys.Audit' ],
328 },
329 description => "Read node RRD statistics (returns PNG)",
330 parameters => {
331 additionalProperties => 0,
332 properties => {
333 node => get_standard_option('pve-node'),
334 timeframe => {
335 description => "Specify the time frame you are interested in.",
336 type => 'string',
337 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
338 },
339 ds => {
340 description => "The list of datasources you want to display.",
341 type => 'string', format => 'pve-configid-list',
342 },
343 cf => {
344 description => "The RRD consolidation function",
345 type => 'string',
346 enum => [ 'AVERAGE', 'MAX' ],
347 optional => 1,
348 },
349 },
350 },
351 returns => {
352 type => "object",
353 properties => {
354 filename => { type => 'string' },
355 },
356 },
357 code => sub {
358 my ($param) = @_;
359
360 return PVE::Cluster::create_rrd_graph(
361 "pve2-node/$param->{node}", $param->{timeframe},
362 $param->{ds}, $param->{cf});
363
364 }});
365
366 __PACKAGE__->register_method({
367 name => 'rrddata',
368 path => 'rrddata',
369 method => 'GET',
370 protected => 1, # fixme: can we avoid that?
371 permissions => {
372 path => '/nodes/{node}',
373 privs => [ 'Sys.Audit' ],
374 },
375 description => "Read node RRD statistics",
376 parameters => {
377 additionalProperties => 0,
378 properties => {
379 node => get_standard_option('pve-node'),
380 timeframe => {
381 description => "Specify the time frame you are interested in.",
382 type => 'string',
383 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
384 },
385 cf => {
386 description => "The RRD consolidation function",
387 type => 'string',
388 enum => [ 'AVERAGE', 'MAX' ],
389 optional => 1,
390 },
391 },
392 },
393 returns => {
394 type => "array",
395 items => {
396 type => "object",
397 properties => {},
398 },
399 },
400 code => sub {
401 my ($param) = @_;
402
403 return PVE::Cluster::create_rrd_data(
404 "pve2-node/$param->{node}", $param->{timeframe}, $param->{cf});
405 }});
406
407 __PACKAGE__->register_method({
408 name => 'syslog',
409 path => 'syslog',
410 method => 'GET',
411 description => "Read system log",
412 proxyto => 'node',
413 permissions => {
414 path => '/nodes/{node}',
415 privs => [ 'Sys.Syslog' ],
416 },
417 protected => 1,
418 parameters => {
419 additionalProperties => 0,
420 properties => {
421 node => get_standard_option('pve-node'),
422 start => {
423 type => 'integer',
424 minimum => 0,
425 optional => 1,
426 },
427 limit => {
428 type => 'integer',
429 minimum => 0,
430 optional => 1,
431 },
432 },
433 },
434 returns => {
435 type => 'array',
436 items => {
437 type => "object",
438 properties => {
439 n => {
440 description=> "Line number",
441 type=> 'integer',
442 },
443 t => {
444 description=> "Line text",
445 type => 'string',
446 }
447 }
448 }
449 },
450 code => sub {
451 my ($param) = @_;
452
453 my $lines = [];
454
455 my $rpcenv = PVE::RPCEnvironment::get();
456 my $user = $rpcenv->get_user();
457 my $node = $param->{node};
458
459 my $fh = IO::File->new("/var/log/syslog", "r");
460 die "unable to open file - $!" if !$fh;
461
462 my $start = $param->{start} || 0;
463 my $limit = $param->{limit} || 50;
464 my $count = 0;
465 my $line;
466 while (defined ($line = <$fh>)) {
467 next if $count++ < $start;
468 next if $limit <= 0;
469 chomp $line;
470 push @$lines, { n => $count, t => $line};
471 $limit--;
472 }
473
474 close($fh);
475
476 # HACK: ExtJS store.guaranteeRange() does not like empty array
477 # so we add a line
478 if (!$count) {
479 $count++;
480 push @$lines, { n => $count, t => "no content"};
481 }
482
483 $rpcenv->set_result_count($count);
484
485 return $lines;
486 }});
487
488 my $sslcert;
489
490 __PACKAGE__->register_method ({
491 name => 'vncshell',
492 path => 'vncshell',
493 method => 'POST',
494 protected => 1,
495 permissions => {
496 path => '/nodes/{node}',
497 privs => [ 'Sys.Console' ],
498 },
499 description => "Creates a VNC Shell proxy.",
500 parameters => {
501 additionalProperties => 0,
502 properties => {
503 node => get_standard_option('pve-node'),
504 },
505 },
506 returns => {
507 additionalProperties => 0,
508 properties => {
509 user => { type => 'string' },
510 ticket => { type => 'string' },
511 cert => { type => 'string' },
512 port => { type => 'integer' },
513 upid => { type => 'string' },
514 },
515 },
516 code => sub {
517 my ($param) = @_;
518
519 my $rpcenv = PVE::RPCEnvironment::get();
520
521 my $user = $rpcenv->get_user();
522
523 my $ticket = PVE::AccessControl::assemble_ticket($user);
524
525 my $node = $param->{node};
526
527 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
528 if !$sslcert;
529
530 my $port = PVE::Tools::next_vnc_port();
531
532 my $remip;
533
534 if ($node ne PVE::INotify::nodename()) {
535 $remip = PVE::Cluster::remote_node_ip($node);
536 }
537
538 # NOTE: vncterm VNC traffic is already TLS encrypted,
539 # so we select the fastest chipher here (or 'none'?)
540 my $remcmd = $remip ?
541 ['/usr/bin/ssh', '-c', 'blowfish-cbc', '-t', $remip] : [];
542
543 my $shcmd = $user eq 'root@pam' ? [ "/bin/bash", "-l" ] : [ "/bin/login" ];
544
545 my $timeout = 10;
546
547 # fixme: do we want to require special auth permissions?
548 # example "-perm Shell"
549 my @cmd = ('/usr/bin/vncterm', '-rfbport', $port,
550 '-timeout', $timeout, '-authpath', "/nodes/$node",
551 '-perm', 'Sys.Console', '-c', @$remcmd, @$shcmd);
552
553 my $realcmd = sub {
554 my $upid = shift;
555
556 syslog ('info', "starting vnc proxy $upid\n");
557
558 my $cmdstr = join (' ', @cmd);
559 syslog ('info', "launch command: $cmdstr");
560
561 if (system(@cmd) != 0) {
562 my $msg = "vncterm failed - $?";
563 syslog ('err', $msg);
564 return;
565 }
566
567 return;
568 };
569
570 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
571
572 return {
573 user => $user,
574 ticket => $ticket,
575 port => $port,
576 upid => $upid,
577 cert => $sslcert,
578 };
579 }});
580
581 __PACKAGE__->register_method({
582 name => 'dns',
583 path => 'dns',
584 method => 'GET',
585 permissions => {
586 path => '/nodes/{node}',
587 privs => [ 'Sys.Audit' ],
588 },
589 description => "Read DNS settings.",
590 proxyto => 'node',
591 parameters => {
592 additionalProperties => 0,
593 properties => {
594 node => get_standard_option('pve-node'),
595 },
596 },
597 returns => {
598 type => "object",
599 additionalProperties => 0,
600 properties => {
601 search => {
602 description => "Search domain for host-name lookup.",
603 type => 'string',
604 optional => 1,
605 },
606 dns1 => {
607 description => 'First name server IP address.',
608 type => 'string',
609 optional => 1,
610 },
611 dns2 => {
612 description => 'Second name server IP address.',
613 type => 'string',
614 optional => 1,
615 },
616 dns3 => {
617 description => 'Third name server IP address.',
618 type => 'string',
619 optional => 1,
620 },
621 },
622 },
623 code => sub {
624 my ($param) = @_;
625
626 my $res = PVE::INotify::read_file('resolvconf');
627
628 return $res;
629 }});
630
631 __PACKAGE__->register_method({
632 name => 'update_dns',
633 path => 'dns',
634 method => 'PUT',
635 description => "Write DNS settings.",
636 proxyto => 'node',
637 protected => 1,
638 parameters => {
639 additionalProperties => 0,
640 properties => {
641 node => get_standard_option('pve-node'),
642 search => {
643 description => "Search domain for host-name lookup.",
644 type => 'string',
645 },
646 dns1 => {
647 description => 'First name server IP address.',
648 type => 'string', format => 'ipv4',
649 optional => 1,
650 },
651 dns2 => {
652 description => 'Second name server IP address.',
653 type => 'string', format => 'ipv4',
654 optional => 1,
655 },
656 dns3 => {
657 description => 'Third name server IP address.',
658 type => 'string', format => 'ipv4',
659 optional => 1,
660 },
661 },
662 },
663 returns => { type => "null" },
664 code => sub {
665 my ($param) = @_;
666
667 PVE::INotify::update_file('resolvconf', $param);
668
669 return undef;
670 }});
671
672 __PACKAGE__->register_method({
673 name => 'time',
674 path => 'time',
675 method => 'GET',
676 permissions => {
677 path => '/nodes/{node}',
678 privs => [ 'Sys.Audit' ],
679 },
680 description => "Read server time and time zone settings.",
681 proxyto => 'node',
682 parameters => {
683 additionalProperties => 0,
684 properties => {
685 node => get_standard_option('pve-node'),
686 },
687 },
688 returns => {
689 type => "object",
690 additionalProperties => 0,
691 properties => {
692 timezone => {
693 description => "Time zone",
694 type => 'string',
695 },
696 time => {
697 description => "Seconds since 1970-01-01 00:00:00 UTC.",
698 type => 'integer',
699 minimum => 1297163644,
700 },
701 localtime => {
702 description => "Seconds since 1970-01-01 00:00:00 (local time)",
703 type => 'integer',
704 minimum => 1297163644,
705 },
706 },
707 },
708 code => sub {
709 my ($param) = @_;
710
711 my $ctime = time();
712 my $ltime = timegm_nocheck(localtime($ctime));
713 my $res = {
714 timezone => PVE::INotify::read_file('timezone'),
715 time => time(),
716 localtime => $ltime,
717 };
718
719 return $res;
720 }});
721
722 __PACKAGE__->register_method({
723 name => 'set_timezone',
724 path => 'time',
725 method => 'PUT',
726 description => "Set time zone.",
727 proxyto => 'node',
728 protected => 1,
729 parameters => {
730 additionalProperties => 0,
731 properties => {
732 node => get_standard_option('pve-node'),
733 timezone => {
734 description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
735 type => 'string',
736 },
737 },
738 },
739 returns => { type => "null" },
740 code => sub {
741 my ($param) = @_;
742
743 PVE::INotify::write_file('timezone', $param->{timezone});
744
745 return undef;
746 }});
747
748 __PACKAGE__->register_method ({
749 name => 'upload',
750 path => 'upload',
751 method => 'POST',
752 permissions => {
753 path => '/storage/{storage}',
754 privs => [ 'Datastore.AllocateSpace' ],
755 },
756 description => "Upload content.",
757 parameters => {
758 additionalProperties => 0,
759 properties => {
760 node => get_standard_option('pve-node'),
761 storage => get_standard_option('pve-storage-id'),
762 filename => {
763 description => "The name of the file to create/upload.",
764 type => 'string',
765 },
766 vmid => get_standard_option
767 ('pve-vmid', {
768 description => "Specify owner VM",
769 optional => 1,
770 }),
771 },
772 },
773 returns => {
774 description => "Volume identifier",
775 type => 'string',
776 },
777 code => sub {
778 my ($param) = @_;
779
780 # todo: can we proxy file uploads to remote nodes?
781 if ($param->{node} ne PVE::INotify::nodename()) {
782 raise_param_exc({ node => "can't upload content to remote node" });
783 }
784
785 my $node = $param->{node};
786 my $storeid = $param->{storage};
787 my $name = $param->{filename};
788
789 my $fh = CGI::upload('filename') || die "unable to get file handle\n";
790
791 syslog ('info', "UPLOAD $name to $node $storeid");
792
793 # fixme:
794 die "upload not implemented\n";
795
796 my $buffer = "";
797 my $tmpname = "/tmp/proxmox_upload-$$.bin";
798
799 eval {
800 open FILE, ">$tmpname" || die "can't open temporary file '$tmpname' - $!\n";
801 while (read($fh, $buffer, 32768)) {
802 die "write failed - $!" unless print FILE $buffer;
803 }
804 close FILE || die " can't close temporary file '$tmpname' - $!\n";
805 };
806 my $err = $@;
807
808 if ($err) {
809 unlink $tmpname;
810 die $err;
811 }
812
813 unlink $tmpname; # fixme: proxy to local host import
814
815 # fixme: return volid
816
817 return undef;
818
819 }});
820
821 package PVE::API2::Nodes;
822
823 use strict;
824 use warnings;
825
826 use PVE::SafeSyslog;
827 use PVE::Cluster;
828 use PVE::RESTHandler;
829 use PVE::RPCEnvironment;
830
831 use base qw(PVE::RESTHandler);
832
833 __PACKAGE__->register_method ({
834 subclass => "PVE::API2::Nodes::Nodeinfo",
835 path => '{node}',
836 });
837
838 __PACKAGE__->register_method ({
839 name => 'index',
840 path => '',
841 method => 'GET',
842 permissions => { user => 'all' },
843 description => "Cluster node index.",
844 parameters => {
845 additionalProperties => 0,
846 properties => {},
847 },
848 returns => {
849 type => 'array',
850 items => {
851 type => "object",
852 properties => {},
853 },
854 links => [ { rel => 'child', href => "{name}" } ],
855 },
856 code => sub {
857 my ($param) = @_;
858
859 my $clinfo = PVE::Cluster::get_clinfo();
860 my $res = [];
861
862 my $nodename = PVE::INotify::nodename();
863 my $nodelist = $clinfo->{nodelist};
864
865 my $rrd = PVE::Cluster::rrd_dump();
866
867 my @nodes = $nodelist ? (keys %$nodelist) : $nodename;
868
869 foreach my $node (@nodes) {
870 my $entry = { name => $node };
871 if (my $d = $rrd->{"pve2-node/$node"}) {
872
873 $entry->{uptime} = $d->[0];
874 $entry->{maxcpu} = $d->[3];
875 $entry->{cpu} = $d->[4];
876 $entry->{maxmem} = $d->[6];
877 $entry->{mem} = $d->[7];
878 $entry->{maxdisk} = $d->[10];
879 $entry->{disk} = $d->[11];
880 }
881
882 push @$res, $entry;
883 }
884
885 return $res;
886 }});
887
888 1;