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
;
47 use base
qw(PVE::RESTHandler);
49 __PACKAGE__-
>register_method ({
50 subclass
=> "PVE::API2::Qemu",
54 __PACKAGE__-
>register_method ({
55 subclass
=> "PVE::API2::LXC",
59 __PACKAGE__-
>register_method ({
60 subclass
=> "PVE::API2::Ceph",
64 __PACKAGE__-
>register_method ({
65 subclass
=> "PVE::API2::VZDump",
69 __PACKAGE__-
>register_method ({
70 subclass
=> "PVE::API2::Services",
74 __PACKAGE__-
>register_method ({
75 subclass
=> "PVE::API2::Subscription",
76 path
=> 'subscription',
79 __PACKAGE__-
>register_method ({
80 subclass
=> "PVE::API2::Network",
84 __PACKAGE__-
>register_method ({
85 subclass
=> "PVE::API2::Tasks",
89 __PACKAGE__-
>register_method ({
90 subclass
=> "PVE::API2::Storage::Scan",
94 __PACKAGE__-
>register_method ({
95 subclass
=> "PVE::API2::Storage::Status",
99 __PACKAGE__-
>register_method ({
100 subclass
=> "PVE::API2::APT",
104 __PACKAGE__-
>register_method ({
105 subclass
=> "PVE::API2::Firewall::Host",
109 __PACKAGE__-
>register_method ({
113 permissions
=> { user
=> 'all' },
114 description
=> "Node index.",
116 additionalProperties
=> 0,
118 node
=> get_standard_option
('pve-node'),
127 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
135 { name
=> 'version' },
136 { name
=> 'syslog' },
137 { name
=> 'status' },
138 { name
=> 'subscription' },
139 { name
=> 'report' },
141 { name
=> 'rrd' }, # fixme: remove?
142 { name
=> 'rrddata' },# fixme: remove?
143 { name
=> 'vncshell' },
144 { name
=> 'spiceshell' },
147 { name
=> 'services' },
149 { name
=> 'storage' },
152 { name
=> 'vzdump' },
153 { name
=> 'network' },
154 { name
=> 'aplinfo' },
155 { name
=> 'startall' },
156 { name
=> 'stopall' },
157 { name
=> 'netstat' },
158 { name
=> 'firewall' },
164 __PACKAGE__-
>register_method ({
169 permissions
=> { user
=> 'all' },
170 description
=> "API version details",
172 additionalProperties
=> 0,
174 node
=> get_standard_option
('pve-node'),
180 version
=> { type
=> 'string' },
181 release
=> { type
=> 'string' },
182 repoid
=> { type
=> 'string' },
186 my ($resp, $param) = @_;
188 return PVE
::pvecfg
::version_info
();
191 __PACKAGE__-
>register_method({
196 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
198 description
=> "Read node status",
201 additionalProperties
=> 0,
203 node
=> get_standard_option
('pve-node'),
220 my ($uptime, $idle) = PVE
::ProcFSTools
::read_proc_uptime
();
221 $res->{uptime
} = $uptime;
223 my ($avg1, $avg5, $avg15) = PVE
::ProcFSTools
::read_loadavg
();
224 $res->{loadavg
} = [ $avg1, $avg5, $avg15];
226 my ($sysname, $nodename, $release, $version, $machine) = POSIX
::uname
();
228 $res->{kversion
} = "$sysname $release $version";
230 $res->{cpuinfo
} = PVE
::ProcFSTools
::read_cpuinfo
();
232 my $stat = PVE
::ProcFSTools
::read_proc_stat
();
233 $res->{cpu
} = $stat->{cpu
};
234 $res->{wait} = $stat->{wait};
236 my $meminfo = PVE
::ProcFSTools
::read_meminfo
();
238 free
=> $meminfo->{memfree
},
239 total
=> $meminfo->{memtotal
},
240 used
=> $meminfo->{memused
},
244 shared
=> $meminfo->{memshared
},
248 free
=> $meminfo->{swapfree
},
249 total
=> $meminfo->{swaptotal
},
250 used
=> $meminfo->{swapused
},
253 $res->{pveversion
} = PVE
::pvecfg
::package() . "/" .
254 PVE
::pvecfg
::version_text
();
256 my $dinfo = df
('/', 1); # output is bytes
259 total
=> $dinfo->{blocks
},
260 avail
=> $dinfo->{bavail
},
261 used
=> $dinfo->{used
},
262 free
=> $dinfo->{bavail
} - $dinfo->{used
},
268 __PACKAGE__-
>register_method({
273 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
275 description
=> "Read tap/vm network device interface counters",
278 additionalProperties
=> 0,
280 node
=> get_standard_option
('pve-node'),
295 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
296 foreach my $dev (keys %$netdev) {
297 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
306 in => $netdev->{$dev}->{transmit
},
307 out
=> $netdev->{$dev}->{receive
},
315 __PACKAGE__-
>register_method({
320 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
322 description
=> "Execute multiple commands in order.",
324 protected
=> 1, # avoid problems with proxy code
326 additionalProperties
=> 0,
328 node
=> get_standard_option
('pve-node'),
330 description
=> "JSON encoded array of commands.",
345 my $rpcenv = PVE
::RPCEnvironment
::get
();
346 my $user = $rpcenv->get_user();
348 my $commands = eval { decode_json
($param->{commands
}); };
350 die "commands param did not contain valid JSON: $@" if $@;
351 die "commands is not an array" if ref($commands) ne "ARRAY";
353 foreach my $cmd (@$commands) {
355 die "$cmd is not a valid command" if (ref($cmd) ne "HASH" || !$cmd->{path
} || !$cmd->{method});
359 my $path = "nodes/$param->{node}/$cmd->{path}";
362 my ($handler, $info) = PVE
::API2-
>find_handler($cmd->{method}, $path, $uri_param);
363 if (!$handler || !$info) {
364 die "no handler for '$path'\n";
367 foreach my $p (keys %{$cmd->{args
}}) {
368 raise_param_exc
({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
369 $uri_param->{$p} = $cmd->{args
}->{$p};
372 # check access permissions
373 $rpcenv->check_api2_permissions($info->{permissions
}, $user, $uri_param);
377 data
=> $handler->handle($info, $uri_param),
381 my $resp = { status
=> HTTP_INTERNAL_SERVER_ERROR
};
382 if (ref($err) eq "PVE::Exception") {
383 $resp->{status
} = $err->{code
} if $err->{code
};
384 $resp->{errors
} = $err->{errors
} if $err->{errors
};
385 $resp->{message
} = $err->{msg
};
387 $resp->{message
} = $err;
397 __PACKAGE__-
>register_method({
402 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
405 description
=> "Reboot or shutdown a node.",
408 additionalProperties
=> 0,
410 node
=> get_standard_option
('pve-node'),
412 description
=> "Specify the command.",
414 enum
=> [qw(reboot shutdown)],
418 returns
=> { type
=> "null" },
422 if ($param->{command
} eq 'reboot') {
423 system ("(sleep 2;/sbin/reboot)&");
424 } elsif ($param->{command
} eq 'shutdown') {
425 system ("(sleep 2;/sbin/poweroff)&");
432 __PACKAGE__-
>register_method({
436 protected
=> 1, # fixme: can we avoid that?
438 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
440 description
=> "Read node RRD statistics (returns PNG)",
442 additionalProperties
=> 0,
444 node
=> get_standard_option
('pve-node'),
446 description
=> "Specify the time frame you are interested in.",
448 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
451 description
=> "The list of datasources you want to display.",
452 type
=> 'string', format
=> 'pve-configid-list',
455 description
=> "The RRD consolidation function",
457 enum
=> [ 'AVERAGE', 'MAX' ],
465 filename
=> { type
=> 'string' },
471 return PVE
::Cluster
::create_rrd_graph
(
472 "pve2-node/$param->{node}", $param->{timeframe
},
473 $param->{ds
}, $param->{cf
});
477 __PACKAGE__-
>register_method({
481 protected
=> 1, # fixme: can we avoid that?
483 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
485 description
=> "Read node RRD statistics",
487 additionalProperties
=> 0,
489 node
=> get_standard_option
('pve-node'),
491 description
=> "Specify the time frame you are interested in.",
493 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
496 description
=> "The RRD consolidation function",
498 enum
=> [ 'AVERAGE', 'MAX' ],
513 return PVE
::Cluster
::create_rrd_data
(
514 "pve2-node/$param->{node}", $param->{timeframe
}, $param->{cf
});
517 __PACKAGE__-
>register_method({
521 description
=> "Read system log",
524 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
528 additionalProperties
=> 0,
530 node
=> get_standard_option
('pve-node'),
543 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
544 description
=> "Display all log since this date-time string.",
549 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
550 description
=> "Display all log until this date-time string.",
561 description
=> "Line number",
565 description
=> "Line text",
574 my $rpcenv = PVE
::RPCEnvironment
::get
();
575 my $user = $rpcenv->get_user();
576 my $node = $param->{node
};
578 my ($count, $lines) = PVE
::Tools
::dump_journal
($param->{start
}, $param->{limit
},
579 $param->{since
}, $param->{until});
581 $rpcenv->set_result_attrib('total', $count);
588 __PACKAGE__-
>register_method ({
594 description
=> "Restricted to users on realm 'pam'",
595 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
597 description
=> "Creates a VNC Shell proxy.",
599 additionalProperties
=> 0,
601 node
=> get_standard_option
('pve-node'),
604 description
=> "Run 'apt-get dist-upgrade' instead of normal shell.",
611 description
=> "use websocket instead of standard vnc.",
616 additionalProperties
=> 0,
618 user
=> { type
=> 'string' },
619 ticket
=> { type
=> 'string' },
620 cert
=> { type
=> 'string' },
621 port
=> { type
=> 'integer' },
622 upid
=> { type
=> 'string' },
628 my $rpcenv = PVE
::RPCEnvironment
::get
();
630 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
632 raise_perm_exc
("realm != pam") if $realm ne 'pam';
634 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
636 my $node = $param->{node
};
638 my $authpath = "/nodes/$node";
640 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
642 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
645 my ($remip, $family);
647 if ($node ne PVE
::INotify
::nodename
()) {
648 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
650 $family = PVE
::Tools
::get_host_address_family
($node);
653 my $port = PVE
::Tools
::next_vnc_port
($family);
655 # NOTE: vncterm VNC traffic is already TLS encrypted,
656 # so we select the fastest chipher here (or 'none'?)
657 my $remcmd = $remip ?
658 ['/usr/bin/ssh', '-t', $remip] : [];
662 if ($user eq 'root@pam') {
663 if ($param->{upgrade
}) {
664 my $upgradecmd = "pveupgrade --shell";
665 $upgradecmd = PVE
::Tools
::shellquote
($upgradecmd) if $remip;
666 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
668 $shcmd = [ '/bin/bash', '-l' ];
671 $shcmd = [ '/bin/login' ];
676 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
677 '-timeout', $timeout, '-authpath', $authpath,
678 '-perm', 'Sys.Console'];
680 if ($param->{websocket
}) {
681 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
682 push @$cmd, '-notls', '-listen', 'localhost';
685 push @$cmd, '-c', @$remcmd, @$shcmd;
690 syslog
('info', "starting vnc proxy $upid\n");
692 my $cmdstr = join (' ', @$cmd);
693 syslog
('info', "launch command: $cmdstr");
696 foreach my $k (keys %ENV) {
697 next if $k eq 'PVE_VNC_TICKET';
698 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME';
703 PVE
::Tools
::run_command
($cmd, errmsg
=> "vncterm failed");
706 syslog
('err', $err);
712 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
714 PVE
::Tools
::wait_for_vnc_port
($port);
725 __PACKAGE__-
>register_method({
726 name
=> 'vncwebsocket',
727 path
=> 'vncwebsocket',
730 description
=> "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
731 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
733 description
=> "Opens a weksocket for VNC traffic.",
735 additionalProperties
=> 0,
737 node
=> get_standard_option
('pve-node'),
739 description
=> "Ticket from previous call to vncproxy.",
744 description
=> "Port number returned by previous vncproxy call.",
754 port
=> { type
=> 'string' },
760 my $rpcenv = PVE
::RPCEnvironment
::get
();
762 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
764 raise_perm_exc
("realm != pam") if $realm ne 'pam';
766 my $authpath = "/nodes/$param->{node}";
768 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $user, $authpath);
770 my $port = $param->{port
};
772 return { port
=> $port };
775 __PACKAGE__-
>register_method ({
776 name
=> 'spiceshell',
777 path
=> 'spiceshell',
782 description
=> "Restricted to users on realm 'pam'",
783 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
785 description
=> "Creates a SPICE shell.",
787 additionalProperties
=> 0,
789 node
=> get_standard_option
('pve-node'),
790 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
793 description
=> "Run 'apt-get dist-upgrade' instead of normal shell.",
799 returns
=> get_standard_option
('remote-viewer-config'),
803 my $rpcenv = PVE
::RPCEnvironment
::get
();
804 my $authuser = $rpcenv->get_user();
806 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($authuser);
808 raise_perm_exc
("realm != pam") if $realm ne 'pam';
810 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
812 my $node = $param->{node
};
813 my $proxy = $param->{proxy
};
815 my $authpath = "/nodes/$node";
816 my $permissions = 'Sys.Console';
820 if ($user eq 'root@pam') {
821 if ($param->{upgrade
}) {
822 my $upgradecmd = "pveupgrade --shell";
823 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
825 $shcmd = [ '/bin/bash', '-l' ];
828 $shcmd = [ '/bin/login' ];
831 my $title = "Shell on '$node'";
833 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
836 __PACKAGE__-
>register_method({
841 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
843 description
=> "Read DNS settings.",
846 additionalProperties
=> 0,
848 node
=> get_standard_option
('pve-node'),
853 additionalProperties
=> 0,
856 description
=> "Search domain for host-name lookup.",
861 description
=> 'First name server IP address.',
866 description
=> 'Second name server IP address.',
871 description
=> 'Third name server IP address.',
880 my $res = PVE
::INotify
::read_file
('resolvconf');
885 __PACKAGE__-
>register_method({
886 name
=> 'update_dns',
889 description
=> "Write DNS settings.",
891 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
896 additionalProperties
=> 0,
898 node
=> get_standard_option
('pve-node'),
900 description
=> "Search domain for host-name lookup.",
904 description
=> 'First name server IP address.',
905 type
=> 'string', format
=> 'ip',
909 description
=> 'Second name server IP address.',
910 type
=> 'string', format
=> 'ip',
914 description
=> 'Third name server IP address.',
915 type
=> 'string', format
=> 'ip',
920 returns
=> { type
=> "null" },
924 PVE
::INotify
::update_file
('resolvconf', $param);
929 __PACKAGE__-
>register_method({
934 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
936 description
=> "Read server time and time zone settings.",
939 additionalProperties
=> 0,
941 node
=> get_standard_option
('pve-node'),
946 additionalProperties
=> 0,
949 description
=> "Time zone",
953 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
955 minimum
=> 1297163644,
958 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
960 minimum
=> 1297163644,
968 my $ltime = timegm_nocheck
(localtime($ctime));
970 timezone
=> PVE
::INotify
::read_file
('timezone'),
978 __PACKAGE__-
>register_method({
979 name
=> 'set_timezone',
982 description
=> "Set time zone.",
984 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
989 additionalProperties
=> 0,
991 node
=> get_standard_option
('pve-node'),
993 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
998 returns
=> { type
=> "null" },
1002 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
1007 __PACKAGE__-
>register_method({
1014 description
=> "Get list of appliances.",
1017 additionalProperties
=> 0,
1019 node
=> get_standard_option
('pve-node'),
1034 my $list = PVE
::APLInfo
::load_data
();
1036 foreach my $template (keys %{$list->{all
}}) {
1037 my $pd = $list->{all
}->{$template};
1038 next if $pd->{'package'} eq 'pve-web-news';
1045 __PACKAGE__-
>register_method({
1046 name
=> 'apl_download',
1050 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1052 description
=> "Download appliance templates.",
1056 additionalProperties
=> 0,
1058 node
=> get_standard_option
('pve-node'),
1059 storage
=> get_standard_option
('pve-storage-id', {
1060 description
=> "The storage where the template will be stored",
1061 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1063 template
=> { type
=> 'string',
1064 description
=> "The template wich will downloaded",
1066 completion
=> \
&complete_templet_repo
,
1070 returns
=> { type
=> "string" },
1074 my $rpcenv = PVE
::RPCEnvironment
::get
();
1076 my $user = $rpcenv->get_user();
1078 my $node = $param->{node
};
1080 my $list = PVE
::APLInfo
::load_data
();
1082 my $template = $param->{template
};
1083 my $pd = $list->{all
}->{$template};
1085 raise_param_exc
({ template
=> "no such template"}) if !$pd;
1087 my $cfg = PVE
::Storage
::config
();
1088 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
1090 die "unknown template type '$pd->{type}'\n"
1091 if !($pd->{type
} eq 'openvz' || $pd->{type
} eq 'lxc');
1093 die "storage '$param->{storage}' does not support templates\n"
1094 if !$scfg->{content
}->{vztmpl
};
1096 my $src = $pd->{location
};
1097 my $tmpldir = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
1098 my $dest = "$tmpldir/$template";
1099 my $tmpdest = "$tmpldir/${template}.tmp.$$";
1104 print "starting template download from: $src\n";
1105 print "target file: $dest\n";
1107 my $check_hash = sub {
1108 my ($template_info, $filename, $noerr) = @_;
1114 open(my $fh, '<', $filename) or die "Can't open '$filename': $!";
1116 if (defined($template_info->{sha512sum
})) {
1117 $expected = $template_info->{sha512sum
};
1118 $digest = Digest
::SHA-
>new(512)->addfile($fh)->hexdigest;
1119 } elsif (defined($template_info->{md5sum
})) {
1121 $expected = $template_info->{md5sum
};
1122 $digest = Digest
::MD5-
>new->addfile($fh)->hexdigest;
1124 die "no expected checksum defined";
1129 die "checking hash failed - $@\n" if $@ && !$noerr;
1131 return ($digest, $digest ?
lc($digest) eq lc($expected) : 0);
1136 my ($hash, $correct) = &$check_hash($pd, $dest, 1);
1138 if ($hash && $correct) {
1139 print "file already exists $hash - no need to download\n";
1145 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1146 if ($dccfg->{http_proxy
}) {
1147 $ENV{http_proxy
} = $dccfg->{http_proxy
};
1150 my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
1151 if (system (@cmd) != 0) {
1152 die "download failed - $!\n";
1155 my ($hash, $correct) = &$check_hash($pd, $tmpdest);
1157 die "could not calculate checksum\n" if !$hash;
1160 my $expected = $pd->{sha512sum
} // $pd->{md5sum
};
1161 die "wrong checksum: $hash != $expected\n";
1164 if (!rename($tmpdest, $dest)) {
1165 die "unable to save file - $!\n";
1177 print "download finished\n";
1180 return $rpcenv->fork_worker('download', undef, $user, $worker);
1183 __PACKAGE__-
>register_method({
1188 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1191 description
=> "Gather various systems information about a node",
1194 additionalProperties
=> 0,
1196 node
=> get_standard_option
('pve-node'),
1203 return PVE
::Report
::generate
();
1206 my $get_start_stop_list = sub {
1207 my ($nodename, $autostart) = @_;
1209 my $vmlist = PVE
::Cluster
::get_vmlist
();
1212 foreach my $vmid (keys %{$vmlist->{ids
}}) {
1213 my $d = $vmlist->{ids
}->{$vmid};
1217 return if $d->{node
} ne $nodename;
1219 my $bootorder = LONG_MAX
;
1222 if ($d->{type
} eq 'lxc') {
1223 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1224 } elsif ($d->{type
} eq 'qemu') {
1225 $conf = PVE
::QemuConfig-
>load_config($vmid);
1227 die "unknown VM type '$d->{type}'\n";
1230 return if $autostart && !$conf->{onboot
};
1232 if ($conf->{startup
}) {
1233 $startup = PVE
::JSONSchema
::pve_parse_startup_order
($conf->{startup
});
1234 $startup->{order
} = $bootorder if !defined($startup->{order
});
1236 $startup = { order
=> $bootorder };
1239 $resList->{$startup->{order
}}->{$vmid} = $startup;
1240 $resList->{$startup->{order
}}->{$vmid}->{type
} = $d->{type
};
1248 __PACKAGE__-
>register_method ({
1254 check
=> ['perm', '/', [ 'VM.PowerMgmt' ]],
1257 description
=> "Start all VMs and containers (when onboot=1).",
1259 additionalProperties
=> 0,
1261 node
=> get_standard_option
('pve-node'),
1265 description
=> "force if onboot=0.",
1275 my $rpcenv = PVE
::RPCEnvironment
::get
();
1276 my $authuser = $rpcenv->get_user();
1278 my $nodename = $param->{node
};
1279 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1281 my $force = $param->{force
};
1285 $rpcenv->{type
} = 'priv'; # to start tasks in background
1287 # wait up to 60 seconds for quorum
1288 for (my $i = 60; $i >= 0; $i--) {
1289 last if PVE
::Cluster
::check_cfs_quorum
($i != 0 ?
1 : 0);
1292 my $autostart = $force ?
undef : 1;
1293 my $startList = &$get_start_stop_list($nodename, $autostart);
1295 # Note: use numeric sorting with <=>
1296 foreach my $order (sort {$a <=> $b} keys %$startList) {
1297 my $vmlist = $startList->{$order};
1299 foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
1300 my $d = $vmlist->{$vmid};
1304 if ($d->{type
} eq 'lxc') {
1305 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1306 next if PVE
::LXC
::Config-
>is_template($conf);
1307 } elsif ($d->{type
} eq 'qemu') {
1308 $conf = PVE
::QemuConfig-
>load_config($vmid);
1309 next if PVE
::QemuConfig-
>is_template($conf);
1311 die "unknown VM type '$d->{type}'\n";
1314 # skip ha managed VMs (started by pve-ha-manager)
1315 next if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1317 PVE
::Cluster
::check_cfs_quorum
(); # abort when we loose quorum
1320 my $default_delay = 0;
1323 if ($d->{type
} eq 'lxc') {
1324 return if PVE
::LXC
::check_running
($vmid);
1325 print STDERR
"Starting CT $vmid\n";
1326 $upid = PVE
::API2
::LXC
::Status-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1327 } elsif ($d->{type
} eq 'qemu') {
1328 $default_delay = 3; # to reduce load
1329 return if PVE
::QemuServer
::check_running
($vmid, 1);
1330 print STDERR
"Starting VM $vmid\n";
1331 $upid = PVE
::API2
::Qemu-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1333 die "unknown VM type '$d->{type}'\n";
1336 my $res = PVE
::Tools
::upid_decode
($upid);
1337 while (PVE
::ProcFSTools
::check_process_running
($res->{pid
})) {
1341 my $status = PVE
::Tools
::upid_read_status
($upid);
1342 if ($status eq 'OK') {
1343 # use default delay to reduce load
1344 my $delay = defined($d->{up
}) ?
int($d->{up
}) : $default_delay;
1346 print STDERR
"Waiting for $delay seconds (startup delay)\n" if $d->{up
};
1347 for (my $i = 0; $i < $delay; $i++) {
1352 if ($d->{type
} eq 'lxc') {
1353 print STDERR
"Starting CT $vmid failed: $status\n";
1354 } elsif ($d->{type
} eq 'qemu') {
1355 print STDERR
"Starting VM $vmid failed: status\n";
1365 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1368 my $create_stop_worker = sub {
1369 my ($nodename, $type, $vmid, $down_timeout) = @_;
1372 if ($type eq 'lxc') {
1373 return if !PVE
::LXC
::check_running
($vmid);
1374 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60;
1375 print STDERR
"Stopping CT $vmid (timeout = $timeout seconds)\n";
1376 $upid = PVE
::API2
::LXC
::Status-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1377 timeout
=> $timeout, forceStop
=> 1 });
1378 } elsif ($type eq 'qemu') {
1379 return if !PVE
::QemuServer
::check_running
($vmid, 1);
1380 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60*3;
1381 print STDERR
"Stopping VM $vmid (timeout = $timeout seconds)\n";
1382 $upid = PVE
::API2
::Qemu-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1383 timeout
=> $timeout, forceStop
=> 1 });
1385 die "unknown VM type '$type'\n";
1391 __PACKAGE__-
>register_method ({
1397 check
=> ['perm', '/', [ 'VM.PowerMgmt' ]],
1400 description
=> "Stop all VMs and Containers.",
1402 additionalProperties
=> 0,
1404 node
=> get_standard_option
('pve-node'),
1413 my $rpcenv = PVE
::RPCEnvironment
::get
();
1414 my $authuser = $rpcenv->get_user();
1416 my $nodename = $param->{node
};
1417 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1421 $rpcenv->{type
} = 'priv'; # to start tasks in background
1423 my $stopList = &$get_start_stop_list($nodename);
1425 my $cpuinfo = PVE
::ProcFSTools
::read_cpuinfo
();
1426 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
1427 # if not set by user spawn max cpu count number of workers
1428 my $maxWorkers = $datacenterconfig->{max_workers
} || $cpuinfo->{cpus
};
1430 foreach my $order (sort {$b <=> $a} keys %$stopList) {
1431 my $vmlist = $stopList->{$order};
1434 my $finish_worker = sub {
1436 my $d = $workers->{$pid};
1438 delete $workers->{$pid};
1440 syslog
('info', "end task $d->{upid}");
1443 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1444 # skip ha managed VMs (stopped by pve-ha-manager)
1445 next if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1447 my $d = $vmlist->{$vmid};
1449 eval { $upid = &$create_stop_worker($nodename, $d->{type
}, $vmid, $d->{down
}); };
1453 my $res = PVE
::Tools
::upid_decode
($upid, 1);
1456 my $pid = $res->{pid
};
1458 $workers->{$pid} = { type
=> $d->{type
}, upid
=> $upid, vmid
=> $vmid };
1459 while (scalar(keys %$workers) >= $maxWorkers) {
1460 foreach my $p (keys %$workers) {
1461 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1462 &$finish_worker($p);
1468 while (scalar(keys %$workers)) {
1469 foreach my $p (keys %$workers) {
1470 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1471 &$finish_worker($p);
1478 syslog
('info', "all VMs and CTs stopped");
1483 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
1486 my $create_migrate_worker = sub {
1487 my ($nodename, $type, $vmid, $target) = @_;
1490 if ($type eq 'lxc') {
1491 my $online = PVE
::LXC
::check_running
($vmid) ?
1 : 0;
1492 print STDERR
"Migrating CT $vmid\n";
1493 $upid = PVE
::API2
::LXC-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1494 online
=> $online });
1495 } elsif ($type eq 'qemu') {
1496 my $online = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
1497 print STDERR
"Migrating VM $vmid\n";
1498 $upid = PVE
::API2
::Qemu-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1499 online
=> $online });
1501 die "unknown VM type '$type'\n";
1504 my $res = PVE
::Tools
::upid_decode
($upid);
1509 __PACKAGE__-
>register_method ({
1510 name
=> 'migrateall',
1511 path
=> 'migrateall',
1516 check
=> ['perm', '/', [ 'VM.Migrate' ]],
1518 description
=> "Migrate all VMs and Containers.",
1520 additionalProperties
=> 0,
1522 node
=> get_standard_option
('pve-node'),
1523 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1525 description
=> "Maximal number of parallel migration job." .
1526 " If not set use 'max_workers' from datacenter.cfg," .
1527 " one of both must be set!",
1540 my $rpcenv = PVE
::RPCEnvironment
::get
();
1541 my $authuser = $rpcenv->get_user();
1543 my $nodename = $param->{node
};
1544 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1546 my $target = $param->{target
};
1548 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
1549 # prefer parameter over datacenter cfg settings
1550 my $maxWorkers = $param->{maxworkers
} || $datacenterconfig->{max_workers
} ||
1551 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
1555 $rpcenv->{type
} = 'priv'; # to start tasks in background
1557 my $migrateList = &$get_start_stop_list($nodename);
1559 foreach my $order (sort {$b <=> $a} keys %$migrateList) {
1560 my $vmlist = $migrateList->{$order};
1562 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1563 my $d = $vmlist->{$vmid};
1565 eval { $pid = &$create_migrate_worker($nodename, $d->{type
}, $vmid, $target); };
1569 $workers->{$pid} = 1;
1570 while (scalar(keys %$workers) >= $maxWorkers) {
1571 foreach my $p (keys %$workers) {
1572 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1573 delete $workers->{$p};
1579 while (scalar(keys %$workers)) {
1580 foreach my $p (keys %$workers) {
1581 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1582 delete $workers->{$p};
1591 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
1595 # bash completion helper
1597 sub complete_templet_repo
{
1598 my ($cmdname, $pname, $cvalue) = @_;
1600 my $repo = PVE
::APLInfo
::load_data
();
1602 foreach my $templ (keys %{$repo->{all
}}) {
1603 next if $templ !~ m/^$cvalue/;
1610 package PVE
::API2
::Nodes
;
1615 use PVE
::SafeSyslog
;
1617 use PVE
::RESTHandler
;
1618 use PVE
::RPCEnvironment
;
1621 use base
qw(PVE::RESTHandler);
1623 __PACKAGE__-
>register_method ({
1624 subclass
=> "PVE::API2::Nodes::Nodeinfo",
1628 __PACKAGE__-
>register_method ({
1632 permissions
=> { user
=> 'all' },
1633 description
=> "Cluster node index.",
1635 additionalProperties
=> 0,
1644 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
1649 my $clinfo = PVE
::Cluster
::get_clinfo
();
1652 my $nodelist = PVE
::Cluster
::get_nodelist
();
1653 my $members = PVE
::Cluster
::get_members
();
1654 my $rrd = PVE
::Cluster
::rrd_dump
();
1656 foreach my $node (@$nodelist) {
1657 my $entry = PVE
::API2Tools
::extract_node_stats
($node, $members, $rrd);