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
;
45 use base
qw(PVE::RESTHandler);
47 __PACKAGE__-
>register_method ({
48 subclass
=> "PVE::API2::Qemu",
52 __PACKAGE__-
>register_method ({
53 subclass
=> "PVE::API2::LXC",
57 __PACKAGE__-
>register_method ({
58 subclass
=> "PVE::API2::Ceph",
62 __PACKAGE__-
>register_method ({
63 subclass
=> "PVE::API2::VZDump",
67 __PACKAGE__-
>register_method ({
68 subclass
=> "PVE::API2::Services",
72 __PACKAGE__-
>register_method ({
73 subclass
=> "PVE::API2::Subscription",
74 path
=> 'subscription',
77 __PACKAGE__-
>register_method ({
78 subclass
=> "PVE::API2::Network",
82 __PACKAGE__-
>register_method ({
83 subclass
=> "PVE::API2::Tasks",
87 __PACKAGE__-
>register_method ({
88 subclass
=> "PVE::API2::Storage::Scan",
92 __PACKAGE__-
>register_method ({
93 subclass
=> "PVE::API2::Storage::Status",
97 __PACKAGE__-
>register_method ({
98 subclass
=> "PVE::API2::APT",
102 __PACKAGE__-
>register_method ({
103 subclass
=> "PVE::API2::Firewall::Host",
107 __PACKAGE__-
>register_method ({
111 permissions
=> { user
=> 'all' },
112 description
=> "Node index.",
114 additionalProperties
=> 0,
116 node
=> get_standard_option
('pve-node'),
125 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
133 { name
=> 'version' },
134 { name
=> 'syslog' },
135 { name
=> 'status' },
136 { name
=> 'subscription' },
137 { name
=> 'report' },
139 { name
=> 'rrd' }, # fixme: remove?
140 { name
=> 'rrddata' },# fixme: remove?
141 { name
=> 'vncshell' },
142 { name
=> 'spiceshell' },
145 { name
=> 'services' },
147 { name
=> 'storage' },
150 { name
=> 'vzdump' },
151 { name
=> 'network' },
152 { name
=> 'aplinfo' },
153 { name
=> 'startall' },
154 { name
=> 'stopall' },
155 { name
=> 'netstat' },
156 { name
=> 'firewall' },
162 __PACKAGE__-
>register_method ({
167 permissions
=> { user
=> 'all' },
168 description
=> "API version details",
170 additionalProperties
=> 0,
172 node
=> get_standard_option
('pve-node'),
178 version
=> { type
=> 'string' },
179 release
=> { type
=> 'string' },
180 repoid
=> { type
=> 'string' },
184 my ($resp, $param) = @_;
186 return PVE
::pvecfg
::version_info
();
189 __PACKAGE__-
>register_method({
194 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
196 description
=> "Read node status",
199 additionalProperties
=> 0,
201 node
=> get_standard_option
('pve-node'),
218 my ($uptime, $idle) = PVE
::ProcFSTools
::read_proc_uptime
();
219 $res->{uptime
} = $uptime;
221 my ($avg1, $avg5, $avg15) = PVE
::ProcFSTools
::read_loadavg
();
222 $res->{loadavg
} = [ $avg1, $avg5, $avg15];
224 my ($sysname, $nodename, $release, $version, $machine) = POSIX
::uname
();
226 $res->{kversion
} = "$sysname $release $version";
228 $res->{cpuinfo
} = PVE
::ProcFSTools
::read_cpuinfo
();
230 my $stat = PVE
::ProcFSTools
::read_proc_stat
();
231 $res->{cpu
} = $stat->{cpu
};
232 $res->{wait} = $stat->{wait};
234 my $meminfo = PVE
::ProcFSTools
::read_meminfo
();
236 free
=> $meminfo->{memfree
},
237 total
=> $meminfo->{memtotal
},
238 used
=> $meminfo->{memused
},
242 shared
=> $meminfo->{memshared
},
246 free
=> $meminfo->{swapfree
},
247 total
=> $meminfo->{swaptotal
},
248 used
=> $meminfo->{swapused
},
251 $res->{pveversion
} = PVE
::pvecfg
::package() . "/" .
252 PVE
::pvecfg
::version_text
();
254 my $dinfo = df
('/', 1); # output is bytes
257 total
=> $dinfo->{blocks
},
258 avail
=> $dinfo->{bavail
},
259 used
=> $dinfo->{used
},
260 free
=> $dinfo->{bavail
} - $dinfo->{used
},
266 __PACKAGE__-
>register_method({
271 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
273 description
=> "Read tap/vm network device interface counters",
276 additionalProperties
=> 0,
278 node
=> get_standard_option
('pve-node'),
293 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
294 foreach my $dev (keys %$netdev) {
295 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
304 in => $netdev->{$dev}->{transmit
},
305 out
=> $netdev->{$dev}->{receive
},
313 __PACKAGE__-
>register_method({
318 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
320 description
=> "Execute multiple commands in order.",
322 protected
=> 1, # avoid problems with proxy code
324 additionalProperties
=> 0,
326 node
=> get_standard_option
('pve-node'),
328 description
=> "JSON encoded array of commands.",
343 my $rpcenv = PVE
::RPCEnvironment
::get
();
344 my $user = $rpcenv->get_user();
346 my $commands = eval { decode_json
($param->{commands
}); };
348 die "commands param did not contain valid JSON: $@" if $@;
349 die "commands is not an array" if ref($commands) ne "ARRAY";
351 foreach my $cmd (@$commands) {
353 die "$cmd is not a valid command" if (ref($cmd) ne "HASH" || !$cmd->{path
} || !$cmd->{method});
357 my $path = "nodes/$param->{node}/$cmd->{path}";
360 my ($handler, $info) = PVE
::API2-
>find_handler($cmd->{method}, $path, $uri_param);
361 if (!$handler || !$info) {
362 die "no handler for '$path'\n";
365 foreach my $p (keys %{$cmd->{args
}}) {
366 raise_param_exc
({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
367 $uri_param->{$p} = $cmd->{args
}->{$p};
370 # check access permissions
371 $rpcenv->check_api2_permissions($info->{permissions
}, $user, $uri_param);
375 data
=> $handler->handle($info, $uri_param),
379 my $resp = { status
=> HTTP_INTERNAL_SERVER_ERROR
};
380 if (ref($err) eq "PVE::Exception") {
381 $resp->{status
} = $err->{code
} if $err->{code
};
382 $resp->{errors
} = $err->{errors
} if $err->{errors
};
383 $resp->{message
} = $err->{msg
};
385 $resp->{message
} = $err;
395 __PACKAGE__-
>register_method({
400 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
403 description
=> "Reboot or shutdown a node.",
406 additionalProperties
=> 0,
408 node
=> get_standard_option
('pve-node'),
410 description
=> "Specify the command.",
412 enum
=> [qw(reboot shutdown)],
416 returns
=> { type
=> "null" },
420 if ($param->{command
} eq 'reboot') {
421 system ("(sleep 2;/sbin/reboot)&");
422 } elsif ($param->{command
} eq 'shutdown') {
423 system ("(sleep 2;/sbin/poweroff)&");
430 __PACKAGE__-
>register_method({
434 protected
=> 1, # fixme: can we avoid that?
436 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
438 description
=> "Read node RRD statistics (returns PNG)",
440 additionalProperties
=> 0,
442 node
=> get_standard_option
('pve-node'),
444 description
=> "Specify the time frame you are interested in.",
446 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
449 description
=> "The list of datasources you want to display.",
450 type
=> 'string', format
=> 'pve-configid-list',
453 description
=> "The RRD consolidation function",
455 enum
=> [ 'AVERAGE', 'MAX' ],
463 filename
=> { type
=> 'string' },
469 return PVE
::Cluster
::create_rrd_graph
(
470 "pve2-node/$param->{node}", $param->{timeframe
},
471 $param->{ds
}, $param->{cf
});
475 __PACKAGE__-
>register_method({
479 protected
=> 1, # fixme: can we avoid that?
481 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
483 description
=> "Read node RRD statistics",
485 additionalProperties
=> 0,
487 node
=> get_standard_option
('pve-node'),
489 description
=> "Specify the time frame you are interested in.",
491 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
494 description
=> "The RRD consolidation function",
496 enum
=> [ 'AVERAGE', 'MAX' ],
511 return PVE
::Cluster
::create_rrd_data
(
512 "pve2-node/$param->{node}", $param->{timeframe
}, $param->{cf
});
515 __PACKAGE__-
>register_method({
519 description
=> "Read system log",
522 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
526 additionalProperties
=> 0,
528 node
=> get_standard_option
('pve-node'),
541 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
542 description
=> "Display all log since this date-time string.",
547 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
548 description
=> "Display all log until this date-time string.",
559 description
=> "Line number",
563 description
=> "Line text",
572 my $rpcenv = PVE
::RPCEnvironment
::get
();
573 my $user = $rpcenv->get_user();
574 my $node = $param->{node
};
576 my ($count, $lines) = PVE
::Tools
::dump_journal
($param->{start
}, $param->{limit
},
577 $param->{since
}, $param->{until});
579 $rpcenv->set_result_attrib('total', $count);
586 __PACKAGE__-
>register_method ({
592 description
=> "Restricted to users on realm 'pam'",
593 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
595 description
=> "Creates a VNC Shell proxy.",
597 additionalProperties
=> 0,
599 node
=> get_standard_option
('pve-node'),
602 description
=> "Run 'apt-get dist-upgrade' instead of normal shell.",
609 description
=> "use websocket instead of standard vnc.",
614 additionalProperties
=> 0,
616 user
=> { type
=> 'string' },
617 ticket
=> { type
=> 'string' },
618 cert
=> { type
=> 'string' },
619 port
=> { type
=> 'integer' },
620 upid
=> { type
=> 'string' },
626 my $rpcenv = PVE
::RPCEnvironment
::get
();
628 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
630 raise_perm_exc
("realm != pam") if $realm ne 'pam';
632 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
634 my $node = $param->{node
};
636 my $authpath = "/nodes/$node";
638 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
640 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
643 my ($remip, $family);
645 if ($node ne PVE
::INotify
::nodename
()) {
646 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
648 $family = PVE
::Tools
::get_host_address_family
($node);
651 my $port = PVE
::Tools
::next_vnc_port
($family);
653 # NOTE: vncterm VNC traffic is already TLS encrypted,
654 # so we select the fastest chipher here (or 'none'?)
655 my $remcmd = $remip ?
656 ['/usr/bin/ssh', '-t', $remip] : [];
660 if ($user eq 'root@pam') {
661 if ($param->{upgrade
}) {
662 my $upgradecmd = "pveupgrade --shell";
663 $upgradecmd = PVE
::Tools
::shellquote
($upgradecmd) if $remip;
664 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
666 $shcmd = [ '/bin/bash', '-l' ];
669 $shcmd = [ '/bin/login' ];
674 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
675 '-timeout', $timeout, '-authpath', $authpath,
676 '-perm', 'Sys.Console'];
678 if ($param->{websocket
}) {
679 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
680 push @$cmd, '-notls', '-listen', 'localhost';
683 push @$cmd, '-c', @$remcmd, @$shcmd;
688 syslog
('info', "starting vnc proxy $upid\n");
690 my $cmdstr = join (' ', @$cmd);
691 syslog
('info', "launch command: $cmdstr");
694 foreach my $k (keys %ENV) {
695 next if $k eq 'PVE_VNC_TICKET';
696 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME';
701 PVE
::Tools
::run_command
($cmd, errmsg
=> "vncterm failed");
704 syslog
('err', $err);
710 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
712 PVE
::Tools
::wait_for_vnc_port
($port);
723 __PACKAGE__-
>register_method({
724 name
=> 'vncwebsocket',
725 path
=> 'vncwebsocket',
728 description
=> "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
729 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
731 description
=> "Opens a weksocket for VNC traffic.",
733 additionalProperties
=> 0,
735 node
=> get_standard_option
('pve-node'),
737 description
=> "Ticket from previous call to vncproxy.",
742 description
=> "Port number returned by previous vncproxy call.",
752 port
=> { type
=> 'string' },
758 my $rpcenv = PVE
::RPCEnvironment
::get
();
760 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
762 raise_perm_exc
("realm != pam") if $realm ne 'pam';
764 my $authpath = "/nodes/$param->{node}";
766 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $user, $authpath);
768 my $port = $param->{port
};
770 return { port
=> $port };
773 __PACKAGE__-
>register_method ({
774 name
=> 'spiceshell',
775 path
=> 'spiceshell',
780 description
=> "Restricted to users on realm 'pam'",
781 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
783 description
=> "Creates a SPICE shell.",
785 additionalProperties
=> 0,
787 node
=> get_standard_option
('pve-node'),
788 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
791 description
=> "Run 'apt-get dist-upgrade' instead of normal shell.",
797 returns
=> get_standard_option
('remote-viewer-config'),
801 my $rpcenv = PVE
::RPCEnvironment
::get
();
802 my $authuser = $rpcenv->get_user();
804 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($authuser);
806 raise_perm_exc
("realm != pam") if $realm ne 'pam';
808 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
810 my $node = $param->{node
};
811 my $proxy = $param->{proxy
};
813 my $authpath = "/nodes/$node";
814 my $permissions = 'Sys.Console';
818 if ($user eq 'root@pam') {
819 if ($param->{upgrade
}) {
820 my $upgradecmd = "pveupgrade --shell";
821 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
823 $shcmd = [ '/bin/bash', '-l' ];
826 $shcmd = [ '/bin/login' ];
829 my $title = "Shell on '$node'";
831 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
834 __PACKAGE__-
>register_method({
839 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
841 description
=> "Read DNS settings.",
844 additionalProperties
=> 0,
846 node
=> get_standard_option
('pve-node'),
851 additionalProperties
=> 0,
854 description
=> "Search domain for host-name lookup.",
859 description
=> 'First name server IP address.',
864 description
=> 'Second name server IP address.',
869 description
=> 'Third name server IP address.',
878 my $res = PVE
::INotify
::read_file
('resolvconf');
883 __PACKAGE__-
>register_method({
884 name
=> 'update_dns',
887 description
=> "Write DNS settings.",
889 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
894 additionalProperties
=> 0,
896 node
=> get_standard_option
('pve-node'),
898 description
=> "Search domain for host-name lookup.",
902 description
=> 'First name server IP address.',
903 type
=> 'string', format
=> 'ip',
907 description
=> 'Second name server IP address.',
908 type
=> 'string', format
=> 'ip',
912 description
=> 'Third name server IP address.',
913 type
=> 'string', format
=> 'ip',
918 returns
=> { type
=> "null" },
922 PVE
::INotify
::update_file
('resolvconf', $param);
927 __PACKAGE__-
>register_method({
932 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
934 description
=> "Read server time and time zone settings.",
937 additionalProperties
=> 0,
939 node
=> get_standard_option
('pve-node'),
944 additionalProperties
=> 0,
947 description
=> "Time zone",
951 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
953 minimum
=> 1297163644,
956 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
958 minimum
=> 1297163644,
966 my $ltime = timegm_nocheck
(localtime($ctime));
968 timezone
=> PVE
::INotify
::read_file
('timezone'),
976 __PACKAGE__-
>register_method({
977 name
=> 'set_timezone',
980 description
=> "Set time zone.",
982 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
987 additionalProperties
=> 0,
989 node
=> get_standard_option
('pve-node'),
991 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
996 returns
=> { type
=> "null" },
1000 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
1005 __PACKAGE__-
>register_method({
1012 description
=> "Get list of appliances.",
1015 additionalProperties
=> 0,
1017 node
=> get_standard_option
('pve-node'),
1032 my $list = PVE
::APLInfo
::load_data
();
1034 foreach my $template (keys %{$list->{all
}}) {
1035 my $pd = $list->{all
}->{$template};
1036 next if $pd->{'package'} eq 'pve-web-news';
1043 __PACKAGE__-
>register_method({
1044 name
=> 'apl_download',
1048 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1050 description
=> "Download appliance templates.",
1054 additionalProperties
=> 0,
1056 node
=> get_standard_option
('pve-node'),
1057 storage
=> get_standard_option
('pve-storage-id', {
1058 description
=> "Only list status for specified storage",
1059 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1061 template
=> { type
=> 'string',
1062 description
=> "The template wich will downloaded",
1064 completion
=> \
&complete_templet_repo
,
1068 returns
=> { type
=> "string" },
1072 my $rpcenv = PVE
::RPCEnvironment
::get
();
1074 my $user = $rpcenv->get_user();
1076 my $node = $param->{node
};
1078 my $list = PVE
::APLInfo
::load_data
();
1080 my $template = $param->{template
};
1081 my $pd = $list->{all
}->{$template};
1083 raise_param_exc
({ template
=> "no such template"}) if !$pd;
1085 my $cfg = PVE
::Storage
::config
();
1086 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
1088 die "cannot download to storage type '$scfg->{type}'"
1089 if !($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs');
1091 die "unknown template type '$pd->{type}'\n"
1092 if !($pd->{type
} eq 'openvz' || $pd->{type
} eq 'lxc');
1094 die "storage '$param->{storage}' does not support templates\n"
1095 if !$scfg->{content
}->{vztmpl
};
1097 my $src = $pd->{location
};
1098 my $tmpldir = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
1099 my $dest = "$tmpldir/$template";
1100 my $tmpdest = "$tmpldir/${template}.tmp.$$";
1105 print "starting template download from: $src\n";
1106 print "target file: $dest\n";
1111 my $md5 = (split (/\s/, `md5sum '$dest'`))[0];
1113 if ($md5 && (lc($md5) eq lc($pd->{md5sum
}))) {
1114 print "file already exists $md5 - no need to download\n";
1120 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1121 if ($dccfg->{http_proxy
}) {
1122 $ENV{http_proxy
} = $dccfg->{http_proxy
};
1125 my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
1126 if (system (@cmd) != 0) {
1127 die "download failed - $!\n";
1130 my $md5 = (split (/\s/, `md5sum '$tmpdest'`))[0];
1132 if (!$md5 || (lc($md5) ne lc($pd->{md5sum
}))) {
1133 die "wrong checksum: $md5 != $pd->{md5sum}\n";
1136 if (!rename($tmpdest, $dest)) {
1137 die "unable to save file - $!\n";
1149 print "download finished\n";
1152 return $rpcenv->fork_worker('download', undef, $user, $worker);
1155 __PACKAGE__-
>register_method({
1160 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1163 description
=> "Gather various systems information about a node",
1166 additionalProperties
=> 0,
1168 node
=> get_standard_option
('pve-node'),
1175 return PVE
::Report
::generate
();
1178 my $get_start_stop_list = sub {
1179 my ($nodename, $autostart) = @_;
1181 my $vmlist = PVE
::Cluster
::get_vmlist
();
1184 foreach my $vmid (keys %{$vmlist->{ids
}}) {
1185 my $d = $vmlist->{ids
}->{$vmid};
1189 return if $d->{node
} ne $nodename;
1191 my $bootorder = LONG_MAX
;
1194 if ($d->{type
} eq 'lxc') {
1195 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1196 } elsif ($d->{type
} eq 'qemu') {
1197 $conf = PVE
::QemuConfig-
>load_config($vmid);
1199 die "unknown VM type '$d->{type}'\n";
1202 return if $autostart && !$conf->{onboot
};
1204 if ($conf->{startup
}) {
1205 $startup = PVE
::JSONSchema
::pve_parse_startup_order
($conf->{startup
});
1206 $startup->{order
} = $bootorder if !defined($startup->{order
});
1208 $startup = { order
=> $bootorder };
1211 $resList->{$startup->{order
}}->{$vmid} = $startup;
1212 $resList->{$startup->{order
}}->{$vmid}->{type
} = $d->{type
};
1220 __PACKAGE__-
>register_method ({
1226 check
=> ['perm', '/', [ 'VM.PowerMgmt' ]],
1229 description
=> "Start all VMs and containers (when onboot=1).",
1231 additionalProperties
=> 0,
1233 node
=> get_standard_option
('pve-node'),
1237 description
=> "force if onboot=0.",
1247 my $rpcenv = PVE
::RPCEnvironment
::get
();
1248 my $authuser = $rpcenv->get_user();
1250 my $nodename = $param->{node
};
1251 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1253 my $force = $param->{force
};
1257 $rpcenv->{type
} = 'priv'; # to start tasks in background
1259 # wait up to 60 seconds for quorum
1260 for (my $i = 60; $i >= 0; $i--) {
1261 last if PVE
::Cluster
::check_cfs_quorum
($i != 0 ?
1 : 0);
1264 my $autostart = $force ?
undef : 1;
1265 my $startList = &$get_start_stop_list($nodename, $autostart);
1267 # Note: use numeric sorting with <=>
1268 foreach my $order (sort {$a <=> $b} keys %$startList) {
1269 my $vmlist = $startList->{$order};
1271 foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
1272 my $d = $vmlist->{$vmid};
1276 if ($d->{type
} eq 'lxc') {
1277 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1278 next if PVE
::LXC
::Config-
>is_template($conf);
1279 } elsif ($d->{type
} eq 'qemu') {
1280 $conf = PVE
::QemuConfig-
>load_config($vmid);
1281 next if PVE
::QemuConfig-
>is_template($conf);
1283 die "unknown VM type '$d->{type}'\n";
1286 # skip ha managed VMs (started by pve-ha-manager)
1287 next if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1289 PVE
::Cluster
::check_cfs_quorum
(); # abort when we loose quorum
1292 my $default_delay = 0;
1295 if ($d->{type
} eq 'lxc') {
1296 return if PVE
::LXC
::check_running
($vmid);
1297 print STDERR
"Starting CT $vmid\n";
1298 $upid = PVE
::API2
::LXC
::Status-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1299 } elsif ($d->{type
} eq 'qemu') {
1300 $default_delay = 3; # to reduce load
1301 return if PVE
::QemuServer
::check_running
($vmid, 1);
1302 print STDERR
"Starting VM $vmid\n";
1303 $upid = PVE
::API2
::Qemu-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1305 die "unknown VM type '$d->{type}'\n";
1308 my $res = PVE
::Tools
::upid_decode
($upid);
1309 while (PVE
::ProcFSTools
::check_process_running
($res->{pid
})) {
1313 my $status = PVE
::Tools
::upid_read_status
($upid);
1314 if ($status eq 'OK') {
1315 # use default delay to reduce load
1316 my $delay = defined($d->{up
}) ?
int($d->{up
}) : $default_delay;
1318 print STDERR
"Waiting for $delay seconds (startup delay)\n" if $d->{up
};
1319 for (my $i = 0; $i < $delay; $i++) {
1324 if ($d->{type
} eq 'lxc') {
1325 print STDERR
"Starting CT $vmid failed: $status\n";
1326 } elsif ($d->{type
} eq 'qemu') {
1327 print STDERR
"Starting VM $vmid failed: status\n";
1337 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1340 my $create_stop_worker = sub {
1341 my ($nodename, $type, $vmid, $down_timeout) = @_;
1344 if ($type eq 'lxc') {
1345 return if !PVE
::LXC
::check_running
($vmid);
1346 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60;
1347 print STDERR
"Stopping CT $vmid (timeout = $timeout seconds)\n";
1348 $upid = PVE
::API2
::LXC
::Status-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1349 timeout
=> $timeout, forceStop
=> 1 });
1350 } elsif ($type eq 'qemu') {
1351 return if !PVE
::QemuServer
::check_running
($vmid, 1);
1352 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60*3;
1353 print STDERR
"Stopping VM $vmid (timeout = $timeout seconds)\n";
1354 $upid = PVE
::API2
::Qemu-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1355 timeout
=> $timeout, forceStop
=> 1 });
1357 die "unknown VM type '$type'\n";
1363 __PACKAGE__-
>register_method ({
1369 check
=> ['perm', '/', [ 'VM.PowerMgmt' ]],
1372 description
=> "Stop all VMs and Containers.",
1374 additionalProperties
=> 0,
1376 node
=> get_standard_option
('pve-node'),
1385 my $rpcenv = PVE
::RPCEnvironment
::get
();
1386 my $authuser = $rpcenv->get_user();
1388 my $nodename = $param->{node
};
1389 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1393 $rpcenv->{type
} = 'priv'; # to start tasks in background
1395 my $stopList = &$get_start_stop_list($nodename);
1397 my $cpuinfo = PVE
::ProcFSTools
::read_cpuinfo
();
1398 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
1399 # if not set by user spawn max cpu count number of workers
1400 my $maxWorkers = $datacenterconfig->{max_workers
} || $cpuinfo->{cpus
};
1402 foreach my $order (sort {$b <=> $a} keys %$stopList) {
1403 my $vmlist = $stopList->{$order};
1406 my $finish_worker = sub {
1408 my $d = $workers->{$pid};
1410 delete $workers->{$pid};
1412 syslog
('info', "end task $d->{upid}");
1415 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1416 # skip ha managed VMs (stopped by pve-ha-manager)
1417 next if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1419 my $d = $vmlist->{$vmid};
1421 eval { $upid = &$create_stop_worker($nodename, $d->{type
}, $vmid, $d->{down
}); };
1425 my $res = PVE
::Tools
::upid_decode
($upid, 1);
1428 my $pid = $res->{pid
};
1430 $workers->{$pid} = { type
=> $d->{type
}, upid
=> $upid, vmid
=> $vmid };
1431 while (scalar(keys %$workers) >= $maxWorkers) {
1432 foreach my $p (keys %$workers) {
1433 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1434 &$finish_worker($p);
1440 while (scalar(keys %$workers)) {
1441 foreach my $p (keys %$workers) {
1442 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1443 &$finish_worker($p);
1450 syslog
('info', "all VMs and CTs stopped");
1455 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
1458 my $create_migrate_worker = sub {
1459 my ($nodename, $type, $vmid, $target) = @_;
1462 if ($type eq 'lxc') {
1463 my $online = PVE
::LXC
::check_running
($vmid) ?
1 : 0;
1464 print STDERR
"Migrating CT $vmid\n";
1465 $upid = PVE
::API2
::LXC-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1466 online
=> $online });
1467 } elsif ($type eq 'qemu') {
1468 my $online = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
1469 print STDERR
"Migrating VM $vmid\n";
1470 $upid = PVE
::API2
::Qemu-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1471 online
=> $online });
1473 die "unknown VM type '$type'\n";
1476 my $res = PVE
::Tools
::upid_decode
($upid);
1481 __PACKAGE__-
>register_method ({
1482 name
=> 'migrateall',
1483 path
=> 'migrateall',
1488 check
=> ['perm', '/', [ 'VM.Migrate' ]],
1490 description
=> "Migrate all VMs and Containers.",
1492 additionalProperties
=> 0,
1494 node
=> get_standard_option
('pve-node'),
1495 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1497 description
=> "Maximal number of parallel migration job." .
1498 " If not set use 'max_workers' from datacenter.cfg," .
1499 " one of both must be set!",
1512 my $rpcenv = PVE
::RPCEnvironment
::get
();
1513 my $authuser = $rpcenv->get_user();
1515 my $nodename = $param->{node
};
1516 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1518 my $target = $param->{target
};
1520 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
1521 # prefer parameter over datacenter cfg settings
1522 my $maxWorkers = $param->{maxworkers
} || $datacenterconfig->{max_workers
} ||
1523 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
1527 $rpcenv->{type
} = 'priv'; # to start tasks in background
1529 my $migrateList = &$get_start_stop_list($nodename);
1531 foreach my $order (sort {$b <=> $a} keys %$migrateList) {
1532 my $vmlist = $migrateList->{$order};
1534 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1535 my $d = $vmlist->{$vmid};
1537 eval { $pid = &$create_migrate_worker($nodename, $d->{type
}, $vmid, $target); };
1541 $workers->{$pid} = 1;
1542 while (scalar(keys %$workers) >= $maxWorkers) {
1543 foreach my $p (keys %$workers) {
1544 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1545 delete $workers->{$p};
1551 while (scalar(keys %$workers)) {
1552 foreach my $p (keys %$workers) {
1553 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1554 delete $workers->{$p};
1563 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
1567 # bash completion helper
1569 sub complete_templet_repo
{
1570 my ($cmdname, $pname, $cvalue) = @_;
1572 my $repo = PVE
::APLInfo
::load_data
();
1574 foreach my $templ (keys %{$repo->{all
}}) {
1575 next if $templ !~ m/^$cvalue/;
1582 package PVE
::API2
::Nodes
;
1587 use PVE
::SafeSyslog
;
1589 use PVE
::RESTHandler
;
1590 use PVE
::RPCEnvironment
;
1593 use base
qw(PVE::RESTHandler);
1595 __PACKAGE__-
>register_method ({
1596 subclass
=> "PVE::API2::Nodes::Nodeinfo",
1600 __PACKAGE__-
>register_method ({
1604 permissions
=> { user
=> 'all' },
1605 description
=> "Cluster node index.",
1607 additionalProperties
=> 0,
1616 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
1621 my $clinfo = PVE
::Cluster
::get_clinfo
();
1624 my $nodelist = PVE
::Cluster
::get_nodelist
();
1625 my $members = PVE
::Cluster
::get_members
();
1626 my $rrd = PVE
::Cluster
::rrd_dump
();
1628 foreach my $node (@$nodelist) {
1629 my $entry = PVE
::API2Tools
::extract_node_stats
($node, $members, $rrd);