1 package PVE
::API2
::Nodes
::Nodeinfo
;
5 use POSIX
qw(LONG_MAX);
7 use Time
::Local
qw(timegm_nocheck);
12 use PVE
::Cluster
qw(cfs_read_file);
14 use PVE
::Exception
qw(raise raise_perm_exc);
16 use PVE
::RPCEnvironment
;
17 use PVE
::JSONSchema
qw(get_standard_option);
18 use PVE
::AccessControl
;
23 use PVE
::API2
::Subscription
;
24 use PVE
::API2
::Services
;
25 use PVE
::API2
::Network
;
27 use PVE
::API2
::Storage
::Scan
;
28 use PVE
::API2
::Storage
::Status
;
30 use PVE
::API2
::OpenVZ
;
31 use PVE
::API2
::VZDump
;
34 use base
qw(PVE::RESTHandler);
36 __PACKAGE__-
>register_method ({
37 subclass
=> "PVE::API2::Qemu",
41 __PACKAGE__-
>register_method ({
42 subclass
=> "PVE::API2::OpenVZ",
46 __PACKAGE__-
>register_method ({
47 subclass
=> "PVE::API2::VZDump",
51 __PACKAGE__-
>register_method ({
52 subclass
=> "PVE::API2::Services",
56 __PACKAGE__-
>register_method ({
57 subclass
=> "PVE::API2::Subscription",
58 path
=> 'subscription',
61 __PACKAGE__-
>register_method ({
62 subclass
=> "PVE::API2::Network",
66 __PACKAGE__-
>register_method ({
67 subclass
=> "PVE::API2::Tasks",
71 __PACKAGE__-
>register_method ({
72 subclass
=> "PVE::API2::Storage::Scan",
76 __PACKAGE__-
>register_method ({
77 subclass
=> "PVE::API2::Storage::Status",
81 __PACKAGE__-
>register_method ({
85 permissions
=> { user
=> 'all' },
86 description
=> "Node index.",
88 additionalProperties
=> 0,
90 node
=> get_standard_option
('pve-node'),
99 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
105 { name
=> 'version' },
106 { name
=> 'syslog' },
107 { name
=> 'status' },
108 { name
=> 'subscription' },
110 { name
=> 'rrd' }, # fixme: remove?
111 { name
=> 'rrddata' },# fixme: remove?
112 { name
=> 'vncshell' },
115 { name
=> 'services' },
117 { name
=> 'storage' },
119 { name
=> 'openvz' },
120 { name
=> 'vzdump' },
121 { name
=> 'ubcfailcnt' },
122 { name
=> 'network' },
123 { name
=> 'aplinfo' },
124 { name
=> 'startall' },
125 { name
=> 'stopall' },
131 __PACKAGE__-
>register_method ({
136 permissions
=> { user
=> 'all' },
137 description
=> "API version details",
139 additionalProperties
=> 0,
141 node
=> get_standard_option
('pve-node'),
147 version
=> { type
=> 'string' },
148 release
=> { type
=> 'string' },
149 repoid
=> { type
=> 'string' },
153 my ($resp, $param) = @_;
155 return PVE
::pvecfg
::version_info
();
158 __PACKAGE__-
>register_method({
159 name
=> 'beancounters_failcnt',
160 path
=> 'ubcfailcnt',
162 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
166 protected
=> 1, # openvz /proc entries are only readable by root
167 description
=> "Get user_beancounters failcnt for all active containers.",
169 additionalProperties
=> 0,
171 node
=> get_standard_option
('pve-node'),
179 id
=> { type
=> 'string' },
180 failcnt
=> { type
=> 'number' },
187 my $ubchash = PVE
::OpenVZ
::read_user_beancounters
();
190 foreach my $vmid (keys %$ubchash) {
192 push @$res, { id
=> $vmid, failcnt
=> $ubchash->{$vmid}->{failcntsum
} };
198 __PACKAGE__-
>register_method({
203 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
205 description
=> "Read node status",
208 additionalProperties
=> 0,
210 node
=> get_standard_option
('pve-node'),
227 my ($uptime, $idle) = PVE
::ProcFSTools
::read_proc_uptime
();
228 $res->{uptime
} = $uptime;
230 my ($avg1, $avg5, $avg15) = PVE
::ProcFSTools
::read_loadavg
();
231 $res->{loadavg
} = [ $avg1, $avg5, $avg15];
233 my ($sysname, $nodename, $release, $version, $machine) = POSIX
::uname
();
235 $res->{kversion
} = "$sysname $release $version";
237 $res->{cpuinfo
} = PVE
::ProcFSTools
::read_cpuinfo
();
239 my $stat = PVE
::ProcFSTools
::read_proc_stat
();
240 $res->{cpu
} = $stat->{cpu
};
241 $res->{wait} = $stat->{wait};
243 my $meminfo = PVE
::ProcFSTools
::read_meminfo
();
245 free
=> $meminfo->{memfree
},
246 total
=> $meminfo->{memtotal
},
247 used
=> $meminfo->{memused
},
250 free
=> $meminfo->{swapfree
},
251 total
=> $meminfo->{swaptotal
},
252 used
=> $meminfo->{swapused
},
255 $res->{pveversion
} = PVE
::pvecfg
::package() . "/" .
256 PVE
::pvecfg
::version_text
();
258 my $dinfo = df
('/', 1); # output is bytes
261 total
=> $dinfo->{blocks
},
262 avail
=> $dinfo->{bavail
},
263 used
=> $dinfo->{used
},
264 free
=> $dinfo->{bavail
} - $dinfo->{used
},
270 __PACKAGE__-
>register_method({
275 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
278 description
=> "Reboot or shutdown a node.",
281 additionalProperties
=> 0,
283 node
=> get_standard_option
('pve-node'),
285 description
=> "Specify the command.",
287 enum
=> [qw(reboot shutdown)],
291 returns
=> { type
=> "null" },
295 if ($param->{command
} eq 'reboot') {
296 system ("(sleep 2;/sbin/reboot)&");
297 } elsif ($param->{command
} eq 'shutdown') {
298 system ("(sleep 2;/sbin/poweroff)&");
305 __PACKAGE__-
>register_method({
309 protected
=> 1, # fixme: can we avoid that?
311 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
313 description
=> "Read node RRD statistics (returns PNG)",
315 additionalProperties
=> 0,
317 node
=> get_standard_option
('pve-node'),
319 description
=> "Specify the time frame you are interested in.",
321 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
324 description
=> "The list of datasources you want to display.",
325 type
=> 'string', format
=> 'pve-configid-list',
328 description
=> "The RRD consolidation function",
330 enum
=> [ 'AVERAGE', 'MAX' ],
338 filename
=> { type
=> 'string' },
344 return PVE
::Cluster
::create_rrd_graph
(
345 "pve2-node/$param->{node}", $param->{timeframe
},
346 $param->{ds
}, $param->{cf
});
350 __PACKAGE__-
>register_method({
354 protected
=> 1, # fixme: can we avoid that?
356 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
358 description
=> "Read node RRD statistics",
360 additionalProperties
=> 0,
362 node
=> get_standard_option
('pve-node'),
364 description
=> "Specify the time frame you are interested in.",
366 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
369 description
=> "The RRD consolidation function",
371 enum
=> [ 'AVERAGE', 'MAX' ],
386 return PVE
::Cluster
::create_rrd_data
(
387 "pve2-node/$param->{node}", $param->{timeframe
}, $param->{cf
});
390 __PACKAGE__-
>register_method({
394 description
=> "Read system log",
397 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
401 additionalProperties
=> 0,
403 node
=> get_standard_option
('pve-node'),
422 description
=> "Line number",
426 description
=> "Line text",
435 my $rpcenv = PVE
::RPCEnvironment
::get
();
436 my $user = $rpcenv->get_user();
437 my $node = $param->{node
};
439 my ($count, $lines) = PVE
::Tools
::dump_logfile
("/var/log/syslog", $param->{start
}, $param->{limit
});
441 $rpcenv->set_result_attrib('total', $count);
448 __PACKAGE__-
>register_method ({
454 description
=> "Restricted to users on realm 'pam'",
455 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
457 description
=> "Creates a VNC Shell proxy.",
459 additionalProperties
=> 0,
461 node
=> get_standard_option
('pve-node'),
465 additionalProperties
=> 0,
467 user
=> { type
=> 'string' },
468 ticket
=> { type
=> 'string' },
469 cert
=> { type
=> 'string' },
470 port
=> { type
=> 'integer' },
471 upid
=> { type
=> 'string' },
477 my $rpcenv = PVE
::RPCEnvironment
::get
();
479 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
481 raise_perm_exc
("realm != pam") if $realm ne 'pam';
483 my $node = $param->{node
};
485 my $authpath = "/nodes/$node";
487 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
489 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
492 my $port = PVE
::Tools
::next_vnc_port
();
496 if ($node ne PVE
::INotify
::nodename
()) {
497 $remip = PVE
::Cluster
::remote_node_ip
($node);
500 # NOTE: vncterm VNC traffic is already TLS encrypted,
501 # so we select the fastest chipher here (or 'none'?)
502 my $remcmd = $remip ?
503 ['/usr/bin/ssh', '-c', 'blowfish-cbc', '-t', $remip] : [];
505 my $shcmd = $user eq 'root@pam' ?
[ "/bin/bash", "-l" ] : [ "/bin/login" ];
509 my @cmd = ('/usr/bin/vncterm', '-rfbport', $port,
510 '-timeout', $timeout, '-authpath', $authpath,
511 '-perm', 'Sys.Console', '-c', @$remcmd, @$shcmd);
516 syslog
('info', "starting vnc proxy $upid\n");
518 my $cmdstr = join (' ', @cmd);
519 syslog
('info', "launch command: $cmdstr");
521 if (system(@cmd) != 0) {
522 my $msg = "vncterm failed - $?";
523 syslog
('err', $msg);
530 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
541 __PACKAGE__-
>register_method({
546 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
548 description
=> "Read DNS settings.",
551 additionalProperties
=> 0,
553 node
=> get_standard_option
('pve-node'),
558 additionalProperties
=> 0,
561 description
=> "Search domain for host-name lookup.",
566 description
=> 'First name server IP address.',
571 description
=> 'Second name server IP address.',
576 description
=> 'Third name server IP address.',
585 my $res = PVE
::INotify
::read_file
('resolvconf');
590 __PACKAGE__-
>register_method({
591 name
=> 'update_dns',
594 description
=> "Write DNS settings.",
596 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
601 additionalProperties
=> 0,
603 node
=> get_standard_option
('pve-node'),
605 description
=> "Search domain for host-name lookup.",
609 description
=> 'First name server IP address.',
610 type
=> 'string', format
=> 'ipv4',
614 description
=> 'Second name server IP address.',
615 type
=> 'string', format
=> 'ipv4',
619 description
=> 'Third name server IP address.',
620 type
=> 'string', format
=> 'ipv4',
625 returns
=> { type
=> "null" },
629 PVE
::INotify
::update_file
('resolvconf', $param);
634 __PACKAGE__-
>register_method({
639 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
641 description
=> "Read server time and time zone settings.",
644 additionalProperties
=> 0,
646 node
=> get_standard_option
('pve-node'),
651 additionalProperties
=> 0,
654 description
=> "Time zone",
658 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
660 minimum
=> 1297163644,
663 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
665 minimum
=> 1297163644,
673 my $ltime = timegm_nocheck
(localtime($ctime));
675 timezone
=> PVE
::INotify
::read_file
('timezone'),
683 __PACKAGE__-
>register_method({
684 name
=> 'set_timezone',
687 description
=> "Set time zone.",
689 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
694 additionalProperties
=> 0,
696 node
=> get_standard_option
('pve-node'),
698 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
703 returns
=> { type
=> "null" },
707 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
712 __PACKAGE__-
>register_method({
719 description
=> "Get list of appliances.",
722 additionalProperties
=> 0,
724 node
=> get_standard_option
('pve-node'),
739 my $list = PVE
::APLInfo
::load_data
();
741 foreach my $template (keys %{$list->{all
}}) {
742 my $pd = $list->{all
}->{$template};
743 next if $pd->{'package'} eq 'pve-web-news';
750 __PACKAGE__-
>register_method({
751 name
=> 'apl_download',
755 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
757 description
=> "Download appliance templates.",
761 additionalProperties
=> 0,
763 node
=> get_standard_option
('pve-node'),
764 storage
=> get_standard_option
('pve-storage-id'),
765 template
=> { type
=> 'string', maxLength
=> 255 },
768 returns
=> { type
=> "string" },
772 my $rpcenv = PVE
::RPCEnvironment
::get
();
774 my $user = $rpcenv->get_user();
776 my $node = $param->{node
};
778 my $list = PVE
::APLInfo
::load_data
();
780 my $template = $param->{template
};
781 my $pd = $list->{all
}->{$template};
783 raise_param_exc
({ template
=> "no such template"}) if !$pd;
785 my $cfg = cfs_read_file
("storage.cfg");
786 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
788 die "cannot download to storage type '$scfg->{type}'"
789 if !($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs');
791 die "unknown template type '$pd->{type}'\n" if $pd->{type
} ne 'openvz';
793 die "storage '$param->{storage}' does not support templates\n"
794 if !$scfg->{content
}->{vztmpl
};
796 my $src = $pd->{location
};
797 my $tmpldir = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
798 my $dest = "$tmpldir/$template";
799 my $tmpdest = "$tmpldir/${template}.tmp.$$";
804 print "starting template download from: $src\n";
805 print "target file: $dest\n";
810 my $md5 = (split (/\s/, `md5sum '$dest'`))[0];
812 if ($md5 && (lc($md5) eq lc($pd->{md5sum
}))) {
813 print "file already exists $md5 - no need to download\n";
819 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
820 if ($dccfg->{http_proxy
}) {
821 $ENV{http_proxy
} = $dccfg->{http_proxy
};
824 my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
825 if (system (@cmd) != 0) {
826 die "download failed - $!\n";
829 my $md5 = (split (/\s/, `md5sum '$tmpdest'`))[0];
831 if (!$md5 || (lc($md5) ne lc($pd->{md5sum
}))) {
832 die "wrong checksum: $md5 != $pd->{md5sum}\n";
835 if (system ('mv', $tmpdest, $dest) != 0) {
836 die "unable to save file - $!\n";
848 print "download finished\n";
851 return $rpcenv->fork_worker('download', undef, $user, $worker);
854 my $get_start_stop_list = sub {
855 my ($nodename, $autostart) = @_;
857 my $cc = PVE
::Cluster
::cfs_read_file
('cluster.conf');
858 my $vmlist = PVE
::Cluster
::get_vmlist
();
861 foreach my $vmid (keys %{$vmlist->{ids
}}) {
862 my $d = $vmlist->{ids
}->{$vmid};
866 return if $d->{node
} ne $nodename;
868 my $bootorder = LONG_MAX
;
870 if ($d->{type
} eq 'openvz') {
871 my $conf = PVE
::OpenVZ
::load_config
($vmid);
872 return if $autostart && !($conf->{onboot
} && $conf->{onboot
}->{value
});
874 if ($conf->{bootorder
} && defined($conf->{bootorder
}->{value
})) {
875 $bootorder = $conf->{bootorder
}->{value
};
877 $startup = { order
=> $bootorder };
879 } elsif ($d->{type
} eq 'qemu') {
880 my $conf = PVE
::QemuServer
::load_config
($vmid);
881 return if $autostart && !$conf->{onboot
};
883 if ($conf->{startup
}) {
884 $startup = PVE
::QemuServer
::parse_startup
($conf->{startup
});
885 $startup->{order
} = $bootorder if !defined($startup->{order
});
887 $startup = { order
=> $bootorder };
890 die "unknown VM type '$d->{type}'\n";
893 # skip ha managed VMs (started by rgmanager)
894 return if PVE
::Cluster
::cluster_conf_lookup_pvevm
($cc, 0, $vmid, 1);
896 $resList->{$startup->{order
}}->{$vmid} = $startup;
897 $resList->{$startup->{order
}}->{$vmid}->{type
} = $d->{type
};
905 __PACKAGE__-
>register_method ({
910 description
=> "Start all VMs and containers (when onboot=1).",
912 additionalProperties
=> 0,
914 node
=> get_standard_option
('pve-node'),
923 my $rpcenv = PVE
::RPCEnvironment
::get
();
924 my $authuser = $rpcenv->get_user();
926 my $nodename = $param->{node
};
927 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
931 $rpcenv->{type
} = 'priv'; # to start tasks in background
933 # wait up to 10 seconds for quorum
934 for (my $i = 10; $i >= 0; $i--) {
935 last if PVE
::Cluster
::check_cfs_quorum
($i != 0 ?
1 : 0);
939 my $startList = &$get_start_stop_list($nodename, 1);
941 # Note: use numeric sorting with <=>
942 foreach my $order (sort {$a <=> $b} keys %$startList) {
943 my $vmlist = $startList->{$order};
945 foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
946 my $d = $vmlist->{$vmid};
948 PVE
::Cluster
::check_cfs_quorum
(); # abort when we loose quorum
951 my $default_delay = 0;
954 if ($d->{type
} eq 'openvz') {
955 return if PVE
::OpenVZ
::check_running
($vmid);
956 print STDERR
"Starting CT $vmid\n";
957 $upid = PVE
::API2
::OpenVZ-
>vm_start({node
=> $nodename, vmid
=> $vmid });
958 } elsif ($d->{type
} eq 'qemu') {
959 $default_delay = 3; # to redruce load
960 return if PVE
::QemuServer
::check_running
($vmid, 1);
961 print STDERR
"Starting VM $vmid\n";
962 $upid = PVE
::API2
::Qemu-
>vm_start({node
=> $nodename, vmid
=> $vmid });
964 die "unknown VM type '$d->{type}'\n";
967 my $res = PVE
::Tools
::upid_decode
($upid);
968 while (PVE
::ProcFSTools
::check_process_running
($res->{pid
})) {
972 my $status = PVE
::Tools
::upid_read_status
($upid);
973 if ($status eq 'OK') {
974 # use default delay to reduce load
975 my $delay = defined($d->{up
}) ?
int($d->{up
}) : $default_delay;
977 print STDERR
"Waiting for $delay seconds (startup delay)\n" if $d->{up
};
978 for (my $i = 0; $i < $delay; $i++) {
983 if ($d->{type
} eq 'openvz') {
984 print STDERR
"Starting CT $vmid failed: $status\n";
985 } elsif ($d->{type
} eq 'qemu') {
986 print STDERR
"Starting VM $vmid failed: status\n";
996 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
999 my $create_stop_worker = sub {
1000 my ($nodename, $type, $vmid, $down_timeout) = @_;
1003 if ($type eq 'openvz') {
1004 return if !PVE
::OpenVZ
::check_running
($vmid);
1005 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60;
1006 print STDERR
"Stopping CT $vmid (timeout = $timeout seconds)\n";
1007 $upid = PVE
::API2
::OpenVZ-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1008 timeout
=> $timeout, forceStop
=> 1 });
1009 } elsif ($type eq 'qemu') {
1010 return if !PVE
::QemuServer
::check_running
($vmid, 1);
1011 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60*3;
1012 print STDERR
"Stopping VM $vmid (timeout = $timeout seconds)\n";
1013 $upid = PVE
::API2
::Qemu-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1014 timeout
=> $timeout, forceStop
=> 1 });
1016 die "unknown VM type '$type'\n";
1019 my $res = PVE
::Tools
::upid_decode
($upid);
1024 __PACKAGE__-
>register_method ({
1029 description
=> "Stop all VMs and Containers.",
1031 additionalProperties
=> 0,
1033 node
=> get_standard_option
('pve-node'),
1042 my $rpcenv = PVE
::RPCEnvironment
::get
();
1043 my $authuser = $rpcenv->get_user();
1045 my $nodename = $param->{node
};
1046 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1050 $rpcenv->{type
} = 'priv'; # to start tasks in background
1052 my $stopList = &$get_start_stop_list($nodename);
1054 my $cpuinfo = PVE
::ProcFSTools
::read_cpuinfo
();
1055 my $maxWorkers = $cpuinfo->{cpus
};
1057 foreach my $order (sort {$b <=> $a} keys %$stopList) {
1058 my $vmlist = $stopList->{$order};
1060 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1061 my $d = $vmlist->{$vmid};
1063 eval { $pid = &$create_stop_worker($nodename, $d->{type
}, $vmid, $d->{down
}); };
1067 $workers->{$pid} = 1;
1068 while (scalar(keys %$workers) >= $maxWorkers) {
1069 foreach my $p (keys %$workers) {
1070 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1071 delete $workers->{$p};
1077 while (scalar(keys %$workers)) {
1078 foreach my $p (keys %$workers) {
1079 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1080 delete $workers->{$p};
1089 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
1093 package PVE
::API2
::Nodes
;
1098 use PVE
::SafeSyslog
;
1100 use PVE
::RESTHandler
;
1101 use PVE
::RPCEnvironment
;
1104 use base
qw(PVE::RESTHandler);
1106 __PACKAGE__-
>register_method ({
1107 subclass
=> "PVE::API2::Nodes::Nodeinfo",
1111 __PACKAGE__-
>register_method ({
1115 permissions
=> { user
=> 'all' },
1116 description
=> "Cluster node index.",
1118 additionalProperties
=> 0,
1127 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
1132 my $clinfo = PVE
::Cluster
::get_clinfo
();
1135 my $nodelist = PVE
::Cluster
::get_nodelist
();
1136 my $members = PVE
::Cluster
::get_members
();
1137 my $rrd = PVE
::Cluster
::rrd_dump
();
1139 foreach my $node (@$nodelist) {
1140 my $entry = PVE
::API2Tools
::extract_node_stats
($node, $members, $rrd);