]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Cluster.pm
use utf8 encoding
[pve-manager.git] / PVE / API2 / Cluster.pm
CommitLineData
aff192e6
DM
1package PVE::API2::Cluster;
2
3use strict;
4use warnings;
5
a0af0132
DM
6use XML::Parser;
7
aff192e6
DM
8use PVE::SafeSyslog;
9use PVE::Tools qw(extract_param);
a06a3eac 10use PVE::INotify;
7cdf443c 11use PVE::Cluster qw(cfs_register_file cfs_lock_file cfs_read_file cfs_write_file);
aff192e6 12use PVE::Storage;
ac27b58d 13use PVE::API2::Backup;
a06a3eac 14use PVE::API2::HAConfig;
aff192e6 15use JSON;
aff192e6
DM
16use PVE::RESTHandler;
17use PVE::RPCEnvironment;
18
19use base qw(PVE::RESTHandler);
20
ac27b58d
DM
21__PACKAGE__->register_method ({
22 subclass => "PVE::API2::Backup",
23 path => 'backup',
24});
25
a06a3eac
DM
26__PACKAGE__->register_method ({
27 subclass => "PVE::API2::HAConfig",
28 path => 'ha',
29});
30
aff192e6
DM
31my $dc_schema = PVE::Cluster::get_datacenter_schema();
32my $dc_properties = {
33 delete => {
34 type => 'string', format => 'pve-configid-list',
35 description => "A list of settings you want to delete.",
36 optional => 1,
37 }
38};
39foreach my $opt (keys %{$dc_schema->{properties}}) {
40 $dc_properties->{$opt} = $dc_schema->{properties}->{$opt};
41}
42
43__PACKAGE__->register_method ({
44 name => 'index',
45 path => '',
46 method => 'GET',
47 description => "Cluster index.",
48 permissions => { user => 'all' },
49 parameters => {
50 additionalProperties => 0,
51 properties => {},
52 },
53 returns => {
54 type => 'array',
55 items => {
56 type => "object",
57 properties => {},
58 },
59 links => [ { rel => 'child', href => "{name}" } ],
60 },
61 code => sub {
62 my ($param) = @_;
63
64 my $result = [
65 { name => 'log' },
66 { name => 'options' },
67 { name => 'resources' },
68 { name => 'tasks' },
ac27b58d 69 { name => 'backup' },
a06a3eac 70 { name => 'ha' },
a0af0132 71 { name => 'status' },
aff192e6
DM
72 ];
73
74 return $result;
75 }});
76
77__PACKAGE__->register_method({
78 name => 'log',
79 path => 'log',
80 method => 'GET',
81 description => "Read cluster log",
82 permissions => { user => 'all' },
83 parameters => {
84 additionalProperties => 0,
85 properties => {
86 max => {
87 type => 'integer',
88 description => "Maximum number of entries.",
89 optional => 1,
90 minimum => 1,
91 }
92 },
93 },
94 returns => {
95 type => 'array',
96 items => {
97 type => "object",
98 properties => {},
99 },
100 },
101 code => sub {
102 my ($param) = @_;
103
104 my $rpcenv = PVE::RPCEnvironment::get();
105
106 my $max = $param->{max} || 0;
107 my $user = $rpcenv->get_user();
108
e4d554ba 109 my $admin = $rpcenv->check($user, "/", [ 'Sys.Syslog' ], 1);
aff192e6
DM
110
111 my $loguser = $admin ? '' : $user;
112
113 my $res = decode_json(PVE::Cluster::get_cluster_log($loguser, $max));
114
115 return $res->{data};
116 }});
117
118__PACKAGE__->register_method({
119 name => 'resources',
120 path => 'resources',
121 method => 'GET',
122 description => "Resources index (cluster wide).",
123 permissions => { user => 'all' },
124 parameters => {
125 additionalProperties => 0,
badcb8d1
DM
126 properties => {
127 type => {
128 type => 'string',
129 optional => 1,
130 enum => ['vm', 'storage', 'node'],
131 },
132 },
aff192e6
DM
133 },
134 returns => {
135 type => 'array',
136 items => {
137 type => "object",
138 properties => {
139 },
140 },
141 },
142 code => sub {
143 my ($param) = @_;
144
145 my $rpcenv = PVE::RPCEnvironment::get();
146 my $user = $rpcenv->get_user();
147
148 my $res = [];
149
bc7bff8e
DM
150 my $nodelist = PVE::Cluster::get_nodelist();
151 my $members = PVE::Cluster::get_members();
aff192e6
DM
152
153 my $rrd = PVE::Cluster::rrd_dump();
154
155 my $vmlist = PVE::Cluster::get_vmlist() || {};
156 my $idlist = $vmlist->{ids} || {};
157
158
159 # we try to generate 'numbers' by using "$X + 0"
badcb8d1
DM
160 if (!$param->{type} || $param->{type} eq 'vm') {
161 foreach my $vmid (keys %$idlist) {
162 my $data = $idlist->{$vmid};
aff192e6 163
aff192e6 164
e4d554ba 165 next if !$rpcenv->check($user, "/vms/$vmid", [ 'VM.Audit' ], 1);
aff192e6 166
badcb8d1
DM
167 my $entry = {
168 id => "$data->{type}/$vmid",
169 vmid => $vmid + 0,
170 node => $data->{node},
171 type => $data->{type},
172 };
aff192e6 173
badcb8d1 174 if (my $d = $rrd->{"pve2-vm/$vmid"}) {
aff192e6 175
bc7bff8e 176 $entry->{uptime} = ($d->[0] || 0) + 0;
badcb8d1
DM
177 $entry->{name} = $d->[1];
178
179 $entry->{maxcpu} = ($d->[3] || 0) + 0;
bc7bff8e 180 $entry->{cpu} = ($d->[4] || 0) + 0;
badcb8d1
DM
181 $entry->{maxmem} = ($d->[5] || 0) + 0;
182 $entry->{mem} = ($d->[6] || 0) + 0;
183 $entry->{maxdisk} = ($d->[7] || 0) + 0;
184 $entry->{disk} = ($d->[8] || 0) + 0;
bc7bff8e 185 }
badcb8d1
DM
186
187 push @$res, $entry;
aff192e6 188 }
aff192e6
DM
189 }
190
badcb8d1 191 if (!$param->{type} || $param->{type} eq 'node') {
bc7bff8e 192 foreach my $node (@$nodelist) {
aff192e6 193 my $entry = {
badcb8d1
DM
194 id => "node/$node",
195 node => $node,
196 type => "node",
197 };
198 if (my $d = $rrd->{"pve2-node/$node"}) {
199
200 if (!$members || # no cluster
201 ($members->{$node} && $members->{$node}->{online})) {
202 $entry->{uptime} = ($d->[0] || 0) + 0;
203 $entry->{cpu} = ($d->[4] || 0) + 0;
204 $entry->{mem} = ($d->[7] || 0) + 0;
205 $entry->{disk} = ($d->[11] || 0) + 0;
206 }
207
208 $entry->{maxcpu} = ($d->[3] || 0) + 0;
209 $entry->{maxmem} = ($d->[6] || 0) + 0;
210 $entry->{maxdisk} = ($d->[10] || 0) + 0;
aff192e6
DM
211 }
212
213 push @$res, $entry;
badcb8d1
DM
214 }
215 }
aff192e6 216
badcb8d1
DM
217 if (!$param->{type} || $param->{type} eq 'storage') {
218
219 my $cfg = PVE::Storage::config();
220 my @sids = PVE::Storage::storage_ids ($cfg);
221
222 foreach my $storeid (@sids) {
223 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
e4d554ba 224 next if !$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Audit' ], 1);
badcb8d1
DM
225 # we create a entry for each node
226 foreach my $node (@$nodelist) {
227 next if !PVE::Storage::storage_check_enabled($cfg, $storeid, $node, 1);
228 my $entry = {
229 id => "storage/$node/$storeid",
230 storage => $storeid,
231 node => $node,
232 type => 'storage',
233 };
234
235 if (my $d = $rrd->{"pve2-storage/$node/$storeid"}) {
236 $entry->{maxdisk} = ($d->[1] || 0) + 0;
237 $entry->{disk} = ($d->[2] || 0) + 0;
238 }
239
240 push @$res, $entry;
241 }
aff192e6
DM
242 }
243 }
244
245 return $res;
246 }});
247
248__PACKAGE__->register_method({
249 name => 'tasks',
250 path => 'tasks',
251 method => 'GET',
252 description => "List recent tasks (cluster wide).",
253 permissions => { user => 'all' },
254 parameters => {
255 additionalProperties => 0,
256 properties => {},
257 },
258 returns => {
259 type => 'array',
260 items => {
261 type => "object",
262 properties => {
263 upid => { type => 'string' },
264 },
265 },
266 },
267 code => sub {
268 my ($param) = @_;
269
270 my $rpcenv = PVE::RPCEnvironment::get();
271 my $user = $rpcenv->get_user();
272
273 my $tlist = PVE::Cluster::get_tasklist();
274
275 my $res = [];
276
277 return $res if !$tlist;
278
e4d554ba 279 my $all = $rpcenv->check($user, "/", [ 'Sys.Audit' ], 1);
aff192e6
DM
280
281 foreach my $task (@$tlist) {
282 push @$res, $task if $all || ($task->{user} eq $user);
283 }
284
285 return $res;
286 }});
287
288__PACKAGE__->register_method({
289 name => 'get_options',
290 path => 'options',
291 method => 'GET',
292 description => "Get datacenter options.",
293 permissions => {
7d020b42 294 check => ['perm', '/', [ 'Sys.Audit' ]],
aff192e6
DM
295 },
296 parameters => {
297 additionalProperties => 0,
298 properties => {},
299 },
300 returns => {
301 type => "object",
302 properties => {},
303 },
304 code => sub {
305 my ($param) = @_;
306 return PVE::Cluster::cfs_read_file('datacenter.cfg');
307 }});
308
309__PACKAGE__->register_method({
310 name => 'set_options',
311 path => 'options',
312 method => 'PUT',
313 description => "Set datacenter options.",
314 permissions => {
7d020b42 315 check => ['perm', '/', [ 'Sys.Modify' ]],
aff192e6
DM
316 },
317 protected => 1,
318 parameters => {
319 additionalProperties => 0,
320 properties => $dc_properties,
321 },
322 returns => { type => "null" },
323 code => sub {
324 my ($param) = @_;
325
326 my $filename = 'datacenter.cfg';
327
328 my $delete = extract_param($param, 'delete');
329
330 my $code = sub {
331
332 my $conf = cfs_read_file($filename);
333
334 foreach my $opt (keys %$param) {
335 $conf->{$opt} = $param->{$opt};
336 }
337
338 foreach my $opt (PVE::Tools::split_list($delete)) {
339 delete $conf->{$opt};
340 };
341
342 cfs_write_file($filename, $conf);
343 };
344
345 cfs_lock_file($filename, undef, $code);
346 die $@ if $@;
347
348 return undef;
349 }});
350
a0af0132
DM
351my $parse_clustat = sub {
352 my ($clinfo, $members, $nodename, $raw) = @_;
353
354 my $createNode = sub {
355 my ($expat, $tag, %attrib) = @_;
356 my $node = { type => $tag, %attrib };
357
358 if ($tag eq 'node') {
359 my $name = $node->{name};
360 return if !$name; # just to be sure
361
362 foreach my $key (qw(estranged local qdisk rgmanager rgmanager_master state)) {
363 $node->{$key} = int($node->{$key}) if defined($node->{$key});
364 }
365 $node->{nodeid} = hex($node->{nodeid}) if defined($node->{nodeid});
366
367 # unique ID for GUI
368 $node->{id} = "node/$node->{name}";
369
370 my $pmxcfs = 0;
371 if (!$members) { # no cluster
372 if ($name eq $nodename) {
373 $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ?
374 }
375 } elsif ($members->{$name}) {
376 $pmxcfs = $members->{$name}->{online} ? 1 : 0
377 }
378 $node->{pmxcfs} = $pmxcfs;
379
380 if ($members && $members->{$name}) {
381 if (my $ip = $members->{$name}->{ip}) {
382 $node->{ip} = $ip;
383 }
384 }
385 } elsif ($tag eq 'group') {
386 my $name = $node->{name};
387 return if !$name; # just to be sure
388 # unique ID for GUI
389 $node->{id} = "group/$node->{name}";
390 } else {
391 $node->{id} = $tag;
392 }
393
394 return $node;
395 };
396
397 my $extract_tags = {
398 cluster => 1,
399 quorum => 1,
400 node => 1,
401 group => 1,
402 };
403
404 my $handlers = {
405 Init => sub {
406 my $expat = shift;
407 $expat->{NodeList} = [];
408 },
409 Final => sub {
410 my $expat = shift;
411 $expat->{NodeList};
412 },
413 Start => sub {
414 my $expat = shift;
415 my $tag = shift;
416 if ($extract_tags->{$tag}) {
417 my $node = &$createNode($expat, $tag, @_);
418 push @{$expat->{NodeList}}, $node;
419 }
420 },
421 };
422
423 my $data = [];
424 if ($raw) {
425 my $parser = new XML::Parser(Handlers => $handlers);
426 $data = $parser->parse($raw);
427 }
428 return $data;
429};
430
431__PACKAGE__->register_method({
432 name => 'get_status',
433 path => 'status',
434 method => 'GET',
435 description => "Get cluster status informations.",
436 permissions => { user => 'all' },
437 protected => 1,
438 parameters => {
439 additionalProperties => 0,
440 properties => {},
441 },
442 returns => {
443 type => 'array',
444 items => {
445 type => "object",
446 properties => {
447 type => {
448 type => 'string'
449 },
450 },
451 },
452 },
453 code => sub {
454 my ($param) = @_;
455
456 # we also add info from pmxcfs
457 my $clinfo = PVE::Cluster::get_clinfo();
458 my $members = PVE::Cluster::get_members();
459 my $nodename = PVE::INotify::nodename();
460
461 if ($members) {
462 my $cmd = ['clustat', '-x'];
463 my $out = '';
464 PVE::Tools::run_command($cmd, outfunc => sub { $out .= shift; });
465 return &$parse_clustat($clinfo, $members, $nodename, $out);
466 } else {
467 # fake entry for local node if no cluster defined
468 my $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ?
469 return [{
470 type => 'node',
471 id => "node/$nodename",
472 name => $nodename,
473 'local' => 1,
474 nodeid => 0,
475 pmxcfs => $pmxcfs,
476 state => 1
477 }];
478 }
479 }});
480
aff192e6 4811;