1 package PVE
::API2
::Nodes
::Nodeinfo
;
5 use POSIX
qw(LONG_MAX);
7 use Time
::Local
qw(timegm_nocheck);
8 use HTTP
::Status
qw(:constants);
14 use PVE
::Cluster
qw(cfs_read_file);
16 use PVE
::Exception
qw(raise raise_perm_exc raise_param_exc);
18 use PVE
::RPCEnvironment
;
19 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::AccessControl
;
26 use PVE
::HA
::Env
::PVE2
;
30 use PVE
::API2
::Subscription
;
31 use PVE
::API2
::Services
;
32 use PVE
::API2
::Network
;
35 use PVE
::API2
::Storage
::Status
;
38 use PVE
::API2
::LXC
::Status
;
39 use PVE
::API2
::VZDump
;
42 use PVE
::API2
::Firewall
::Host
;
43 use PVE
::API2
::Replication
;
44 use PVE
::API2
::Certificates
;
45 use PVE
::API2
::NodeConfig
;
46 use PVE
::API2
::Hardware
;
53 use base
qw(PVE::RESTHandler);
55 __PACKAGE__-
>register_method ({
56 subclass
=> "PVE::API2::Qemu",
60 __PACKAGE__-
>register_method ({
61 subclass
=> "PVE::API2::LXC",
65 __PACKAGE__-
>register_method ({
66 subclass
=> "PVE::API2::Ceph",
70 __PACKAGE__-
>register_method ({
71 subclass
=> "PVE::API2::VZDump",
75 __PACKAGE__-
>register_method ({
76 subclass
=> "PVE::API2::Services",
80 __PACKAGE__-
>register_method ({
81 subclass
=> "PVE::API2::Subscription",
82 path
=> 'subscription',
85 __PACKAGE__-
>register_method ({
86 subclass
=> "PVE::API2::Network",
90 __PACKAGE__-
>register_method ({
91 subclass
=> "PVE::API2::Tasks",
95 __PACKAGE__-
>register_method ({
96 subclass
=> "PVE::API2::Scan",
100 __PACKAGE__-
>register_method ({
101 subclass
=> "PVE::API2::Hardware",
106 __PACKAGE__-
>register_method ({
107 subclass
=> "PVE::API2::Storage::Status",
111 __PACKAGE__-
>register_method ({
112 subclass
=> "PVE::API2::Disks",
116 __PACKAGE__-
>register_method ({
117 subclass
=> "PVE::API2::APT",
121 __PACKAGE__-
>register_method ({
122 subclass
=> "PVE::API2::Firewall::Host",
126 __PACKAGE__-
>register_method ({
127 subclass
=> "PVE::API2::Replication",
128 path
=> 'replication',
131 __PACKAGE__-
>register_method ({
132 subclass
=> "PVE::API2::Certificates",
133 path
=> 'certificates',
137 __PACKAGE__-
>register_method ({
138 subclass
=> "PVE::API2::NodeConfig",
142 __PACKAGE__-
>register_method ({
146 permissions
=> { user
=> 'all' },
147 description
=> "Node index.",
149 additionalProperties
=> 0,
151 node
=> get_standard_option
('pve-node'),
160 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
169 { name
=> 'version' },
170 { name
=> 'syslog' },
171 { name
=> 'journal' },
172 { name
=> 'status' },
173 { name
=> 'wakeonlan' },
174 { name
=> 'subscription' },
175 { name
=> 'report' },
177 { name
=> 'rrd' }, # fixme: remove?
178 { name
=> 'rrddata' },# fixme: remove?
179 { name
=> 'replication' },
180 { name
=> 'vncshell' },
181 { name
=> 'termproxy' },
182 { name
=> 'spiceshell' },
185 { name
=> 'services' },
187 { name
=> 'storage' },
190 { name
=> 'vzdump' },
191 { name
=> 'network' },
192 { name
=> 'aplinfo' },
193 { name
=> 'startall' },
194 { name
=> 'stopall' },
195 { name
=> 'netstat' },
196 { name
=> 'firewall' },
197 { name
=> 'certificates' },
198 { name
=> 'config' },
205 __PACKAGE__-
>register_method ({
210 permissions
=> { user
=> 'all' },
211 description
=> "API version details",
213 additionalProperties
=> 0,
215 node
=> get_standard_option
('pve-node'),
223 description
=> 'The current installed pve-manager package version',
227 description
=> 'The current installed Proxmox VE Release',
231 description
=> 'The short git commit hash ID from which this version was build',
236 my ($resp, $param) = @_;
238 return PVE
::pvecfg
::version_info
();
241 __PACKAGE__-
>register_method({
246 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
248 description
=> "Read node status",
251 additionalProperties
=> 0,
253 node
=> get_standard_option
('pve-node'),
270 my ($uptime, $idle) = PVE
::ProcFSTools
::read_proc_uptime
();
271 $res->{uptime
} = $uptime;
273 my ($avg1, $avg5, $avg15) = PVE
::ProcFSTools
::read_loadavg
();
274 $res->{loadavg
} = [ $avg1, $avg5, $avg15];
276 my ($sysname, $nodename, $release, $version, $machine) = POSIX
::uname
();
278 $res->{kversion
} = "$sysname $release $version";
280 $res->{cpuinfo
} = PVE
::ProcFSTools
::read_cpuinfo
();
282 my $stat = PVE
::ProcFSTools
::read_proc_stat
();
283 $res->{cpu
} = $stat->{cpu
};
284 $res->{wait} = $stat->{wait};
286 my $meminfo = PVE
::ProcFSTools
::read_meminfo
();
288 free
=> $meminfo->{memfree
},
289 total
=> $meminfo->{memtotal
},
290 used
=> $meminfo->{memused
},
294 shared
=> $meminfo->{memshared
},
298 free
=> $meminfo->{swapfree
},
299 total
=> $meminfo->{swaptotal
},
300 used
=> $meminfo->{swapused
},
303 $res->{pveversion
} = PVE
::pvecfg
::package() . "/" .
304 PVE
::pvecfg
::version_text
();
306 my $dinfo = df
('/', 1); # output is bytes
309 total
=> $dinfo->{blocks
},
310 avail
=> $dinfo->{bavail
},
311 used
=> $dinfo->{used
},
312 free
=> $dinfo->{blocks
} - $dinfo->{used
},
318 __PACKAGE__-
>register_method({
323 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
325 description
=> "Read tap/vm network device interface counters",
328 additionalProperties
=> 0,
330 node
=> get_standard_option
('pve-node'),
345 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
346 foreach my $dev (keys %$netdev) {
347 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
356 in => $netdev->{$dev}->{transmit
},
357 out
=> $netdev->{$dev}->{receive
},
365 __PACKAGE__-
>register_method({
370 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
372 description
=> "Execute multiple commands in order.",
374 protected
=> 1, # avoid problems with proxy code
376 additionalProperties
=> 0,
378 node
=> get_standard_option
('pve-node'),
380 description
=> "JSON encoded array of commands.",
395 my $rpcenv = PVE
::RPCEnvironment
::get
();
396 my $user = $rpcenv->get_user();
398 my $commands = eval { decode_json
($param->{commands
}); };
400 die "commands param did not contain valid JSON: $@" if $@;
401 die "commands is not an array" if ref($commands) ne "ARRAY";
403 foreach my $cmd (@$commands) {
405 die "$cmd is not a valid command" if (ref($cmd) ne "HASH" || !$cmd->{path
} || !$cmd->{method});
409 my $path = "nodes/$param->{node}/$cmd->{path}";
412 my ($handler, $info) = PVE
::API2-
>find_handler($cmd->{method}, $path, $uri_param);
413 if (!$handler || !$info) {
414 die "no handler for '$path'\n";
417 foreach my $p (keys %{$cmd->{args
}}) {
418 raise_param_exc
({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
419 $uri_param->{$p} = $cmd->{args
}->{$p};
422 # check access permissions
423 $rpcenv->check_api2_permissions($info->{permissions
}, $user, $uri_param);
427 data
=> $handler->handle($info, $uri_param),
431 my $resp = { status
=> HTTP_INTERNAL_SERVER_ERROR
};
432 if (ref($err) eq "PVE::Exception") {
433 $resp->{status
} = $err->{code
} if $err->{code
};
434 $resp->{errors
} = $err->{errors
} if $err->{errors
};
435 $resp->{message
} = $err->{msg
};
437 $resp->{message
} = $err;
447 __PACKAGE__-
>register_method({
452 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
455 description
=> "Reboot or shutdown a node.",
458 additionalProperties
=> 0,
460 node
=> get_standard_option
('pve-node'),
462 description
=> "Specify the command.",
464 enum
=> [qw(reboot shutdown)],
468 returns
=> { type
=> "null" },
472 if ($param->{command
} eq 'reboot') {
473 system ("(sleep 2;/sbin/reboot)&");
474 } elsif ($param->{command
} eq 'shutdown') {
475 system ("(sleep 2;/sbin/poweroff)&");
481 __PACKAGE__-
>register_method({
486 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
489 description
=> "Try to wake a node via 'wake on LAN' network packet.",
491 additionalProperties
=> 0,
493 node
=> get_standard_option
('pve-node', {
494 description
=> 'target node for wake on LAN packet',
496 my $members = PVE
::Cluster
::get_members
();
497 return [ grep { !$members->{$_}->{online
} } keys %$members ];
504 format
=> 'mac-addr',
505 description
=> 'MAC address used to assemble the WoL magic packet.',
510 my $node = $param->{node
};
512 die "'$node' is local node, cannot wake my self!\n"
513 if $node eq 'localhost' || $node eq PVE
::INotify
::nodename
();
515 PVE
::Cluster
::check_node_exists
($node);
517 my $config = PVE
::NodeConfig
::load_config
($node);
518 my $mac_addr = $config->{wakeonlan
};
519 if (!defined($mac_addr)) {
520 die "No wake on LAN MAC address defined for '$node'!\n";
524 my $packet = chr(0xff) x
6 . pack('H*', $mac_addr) x
16;
526 my $addr = gethostbyname('255.255.255.255');
527 my $port = getservbyname('discard', 'udp');
528 my $to = Socket
::pack_sockaddr_in
($port, $addr);
530 socket(my $sock, Socket
::AF_INET
, Socket
::SOCK_DGRAM
, Socket
::IPPROTO_UDP
)
531 || die "Unable to open socket: $!\n";
532 setsockopt($sock, Socket
::SOL_SOCKET
, Socket
::SO_BROADCAST
, 1)
533 || die "Unable to set socket option: $!\n";
535 send($sock, $packet, 0, $to)
536 || die "Unable to send packet: $!\n";
540 return $config->{wakeonlan
};
543 __PACKAGE__-
>register_method({
547 protected
=> 1, # fixme: can we avoid that?
549 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
551 description
=> "Read node RRD statistics (returns PNG)",
553 additionalProperties
=> 0,
555 node
=> get_standard_option
('pve-node'),
557 description
=> "Specify the time frame you are interested in.",
559 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
562 description
=> "The list of datasources you want to display.",
563 type
=> 'string', format
=> 'pve-configid-list',
566 description
=> "The RRD consolidation function",
568 enum
=> [ 'AVERAGE', 'MAX' ],
576 filename
=> { type
=> 'string' },
582 return PVE
::Cluster
::create_rrd_graph
(
583 "pve2-node/$param->{node}", $param->{timeframe
},
584 $param->{ds
}, $param->{cf
});
588 __PACKAGE__-
>register_method({
592 protected
=> 1, # fixme: can we avoid that?
594 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
596 description
=> "Read node RRD statistics",
598 additionalProperties
=> 0,
600 node
=> get_standard_option
('pve-node'),
602 description
=> "Specify the time frame you are interested in.",
604 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
607 description
=> "The RRD consolidation function",
609 enum
=> [ 'AVERAGE', 'MAX' ],
624 return PVE
::Cluster
::create_rrd_data
(
625 "pve2-node/$param->{node}", $param->{timeframe
}, $param->{cf
});
628 __PACKAGE__-
>register_method({
632 description
=> "Read system log",
635 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
639 additionalProperties
=> 0,
641 node
=> get_standard_option
('pve-node'),
654 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
655 description
=> "Display all log since this date-time string.",
660 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
661 description
=> "Display all log until this date-time string.",
665 description
=> "Service ID",
678 description
=> "Line number",
682 description
=> "Line text",
691 my $rpcenv = PVE
::RPCEnvironment
::get
();
692 my $user = $rpcenv->get_user();
693 my $node = $param->{node
};
696 if ($param->{service
}) {
697 my $service_aliases = {
698 'postfix' => 'postfix@-',
701 $service = $service_aliases->{$param->{service
}} // $param->{service
};
704 my ($count, $lines) = PVE
::Tools
::dump_journal
($param->{start
}, $param->{limit
},
705 $param->{since
}, $param->{until}, $service);
707 $rpcenv->set_result_attrib('total', $count);
712 __PACKAGE__-
>register_method({
716 description
=> "Read Journal",
719 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
723 additionalProperties
=> 0,
725 node
=> get_standard_option
('pve-node'),
729 description
=> "Display all log since this UNIX epoch. Conflicts with 'startcursor'.",
735 description
=> "Display all log until this UNIX epoch. Conflicts with 'endcursor'.",
739 description
=> "Limit to the last X lines. Conflicts with a range.",
745 description
=> "Start after the given Cursor. Conflicts with 'since'",
750 description
=> "End before the given Cursor. Conflicts with 'until'",
765 my $rpcenv = PVE
::RPCEnvironment
::get
();
766 my $user = $rpcenv->get_user();
768 my $cmd = ["/usr/bin/mini-journalreader"];
769 push @$cmd, '-n', $param->{lastentries
} if $param->{lastentries
};
770 push @$cmd, '-b', $param->{since
} if $param->{since
};
771 push @$cmd, '-e', $param->{until} if $param->{until};
772 push @$cmd, '-f', $param->{startcursor
} if $param->{startcursor
};
773 push @$cmd, '-t', $param->{endcursor
} if $param->{endcursor
};
776 my $parser = sub { push @$lines, shift };
778 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
785 my $shell_cmd_map = {
786 'login' => [ '/bin/login', '-f', 'root' ],
787 'upgrade' => [ '/usr/bin/pveupgrade', '--shell' ],
788 'ceph_install' => [ '/usr/bin/pveceph', 'install' ],
791 sub get_shell_command
{
792 my ($user, $shellcmd) = @_;
794 if ($user eq 'root@pam') {
795 if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) {
796 return $shell_cmd_map->{$shellcmd};
798 return [ '/bin/login', '-f', 'root' ];
801 return [ '/bin/login' ];
805 __PACKAGE__-
>register_method ({
811 description
=> "Restricted to users on realm 'pam'",
812 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
814 description
=> "Creates a VNC Shell proxy.",
816 additionalProperties
=> 0,
818 node
=> get_standard_option
('pve-node'),
821 description
=> "Deprecated, use the 'cmd' property instead! Run 'apt-get dist-upgrade' instead of normal shell.",
827 description
=> "Run specific command or default to login.",
828 enum
=> [keys %$shell_cmd_map],
835 description
=> "use websocket instead of standard vnc.",
839 description
=> "sets the width of the console in pixels.",
846 description
=> "sets the height of the console in pixels.",
854 additionalProperties
=> 0,
856 user
=> { type
=> 'string' },
857 ticket
=> { type
=> 'string' },
858 cert
=> { type
=> 'string' },
859 port
=> { type
=> 'integer' },
860 upid
=> { type
=> 'string' },
866 my $rpcenv = PVE
::RPCEnvironment
::get
();
868 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
870 raise_perm_exc
("realm != pam") if $realm ne 'pam';
872 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
874 my $node = $param->{node
};
876 my $authpath = "/nodes/$node";
878 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
880 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
883 my ($remip, $family);
885 if ($node ne PVE
::INotify
::nodename
()) {
886 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
888 $family = PVE
::Tools
::get_host_address_family
($node);
891 my $port = PVE
::Tools
::next_vnc_port
($family);
893 # NOTE: vncterm VNC traffic is already TLS encrypted,
894 # so we select the fastest chipher here (or 'none'?)
895 my $remcmd = $remip ?
896 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
898 # FIXME: remove with 6.0
899 if ($param->{upgrade
}) {
900 $param->{cmd
} = 'upgrade';
902 my $shcmd = get_shell_command
($user, $param->{cmd
});
906 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
907 '-timeout', $timeout, '-authpath', $authpath,
908 '-perm', 'Sys.Console'];
910 if ($param->{width
}) {
911 push @$cmd, '-width', $param->{width
};
914 if ($param->{height
}) {
915 push @$cmd, '-height', $param->{height
};
918 if ($param->{websocket
}) {
919 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
920 push @$cmd, '-notls', '-listen', 'localhost';
923 push @$cmd, '-c', @$remcmd, @$shcmd;
928 syslog
('info', "starting vnc proxy $upid\n");
930 my $cmdstr = join (' ', @$cmd);
931 syslog
('info', "launch command: $cmdstr");
934 foreach my $k (keys %ENV) {
935 next if $k eq 'PVE_VNC_TICKET';
936 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
941 PVE
::Tools
::run_command
($cmd, errmsg
=> "vncterm failed", keeplocale
=> 1);
944 syslog
('err', $err);
950 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
952 PVE
::Tools
::wait_for_vnc_port
($port);
963 __PACKAGE__-
>register_method ({
969 description
=> "Restricted to users on realm 'pam'",
970 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
972 description
=> "Creates a VNC Shell proxy.",
974 additionalProperties
=> 0,
976 node
=> get_standard_option
('pve-node'),
979 description
=> "Deprecated, use the 'cmd' property instead! Run 'apt-get dist-upgrade' instead of normal shell.",
985 description
=> "Run specific command or default to login.",
986 enum
=> [keys %$shell_cmd_map],
993 additionalProperties
=> 0,
995 user
=> { type
=> 'string' },
996 ticket
=> { type
=> 'string' },
997 port
=> { type
=> 'integer' },
998 upid
=> { type
=> 'string' },
1004 my $rpcenv = PVE
::RPCEnvironment
::get
();
1006 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
1008 raise_perm_exc
("realm != pam") if $realm ne 'pam';
1010 my $node = $param->{node
};
1012 my $authpath = "/nodes/$node";
1014 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
1016 my ($remip, $family);
1018 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1019 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1021 $family = PVE
::Tools
::get_host_address_family
($node);
1024 my $port = PVE
::Tools
::next_vnc_port
($family);
1026 my $remcmd = $remip ?
1027 ['/usr/bin/ssh', '-e', 'none', '-t', $remip , '--'] : [];
1028 # FIXME: remove with 6.0
1029 if ($param->{upgrade
}) {
1030 $param->{cmd
} = 'upgrade';
1032 my $shcmd = get_shell_command
($user, $param->{cmd
});
1037 syslog
('info', "starting termproxy $upid\n");
1039 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1040 '--perm', 'Sys.Console', '--'];
1041 push @$cmd, @$remcmd, @$shcmd;
1043 PVE
::Tools
::run_command
($cmd);
1046 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
1048 PVE
::Tools
::wait_for_vnc_port
($port);
1058 __PACKAGE__-
>register_method({
1059 name
=> 'vncwebsocket',
1060 path
=> 'vncwebsocket',
1063 description
=> "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
1064 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1066 description
=> "Opens a weksocket for VNC traffic.",
1068 additionalProperties
=> 0,
1070 node
=> get_standard_option
('pve-node'),
1072 description
=> "Ticket from previous call to vncproxy.",
1077 description
=> "Port number returned by previous vncproxy call.",
1087 port
=> { type
=> 'string' },
1093 my $rpcenv = PVE
::RPCEnvironment
::get
();
1095 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
1097 raise_perm_exc
("realm != pam") if $realm ne 'pam';
1099 my $authpath = "/nodes/$param->{node}";
1101 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $user, $authpath);
1103 my $port = $param->{port
};
1105 return { port
=> $port };
1108 __PACKAGE__-
>register_method ({
1109 name
=> 'spiceshell',
1110 path
=> 'spiceshell',
1115 description
=> "Restricted to users on realm 'pam'",
1116 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1118 description
=> "Creates a SPICE shell.",
1120 additionalProperties
=> 0,
1122 node
=> get_standard_option
('pve-node'),
1123 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1126 description
=> "Deprecated, use the 'cmd' property instead! Run 'apt-get dist-upgrade' instead of normal shell.",
1132 description
=> "Run specific command or default to login.",
1133 enum
=> [keys %$shell_cmd_map],
1139 returns
=> get_standard_option
('remote-viewer-config'),
1143 my $rpcenv = PVE
::RPCEnvironment
::get
();
1144 my $authuser = $rpcenv->get_user();
1146 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($authuser);
1148 raise_perm_exc
("realm != pam") if $realm ne 'pam';
1150 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
1152 my $node = $param->{node
};
1153 my $proxy = $param->{proxy
};
1155 my $authpath = "/nodes/$node";
1156 my $permissions = 'Sys.Console';
1157 # FIXME: remove with 6.0
1158 if ($param->{upgrade
}) {
1159 $param->{cmd
} = 'upgrade';
1161 my $shcmd = get_shell_command
($user, $param->{cmd
});
1163 my $title = "Shell on '$node'";
1165 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
1168 __PACKAGE__-
>register_method({
1173 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1175 description
=> "Read DNS settings.",
1178 additionalProperties
=> 0,
1180 node
=> get_standard_option
('pve-node'),
1185 additionalProperties
=> 0,
1188 description
=> "Search domain for host-name lookup.",
1193 description
=> 'First name server IP address.',
1198 description
=> 'Second name server IP address.',
1203 description
=> 'Third name server IP address.',
1212 my $res = PVE
::INotify
::read_file
('resolvconf');
1217 __PACKAGE__-
>register_method({
1218 name
=> 'update_dns',
1221 description
=> "Write DNS settings.",
1223 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1228 additionalProperties
=> 0,
1230 node
=> get_standard_option
('pve-node'),
1232 description
=> "Search domain for host-name lookup.",
1236 description
=> 'First name server IP address.',
1237 type
=> 'string', format
=> 'ip',
1241 description
=> 'Second name server IP address.',
1242 type
=> 'string', format
=> 'ip',
1246 description
=> 'Third name server IP address.',
1247 type
=> 'string', format
=> 'ip',
1252 returns
=> { type
=> "null" },
1256 PVE
::INotify
::update_file
('resolvconf', $param);
1261 __PACKAGE__-
>register_method({
1266 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1268 description
=> "Read server time and time zone settings.",
1271 additionalProperties
=> 0,
1273 node
=> get_standard_option
('pve-node'),
1278 additionalProperties
=> 0,
1281 description
=> "Time zone",
1285 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
1287 minimum
=> 1297163644,
1288 renderer
=> 'timestamp',
1291 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
1293 minimum
=> 1297163644,
1294 renderer
=> 'timestamp_gmt',
1302 my $ltime = timegm_nocheck
(localtime($ctime));
1304 timezone
=> PVE
::INotify
::read_file
('timezone'),
1306 localtime => $ltime,
1312 __PACKAGE__-
>register_method({
1313 name
=> 'set_timezone',
1316 description
=> "Set time zone.",
1318 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1323 additionalProperties
=> 0,
1325 node
=> get_standard_option
('pve-node'),
1327 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
1332 returns
=> { type
=> "null" },
1336 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
1341 __PACKAGE__-
>register_method({
1348 description
=> "Get list of appliances.",
1351 additionalProperties
=> 0,
1353 node
=> get_standard_option
('pve-node'),
1368 my $list = PVE
::APLInfo
::load_data
();
1370 foreach my $template (keys %{$list->{all
}}) {
1371 my $pd = $list->{all
}->{$template};
1372 next if $pd->{'package'} eq 'pve-web-news';
1379 __PACKAGE__-
>register_method({
1380 name
=> 'apl_download',
1384 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1386 description
=> "Download appliance templates.",
1390 additionalProperties
=> 0,
1392 node
=> get_standard_option
('pve-node'),
1393 storage
=> get_standard_option
('pve-storage-id', {
1394 description
=> "The storage where the template will be stored",
1395 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1397 template
=> { type
=> 'string',
1398 description
=> "The template wich will downloaded",
1400 completion
=> \
&complete_templet_repo
,
1404 returns
=> { type
=> "string" },
1408 my $rpcenv = PVE
::RPCEnvironment
::get
();
1410 my $user = $rpcenv->get_user();
1412 my $node = $param->{node
};
1414 my $list = PVE
::APLInfo
::load_data
();
1416 my $template = $param->{template
};
1417 my $pd = $list->{all
}->{$template};
1419 raise_param_exc
({ template
=> "no such template"}) if !$pd;
1421 my $cfg = PVE
::Storage
::config
();
1422 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
1424 die "unknown template type '$pd->{type}'\n"
1425 if !($pd->{type
} eq 'openvz' || $pd->{type
} eq 'lxc');
1427 die "storage '$param->{storage}' does not support templates\n"
1428 if !$scfg->{content
}->{vztmpl
};
1430 my $src = $pd->{location
};
1431 my $tmpldir = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
1432 my $dest = "$tmpldir/$template";
1433 my $tmpdest = "$tmpldir/${template}.tmp.$$";
1438 print "starting template download from: $src\n";
1439 print "target file: $dest\n";
1441 my $check_hash = sub {
1442 my ($template_info, $filename, $noerr) = @_;
1448 open(my $fh, '<', $filename) or die "Can't open '$filename': $!";
1450 if (defined($template_info->{sha512sum
})) {
1451 $expected = $template_info->{sha512sum
};
1452 $digest = Digest
::SHA-
>new(512)->addfile($fh)->hexdigest;
1453 } elsif (defined($template_info->{md5sum
})) {
1455 $expected = $template_info->{md5sum
};
1456 $digest = Digest
::MD5-
>new->addfile($fh)->hexdigest;
1458 die "no expected checksum defined";
1463 die "checking hash failed - $@\n" if $@ && !$noerr;
1465 return ($digest, $digest ?
lc($digest) eq lc($expected) : 0);
1470 my ($hash, $correct) = &$check_hash($pd, $dest, 1);
1472 if ($hash && $correct) {
1473 print "file already exists $hash - no need to download\n";
1479 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1480 if ($dccfg->{http_proxy
}) {
1481 $ENV{http_proxy
} = $dccfg->{http_proxy
};
1484 my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
1485 if (system (@cmd) != 0) {
1486 die "download failed - $!\n";
1489 my ($hash, $correct) = &$check_hash($pd, $tmpdest);
1491 die "could not calculate checksum\n" if !$hash;
1494 my $expected = $pd->{sha512sum
} // $pd->{md5sum
};
1495 die "wrong checksum: $hash != $expected\n";
1498 if (!rename($tmpdest, $dest)) {
1499 die "unable to save file - $!\n";
1511 print "download finished\n";
1514 return $rpcenv->fork_worker('download', undef, $user, $worker);
1517 __PACKAGE__-
>register_method({
1522 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1525 description
=> "Gather various systems information about a node",
1528 additionalProperties
=> 0,
1530 node
=> get_standard_option
('pve-node'),
1537 return PVE
::Report
::generate
();
1540 # returns a list of VMIDs, those can be filtered by
1541 # * current parent node
1543 # * guest is a template (default: skip)
1544 # * guest is HA manged (default: skip)
1545 my $get_filtered_vmlist = sub {
1546 my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
1548 my $vmlist = PVE
::Cluster
::get_vmlist
();
1550 my $vms_allowed = {};
1551 if (defined($vmfilter)) {
1552 foreach my $vmid (PVE
::Tools
::split_list
($vmfilter)) {
1553 $vms_allowed->{$vmid} = 1;
1558 foreach my $vmid (keys %{$vmlist->{ids
}}) {
1559 next if %$vms_allowed && !$vms_allowed->{$vmid};
1561 my $d = $vmlist->{ids
}->{$vmid};
1562 next if $nodename && $d->{node
} ne $nodename;
1566 if ($d->{type
} eq 'lxc') {
1567 $class = 'PVE::LXC::Config';
1568 } elsif ($d->{type
} eq 'qemu') {
1569 $class = 'PVE::QemuConfig';
1571 die "unknown VM type '$d->{type}'\n";
1574 my $conf = $class->load_config($vmid);
1575 return if !$templates && $class->is_template($conf);
1576 return if !$ha_managed && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1578 $res->{$vmid}->{conf
} = $conf;
1579 $res->{$vmid}->{type
} = $d->{type
};
1580 $res->{$vmid}->{class} = $class;
1588 # return all VMs which should get started/stopped on power up/down
1589 my $get_start_stop_list = sub {
1590 my ($nodename, $autostart, $vmfilter) = @_;
1592 # do not skip HA vms on force or if a specific VMID set is wanted
1593 my $include_ha_managed = defined($vmfilter) ?
1 : 0;
1595 my $vmlist = &$get_filtered_vmlist($nodename, $vmfilter, undef, $include_ha_managed);
1598 foreach my $vmid (keys %$vmlist) {
1599 my $conf = $vmlist->{$vmid}->{conf
};
1601 next if $autostart && !$conf->{onboot
};
1604 if ($conf->{startup
}) {
1605 $startup = PVE
::JSONSchema
::pve_parse_startup_order
($conf->{startup
});
1608 $startup->{order
} = LONG_MAX
if !defined($startup->{order
});
1610 $resList->{$startup->{order
}}->{$vmid} = $startup;
1611 $resList->{$startup->{order
}}->{$vmid}->{type
} = $vmlist->{$vmid}->{type
};
1617 my $remove_locks_on_startup = sub {
1618 my ($nodename) = @_;
1620 my $vmlist = &$get_filtered_vmlist($nodename, undef, undef, 1);
1622 foreach my $vmid (keys %$vmlist) {
1623 my $conf = $vmlist->{$vmid}->{conf
};
1624 my $class = $vmlist->{$vmid}->{class};
1627 if ($class->has_lock($conf, 'backup')) {
1628 $class->remove_lock($vmid, 'backup');
1629 my $msg = "removed left over backup lock from '$vmid'!";
1630 warn "$msg\n"; # prints to task log
1631 syslog
('warning', $msg);
1637 __PACKAGE__-
>register_method ({
1643 check
=> ['perm', '/', [ 'VM.PowerMgmt' ]],
1646 description
=> "Start all VMs and containers (when onboot=1).",
1648 additionalProperties
=> 0,
1650 node
=> get_standard_option
('pve-node'),
1654 description
=> "force if onboot=0.",
1657 description
=> "Only consider Guests with these IDs.",
1658 type
=> 'string', format
=> 'pve-vmid-list',
1669 my $rpcenv = PVE
::RPCEnvironment
::get
();
1670 my $authuser = $rpcenv->get_user();
1672 my $nodename = $param->{node
};
1673 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1675 my $force = $param->{force
};
1679 $rpcenv->{type
} = 'priv'; # to start tasks in background
1681 if (!PVE
::Cluster
::check_cfs_quorum
(1)) {
1682 print "waiting for quorum ...\n";
1685 } while (!PVE
::Cluster
::check_cfs_quorum
(1));
1686 print "got quorum\n";
1689 eval { # remove backup locks, but avoid running into a scheduled backup job
1690 PVE
::Tools
::lock_file
('/var/run/vzdump.lock', 10, $remove_locks_on_startup, $nodename);
1693 my $autostart = $force ?
undef : 1;
1694 my $startList = &$get_start_stop_list($nodename, $autostart, $param->{vms
});
1696 # Note: use numeric sorting with <=>
1697 foreach my $order (sort {$a <=> $b} keys %$startList) {
1698 my $vmlist = $startList->{$order};
1700 foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
1701 my $d = $vmlist->{$vmid};
1703 PVE
::Cluster
::check_cfs_quorum
(); # abort when we loose quorum
1706 my $default_delay = 0;
1710 if ($d->{type
} eq 'lxc') {
1712 return if PVE
::LXC
::check_running
($vmid);
1713 print STDERR
"Starting CT $vmid\n";
1714 $upid = PVE
::API2
::LXC
::Status-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1715 } elsif ($d->{type
} eq 'qemu') {
1717 $default_delay = 3; # to reduce load
1718 return if PVE
::QemuServer
::check_running
($vmid, 1);
1719 print STDERR
"Starting VM $vmid\n";
1720 $upid = PVE
::API2
::Qemu-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1722 die "unknown VM type '$d->{type}'\n";
1725 my $res = PVE
::Tools
::upid_decode
($upid);
1726 while (PVE
::ProcFSTools
::check_process_running
($res->{pid
})) {
1730 my $status = PVE
::Tools
::upid_read_status
($upid);
1731 if ($status eq 'OK') {
1732 # use default delay to reduce load
1733 my $delay = defined($d->{up
}) ?
int($d->{up
}) : $default_delay;
1735 print STDERR
"Waiting for $delay seconds (startup delay)\n" if $d->{up
};
1736 for (my $i = 0; $i < $delay; $i++) {
1741 print STDERR
"Starting $typeText $vmid failed: $status\n";
1750 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1753 my $create_stop_worker = sub {
1754 my ($nodename, $type, $vmid, $down_timeout) = @_;
1757 if ($type eq 'lxc') {
1758 return if !PVE
::LXC
::check_running
($vmid);
1759 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60;
1760 print STDERR
"Stopping CT $vmid (timeout = $timeout seconds)\n";
1761 $upid = PVE
::API2
::LXC
::Status-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1762 timeout
=> $timeout, forceStop
=> 1 });
1763 } elsif ($type eq 'qemu') {
1764 return if !PVE
::QemuServer
::check_running
($vmid, 1);
1765 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60*3;
1766 print STDERR
"Stopping VM $vmid (timeout = $timeout seconds)\n";
1767 $upid = PVE
::API2
::Qemu-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1768 timeout
=> $timeout, forceStop
=> 1 });
1770 die "unknown VM type '$type'\n";
1776 __PACKAGE__-
>register_method ({
1782 check
=> ['perm', '/', [ 'VM.PowerMgmt' ]],
1785 description
=> "Stop all VMs and Containers.",
1787 additionalProperties
=> 0,
1789 node
=> get_standard_option
('pve-node'),
1791 description
=> "Only consider Guests with these IDs.",
1792 type
=> 'string', format
=> 'pve-vmid-list',
1803 my $rpcenv = PVE
::RPCEnvironment
::get
();
1804 my $authuser = $rpcenv->get_user();
1806 my $nodename = $param->{node
};
1807 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1811 $rpcenv->{type
} = 'priv'; # to start tasks in background
1813 my $stopList = &$get_start_stop_list($nodename, undef, $param->{vms
});
1815 my $cpuinfo = PVE
::ProcFSTools
::read_cpuinfo
();
1816 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
1817 # if not set by user spawn max cpu count number of workers
1818 my $maxWorkers = $datacenterconfig->{max_workers
} || $cpuinfo->{cpus
};
1820 foreach my $order (sort {$b <=> $a} keys %$stopList) {
1821 my $vmlist = $stopList->{$order};
1824 my $finish_worker = sub {
1826 my $d = $workers->{$pid};
1828 delete $workers->{$pid};
1830 syslog
('info', "end task $d->{upid}");
1833 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1834 my $d = $vmlist->{$vmid};
1836 eval { $upid = &$create_stop_worker($nodename, $d->{type
}, $vmid, $d->{down
}); };
1840 my $res = PVE
::Tools
::upid_decode
($upid, 1);
1843 my $pid = $res->{pid
};
1845 $workers->{$pid} = { type
=> $d->{type
}, upid
=> $upid, vmid
=> $vmid };
1846 while (scalar(keys %$workers) >= $maxWorkers) {
1847 foreach my $p (keys %$workers) {
1848 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1849 &$finish_worker($p);
1855 while (scalar(keys %$workers)) {
1856 foreach my $p (keys %$workers) {
1857 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1858 &$finish_worker($p);
1865 syslog
('info', "all VMs and CTs stopped");
1870 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
1873 my $create_migrate_worker = sub {
1874 my ($nodename, $type, $vmid, $target) = @_;
1877 if ($type eq 'lxc') {
1878 my $online = PVE
::LXC
::check_running
($vmid) ?
1 : 0;
1879 print STDERR
"Migrating CT $vmid\n";
1880 $upid = PVE
::API2
::LXC-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1881 restart
=> $online });
1882 } elsif ($type eq 'qemu') {
1883 my $online = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
1884 print STDERR
"Migrating VM $vmid\n";
1885 $upid = PVE
::API2
::Qemu-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1886 online
=> $online });
1888 die "unknown VM type '$type'\n";
1891 my $res = PVE
::Tools
::upid_decode
($upid);
1896 __PACKAGE__-
>register_method ({
1897 name
=> 'migrateall',
1898 path
=> 'migrateall',
1903 check
=> ['perm', '/', [ 'VM.Migrate' ]],
1905 description
=> "Migrate all VMs and Containers.",
1907 additionalProperties
=> 0,
1909 node
=> get_standard_option
('pve-node'),
1910 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1912 description
=> "Maximal number of parallel migration job." .
1913 " If not set use 'max_workers' from datacenter.cfg," .
1914 " one of both must be set!",
1920 description
=> "Only consider Guests with these IDs.",
1921 type
=> 'string', format
=> 'pve-vmid-list',
1932 my $rpcenv = PVE
::RPCEnvironment
::get
();
1933 my $authuser = $rpcenv->get_user();
1935 my $nodename = $param->{node
};
1936 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1938 my $target = $param->{target
};
1939 raise_param_exc
({ target
=> "target is local node."}) if $target eq $nodename;
1941 PVE
::Cluster
::check_cfs_quorum
();
1943 PVE
::Cluster
::check_node_exists
($target);
1945 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
1946 # prefer parameter over datacenter cfg settings
1947 my $maxWorkers = $param->{maxworkers
} || $datacenterconfig->{max_workers
} ||
1948 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
1951 $rpcenv->{type
} = 'priv'; # to start tasks in background
1953 my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms
}, 1, 1);
1956 foreach my $vmid (sort keys %$vmlist) {
1957 my $d = $vmlist->{$vmid};
1959 eval { $pid = &$create_migrate_worker($nodename, $d->{type
}, $vmid, $target); };
1963 $workers->{$pid} = 1;
1964 while (scalar(keys %$workers) >= $maxWorkers) {
1965 foreach my $p (keys %$workers) {
1966 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1967 delete $workers->{$p};
1973 while (scalar(keys %$workers)) {
1974 foreach my $p (keys %$workers) {
1975 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1976 delete $workers->{$p};
1984 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
1988 __PACKAGE__-
>register_method ({
1989 name
=> 'get_etc_hosts',
1995 check
=> ['perm', '/', [ 'Sys.Audit' ]],
1997 description
=> "Get the content of /etc/hosts.",
1999 additionalProperties
=> 0,
2001 node
=> get_standard_option
('pve-node'),
2007 digest
=> get_standard_option
('pve-config-digest'),
2010 description
=> 'The content of /etc/hosts.'
2017 return PVE
::INotify
::read_file
('etchosts');
2021 __PACKAGE__-
>register_method ({
2022 name
=> 'write_etc_hosts',
2028 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
2030 description
=> "Write /etc/hosts.",
2032 additionalProperties
=> 0,
2034 node
=> get_standard_option
('pve-node'),
2035 digest
=> get_standard_option
('pve-config-digest'),
2038 description
=> 'The target content of /etc/hosts.'
2048 PVE
::Tools
::lock_file
('/var/lock/pve-etchosts.lck', undef, sub{
2049 if ($param->{digest
}) {
2050 my $hosts = PVE
::INotify
::read_file
('etchosts');
2051 PVE
::Tools
::assert_if_modified
($hosts->{digest
}, $param->{digest
});
2053 PVE
::INotify
::write_file
('etchosts', $param->{data
});
2060 # bash completion helper
2062 sub complete_templet_repo
{
2063 my ($cmdname, $pname, $cvalue) = @_;
2065 my $repo = PVE
::APLInfo
::load_data
();
2067 foreach my $templ (keys %{$repo->{all
}}) {
2068 next if $templ !~ m/^$cvalue/;
2075 package PVE
::API2
::Nodes
;
2080 use PVE
::SafeSyslog
;
2082 use PVE
::RESTHandler
;
2083 use PVE
::RPCEnvironment
;
2085 use PVE
::JSONSchema
qw(get_standard_option);
2087 use base
qw(PVE::RESTHandler);
2089 __PACKAGE__-
>register_method ({
2090 subclass
=> "PVE::API2::Nodes::Nodeinfo",
2094 __PACKAGE__-
>register_method ({
2098 permissions
=> { user
=> 'all' },
2099 description
=> "Cluster node index.",
2101 additionalProperties
=> 0,
2109 node
=> get_standard_option
('pve-node'),
2111 description
=> "Node status.",
2113 enum
=> ['unknown', 'online', 'offline'],
2116 description
=> "CPU utilization.",
2119 renderer
=> 'fraction_as_percentage',
2122 description
=> "Number of available CPUs.",
2127 description
=> "Used memory in bytes.",
2130 renderer
=> 'bytes',
2133 description
=> "Number of available memory in bytes.",
2136 renderer
=> 'bytes',
2139 description
=> "Support level.",
2144 description
=> "Node uptime in seconds.",
2147 renderer
=> 'duration',
2149 ssl_fingerprint
=> {
2150 description
=> "The SSL fingerprint for the node certificate.",
2156 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
2161 my $rpcenv = PVE
::RPCEnvironment
::get
();
2162 my $authuser = $rpcenv->get_user();
2164 my $clinfo = PVE
::Cluster
::get_clinfo
();
2167 my $nodelist = PVE
::Cluster
::get_nodelist
();
2168 my $members = PVE
::Cluster
::get_members
();
2169 my $rrd = PVE
::Cluster
::rrd_dump
();
2171 foreach my $node (@$nodelist) {
2172 my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1);
2173 my $entry = PVE
::API2Tools
::extract_node_stats
($node, $members, $rrd, !$can_audit);
2174 $entry->{ssl_fingerprint
} = PVE
::Cluster
::get_node_fingerprint
($node);