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