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);
18 use PVE
::RPCEnvironment
;
19 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::AccessControl
;
27 use PVE
::API2
::Subscription
;
28 use PVE
::API2
::Services
;
29 use PVE
::API2
::Network
;
31 use PVE
::API2
::Storage
::Scan
;
32 use PVE
::API2
::Storage
::Status
;
35 use PVE
::API2
::LXC
::Status
;
36 use PVE
::API2
::VZDump
;
39 use PVE
::API2
::Firewall
::Host
;
42 use base
qw(PVE::RESTHandler);
44 __PACKAGE__-
>register_method ({
45 subclass
=> "PVE::API2::Qemu",
49 __PACKAGE__-
>register_method ({
50 subclass
=> "PVE::API2::LXC",
54 __PACKAGE__-
>register_method ({
55 subclass
=> "PVE::API2::Ceph",
59 __PACKAGE__-
>register_method ({
60 subclass
=> "PVE::API2::VZDump",
64 __PACKAGE__-
>register_method ({
65 subclass
=> "PVE::API2::Services",
69 __PACKAGE__-
>register_method ({
70 subclass
=> "PVE::API2::Subscription",
71 path
=> 'subscription',
74 __PACKAGE__-
>register_method ({
75 subclass
=> "PVE::API2::Network",
79 __PACKAGE__-
>register_method ({
80 subclass
=> "PVE::API2::Tasks",
84 __PACKAGE__-
>register_method ({
85 subclass
=> "PVE::API2::Storage::Scan",
89 __PACKAGE__-
>register_method ({
90 subclass
=> "PVE::API2::Storage::Status",
94 __PACKAGE__-
>register_method ({
95 subclass
=> "PVE::API2::APT",
99 __PACKAGE__-
>register_method ({
100 subclass
=> "PVE::API2::Firewall::Host",
104 __PACKAGE__-
>register_method ({
108 permissions
=> { user
=> 'all' },
109 description
=> "Node index.",
111 additionalProperties
=> 0,
113 node
=> get_standard_option
('pve-node'),
122 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
130 { name
=> 'version' },
131 { name
=> 'syslog' },
132 { name
=> 'status' },
133 { name
=> 'subscription' },
135 { name
=> 'rrd' }, # fixme: remove?
136 { name
=> 'rrddata' },# fixme: remove?
137 { name
=> 'vncshell' },
138 { name
=> 'spiceshell' },
141 { name
=> 'services' },
143 { name
=> 'storage' },
146 { name
=> 'vzdump' },
147 { name
=> 'network' },
148 { name
=> 'aplinfo' },
149 { name
=> 'startall' },
150 { name
=> 'stopall' },
151 { name
=> 'netstat' },
152 { name
=> 'firewall' },
158 __PACKAGE__-
>register_method ({
163 permissions
=> { user
=> 'all' },
164 description
=> "API version details",
166 additionalProperties
=> 0,
168 node
=> get_standard_option
('pve-node'),
174 version
=> { type
=> 'string' },
175 release
=> { type
=> 'string' },
176 repoid
=> { type
=> 'string' },
180 my ($resp, $param) = @_;
182 return PVE
::pvecfg
::version_info
();
185 __PACKAGE__-
>register_method({
190 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
192 description
=> "Read node status",
195 additionalProperties
=> 0,
197 node
=> get_standard_option
('pve-node'),
214 my ($uptime, $idle) = PVE
::ProcFSTools
::read_proc_uptime
();
215 $res->{uptime
} = $uptime;
217 my ($avg1, $avg5, $avg15) = PVE
::ProcFSTools
::read_loadavg
();
218 $res->{loadavg
} = [ $avg1, $avg5, $avg15];
220 my ($sysname, $nodename, $release, $version, $machine) = POSIX
::uname
();
222 $res->{kversion
} = "$sysname $release $version";
224 $res->{cpuinfo
} = PVE
::ProcFSTools
::read_cpuinfo
();
226 my $stat = PVE
::ProcFSTools
::read_proc_stat
();
227 $res->{cpu
} = $stat->{cpu
};
228 $res->{wait} = $stat->{wait};
230 my $meminfo = PVE
::ProcFSTools
::read_meminfo
();
232 free
=> $meminfo->{memfree
},
233 total
=> $meminfo->{memtotal
},
234 used
=> $meminfo->{memused
},
238 shared
=> $meminfo->{memshared
},
242 free
=> $meminfo->{swapfree
},
243 total
=> $meminfo->{swaptotal
},
244 used
=> $meminfo->{swapused
},
247 $res->{pveversion
} = PVE
::pvecfg
::package() . "/" .
248 PVE
::pvecfg
::version_text
();
250 my $dinfo = df
('/', 1); # output is bytes
253 total
=> $dinfo->{blocks
},
254 avail
=> $dinfo->{bavail
},
255 used
=> $dinfo->{used
},
256 free
=> $dinfo->{bavail
} - $dinfo->{used
},
262 __PACKAGE__-
>register_method({
267 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
269 description
=> "Read tap/vm network device interface counters",
272 additionalProperties
=> 0,
274 node
=> get_standard_option
('pve-node'),
289 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
290 foreach my $dev (keys %$netdev) {
291 next if $dev !~ m/^tap([1-9]\d*)i(\d+)$/;
300 in => $netdev->{$dev}->{transmit
},
301 out
=> $netdev->{$dev}->{receive
},
309 __PACKAGE__-
>register_method({
314 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
316 description
=> "Execute multiple commands in order.",
318 protected
=> 1, # avoid problems with proxy code
320 additionalProperties
=> 0,
322 node
=> get_standard_option
('pve-node'),
324 description
=> "JSON encoded array of commands.",
339 my $rpcenv = PVE
::RPCEnvironment
::get
();
340 my $user = $rpcenv->get_user();
342 my $commands = eval { decode_json
($param->{commands
}); };
344 die "commands param did not contain valid JSON: $@" if $@;
345 die "commands is not an array" if ref($commands) ne "ARRAY";
347 foreach my $cmd (@$commands) {
349 die "$cmd is not a valid command" if (ref($cmd) ne "HASH" || !$cmd->{path
} || !$cmd->{method});
353 my $path = "nodes/$param->{node}/$cmd->{path}";
356 my ($handler, $info) = PVE
::API2-
>find_handler($cmd->{method}, $path, $uri_param);
357 if (!$handler || !$info) {
358 die "no handler for '$path'\n";
361 foreach my $p (keys %{$cmd->{args
}}) {
362 raise_param_exc
({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
363 $uri_param->{$p} = $cmd->{args
}->{$p};
366 # check access permissions
367 $rpcenv->check_api2_permissions($info->{permissions
}, $user, $uri_param);
371 data
=> $handler->handle($info, $uri_param),
375 my $resp = { status
=> HTTP_INTERNAL_SERVER_ERROR
};
376 if (ref($err) eq "PVE::Exception") {
377 $resp->{status
} = $err->{code
} if $err->{code
};
378 $resp->{errors
} = $err->{errors
} if $err->{errors
};
379 $resp->{message
} = $err->{msg
};
381 $resp->{message
} = $err;
391 __PACKAGE__-
>register_method({
396 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
399 description
=> "Reboot or shutdown a node.",
402 additionalProperties
=> 0,
404 node
=> get_standard_option
('pve-node'),
406 description
=> "Specify the command.",
408 enum
=> [qw(reboot shutdown)],
412 returns
=> { type
=> "null" },
416 if ($param->{command
} eq 'reboot') {
417 system ("(sleep 2;/sbin/reboot)&");
418 } elsif ($param->{command
} eq 'shutdown') {
419 system ("(sleep 2;/sbin/poweroff)&");
426 __PACKAGE__-
>register_method({
430 protected
=> 1, # fixme: can we avoid that?
432 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
434 description
=> "Read node RRD statistics (returns PNG)",
436 additionalProperties
=> 0,
438 node
=> get_standard_option
('pve-node'),
440 description
=> "Specify the time frame you are interested in.",
442 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
445 description
=> "The list of datasources you want to display.",
446 type
=> 'string', format
=> 'pve-configid-list',
449 description
=> "The RRD consolidation function",
451 enum
=> [ 'AVERAGE', 'MAX' ],
459 filename
=> { type
=> 'string' },
465 return PVE
::Cluster
::create_rrd_graph
(
466 "pve2-node/$param->{node}", $param->{timeframe
},
467 $param->{ds
}, $param->{cf
});
471 __PACKAGE__-
>register_method({
475 protected
=> 1, # fixme: can we avoid that?
477 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
479 description
=> "Read node RRD statistics",
481 additionalProperties
=> 0,
483 node
=> get_standard_option
('pve-node'),
485 description
=> "Specify the time frame you are interested in.",
487 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
490 description
=> "The RRD consolidation function",
492 enum
=> [ 'AVERAGE', 'MAX' ],
507 return PVE
::Cluster
::create_rrd_data
(
508 "pve2-node/$param->{node}", $param->{timeframe
}, $param->{cf
});
511 __PACKAGE__-
>register_method({
515 description
=> "Read system log",
518 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
522 additionalProperties
=> 0,
524 node
=> get_standard_option
('pve-node'),
543 description
=> "Line number",
547 description
=> "Line text",
556 my $rpcenv = PVE
::RPCEnvironment
::get
();
557 my $user = $rpcenv->get_user();
558 my $node = $param->{node
};
560 my ($count, $lines) = PVE
::Tools
::dump_journal
($param->{start
}, $param->{limit
});
562 $rpcenv->set_result_attrib('total', $count);
569 __PACKAGE__-
>register_method ({
575 description
=> "Restricted to users on realm 'pam'",
576 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
578 description
=> "Creates a VNC Shell proxy.",
580 additionalProperties
=> 0,
582 node
=> get_standard_option
('pve-node'),
585 description
=> "Run 'apt-get dist-upgrade' instead of normal shell.",
592 description
=> "use websocket instead of standard vnc.",
597 additionalProperties
=> 0,
599 user
=> { type
=> 'string' },
600 ticket
=> { type
=> 'string' },
601 cert
=> { type
=> 'string' },
602 port
=> { type
=> 'integer' },
603 upid
=> { type
=> 'string' },
609 my $rpcenv = PVE
::RPCEnvironment
::get
();
611 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
613 raise_perm_exc
("realm != pam") if $realm ne 'pam';
615 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
617 my $node = $param->{node
};
619 my $authpath = "/nodes/$node";
621 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
623 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
626 my ($remip, $family);
628 if ($node ne PVE
::INotify
::nodename
()) {
629 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
631 $family = PVE
::Tools
::get_host_address_family
($node);
634 my $port = PVE
::Tools
::next_vnc_port
($family);
636 # NOTE: vncterm VNC traffic is already TLS encrypted,
637 # so we select the fastest chipher here (or 'none'?)
638 my $remcmd = $remip ?
639 ['/usr/bin/ssh', '-t', $remip] : [];
643 if ($user eq 'root@pam') {
644 if ($param->{upgrade
}) {
645 my $upgradecmd = "pveupgrade --shell";
646 $upgradecmd = PVE
::Tools
::shellquote
($upgradecmd) if $remip;
647 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
649 $shcmd = [ '/bin/bash', '-l' ];
652 $shcmd = [ '/bin/login' ];
657 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
658 '-timeout', $timeout, '-authpath', $authpath,
659 '-perm', 'Sys.Console'];
661 if ($param->{websocket
}) {
662 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
663 push @$cmd, '-notls', '-listen', 'localhost';
666 push @$cmd, '-c', @$remcmd, @$shcmd;
671 syslog
('info', "starting vnc proxy $upid\n");
673 my $cmdstr = join (' ', @$cmd);
674 syslog
('info', "launch command: $cmdstr");
677 foreach my $k (keys %ENV) {
678 next if $k eq 'PVE_VNC_TICKET';
679 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME';
684 PVE
::Tools
::run_command
($cmd, errmsg
=> "vncterm failed");
687 syslog
('err', $err);
693 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
695 PVE
::Tools
::wait_for_vnc_port
($port);
706 __PACKAGE__-
>register_method({
707 name
=> 'vncwebsocket',
708 path
=> 'vncwebsocket',
711 description
=> "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
712 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
714 description
=> "Opens a weksocket for VNC traffic.",
716 additionalProperties
=> 0,
718 node
=> get_standard_option
('pve-node'),
720 description
=> "Ticket from previous call to vncproxy.",
725 description
=> "Port number returned by previous vncproxy call.",
735 port
=> { type
=> 'string' },
741 my $rpcenv = PVE
::RPCEnvironment
::get
();
743 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
745 raise_perm_exc
("realm != pam") if $realm ne 'pam';
747 my $authpath = "/nodes/$param->{node}";
749 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $user, $authpath);
751 my $port = $param->{port
};
753 return { port
=> $port };
756 __PACKAGE__-
>register_method ({
757 name
=> 'spiceshell',
758 path
=> 'spiceshell',
763 description
=> "Restricted to users on realm 'pam'",
764 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
766 description
=> "Creates a SPICE shell.",
768 additionalProperties
=> 0,
770 node
=> get_standard_option
('pve-node'),
771 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
774 description
=> "Run 'apt-get dist-upgrade' instead of normal shell.",
780 returns
=> get_standard_option
('remote-viewer-config'),
784 my $rpcenv = PVE
::RPCEnvironment
::get
();
785 my $authuser = $rpcenv->get_user();
787 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($authuser);
789 raise_perm_exc
("realm != pam") if $realm ne 'pam';
791 raise_perm_exc
('user != root@pam') if $param->{upgrade
} && $user ne 'root@pam';
793 my $node = $param->{node
};
794 my $proxy = $param->{proxy
};
796 my $authpath = "/nodes/$node";
797 my $permissions = 'Sys.Console';
801 if ($user eq 'root@pam') {
802 if ($param->{upgrade
}) {
803 my $upgradecmd = "pveupgrade --shell";
804 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
806 $shcmd = [ '/bin/bash', '-l' ];
809 $shcmd = [ '/bin/login' ];
812 my $title = "Shell on '$node'";
814 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
817 __PACKAGE__-
>register_method({
822 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
824 description
=> "Read DNS settings.",
827 additionalProperties
=> 0,
829 node
=> get_standard_option
('pve-node'),
834 additionalProperties
=> 0,
837 description
=> "Search domain for host-name lookup.",
842 description
=> 'First name server IP address.',
847 description
=> 'Second name server IP address.',
852 description
=> 'Third name server IP address.',
861 my $res = PVE
::INotify
::read_file
('resolvconf');
866 __PACKAGE__-
>register_method({
867 name
=> 'update_dns',
870 description
=> "Write DNS settings.",
872 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
877 additionalProperties
=> 0,
879 node
=> get_standard_option
('pve-node'),
881 description
=> "Search domain for host-name lookup.",
885 description
=> 'First name server IP address.',
886 type
=> 'string', format
=> 'ip',
890 description
=> 'Second name server IP address.',
891 type
=> 'string', format
=> 'ip',
895 description
=> 'Third name server IP address.',
896 type
=> 'string', format
=> 'ip',
901 returns
=> { type
=> "null" },
905 PVE
::INotify
::update_file
('resolvconf', $param);
910 __PACKAGE__-
>register_method({
915 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
917 description
=> "Read server time and time zone settings.",
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
927 additionalProperties
=> 0,
930 description
=> "Time zone",
934 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
936 minimum
=> 1297163644,
939 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
941 minimum
=> 1297163644,
949 my $ltime = timegm_nocheck
(localtime($ctime));
951 timezone
=> PVE
::INotify
::read_file
('timezone'),
959 __PACKAGE__-
>register_method({
960 name
=> 'set_timezone',
963 description
=> "Set time zone.",
965 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
970 additionalProperties
=> 0,
972 node
=> get_standard_option
('pve-node'),
974 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
979 returns
=> { type
=> "null" },
983 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
988 __PACKAGE__-
>register_method({
995 description
=> "Get list of appliances.",
998 additionalProperties
=> 0,
1000 node
=> get_standard_option
('pve-node'),
1015 my $list = PVE
::APLInfo
::load_data
();
1017 foreach my $template (keys %{$list->{all
}}) {
1018 my $pd = $list->{all
}->{$template};
1019 next if $pd->{'package'} eq 'pve-web-news';
1026 __PACKAGE__-
>register_method({
1027 name
=> 'apl_download',
1031 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1033 description
=> "Download appliance templates.",
1037 additionalProperties
=> 0,
1039 node
=> get_standard_option
('pve-node'),
1040 storage
=> get_standard_option
('pve-storage-id'),
1041 template
=> { type
=> 'string', maxLength
=> 255 },
1044 returns
=> { type
=> "string" },
1048 my $rpcenv = PVE
::RPCEnvironment
::get
();
1050 my $user = $rpcenv->get_user();
1052 my $node = $param->{node
};
1054 my $list = PVE
::APLInfo
::load_data
();
1056 my $template = $param->{template
};
1057 my $pd = $list->{all
}->{$template};
1059 raise_param_exc
({ template
=> "no such template"}) if !$pd;
1061 my $cfg = cfs_read_file
("storage.cfg");
1062 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
1064 die "cannot download to storage type '$scfg->{type}'"
1065 if !($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs');
1067 die "unknown template type '$pd->{type}'\n"
1068 if !($pd->{type
} eq 'openvz' || $pd->{type
} eq 'lxc');
1070 die "storage '$param->{storage}' does not support templates\n"
1071 if !$scfg->{content
}->{vztmpl
};
1073 my $src = $pd->{location
};
1074 my $tmpldir = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
1075 my $dest = "$tmpldir/$template";
1076 my $tmpdest = "$tmpldir/${template}.tmp.$$";
1081 print "starting template download from: $src\n";
1082 print "target file: $dest\n";
1087 my $md5 = (split (/\s/, `md5sum '$dest'`))[0];
1089 if ($md5 && (lc($md5) eq lc($pd->{md5sum
}))) {
1090 print "file already exists $md5 - no need to download\n";
1096 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1097 if ($dccfg->{http_proxy
}) {
1098 $ENV{http_proxy
} = $dccfg->{http_proxy
};
1101 my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
1102 if (system (@cmd) != 0) {
1103 die "download failed - $!\n";
1106 my $md5 = (split (/\s/, `md5sum '$tmpdest'`))[0];
1108 if (!$md5 || (lc($md5) ne lc($pd->{md5sum
}))) {
1109 die "wrong checksum: $md5 != $pd->{md5sum}\n";
1112 if (system ('mv', $tmpdest, $dest) != 0) {
1113 die "unable to save file - $!\n";
1125 print "download finished\n";
1128 return $rpcenv->fork_worker('download', undef, $user, $worker);
1132 my $get_start_stop_list = sub {
1133 my ($nodename, $autostart) = @_;
1135 my $vmlist = PVE
::Cluster
::get_vmlist
();
1138 foreach my $vmid (keys %{$vmlist->{ids
}}) {
1139 my $d = $vmlist->{ids
}->{$vmid};
1143 return if $d->{node
} ne $nodename;
1145 my $bootorder = LONG_MAX
;
1148 if ($d->{type
} eq 'lxc') {
1149 $conf = PVE
::LXC
::load_config
($vmid);
1150 } elsif ($d->{type
} eq 'qemu') {
1151 $conf = PVE
::QemuServer
::load_config
($vmid);
1153 die "unknown VM type '$d->{type}'\n";
1156 return if $autostart && !$conf->{onboot
};
1158 if ($conf->{startup
}) {
1159 $startup = PVE
::JSONSchema
::pve_parse_startup_order
($conf->{startup
});
1160 $startup->{order
} = $bootorder if !defined($startup->{order
});
1162 $startup = { order
=> $bootorder };
1165 # skip ha managed VMs (started by pve-ha-manager)
1166 return if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1168 $resList->{$startup->{order
}}->{$vmid} = $startup;
1169 $resList->{$startup->{order
}}->{$vmid}->{type
} = $d->{type
};
1177 __PACKAGE__-
>register_method ({
1183 description
=> "Start all VMs and containers (when onboot=1).",
1185 additionalProperties
=> 0,
1187 node
=> get_standard_option
('pve-node'),
1191 description
=> "force if onboot=0.",
1201 my $rpcenv = PVE
::RPCEnvironment
::get
();
1202 my $authuser = $rpcenv->get_user();
1204 my $nodename = $param->{node
};
1205 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1207 my $force = $param->{force
};
1211 $rpcenv->{type
} = 'priv'; # to start tasks in background
1213 # wait up to 10 seconds for quorum
1214 for (my $i = 10; $i >= 0; $i--) {
1215 last if PVE
::Cluster
::check_cfs_quorum
($i != 0 ?
1 : 0);
1218 my $autostart = $force ?
undef : 1;
1219 my $startList = &$get_start_stop_list($nodename, $autostart);
1221 # Note: use numeric sorting with <=>
1222 foreach my $order (sort {$a <=> $b} keys %$startList) {
1223 my $vmlist = $startList->{$order};
1225 foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
1226 my $d = $vmlist->{$vmid};
1228 PVE
::Cluster
::check_cfs_quorum
(); # abort when we loose quorum
1231 my $default_delay = 0;
1234 if ($d->{type
} eq 'lxc') {
1235 return if PVE
::LXC
::check_running
($vmid);
1236 print STDERR
"Starting CT $vmid\n";
1237 $upid = PVE
::API2
::LXC
::Status-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1238 } elsif ($d->{type
} eq 'qemu') {
1239 $default_delay = 3; # to redruce load
1240 return if PVE
::QemuServer
::check_running
($vmid, 1);
1241 print STDERR
"Starting VM $vmid\n";
1242 $upid = PVE
::API2
::Qemu-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1244 die "unknown VM type '$d->{type}'\n";
1247 my $res = PVE
::Tools
::upid_decode
($upid);
1248 while (PVE
::ProcFSTools
::check_process_running
($res->{pid
})) {
1252 my $status = PVE
::Tools
::upid_read_status
($upid);
1253 if ($status eq 'OK') {
1254 # use default delay to reduce load
1255 my $delay = defined($d->{up
}) ?
int($d->{up
}) : $default_delay;
1257 print STDERR
"Waiting for $delay seconds (startup delay)\n" if $d->{up
};
1258 for (my $i = 0; $i < $delay; $i++) {
1263 if ($d->{type
} eq 'lxc') {
1264 print STDERR
"Starting CT $vmid failed: $status\n";
1265 } elsif ($d->{type
} eq 'qemu') {
1266 print STDERR
"Starting VM $vmid failed: status\n";
1276 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1279 my $create_stop_worker = sub {
1280 my ($nodename, $type, $vmid, $down_timeout) = @_;
1283 if ($type eq 'lxc') {
1284 return if !PVE
::LXC
::check_running
($vmid);
1285 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60;
1286 print STDERR
"Stopping CT $vmid (timeout = $timeout seconds)\n";
1287 $upid = PVE
::API2
::LXC
::Status-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1288 timeout
=> $timeout, forceStop
=> 1 });
1289 } elsif ($type eq 'qemu') {
1290 return if !PVE
::QemuServer
::check_running
($vmid, 1);
1291 my $timeout = defined($down_timeout) ?
int($down_timeout) : 60*3;
1292 print STDERR
"Stopping VM $vmid (timeout = $timeout seconds)\n";
1293 $upid = PVE
::API2
::Qemu-
>vm_shutdown({node
=> $nodename, vmid
=> $vmid,
1294 timeout
=> $timeout, forceStop
=> 1 });
1296 die "unknown VM type '$type'\n";
1299 my $res = PVE
::Tools
::upid_decode
($upid);
1304 __PACKAGE__-
>register_method ({
1310 description
=> "Stop all VMs and Containers.",
1312 additionalProperties
=> 0,
1314 node
=> get_standard_option
('pve-node'),
1323 my $rpcenv = PVE
::RPCEnvironment
::get
();
1324 my $authuser = $rpcenv->get_user();
1326 my $nodename = $param->{node
};
1327 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1331 $rpcenv->{type
} = 'priv'; # to start tasks in background
1333 my $stopList = &$get_start_stop_list($nodename);
1335 my $cpuinfo = PVE
::ProcFSTools
::read_cpuinfo
();
1336 my $maxWorkers = $cpuinfo->{cpus
};
1338 foreach my $order (sort {$b <=> $a} keys %$stopList) {
1339 my $vmlist = $stopList->{$order};
1341 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1342 my $d = $vmlist->{$vmid};
1344 eval { $pid = &$create_stop_worker($nodename, $d->{type
}, $vmid, $d->{down
}); };
1348 $workers->{$pid} = 1;
1349 while (scalar(keys %$workers) >= $maxWorkers) {
1350 foreach my $p (keys %$workers) {
1351 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1352 delete $workers->{$p};
1358 while (scalar(keys %$workers)) {
1359 foreach my $p (keys %$workers) {
1360 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1361 delete $workers->{$p};
1370 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
1374 my $create_migrate_worker = sub {
1375 my ($nodename, $type, $vmid, $target) = @_;
1378 if ($type eq 'lxc') {
1379 my $online = PVE
::LXC
::check_running
($vmid) ?
1 : 0;
1380 print STDERR
"Migrating CT $vmid\n";
1381 $upid = PVE
::API2
::LXC-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1382 online
=> $online });
1383 } elsif ($type eq 'qemu') {
1384 my $online = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
1385 print STDERR
"Migrating VM $vmid\n";
1386 $upid = PVE
::API2
::Qemu-
>migrate_vm({node
=> $nodename, vmid
=> $vmid, target
=> $target,
1387 online
=> $online });
1389 die "unknown VM type '$type'\n";
1392 my $res = PVE
::Tools
::upid_decode
($upid);
1397 __PACKAGE__-
>register_method ({
1398 name
=> 'migrateall',
1399 path
=> 'migrateall',
1403 description
=> "Migrate all VMs and Containers.",
1405 additionalProperties
=> 0,
1407 node
=> get_standard_option
('pve-node'),
1408 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
1410 description
=> "Max parralel migration job.",
1422 my $rpcenv = PVE
::RPCEnvironment
::get
();
1423 my $authuser = $rpcenv->get_user();
1425 my $nodename = $param->{node
};
1426 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1428 my $target = $param->{target
};
1429 my $maxWorkers = $param->{maxworkers
};
1433 $rpcenv->{type
} = 'priv'; # to start tasks in background
1435 my $migrateList = &$get_start_stop_list($nodename);
1437 foreach my $order (sort {$b <=> $a} keys %$migrateList) {
1438 my $vmlist = $migrateList->{$order};
1440 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1441 my $d = $vmlist->{$vmid};
1443 eval { $pid = &$create_migrate_worker($nodename, $d->{type
}, $vmid, $target); };
1447 $workers->{$pid} = 1;
1448 while (scalar(keys %$workers) >= $maxWorkers) {
1449 foreach my $p (keys %$workers) {
1450 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1451 delete $workers->{$p};
1457 while (scalar(keys %$workers)) {
1458 foreach my $p (keys %$workers) {
1459 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
1460 delete $workers->{$p};
1469 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
1473 package PVE
::API2
::Nodes
;
1478 use PVE
::SafeSyslog
;
1480 use PVE
::RESTHandler
;
1481 use PVE
::RPCEnvironment
;
1484 use base
qw(PVE::RESTHandler);
1486 __PACKAGE__-
>register_method ({
1487 subclass
=> "PVE::API2::Nodes::Nodeinfo",
1491 __PACKAGE__-
>register_method ({
1495 permissions
=> { user
=> 'all' },
1496 description
=> "Cluster node index.",
1498 additionalProperties
=> 0,
1507 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
1512 my $clinfo = PVE
::Cluster
::get_clinfo
();
1515 my $nodelist = PVE
::Cluster
::get_nodelist
();
1516 my $members = PVE
::Cluster
::get_members
();
1517 my $rrd = PVE
::Cluster
::rrd_dump
();
1519 foreach my $node (@$nodelist) {
1520 my $entry = PVE
::API2Tools
::extract_node_stats
($node, $members, $rrd);