]>
Commit | Line | Data |
---|---|---|
50306453 DM |
1 | package PVE::API2::HA::Status; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
475f19fe TL |
6 | use PVE::Cluster qw(cfs_read_file); |
7 | use PVE::DataCenterConfig; | |
d0625985 | 8 | use PVE::INotify; |
50306453 DM |
9 | use PVE::JSONSchema qw(get_standard_option); |
10 | use PVE::RPCEnvironment; | |
d0625985 | 11 | use PVE::SafeSyslog; |
50306453 | 12 | |
d0625985 | 13 | use PVE::HA::Config; |
50306453 | 14 | |
d0625985 | 15 | use PVE::RESTHandler; |
50306453 DM |
16 | use base qw(PVE::RESTHandler); |
17 | ||
18 | my $nodename = PVE::INotify::nodename(); | |
19 | ||
20 | my $timestamp_to_status = sub { | |
21 | my ($ctime, $timestamp) = @_; | |
22 | ||
23 | my $tdiff = $ctime - $timestamp; | |
24 | if ($tdiff > 30) { | |
25 | return "old timestamp - dead?"; | |
26 | } elsif ($tdiff < -2) { | |
27 | return "detected time drift!"; | |
28 | } else { | |
29 | return "active"; | |
30 | } | |
31 | }; | |
32 | ||
5c9d7bc9 | 33 | __PACKAGE__->register_method ({ |
289e4784 TL |
34 | name => 'index', |
35 | path => '', | |
5c9d7bc9 DM |
36 | method => 'GET', |
37 | permissions => { user => 'all' }, | |
38 | description => "Directory index.", | |
39 | parameters => { | |
d0625985 | 40 | additionalProperties => 0, |
5c9d7bc9 DM |
41 | properties => {}, |
42 | }, | |
43 | returns => { | |
44 | type => 'array', | |
45 | items => { | |
46 | type => "object", | |
47 | properties => {}, | |
48 | }, | |
49 | links => [ { rel => 'child', href => "{name}" } ], | |
50 | }, | |
51 | code => sub { | |
52 | my ($param) = @_; | |
289e4784 | 53 | |
5c9d7bc9 DM |
54 | my $result = [ |
55 | { name => 'current' }, | |
56 | { name => 'manager_status' }, | |
d0625985 | 57 | ]; |
5c9d7bc9 DM |
58 | |
59 | return $result; | |
60 | }}); | |
61 | ||
50306453 | 62 | __PACKAGE__->register_method ({ |
289e4784 | 63 | name => 'status', |
5c9d7bc9 | 64 | path => 'current', |
50306453 DM |
65 | method => 'GET', |
66 | description => "Get HA manger status.", | |
0fcba7ab TL |
67 | permissions => { |
68 | check => ['perm', '/', [ 'Sys.Audit' ]], | |
69 | }, | |
50306453 | 70 | parameters => { |
d0625985 | 71 | additionalProperties => 0, |
50306453 DM |
72 | properties => {}, |
73 | }, | |
afa1aa9c FE |
74 | returns => { |
75 | type => 'array', | |
76 | items => { | |
77 | type => 'object', | |
78 | properties => { | |
79 | id => { | |
80 | description => "Status entry ID (quorum, master, lrm:<node>, service:<sid>).", | |
81 | type => "string", | |
82 | }, | |
83 | node => { | |
84 | description => "Node associated to status entry.", | |
85 | type => "string", | |
86 | }, | |
87 | status => { | |
88 | description => "Status of the entry (value depends on type).", | |
89 | type => "string", | |
90 | }, | |
91 | type => { | |
92 | description => "Type of status entry.", | |
93 | enum => ["quorum", "master", "lrm", "service"], | |
94 | }, | |
95 | quorate => { | |
96 | description => "For type 'quorum'. Whether the cluster is quorate or not.", | |
97 | type => "boolean", | |
98 | optional => 1, | |
99 | }, | |
100 | timestamp => { | |
101 | description => "For type 'lrm','master'. Timestamp of the status information.", | |
102 | type => "integer", | |
103 | optional => 1, | |
104 | }, | |
105 | crm_state => { | |
106 | description => "For type 'service'. Service state as seen by the CRM.", | |
107 | type => "string", | |
108 | optional => 1, | |
109 | }, | |
110 | max_relocate => { | |
111 | description => "For type 'service'.", | |
112 | type => "integer", | |
113 | optional => 1, | |
114 | }, | |
115 | max_restart => { | |
116 | description => "For type 'service'.", | |
117 | type => "integer", | |
118 | optional => 1, | |
119 | }, | |
120 | request_state => { | |
121 | description => "For type 'service'. Requested service state.", | |
122 | type => "string", | |
123 | optional => 1, | |
124 | }, | |
125 | sid => { | |
126 | description => "For type 'service'. Service ID.", | |
127 | type => "string", | |
128 | optional => 1, | |
129 | }, | |
130 | state => { | |
131 | description => "For type 'service'. Verbose service state.", | |
132 | type => "string", | |
133 | optional => 1, | |
134 | }, | |
135 | }, | |
136 | }, | |
137 | }, | |
50306453 DM |
138 | code => sub { |
139 | my ($param) = @_; | |
140 | ||
141 | my $res = []; | |
322d3550 TL |
142 | |
143 | my $quorate = PVE::Cluster::check_cfs_quorum(1); | |
d0625985 TL |
144 | push @$res, { |
145 | id => 'quorum', type => 'quorum', | |
146 | node => $nodename, | |
147 | status => $quorate ? 'OK' : "No quorum on node '$nodename'!", | |
148 | quorate => $quorate ? 1 : 0, | |
149 | }; | |
322d3550 | 150 | |
c12b9a1d | 151 | my $status = PVE::HA::Config::read_manager_status(); |
322d3550 | 152 | |
c12b9a1d | 153 | my $service_config = PVE::HA::Config::read_and_check_resources_config(); |
50306453 | 154 | |
c12b9a1d | 155 | my $ctime = time(); |
50306453 DM |
156 | |
157 | if (defined($status->{master_node}) && defined($status->{timestamp})) { | |
158 | my $master = $status->{master_node}; | |
159 | my $status_str = &$timestamp_to_status($ctime, $status->{timestamp}); | |
322d3550 TL |
160 | # mark crm idle if it has no service configured and is not active |
161 | if ($quorate && $status_str ne 'active' && !keys %{$service_config}) { | |
162 | $status_str = 'idle'; | |
163 | } | |
475f19fe TL |
164 | |
165 | my $extra_status = ''; | |
166 | ||
167 | my $datacenter_config = eval { cfs_read_file('datacenter.cfg') } // {}; | |
168 | if (my $crs = $datacenter_config->{crs}) { | |
169 | $extra_status = " - $crs->{ha} load CRS" if $crs->{ha} && $crs->{ha} ne 'basic'; | |
170 | } | |
50306453 | 171 | my $time_str = localtime($status->{timestamp}); |
475f19fe | 172 | my $status_text = "$master ($status_str, $time_str)$extra_status"; |
d0625985 TL |
173 | push @$res, { |
174 | id => 'master', type => 'master', | |
175 | node => $master, | |
176 | status => $status_text, | |
177 | timestamp => $status->{timestamp}, | |
178 | }; | |
322d3550 | 179 | } |
331a9f00 DM |
180 | |
181 | # compute active services for all nodes | |
182 | my $active_count = {}; | |
183 | foreach my $sid (sort keys %{$status->{service_status}}) { | |
184 | my $sd = $status->{service_status}->{$sid}; | |
185 | next if !$sd->{node}; | |
186 | $active_count->{$sd->{node}} = 0 if !defined($active_count->{$sd->{node}}); | |
187 | my $req_state = $sd->{state}; | |
188 | next if !defined($req_state); | |
189 | next if $req_state eq 'stopped'; | |
190 | next if $req_state eq 'freeze'; | |
191 | $active_count->{$sd->{node}}++; | |
192 | } | |
289e4784 | 193 | |
50306453 | 194 | foreach my $node (sort keys %{$status->{node_status}}) { |
c12b9a1d | 195 | my $lrm_status = PVE::HA::Config::read_lrm_status($node); |
50306453 DM |
196 | my $id = "lrm:$node"; |
197 | if (!$lrm_status->{timestamp}) { | |
289e4784 TL |
198 | push @$res, { id => $id, type => 'lrm', node => $node, |
199 | status => "$node (unable to read lrm status)"}; | |
50306453 DM |
200 | } else { |
201 | my $status_str = &$timestamp_to_status($ctime, $lrm_status->{timestamp}); | |
77bcb60a TL |
202 | my $lrm_mode = $lrm_status->{mode}; |
203 | ||
331a9f00 | 204 | if ($status_str eq 'active') { |
77bcb60a | 205 | $lrm_mode ||= 'active'; |
331a9f00 DM |
206 | my $lrm_state = $lrm_status->{state} || 'unknown'; |
207 | if ($lrm_mode ne 'active') { | |
208 | $status_str = "$lrm_mode mode"; | |
209 | } else { | |
210 | if ($lrm_state eq 'wait_for_agent_lock' && !$active_count->{$node}) { | |
211 | $status_str = 'idle'; | |
212 | } else { | |
213 | $status_str = $lrm_state; | |
214 | } | |
215 | } | |
77bcb60a TL |
216 | } elsif ($lrm_mode && $lrm_mode eq 'maintenance') { |
217 | $status_str = "$lrm_mode mode"; | |
331a9f00 DM |
218 | } |
219 | ||
50306453 DM |
220 | my $time_str = localtime($lrm_status->{timestamp}); |
221 | my $status_text = "$node ($status_str, $time_str)"; | |
d0625985 TL |
222 | push @$res, { |
223 | id => $id, type => 'lrm', | |
224 | node => $node, | |
225 | status => $status_text, | |
226 | timestamp => $lrm_status->{timestamp}, | |
227 | }; | |
50306453 DM |
228 | } |
229 | } | |
230 | ||
30b36b89 DM |
231 | my $add_service = sub { |
232 | my ($sid, $sc, $ss) = @_; | |
233 | ||
98808a5a DM |
234 | my $data = { id => "service:$sid", type => 'service', sid => $sid }; |
235 | ||
30b36b89 | 236 | if ($ss) { |
98808a5a | 237 | $data->{node} = $ss->{node}; |
98808a5a | 238 | $data->{crm_state} = $ss->{state}; |
c9b21b5a | 239 | } elsif ($sc) { |
98808a5a | 240 | $data->{node} = $sc->{node}; |
98808a5a | 241 | } |
fbda2658 | 242 | my $node = $data->{node} // '---'; # to be save against manual tinkering |
30b36b89 | 243 | |
aa68337a | 244 | $data->{state} = PVE::HA::Tools::get_verbose_service_state($ss, $sc); |
fbda2658 | 245 | $data->{status} = "$sid ($node, $data->{state})"; # backward compat. and CLI |
aa68337a | 246 | |
98808a5a DM |
247 | # also return common resource attributes |
248 | if (defined($sc)) { | |
249 | $data->{request_state} = $sc->{state}; | |
250 | foreach my $key (qw(group max_restart max_relocate comment)) { | |
251 | $data->{$key} = $sc->{$key} if defined($sc->{$key}); | |
252 | } | |
30b36b89 | 253 | } |
98808a5a DM |
254 | |
255 | push @$res, $data; | |
30b36b89 DM |
256 | }; |
257 | ||
50306453 | 258 | foreach my $sid (sort keys %{$status->{service_status}}) { |
30b36b89 DM |
259 | my $sc = $service_config->{$sid}; |
260 | my $ss = $status->{service_status}->{$sid}; | |
261 | $add_service->($sid, $sc, $ss); | |
50306453 | 262 | } |
2afff38a TL |
263 | |
264 | # show also service which aren't yet processed by the CRM | |
265 | foreach my $sid (sort keys %$service_config) { | |
266 | next if $status->{service_status}->{$sid}; | |
30b36b89 DM |
267 | my $sc = $service_config->{$sid}; |
268 | $add_service->($sid, $sc); | |
2afff38a TL |
269 | } |
270 | ||
50306453 DM |
271 | return $res; |
272 | }}); | |
273 | ||
5c9d7bc9 | 274 | __PACKAGE__->register_method ({ |
289e4784 | 275 | name => 'manager_status', |
5c9d7bc9 DM |
276 | path => 'manager_status', |
277 | method => 'GET', | |
278 | description => "Get full HA manger status, including LRM status.", | |
0fcba7ab TL |
279 | permissions => { |
280 | check => ['perm', '/', [ 'Sys.Audit' ]], | |
281 | }, | |
5c9d7bc9 | 282 | parameters => { |
d0625985 | 283 | additionalProperties => 0, |
5c9d7bc9 DM |
284 | properties => {}, |
285 | }, | |
286 | returns => { type => 'object' }, | |
287 | code => sub { | |
288 | my ($param) = @_; | |
50306453 | 289 | |
c12b9a1d | 290 | my $status = PVE::HA::Config::read_manager_status(); |
289e4784 | 291 | |
5c9d7bc9 DM |
292 | my $data = { manager_status => $status }; |
293 | ||
294 | $data->{quorum} = { | |
295 | node => $nodename, | |
296 | quorate => PVE::Cluster::check_cfs_quorum(1), | |
297 | }; | |
289e4784 | 298 | |
5c9d7bc9 | 299 | foreach my $node (sort keys %{$status->{node_status}}) { |
c12b9a1d | 300 | my $lrm_status = PVE::HA::Config::read_lrm_status($node); |
5c9d7bc9 DM |
301 | $data->{lrm_status}->{$node} = $lrm_status; |
302 | } | |
303 | ||
304 | return $data; | |
305 | }}); | |
50306453 DM |
306 | |
307 | 1; |