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
;
34 use PVE
::API2
::Storage
::Scan
;
35 use PVE
::API2
::Storage
::Status
;
38 use PVE
::API2
::LXC
::Status
;
39 use PVE
::API2
::VZDump
;
42 use PVE
::API2
::Firewall
::Host
;
48 use base
qw(PVE::RESTHandler);
50 __PACKAGE__-
>register_method ({
51 subclass
=> "PVE::API2::Qemu",
55 __PACKAGE__-
>register_method ({
56 subclass
=> "PVE::API2::LXC",
60 __PACKAGE__-
>register_method ({
61 subclass
=> "PVE::API2::Ceph",
65 __PACKAGE__-
>register_method ({
66 subclass
=> "PVE::API2::VZDump",
70 __PACKAGE__-
>register_method ({
71 subclass
=> "PVE::API2::Services",
75 __PACKAGE__-
>register_method ({
76 subclass
=> "PVE::API2::Subscription",
77 path
=> 'subscription',
80 __PACKAGE__-
>register_method ({
81 subclass
=> "PVE::API2::Network",
85 __PACKAGE__-
>register_method ({
86 subclass
=> "PVE::API2::Tasks",
90 __PACKAGE__-
>register_method ({
91 subclass
=> "PVE::API2::Storage::Scan",
95 __PACKAGE__-
>register_method ({
96 subclass
=> "PVE::API2::Storage::Status",
100 __PACKAGE__-
>register_method ({
101 subclass
=> "PVE::API2::Disks",
105 __PACKAGE__-
>register_method ({
106 subclass
=> "PVE::API2::APT",
110 __PACKAGE__-
>register_method ({
111 subclass
=> "PVE::API2::Firewall::Host",
115 __PACKAGE__-
>register_method ({
119 permissions
=> { user
=> 'all' },
120 description
=> "Node index.",
122 additionalProperties
=> 0,
124 node
=> get_standard_option
('pve-node'),
133 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
142 { name
=> 'version' },
143 { name
=> 'syslog' },
144 { name
=> 'status' },
145 { name
=> 'subscription' },
146 { name
=> 'report' },
148 { name
=> 'rrd' }, # fixme: remove?
149 { name
=> 'rrddata' },# fixme: remove?
150 { name
=> 'vncshell' },
151 { name
=> 'spiceshell' },
154 { name
=> 'services' },
156 { name
=> 'storage' },
159 { name
=> 'vzdump' },
160 { name
=> 'network' },
161 { name
=> 'aplinfo' },
162 { name
=> 'startall' },
163 { name
=> 'stopall' },
164 { name
=> 'netstat' },
165 { name
=> 'firewall' },
171 __PACKAGE__-
>register_method ({
176 permissions
=> { user
=> 'all' },
177 description
=> "API version details",
179 additionalProperties
=> 0,
181 node
=> get_standard_option
('pve-node'),
187 version
=> { type
=> 'string' },
188 release
=> { type
=> 'string' },
189 repoid
=> { type
=> 'string' },
193 my ($resp, $param) = @_;
195 return PVE
::pvecfg
::version_info
();
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
},
251 shared
=> $meminfo->{memshared
},
255 free
=> $meminfo->{swapfree
},
256 total
=> $meminfo->{swaptotal
},
257 used
=> $meminfo->{swapused
},
260 $res->{pveversion
} = PVE
::pvecfg
::package() . "/" .
261 PVE
::pvecfg
::version_text
();
263 my $dinfo = df
('/', 1); # output is bytes
266 total
=> $dinfo->{blocks
},
267 avail
=> $dinfo->{bavail
},
268 used
=> $dinfo->{used
},
269 free
=> $dinfo->{bavail
} - $dinfo->{used
},
275 __PACKAGE__-
>register_method({
280 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
282 description
=> "Read tap/vm network device interface counters",
285 additionalProperties
=> 0,
287 node
=> get_standard_option
('pve-node'),
302 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
303 foreach my $dev (keys %$netdev) {
304 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
313 in => $netdev->{$dev}->{transmit
},
314 out
=> $netdev->{$dev}->{receive
},
322 __PACKAGE__-
>register_method({
327 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
329 description
=> "Execute multiple commands in order.",
331 protected
=> 1, # avoid problems with proxy code
333 additionalProperties
=> 0,
335 node
=> get_standard_option
('pve-node'),
337 description
=> "JSON encoded array of commands.",
352 my $rpcenv = PVE
::RPCEnvironment
::get
();
353 my $user = $rpcenv->get_user();
355 my $commands = eval { decode_json
($param->{commands
}); };
357 die "commands param did not contain valid JSON: $@" if $@;
358 die "commands is not an array" if ref($commands) ne "ARRAY";
360 foreach my $cmd (@$commands) {
362 die "$cmd is not a valid command" if (ref($cmd) ne "HASH" || !$cmd->{path
} || !$cmd->{method});
366 my $path = "nodes/$param->{node}/$cmd->{path}";
369 my ($handler, $info) = PVE
::API2-
>find_handler($cmd->{method}, $path, $uri_param);
370 if (!$handler || !$info) {
371 die "no handler for '$path'\n";
374 foreach my $p (keys %{$cmd->{args
}}) {
375 raise_param_exc
({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
376 $uri_param->{$p} = $cmd->{args
}->{$p};
379 # check access permissions
380 $rpcenv->check_api2_permissions($info->{permissions
}, $user, $uri_param);
384 data
=> $handler->handle($info, $uri_param),
388 my $resp = { status
=> HTTP_INTERNAL_SERVER_ERROR
};
389 if (ref($err) eq "PVE::Exception") {
390 $resp->{status
} = $err->{code
} if $err->{code
};
391 $resp->{errors
} = $err->{errors
} if $err->{errors
};
392 $resp->{message
} = $err->{msg
};
394 $resp->{message
} = $err;
404 __PACKAGE__-
>register_method({
409 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
412 description
=> "Reboot or shutdown a node.",
415 additionalProperties
=> 0,
417 node
=> get_standard_option
('pve-node'),
419 description
=> "Specify the command.",
421 enum
=> [qw(reboot shutdown)],
425 returns
=> { type
=> "null" },
429 if ($param->{command
} eq 'reboot') {
430 system ("(sleep 2;/sbin/reboot)&");
431 } elsif ($param->{command
} eq 'shutdown') {
432 system ("(sleep 2;/sbin/poweroff)&");
439 __PACKAGE__-
>register_method({
443 protected
=> 1, # fixme: can we avoid that?
445 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
447 description
=> "Read node RRD statistics (returns PNG)",
449 additionalProperties
=> 0,
451 node
=> get_standard_option
('pve-node'),
453 description
=> "Specify the time frame you are interested in.",
455 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
458 description
=> "The list of datasources you want to display.",
459 type
=> 'string', format
=> 'pve-configid-list',
462 description
=> "The RRD consolidation function",
464 enum
=> [ 'AVERAGE', 'MAX' ],
472 filename
=> { type
=> 'string' },
478 return PVE
::Cluster
::create_rrd_graph
(
479 "pve2-node/$param->{node}", $param->{timeframe
},
480 $param->{ds
}, $param->{cf
});
484 __PACKAGE__-
>register_method({
488 protected
=> 1, # fixme: can we avoid that?
490 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
492 description
=> "Read node RRD statistics",
494 additionalProperties
=> 0,
496 node
=> get_standard_option
('pve-node'),
498 description
=> "Specify the time frame you are interested in.",
500 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
503 description
=> "The RRD consolidation function",
505 enum
=> [ 'AVERAGE', 'MAX' ],
520 return PVE
::Cluster
::create_rrd_data
(
521 "pve2-node/$param->{node}", $param->{timeframe
}, $param->{cf
});
524 __PACKAGE__-
>register_method({
528 description
=> "Read system log",
531 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
535 additionalProperties
=> 0,
537 node
=> get_standard_option
('pve-node'),
550 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
551 description
=> "Display all log since this date-time string.",
556 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
557 description
=> "Display all log until this date-time string.",
568 description
=> "Line number",
572 description
=> "Line text",
581 my $rpcenv = PVE
::RPCEnvironment
::get
();
582 my $user = $rpcenv->get_user();
583 my $node = $param->{node
};
585 my ($count, $lines) = PVE
::Tools
::dump_journal
($param->{start
}, $param->{limit
},
586 $param->{since
}, $param->{until});
588 $rpcenv->set_result_attrib('total', $count);
595 __PACKAGE__-
>register_method ({
601 description
=> "Restricted to users on realm 'pam'",
602 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
604 description
=> "Creates a VNC Shell proxy.",
606 additionalProperties
=> 0,
608 node
=> get_standard_option
('pve-node'),
611 description
=> "Run 'apt-get dist-upgrade' instead of normal shell.",
618 description
=> "use websocket instead of standard vnc.",
623 additionalProperties
=> 0,
625 user
=> { type
=> 'string' },
626 ticket
=> { type
=> 'string' },
627 cert
=> { type
=> 'string' },
628 port
=> { type
=> 'integer' },
629 upid
=> { type
=> 'string' },
635 my $rpcenv = PVE
::RPCEnvironment
::get
();
637 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
639 raise_perm_exc
("realm != pam") if $realm ne 'pam';
641 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
643 my $node = $param->{node
};
645 my $authpath = "/nodes/$node";
647 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
649 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
652 my ($remip, $family);
654 if ($node ne PVE
::INotify
::nodename
()) {
655 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
657 $family = PVE
::Tools
::get_host_address_family
($node);
660 my $port = PVE
::Tools
::next_vnc_port
($family);
662 # NOTE: vncterm VNC traffic is already TLS encrypted,
663 # so we select the fastest chipher here (or 'none'?)
664 my $remcmd = $remip ?
665 ['/usr/bin/ssh', '-t', $remip] : [];
669 if ($user eq 'root@pam') {
670 if ($param->{upgrade
}) {
671 my $upgradecmd = "pveupgrade --shell";
672 $upgradecmd = PVE
::Tools
::shellquote
($upgradecmd) if $remip;
673 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
675 $shcmd = [ '/bin/bash', '-l' ];
678 $shcmd = [ '/bin/login' ];
683 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
684 '-timeout', $timeout, '-authpath', $authpath,
685 '-perm', 'Sys.Console'];
687 if ($param->{websocket
}) {
688 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
689 push @$cmd, '-notls', '-listen', 'localhost';
692 push @$cmd, '-c', @$remcmd, @$shcmd;
697 syslog
('info', "starting vnc proxy $upid\n");
699 my $cmdstr = join (' ', @$cmd);
700 syslog
('info', "launch command: $cmdstr");
703 foreach my $k (keys %ENV) {
704 next if $k eq 'PVE_VNC_TICKET';
705 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
710 PVE
::Tools
::run_command
($cmd, errmsg
=> "vncterm failed", keeplocale
=> 1);
713 syslog
('err', $err);
719 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
721 PVE
::Tools
::wait_for_vnc_port
($port);
732 __PACKAGE__-
>register_method({
733 name
=> 'vncwebsocket',
734 path
=> 'vncwebsocket',
737 description
=> "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
738 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
740 description
=> "Opens a weksocket for VNC traffic.",
742 additionalProperties
=> 0,
744 node
=> get_standard_option
('pve-node'),
746 description
=> "Ticket from previous call to vncproxy.",
751 description
=> "Port number returned by previous vncproxy call.",
761 port
=> { type
=> 'string' },
767 my $rpcenv = PVE
::RPCEnvironment
::get
();
769 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
771 raise_perm_exc
("realm != pam") if $realm ne 'pam';
773 my $authpath = "/nodes/$param->{node}";
775 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $user, $authpath);
777 my $port = $param->{port
};
779 return { port
=> $port };
782 __PACKAGE__-
>register_method ({
783 name
=> 'spiceshell',
784 path
=> 'spiceshell',
789 description
=> "Restricted to users on realm 'pam'",
790 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
792 description
=> "Creates a SPICE shell.",
794 additionalProperties
=> 0,
796 node
=> get_standard_option
('pve-node'),
797 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
800 description
=> "Run 'apt-get dist-upgrade' instead of normal shell.",
806 returns
=> get_standard_option
('remote-viewer-config'),
810 my $rpcenv = PVE
::RPCEnvironment
::get
();
811 my $authuser = $rpcenv->get_user();
813 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($authuser);
815 raise_perm_exc
("realm != pam") if $realm ne 'pam';
817 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
819 my $node = $param->{node
};
820 my $proxy = $param->{proxy
};
822 my $authpath = "/nodes/$node";
823 my $permissions = 'Sys.Console';
827 if ($user eq 'root@pam') {
828 if ($param->{upgrade
}) {
829 my $upgradecmd = "pveupgrade --shell";
830 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
832 $shcmd = [ '/bin/bash', '-l' ];
835 $shcmd = [ '/bin/login' ];
838 my $title = "Shell on '$node'";
840 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
843 __PACKAGE__-
>register_method({
848 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
850 description
=> "Read DNS settings.",
853 additionalProperties
=> 0,
855 node
=> get_standard_option
('pve-node'),
860 additionalProperties
=> 0,
863 description
=> "Search domain for host-name lookup.",
868 description
=> 'First name server IP address.',
873 description
=> 'Second name server IP address.',
878 description
=> 'Third name server IP address.',
887 my $res = PVE
::INotify
::read_file
('resolvconf');
892 __PACKAGE__-
>register_method({
893 name
=> 'update_dns',
896 description
=> "Write DNS settings.",
898 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
903 additionalProperties
=> 0,
905 node
=> get_standard_option
('pve-node'),
907 description
=> "Search domain for host-name lookup.",
911 description
=> 'First name server IP address.',
912 type
=> 'string', format
=> 'ip',
916 description
=> 'Second name server IP address.',
917 type
=> 'string', format
=> 'ip',
921 description
=> 'Third name server IP address.',
922 type
=> 'string', format
=> 'ip',
927 returns
=> { type
=> "null" },
931 PVE
::INotify
::update_file
('resolvconf', $param);
936 __PACKAGE__-
>register_method({
941 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
943 description
=> "Read server time and time zone settings.",
946 additionalProperties
=> 0,
948 node
=> get_standard_option
('pve-node'),
953 additionalProperties
=> 0,
956 description
=> "Time zone",
960 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
962 minimum
=> 1297163644,
965 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
967 minimum
=> 1297163644,
975 my $ltime = timegm_nocheck
(localtime($ctime));
977 timezone
=> PVE
::INotify
::read_file
('timezone'),
985 __PACKAGE__-
>register_method({
986 name
=> 'set_timezone',
989 description
=> "Set time zone.",
991 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
996 additionalProperties
=> 0,
998 node
=> get_standard_option
('pve-node'),
1000 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
1005 returns
=> { type
=> "null" },
1009 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
1014 __PACKAGE__-
>register_method({
1021 description
=> "Get list of appliances.",
1024 additionalProperties
=> 0,
1026 node
=> get_standard_option
('pve-node'),
1041 my $list = PVE
::APLInfo
::load_data
();
1043 foreach my $template (keys %{$list->{all
}}) {
1044 my $pd = $list->{all
}->{$template};
1045 next if $pd->{'package'} eq 'pve-web-news';
1052 __PACKAGE__-
>register_method({
1053 name
=> 'apl_download',
1057 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1059 description
=> "Download appliance templates.",
1063 additionalProperties
=> 0,
1065 node
=> get_standard_option
('pve-node'),
1066 storage
=> get_standard_option
('pve-storage-id', {
1067 description
=> "The storage where the template will be stored",
1068 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1070 template
=> { type
=> 'string',
1071 description
=> "The template wich will downloaded",
1073 completion
=> \
&complete_templet_repo
,
1077 returns
=> { type
=> "string" },
1081 my $rpcenv = PVE
::RPCEnvironment
::get
();
1083 my $user = $rpcenv->get_user();
1085 my $node = $param->{node
};
1087 my $list = PVE
::APLInfo
::load_data
();
1089 my $template = $param->{template
};
1090 my $pd = $list->{all
}->{$template};
1092 raise_param_exc
({ template
=> "no such template"}) if !$pd;
1094 my $cfg = PVE
::Storage
::config
();
1095 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
1097 die "unknown template type '$pd->{type}'\n"
1098 if !($pd->{type
} eq 'openvz' || $pd->{type
} eq 'lxc');
1100 die "storage '$param->{storage}' does not support templates\n"
1101 if !$scfg->{content
}->{vztmpl
};
1103 my $src = $pd->{location
};
1104 my $tmpldir = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
1105 my $dest = "$tmpldir/$template";
1106 my $tmpdest = "$tmpldir/${template}.tmp.$$";
1111 print "starting template download from: $src\n";
1112 print "target file: $dest\n";
1114 my $check_hash = sub {
1115 my ($template_info, $filename, $noerr) = @_;
1121 open(my $fh, '<', $filename) or die "Can't open '$filename': $!";
1123 if (defined($template_info->{sha512sum
})) {
1124 $expected = $template_info->{sha512sum
};
1125 $digest = Digest
::SHA-
>new(512)->addfile($fh)->hexdigest;
1126 } elsif (defined($template_info->{md5sum
})) {
1128 $expected = $template_info->{md5sum
};
1129 $digest = Digest
::MD5-
>new->addfile($fh)->hexdigest;
1131 die "no expected checksum defined";
1136 die "checking hash failed - $@\n" if $@ && !$noerr;
1138 return ($digest, $digest ?
lc($digest) eq lc($expected) : 0);
1143 my ($hash, $correct) = &$check_hash($pd, $dest, 1);
1145 if ($hash && $correct) {
1146 print "file already exists $hash - no need to download\n";
1152 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1153 if ($dccfg->{http_proxy
}) {
1154 $ENV{http_proxy
} = $dccfg->{http_proxy
};
1157 my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
1158 if (system (@cmd) != 0) {
1159 die "download failed - $!\n";
1162 my ($hash, $correct) = &$check_hash($pd, $tmpdest);
1164 die "could not calculate checksum\n" if !$hash;
1167 my $expected = $pd->{sha512sum
} // $pd->{md5sum
};
1168 die "wrong checksum: $hash != $expected\n";
1171 if (!rename($tmpdest, $dest)) {
1172 die "unable to save file - $!\n";
1184 print "download finished\n";
1187 return $rpcenv->fork_worker('download', undef, $user, $worker);
1190 __PACKAGE__-
>register_method({
1195 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1198 description
=> "Gather various systems information about a node",
1201 additionalProperties
=> 0,
1203 node
=> get_standard_option
('pve-node'),
1210 return PVE
::Report
::generate
();
1213 # returns a list of VMIDs, those can be filtered by
1214 # * current parent node
1216 # * guest is a template (default: skip)
1217 # * guest is HA manged (default: skip)
1218 my $get_filtered_vmlist = sub {
1219 my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
1221 my $vmlist = PVE
::Cluster
::get_vmlist
();
1223 my $vms_allowed = {};
1224 if (defined($vmfilter)) {
1225 foreach my $vmid (PVE
::Tools
::split_list
($vmfilter)) {
1226 $vms_allowed->{$vmid} = 1;
1231 foreach my $vmid (keys %{$vmlist->{ids
}}) {
1232 next if %$vms_allowed && !$vms_allowed->{$vmid};
1234 my $d = $vmlist->{ids
}->{$vmid};
1235 next if $nodename && $d->{node
} ne $nodename;
1239 if ($d->{type
} eq 'lxc') {
1240 $class = 'PVE::LXC::Config';
1241 } elsif ($d->{type
} eq 'qemu') {
1242 $class = 'PVE::QemuConfig';
1244 die "unknown VM type '$d->{type}'\n";
1247 my $conf = $class->load_config($vmid);
1248 return if !$templates && $class->is_template($conf);
1249 return if !$ha_managed && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1251 $res->{$vmid} = $conf;
1252 $res->{$vmid}->{type
} = $d->{type
};
1260 # return all VMs which should get started/stopped on power up/down
1261 my $get_start_stop_list = sub {
1262 my ($nodename, $autostart, $vmfilter) = @_;
1264 my $vmlist = &$get_filtered_vmlist($nodename, $vmfilter);
1267 foreach my $vmid (keys %$vmlist) {
1268 my $conf = $vmlist->{$vmid};
1270 next if $autostart && !$conf->{onboot
};
1273 if ($conf->{startup
}) {
1274 $startup = PVE
::JSONSchema
::pve_parse_startup_order
($conf->{startup
});
1277 $startup->{order
} = LONG_MAX
if !defined($startup->{order
});
1279 $resList->{$startup->{order
}}->{$vmid} = $startup;
1280 $resList->{$startup->{order
}}->{$vmid}->{type
} = $conf->{type
};
1286 __PACKAGE__-
>register_method ({
1292 check
=> ['perm', '/', [ 'VM.PowerMgmt' ]],
1295 description
=> "Start all VMs and containers (when onboot=1).",
1297 additionalProperties
=> 0,
1299 node
=> get_standard_option
('pve-node'),
1303 description
=> "force if onboot=0.",
1306 description
=> "Only consider Guests with these IDs.",
1307 type
=> 'string', format
=> 'pve-vmid-list',
1318 my $rpcenv = PVE
::RPCEnvironment
::get
();
1319 my $authuser = $rpcenv->get_user();
1321 my $nodename = $param->{node
};
1322 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1324 my $force = $param->{force
};
1328 $rpcenv->{type
} = 'priv'; # to start tasks in background
1330 if (!PVE
::Cluster
::check_cfs_quorum
(1)) {
1331 print "waiting for quorum ...\n";
1334 } while (!PVE
::Cluster
::check_cfs_quorum
(1));
1335 print "got quorum\n";
1337 my $autostart = $force ?
undef : 1;
1338 my $startList = &$get_start_stop_list($nodename, $autostart, $param->{vms
});
1340 # Note: use numeric sorting with <=>
1341 foreach my $order (sort {$a <=> $b} keys %$startList) {
1342 my $vmlist = $startList->{$order};
1344 foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
1345 my $d = $vmlist->{$vmid};
1347 PVE
::Cluster
::check_cfs_quorum
(); # abort when we loose quorum
1350 my $default_delay = 0;
1353 if ($d->{type
} eq 'lxc') {
1354 return if PVE
::LXC
::check_running
($vmid);
1355 print STDERR
"Starting CT $vmid\n";
1356 $upid = PVE
::API2
::LXC
::Status-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1357 } elsif ($d->{type
} eq 'qemu') {
1358 $default_delay = 3; # to reduce load
1359 return if PVE
::QemuServer
::check_running
($vmid, 1);
1360 print STDERR
"Starting VM $vmid\n";
1361 $upid = PVE
::API2
::Qemu-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1363 die "unknown VM type '$d->{type}'\n";
1366 my $res = PVE
::Tools
::upid_decode
($upid);
1367 while (PVE
::ProcFSTools
::check_process_running
($res->{pid
})) {
1371 my $status = PVE
::Tools
::upid_read_status
($upid);
1372 if ($status eq 'OK') {
1373 # use default delay to reduce load
1374 my $delay = defined($d->{up
}) ?
int($d->{up
}) : $default_delay;
1376 print STDERR
"Waiting for $delay seconds (startup delay)\n" if $d->{up
};
1377 for (my $i = 0; $i < $delay; $i++) {
1382 if ($d->{type
} eq 'lxc') {
1383 print STDERR
"Starting CT $vmid failed: $status\n";
1384 } elsif ($d->{type
} eq 'qemu') {
1385 print STDERR
"Starting VM $vmid failed: status\n";
1395 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1398 my $create_stop_worker = sub {
1399 my ($nodename, $type, $vmid, $down_timeout) = @_;
1402 if ($type eq 'lxc') {
1403 return if !PVE
::LXC
::check_running
($vmid);
1404 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60;
1405 print STDERR
"Stopping CT $vmid (timeout = $timeout seconds)\n";
1406 $upid = PVE
::API2
::LXC
::Status-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1407 timeout
=> $timeout, forceStop
=> 1 });
1408 } elsif ($type eq 'qemu') {
1409 return if !PVE
::QemuServer
::check_running
($vmid, 1);
1410 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60*3;
1411 print STDERR
"Stopping VM $vmid (timeout = $timeout seconds)\n";
1412 $upid = PVE
::API2
::Qemu-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1413 timeout
=> $timeout, forceStop
=> 1 });
1415 die "unknown VM type '$type'\n";
1421 __PACKAGE__-
>register_method ({
1427 check
=> ['perm', '/', [ 'VM.PowerMgmt' ]],
1430 description
=> "Stop all VMs and Containers.",
1432 additionalProperties
=> 0,
1434 node
=> get_standard_option
('pve-node'),
1436 description
=> "Only consider Guests with these IDs.",
1437 type
=> 'string', format
=> 'pve-vmid-list',
1448 my $rpcenv = PVE
::RPCEnvironment
::get
();
1449 my $authuser = $rpcenv->get_user();
1451 my $nodename = $param->{node
};
1452 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1456 $rpcenv->{type
} = 'priv'; # to start tasks in background
1458 my $stopList = &$get_start_stop_list($nodename, undef, $param->{vms
});
1460 my $cpuinfo = PVE
::ProcFSTools
::read_cpuinfo
();
1461 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
1462 # if not set by user spawn max cpu count number of workers
1463 my $maxWorkers = $datacenterconfig->{max_workers
} || $cpuinfo->{cpus
};
1465 foreach my $order (sort {$b <=> $a} keys %$stopList) {
1466 my $vmlist = $stopList->{$order};
1469 my $finish_worker = sub {
1471 my $d = $workers->{$pid};
1473 delete $workers->{$pid};
1475 syslog
('info', "end task $d->{upid}");
1478 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1479 my $d = $vmlist->{$vmid};
1481 eval { $upid = &$create_stop_worker($nodename, $d->{type
}, $vmid, $d->{down
}); };
1485 my $res = PVE
::Tools
::upid_decode
($upid, 1);
1488 my $pid = $res->{pid
};
1490 $workers->{$pid} = { type
=> $d->{type
}, upid
=> $upid, vmid
=> $vmid };
1491 while (scalar(keys %$workers) >= $maxWorkers) {
1492 foreach my $p (keys %$workers) {
1493 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1494 &$finish_worker($p);
1500 while (scalar(keys %$workers)) {
1501 foreach my $p (keys %$workers) {
1502 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1503 &$finish_worker($p);
1510 syslog
('info', "all VMs and CTs stopped");
1515 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
1518 my $create_migrate_worker = sub {
1519 my ($nodename, $type, $vmid, $target) = @_;
1522 if ($type eq 'lxc') {
1523 my $online = PVE
::LXC
::check_running
($vmid) ?
1 : 0;
1524 print STDERR
"Migrating CT $vmid\n";
1525 $upid = PVE
::API2
::LXC-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1526 online
=> $online });
1527 } elsif ($type eq 'qemu') {
1528 my $online = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
1529 print STDERR
"Migrating VM $vmid\n";
1530 $upid = PVE
::API2
::Qemu-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1531 online
=> $online });
1533 die "unknown VM type '$type'\n";
1536 my $res = PVE
::Tools
::upid_decode
($upid);
1541 __PACKAGE__-
>register_method ({
1542 name
=> 'migrateall',
1543 path
=> 'migrateall',
1548 check
=> ['perm', '/', [ 'VM.Migrate' ]],
1550 description
=> "Migrate all VMs and Containers.",
1552 additionalProperties
=> 0,
1554 node
=> get_standard_option
('pve-node'),
1555 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1557 description
=> "Maximal number of parallel migration job." .
1558 " If not set use 'max_workers' from datacenter.cfg," .
1559 " one of both must be set!",
1565 description
=> "Only consider Guests with these IDs.",
1566 type
=> 'string', format
=> 'pve-vmid-list',
1577 my $rpcenv = PVE
::RPCEnvironment
::get
();
1578 my $authuser = $rpcenv->get_user();
1580 my $nodename = $param->{node
};
1581 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1583 my $target = $param->{target
};
1585 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
1586 # prefer parameter over datacenter cfg settings
1587 my $maxWorkers = $param->{maxworkers
} || $datacenterconfig->{max_workers
} ||
1588 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
1591 $rpcenv->{type
} = 'priv'; # to start tasks in background
1593 my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms
}, 1, 1);
1596 foreach my $vmid (sort keys %$vmlist) {
1597 my $d = $vmlist->{$vmid};
1599 eval { $pid = &$create_migrate_worker($nodename, $d->{type
}, $vmid, $target); };
1603 $workers->{$pid} = 1;
1604 while (scalar(keys %$workers) >= $maxWorkers) {
1605 foreach my $p (keys %$workers) {
1606 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1607 delete $workers->{$p};
1613 while (scalar(keys %$workers)) {
1614 foreach my $p (keys %$workers) {
1615 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1616 delete $workers->{$p};
1624 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
1628 # bash completion helper
1630 sub complete_templet_repo
{
1631 my ($cmdname, $pname, $cvalue) = @_;
1633 my $repo = PVE
::APLInfo
::load_data
();
1635 foreach my $templ (keys %{$repo->{all
}}) {
1636 next if $templ !~ m/^$cvalue/;
1643 package PVE
::API2
::Nodes
;
1648 use PVE
::SafeSyslog
;
1650 use PVE
::RESTHandler
;
1651 use PVE
::RPCEnvironment
;
1654 use base
qw(PVE::RESTHandler);
1656 __PACKAGE__-
>register_method ({
1657 subclass
=> "PVE::API2::Nodes::Nodeinfo",
1661 __PACKAGE__-
>register_method ({
1665 permissions
=> { user
=> 'all' },
1666 description
=> "Cluster node index.",
1668 additionalProperties
=> 0,
1677 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
1682 my $clinfo = PVE
::Cluster
::get_clinfo
();
1685 my $nodelist = PVE
::Cluster
::get_nodelist
();
1686 my $members = PVE
::Cluster
::get_members
();
1687 my $rrd = PVE
::Cluster
::rrd_dump
();
1689 foreach my $node (@$nodelist) {
1690 my $entry = PVE
::API2Tools
::extract_node_stats
($node, $members, $rrd);