]> git.proxmox.com Git - pve-ha-manager.git/blame - src/PVE/HA/Manager.pm
manager: set resource scheduler mode upon init
[pve-ha-manager.git] / src / PVE / HA / Manager.pm
CommitLineData
c0bbd038
DM
1package PVE::HA::Manager;
2
3use strict;
4use warnings;
c4a221bc 5use Digest::MD5 qw(md5_base64);
c0bbd038 6
c142ebc9 7use PVE::Tools;
a89ff919 8use PVE::HA::Tools ':exit_codes';
c0bbd038 9use PVE::HA::NodeStatus;
5d724d4d 10use PVE::HA::Usage::Basic;
c0bbd038 11
a3ffb0b3
TL
12## Variable Name & Abbreviations Convention
13#
14# The HA stack has some variables it uses frequently and thus abbreviates it such that it may be
15# confusing for new readers. Here's a short list of the most common used.
16#
17# NOTE: variables should be assumed to be read only if not otherwise stated, only use the specific
18# methods to re-compute/read/alter them.
19#
20# - $haenv -> HA environment, the main interface to the simulator/test/real world
21# - $sid -> Service ID, unique identifier for a service, `type:vmid` is common
22#
23# - $ms -> Master/Manager Status, contains runtime info from the current active manager
24# - $ns -> Node Status, hash holding online/offline status about all nodes
25#
26# - $ss -> Service Status, hash holding the current state (last LRM cmd result, failed starts
27# or migrates, maintenance fallback node, for *all* services ...
28# - $sd -> Service Data, the service status of a *single* service, iow. $ss->{$sid}
29#
30# - $sc -> Service Configuration, hash for all services including target state, group, ...
0869c306 31# - $cd -> Configuration Data, the service config of a *single* service, iow. $sc->{$sid}
a3ffb0b3
TL
32#
33# Try to avoid adding new two letter (or similar over abbreviated) names, but also don't send
34# patches for changing above, as that set is mostly sensible and should be easy to remember once
35# spending a bit time in the HA code base.
36
c0bbd038 37sub new {
8f0bb968 38 my ($this, $haenv) = @_;
c0bbd038
DM
39
40 my $class = ref($this) || $this;
41
6ee64cfc 42 my $self = bless { haenv => $haenv }, $class;
8f0bb968 43
6ee64cfc 44 my $old_ms = $haenv->read_manager_status();
8f0bb968 45
6ee64cfc
TL
46 # we only copy the state part of the manager which cannot be auto generated
47
48 $self->{ns} = PVE::HA::NodeStatus->new($haenv, $old_ms->{node_status} || {});
8f0bb968 49
59fd7207 50 # fixme: use separate class PVE::HA::ServiceStatus
6ee64cfc
TL
51 $self->{ss} = $old_ms->{service_status} || {};
52
53 $self->{ms} = { master_node => $haenv->nodename() };
c0bbd038 54
f74f8ffb
FE
55 my $dc_cfg = $haenv->get_datacenter_settings();
56 $self->{'scheduler-mode'} = $dc_cfg->{crs}->{ha} ? $dc_cfg->{crs}->{ha} : 'basic';
57 $haenv->log('info', "using scheduler mode '$self->{'scheduler-mode'}'")
58 if $self->{'scheduler-mode'} ne 'basic';
59
c0bbd038
DM
60 return $self;
61}
62
d84da043
DM
63sub cleanup {
64 my ($self) = @_;
65
66 # todo: ?
67}
68
8f0bb968 69sub flush_master_status {
c0bbd038
DM
70 my ($self) = @_;
71
59fd7207 72 my ($haenv, $ms, $ns, $ss) = ($self->{haenv}, $self->{ms}, $self->{ns}, $self->{ss});
c0bbd038 73
8f0bb968 74 $ms->{node_status} = $ns->{status};
59fd7207 75 $ms->{service_status} = $ss;
d2f612cf 76 $ms->{timestamp} = $haenv->get_time();
289e4784 77
8f0bb968 78 $haenv->write_manager_status($ms);
289e4784 79}
c0bbd038 80
48a6ba2a
TL
81sub get_service_group {
82 my ($groups, $online_node_usage, $service_conf) = @_;
f7ccd1b3 83
09c5c4bf
TL
84 my $group = {};
85 # add all online nodes to default group to allow try_next when no group set
5d724d4d 86 $group->{nodes}->{$_} = 1 for $online_node_usage->list_nodes();
abc920b4 87
09c5c4bf 88 # overwrite default if service is bound to a specific group
3458a0e3
TL
89 if (my $group_id = $service_conf->{group}) {
90 $group = $groups->{ids}->{$group_id} if $groups->{ids}->{$group_id};
91 }
abc920b4 92
48a6ba2a
TL
93 return $group;
94}
95
96# groups available nodes with their priority as group index
97sub get_node_priority_groups {
98 my ($group, $online_node_usage) = @_;
99
abc920b4
DM
100 my $pri_groups = {};
101 my $group_members = {};
e0a56314 102 foreach my $entry (keys %{$group->{nodes}}) {
abc920b4
DM
103 my ($node, $pri) = ($entry, 0);
104 if ($entry =~ m/^(\S+):(\d+)$/) {
105 ($node, $pri) = ($1, $2);
106 }
5d724d4d 107 next if !$online_node_usage->contains_node($node); # offline
abc920b4
DM
108 $pri_groups->{$pri}->{$node} = 1;
109 $group_members->{$node} = $pri;
110 }
f7ccd1b3 111
abc920b4
DM
112 # add non-group members to unrestricted groups (priority -1)
113 if (!$group->{restricted}) {
114 my $pri = -1;
5d724d4d 115 for my $node ($online_node_usage->list_nodes()) {
abc920b4
DM
116 next if defined($group_members->{$node});
117 $pri_groups->{$pri}->{$node} = 1;
118 $group_members->{$node} = -1;
119 }
120 }
121
48a6ba2a
TL
122 return ($pri_groups, $group_members);
123}
124
125sub select_service_node {
b2598576 126 my ($groups, $online_node_usage, $sid, $service_conf, $current_node, $try_next, $tried_nodes, $maintenance_fallback) = @_;
48a6ba2a
TL
127
128 my $group = get_service_group($groups, $online_node_usage, $service_conf);
129
130 my ($pri_groups, $group_members) = get_node_priority_groups($group, $online_node_usage);
131
abc920b4
DM
132 my @pri_list = sort {$b <=> $a} keys %$pri_groups;
133 return undef if !scalar(@pri_list);
09c5c4bf
TL
134
135 # stay on current node if possible (avoids random migrations)
abc920b4
DM
136 if (!$try_next && $group->{nofailback} && defined($group_members->{$current_node})) {
137 return $current_node;
138 }
139
140 # select node from top priority node list
141
142 my $top_pri = $pri_list[0];
143
e6eeb7dc
TL
144 # try to avoid nodes where the service failed already if we want to relocate
145 if ($try_next) {
146 foreach my $node (@$tried_nodes) {
147 delete $pri_groups->{$top_pri}->{$node};
148 }
149 }
150
5d724d4d 151 my $scores = $online_node_usage->score_nodes_to_start_service($sid, $current_node);
289e4784 152 my @nodes = sort {
5d724d4d 153 $scores->{$a} <=> $scores->{$b} || $a cmp $b
c142ebc9 154 } keys %{$pri_groups->{$top_pri}};
abc920b4
DM
155
156 my $found;
1280368d 157 my $found_maintenance_fallback;
abc920b4
DM
158 for (my $i = scalar(@nodes) - 1; $i >= 0; $i--) {
159 my $node = $nodes[$i];
160 if ($node eq $current_node) {
161 $found = $i;
abc920b4 162 }
2167dd1e 163 if (defined($maintenance_fallback) && $node eq $maintenance_fallback) {
1280368d 164 $found_maintenance_fallback = $i;
2167dd1e
TL
165 }
166 }
167
1280368d
TL
168 if (defined($found_maintenance_fallback)) {
169 return $nodes[$found_maintenance_fallback];
abc920b4
DM
170 }
171
abc920b4 172 if ($try_next) {
abc920b4
DM
173 if (defined($found) && ($found < (scalar(@nodes) - 1))) {
174 return $nodes[$found + 1];
175 } else {
176 return $nodes[0];
177 }
3ac4cc87
TL
178 } elsif (defined($found)) {
179 return $nodes[$found];
abc920b4 180 } else {
abc920b4 181 return $nodes[0];
abc920b4 182 }
f7ccd1b3
DM
183}
184
c4a221bc
DM
185my $uid_counter = 0;
186
d55aa611
DM
187sub compute_new_uuid {
188 my ($state) = @_;
289e4784 189
d55aa611
DM
190 $uid_counter++;
191 return md5_base64($state . $$ . time() . $uid_counter);
192}
193
618fbeda
DM
194my $valid_service_states = {
195 stopped => 1,
196 request_stop => 1,
197 started => 1,
198 fence => 1,
c259b1a8 199 recovery => 1,
618fbeda 200 migrate => 1,
b0fdf86a 201 relocate => 1,
9c7d068b 202 freeze => 1,
618fbeda
DM
203 error => 1,
204};
205
270d4406
DM
206sub recompute_online_node_usage {
207 my ($self) = @_;
208
5d724d4d 209 my $online_node_usage = PVE::HA::Usage::Basic->new($self->{haenv});
270d4406
DM
210
211 my $online_nodes = $self->{ns}->list_online_nodes();
212
5d724d4d 213 $online_node_usage->add_node($_) for $online_nodes->@*;
270d4406
DM
214
215 foreach my $sid (keys %{$self->{ss}}) {
216 my $sd = $self->{ss}->{$sid};
217 my $state = $sd->{state};
6f818da1 218 my $target = $sd->{target}; # optional
5d724d4d 219 if ($online_node_usage->contains_node($sd->{node})) {
c259b1a8
TL
220 if (
221 $state eq 'started' || $state eq 'request_stop' || $state eq 'fence' ||
222 $state eq 'freeze' || $state eq 'error' || $state eq 'recovery'
223 ) {
5d724d4d 224 $online_node_usage->add_service_usage_to_node($sd->{node}, $sid, $sd->{node});
270d4406 225 } elsif (($state eq 'migrate') || ($state eq 'relocate')) {
5d724d4d 226 my $source = $sd->{node};
5c2eef4b 227 # count it for both, source and target as load is put on both
5d724d4d
FE
228 $online_node_usage->add_service_usage_to_node($source, $sid, $source, $target);
229 $online_node_usage->add_service_usage_to_node($target, $sid, $source, $target);
270d4406
DM
230 } elsif ($state eq 'stopped') {
231 # do nothing
232 } else {
feea3913 233 die "should not be reached (sid = '$sid', state = '$state')";
270d4406 234 }
5d724d4d 235 } elsif (defined($target) && $online_node_usage->contains_node($target)) {
066fd016
TL
236 if ($state eq 'migrate' || $state eq 'relocate') {
237 # to correctly track maintenance modi and also consider the target as used for the
238 # case a node dies, as we cannot really know if the to-be-aborted incoming migration
239 # has already cleaned up all used resources
5d724d4d 240 $online_node_usage->add_service_usage_to_node($target, $sid, $sd->{node}, $target);
066fd016 241 }
270d4406
DM
242 }
243 }
244
245 $self->{online_node_usage} = $online_node_usage;
246}
247
4e01bc86
DM
248my $change_service_state = sub {
249 my ($self, $sid, $new_state, %params) = @_;
250
251 my ($haenv, $ss) = ($self->{haenv}, $self->{ss});
252
253 my $sd = $ss->{$sid} || die "no such service '$sid";
254
255 my $old_state = $sd->{state};
e4ffb299 256 my $old_node = $sd->{node};
46139211 257 my $old_failed_nodes = $sd->{failed_nodes};
2167dd1e 258 my $old_maintenance_node = $sd->{maintenance_node};
4e01bc86
DM
259
260 die "no state change" if $old_state eq $new_state; # just to be sure
261
618fbeda
DM
262 die "invalid CRM service state '$new_state'\n" if !$valid_service_states->{$new_state};
263
e4ffb299
DM
264 foreach my $k (keys %$sd) { delete $sd->{$k}; };
265
266 $sd->{state} = $new_state;
267 $sd->{node} = $old_node;
ea998b07 268 $sd->{failed_nodes} = $old_failed_nodes if defined($old_failed_nodes);
2167dd1e 269 $sd->{maintenance_node} = $old_maintenance_node if defined($old_maintenance_node);
e4ffb299
DM
270
271 my $text_state = '';
ba623362 272 foreach my $k (sort keys %params) {
4e01bc86 273 my $v = $params{$k};
e4ffb299
DM
274 $text_state .= ", " if $text_state;
275 $text_state .= "$k = $v";
4e01bc86
DM
276 $sd->{$k} = $v;
277 }
270d4406
DM
278
279 $self->recompute_online_node_usage();
280
d55aa611 281 $sd->{uid} = compute_new_uuid($new_state);
4e01bc86 282
24678a59
TL
283 $text_state = " ($text_state)" if $text_state;
284 $haenv->log('info', "service '$sid': state changed from '${old_state}'" .
285 " to '${new_state}'$text_state");
4e01bc86
DM
286};
287
5dd3ed86
TL
288# clean up a possible bad state from a recovered service to allow its start
289my $fence_recovery_cleanup = sub {
290 my ($self, $sid, $fenced_node) = @_;
291
292 my $haenv = $self->{haenv};
293
0087839a 294 my (undef, $type, $id) = $haenv->parse_sid($sid);
5dd3ed86
TL
295 my $plugin = PVE::HA::Resources->lookup($type);
296
297 # should not happen
298 die "unknown resource type '$type'" if !$plugin;
299
32ea51dd
TL
300 # locks may block recovery, cleanup those which are safe to remove after fencing,
301 # i.e., after the original node was reset and thus all it's state
3458a0e3
TL
302 my $removable_locks = [
303 'backup',
304 'mounted',
305 'migrate',
306 'clone',
307 'rollback',
308 'snapshot',
309 'snapshot-delete',
310 'suspending',
311 'suspended',
312 ];
5dd3ed86
TL
313 if (my $removed_lock = $plugin->remove_locks($haenv, $id, $removable_locks, $fenced_node)) {
314 $haenv->log('warning', "removed leftover lock '$removed_lock' from recovered " .
315 "service '$sid' to allow its start.");
316 }
317};
318
289e4784 319# read LRM status for all nodes
c4a221bc 320sub read_lrm_status {
332170bd 321 my ($self) = @_;
c4a221bc 322
9c7d068b 323 my $nodes = $self->{ns}->list_nodes();
c4a221bc
DM
324 my $haenv = $self->{haenv};
325
9c7d068b
DM
326 my $results = {};
327 my $modes = {};
332170bd 328 foreach my $node (@$nodes) {
9c7d068b 329 my $lrm_status = $haenv->read_lrm_status($node);
02ffd753 330 $modes->{$node} = $lrm_status->{mode} || 'active';
9c7d068b
DM
331 foreach my $uid (keys %{$lrm_status->{results}}) {
332 next if $results->{$uid}; # should not happen
333 $results->{$uid} = $lrm_status->{results}->{$uid};
c4a221bc
DM
334 }
335 }
336
9c7d068b 337 return ($results, $modes);
c4a221bc
DM
338}
339
aa98a844
DM
340# read new crm commands and save them into crm master status
341sub update_crm_commands {
342 my ($self) = @_;
343
344 my ($haenv, $ms, $ns, $ss) = ($self->{haenv}, $self->{ms}, $self->{ns}, $self->{ss});
345
346 my $cmdlist = $haenv->read_crm_commands();
bf7febe3 347
aa98a844
DM
348 foreach my $cmd (split(/\n/, $cmdlist)) {
349 chomp $cmd;
350
b0fdf86a 351 if ($cmd =~ m/^(migrate|relocate)\s+(\S+)\s+(\S+)$/) {
289e4784 352 my ($task, $sid, $node) = ($1, $2, $3);
aa98a844
DM
353 if (my $sd = $ss->{$sid}) {
354 if (!$ns->node_is_online($node)) {
355 $haenv->log('err', "crm command error - node not online: $cmd");
356 } else {
357 if ($node eq $sd->{node}) {
358 $haenv->log('info', "ignore crm command - service already on target node: $cmd");
289e4784 359 } else {
aa98a844 360 $haenv->log('info', "got crm command: $cmd");
3d42b01b 361 $ss->{$sid}->{cmd} = [ $task, $node ];
aa98a844
DM
362 }
363 }
364 } else {
365 $haenv->log('err', "crm command error - no such service: $cmd");
366 }
367
21caf0db
FE
368 } elsif ($cmd =~ m/^stop\s+(\S+)\s+(\S+)$/) {
369 my ($sid, $timeout) = ($1, $2);
370 if (my $sd = $ss->{$sid}) {
371 $haenv->log('info', "got crm command: $cmd");
372 $ss->{$sid}->{cmd} = [ 'stop', $timeout ];
373 } else {
374 $haenv->log('err', "crm command error - no such service: $cmd");
375 }
aa98a844
DM
376 } else {
377 $haenv->log('err', "unable to parse crm command: $cmd");
378 }
379 }
380
381}
382
8f0bb968
DM
383sub manage {
384 my ($self) = @_;
c0bbd038 385
59fd7207 386 my ($haenv, $ms, $ns, $ss) = ($self->{haenv}, $self->{ms}, $self->{ns}, $self->{ss});
c0bbd038 387
99278e06
TL
388 my ($node_info) = $haenv->get_node_info();
389 my ($lrm_results, $lrm_modes) = $self->read_lrm_status();
390
391 $ns->update($node_info, $lrm_modes);
c79442f2 392
99278e06 393 if (!$ns->node_is_operational($haenv->nodename())) {
e5986717 394 $haenv->log('info', "master seems offline");
c79442f2
DM
395 return;
396 }
397
f7ccd1b3
DM
398 my $sc = $haenv->read_service_config();
399
abc920b4
DM
400 $self->{groups} = $haenv->read_group_config(); # update
401
f7ccd1b3
DM
402 # compute new service status
403
404 # add new service
cc32a8f3 405 foreach my $sid (sort keys %$sc) {
f7ccd1b3 406 next if $ss->{$sid}; # already there
77499288 407 my $cd = $sc->{$sid};
667670b2
TL
408 next if $cd->{state} eq 'ignored';
409
77499288 410 $haenv->log('info', "adding new service '$sid' on node '$cd->{node}'");
f7ccd1b3 411 # assume we are running to avoid relocate running service at add
bb07bd2c 412 my $state = ($cd->{state} eq 'started') ? 'started' : 'request_stop';
77499288 413 $ss->{$sid} = { state => $state, node => $cd->{node},
d55aa611 414 uid => compute_new_uuid('started') };
f7ccd1b3
DM
415 }
416
667670b2 417 # remove stale or ignored services from manager state
4e5764af 418 foreach my $sid (keys %$ss) {
667670b2
TL
419 next if $sc->{$sid} && $sc->{$sid}->{state} ne 'ignored';
420
421 my $reason = defined($sc->{$sid}) ? 'ignored state requested' : 'no config';
422 $haenv->log('info', "removing stale service '$sid' ($reason)");
423
46139211 424 # remove all service related state information
4e5764af
DM
425 delete $ss->{$sid};
426 }
5a28da91 427
aa98a844
DM
428 $self->update_crm_commands();
429
c79442f2
DM
430 for (;;) {
431 my $repeat = 0;
289e4784 432
270d4406 433 $self->recompute_online_node_usage();
f7ccd1b3 434
a5e4bef4 435 foreach my $sid (sort keys %$ss) {
c79442f2
DM
436 my $sd = $ss->{$sid};
437 my $cd = $sc->{$sid} || { state => 'disabled' };
f7ccd1b3 438
9c7d068b 439 my $lrm_res = $sd->{uid} ? $lrm_results->{$sd->{uid}} : undef;
a875fbe8 440
c79442f2
DM
441 my $last_state = $sd->{state};
442
443 if ($last_state eq 'stopped') {
444
abc920b4 445 $self->next_state_stopped($sid, $cd, $sd, $lrm_res);
f7ccd1b3 446
c79442f2 447 } elsif ($last_state eq 'started') {
f7ccd1b3 448
abc920b4 449 $self->next_state_started($sid, $cd, $sd, $lrm_res);
f7ccd1b3 450
b0fdf86a 451 } elsif ($last_state eq 'migrate' || $last_state eq 'relocate') {
f7ccd1b3 452
8aaa0e36 453 $self->next_state_migrate_relocate($sid, $cd, $sd, $lrm_res);
f7ccd1b3 454
c79442f2 455 } elsif ($last_state eq 'fence') {
f7ccd1b3 456
21e37ed4 457 # do nothing here - wait until fenced
f7ccd1b3 458
c259b1a8
TL
459 } elsif ($last_state eq 'recovery') {
460
461 $self->next_state_recovery($sid, $cd, $sd, $lrm_res);
462
c79442f2 463 } elsif ($last_state eq 'request_stop') {
f7ccd1b3 464
0df5b3dd 465 $self->next_state_request_stop($sid, $cd, $sd, $lrm_res);
618fbeda 466
9c7d068b
DM
467 } elsif ($last_state eq 'freeze') {
468
469 my $lrm_mode = $sd->{node} ? $lrm_modes->{$sd->{node}} : undef;
9c7d068b 470 # unfreeze
bb07bd2c 471 my $state = ($cd->{state} eq 'started') ? 'started' : 'request_stop';
af14d5f3 472 &$change_service_state($self, $sid, $state)
02ffd753 473 if $lrm_mode && $lrm_mode eq 'active';
9c7d068b 474
e88469ba
DM
475 } elsif ($last_state eq 'error') {
476
a2881965 477 $self->next_state_error($sid, $cd, $sd, $lrm_res);
e88469ba 478
a875fbe8
DM
479 } else {
480
481 die "unknown service state '$last_state'";
618fbeda 482 }
21e37ed4 483
9c7d068b 484 my $lrm_mode = $sd->{node} ? $lrm_modes->{$sd->{node}} : undef;
07adc6a6
DM
485 if ($lrm_mode && $lrm_mode eq 'restart') {
486 if (($sd->{state} eq 'started' || $sd->{state} eq 'stopped' ||
487 $sd->{state} eq 'request_stop')) {
488 &$change_service_state($self, $sid, 'freeze');
489 }
9c7d068b 490 }
07adc6a6 491
c79442f2 492 $repeat = 1 if $sd->{state} ne $last_state;
f7ccd1b3
DM
493 }
494
21e37ed4
DM
495 # handle fencing
496 my $fenced_nodes = {};
9b2dbc2a 497 foreach my $sid (sort keys %$ss) {
2deff1ae
TL
498 my ($service_state, $service_node) = $ss->{$sid}->@{'state', 'node'};
499 next if $service_state ne 'fence';
0dcb6597
TL
500
501 if (!defined($fenced_nodes->{$service_node})) {
2deff1ae 502 $fenced_nodes->{$service_node} = $ns->fence_node($service_node) || 0;
21e37ed4
DM
503 }
504
0dcb6597 505 next if !$fenced_nodes->{$service_node};
21e37ed4 506
9da84a0d 507 # node fence was successful - recover service
c259b1a8 508 $change_service_state->($self, $sid, 'recovery');
0dcb6597 509 $repeat = 1; # for faster recovery execution
21e37ed4
DM
510 }
511
2deff1ae
TL
512 # Avoid that a node without services in 'fence' state (e.g., removed
513 # manually by admin) is stuck with the 'fence' node state.
514 for my $node (sort grep { !defined($fenced_nodes->{$_}) } keys $ns->{status}->%*) {
7dc92703 515 next if $ns->get_node_state($node) ne 'fence';
7dc92703 516
2deff1ae
TL
517 $haenv->log('notice', "node '$node' in fence state but no services to-fence! admin interference?!");
518 $repeat = 1 if $ns->fence_node($node);
7dc92703
FE
519 }
520
c79442f2 521 last if !$repeat;
f7ccd1b3 522 }
f7ccd1b3 523
8f0bb968 524 $self->flush_master_status();
c0bbd038
DM
525}
526
a875fbe8
DM
527# functions to compute next service states
528# $cd: service configuration data (read only)
529# $sd: service status data (read only)
530#
531# Note: use change_service_state() to alter state
532#
533
0df5b3dd
DM
534sub next_state_request_stop {
535 my ($self, $sid, $cd, $sd, $lrm_res) = @_;
536
537 my $haenv = $self->{haenv};
538 my $ns = $self->{ns};
539
540 # check result from LRM daemon
541 if ($lrm_res) {
542 my $exit_code = $lrm_res->{exit_code};
a89ff919 543 if ($exit_code == SUCCESS) {
0df5b3dd
DM
544 &$change_service_state($self, $sid, 'stopped');
545 return;
546 } else {
33f01524 547 $haenv->log('err', "service '$sid' stop failed (exit code $exit_code)");
0df5b3dd
DM
548 &$change_service_state($self, $sid, 'error'); # fixme: what state?
549 return;
550 }
551 }
552
ce3d7003 553 if ($ns->node_is_offline_delayed($sd->{node})) {
0df5b3dd
DM
554 &$change_service_state($self, $sid, 'fence');
555 return;
556 }
557}
558
8aaa0e36
DM
559sub next_state_migrate_relocate {
560 my ($self, $sid, $cd, $sd, $lrm_res) = @_;
561
562 my $haenv = $self->{haenv};
563 my $ns = $self->{ns};
564
565 # check result from LRM daemon
566 if ($lrm_res) {
567 my $exit_code = $lrm_res->{exit_code};
bb07bd2c 568 my $req_state = $cd->{state} eq 'started' ? 'started' : 'request_stop';
a89ff919 569 if ($exit_code == SUCCESS) {
542a9902 570 &$change_service_state($self, $sid, $req_state, node => $sd->{target});
8aaa0e36 571 return;
660596ce
TL
572 } elsif ($exit_code == EWRONG_NODE) {
573 $haenv->log('err', "service '$sid' - migration failed: service" .
574 " registered on wrong node!");
575 &$change_service_state($self, $sid, 'error');
8aaa0e36
DM
576 } else {
577 $haenv->log('err', "service '$sid' - migration failed (exit code $exit_code)");
542a9902 578 &$change_service_state($self, $sid, $req_state, node => $sd->{node});
8aaa0e36
DM
579 return;
580 }
581 }
582
ce3d7003 583 if ($ns->node_is_offline_delayed($sd->{node})) {
8aaa0e36
DM
584 &$change_service_state($self, $sid, 'fence');
585 return;
586 }
587}
588
a875fbe8 589sub next_state_stopped {
abc920b4 590 my ($self, $sid, $cd, $sd, $lrm_res) = @_;
a875fbe8
DM
591
592 my $haenv = $self->{haenv};
e88469ba 593 my $ns = $self->{ns};
a875fbe8 594
ff6f1c5c
DM
595 if ($sd->{node} ne $cd->{node}) {
596 # this can happen if we fence a node with active migrations
597 # hack: modify $sd (normally this should be considered read-only)
24678a59 598 $haenv->log('info', "fixup service '$sid' location ($sd->{node} => $cd->{node})");
289e4784 599 $sd->{node} = $cd->{node};
ff6f1c5c
DM
600 }
601
94b7ebe2 602 if ($sd->{cmd}) {
21caf0db 603 my $cmd = shift @{$sd->{cmd}};
94b7ebe2 604
b0fdf86a 605 if ($cmd eq 'migrate' || $cmd eq 'relocate') {
21caf0db 606 my $target = shift @{$sd->{cmd}};
94b7ebe2 607 if (!$ns->node_is_online($target)) {
b0fdf86a 608 $haenv->log('err', "ignore service '$sid' $cmd request - node '$target' not online");
e88469ba 609 } elsif ($sd->{node} eq $target) {
b0fdf86a 610 $haenv->log('info', "ignore service '$sid' $cmd request - service already on node '$target'");
94b7ebe2 611 } else {
9dad9c88
TL
612 &$change_service_state($self, $sid, $cmd, node => $sd->{node},
613 target => $target);
9da84a0d 614 return;
94b7ebe2 615 }
21caf0db
FE
616 } elsif ($cmd eq 'stop') {
617 $haenv->log('info', "ignore service '$sid' $cmd request - service already stopped");
94b7ebe2 618 } else {
289e4784 619 $haenv->log('err', "unknown command '$cmd' for service '$sid'");
94b7ebe2 620 }
21caf0db 621 delete $sd->{cmd};
35cbb764 622 }
94b7ebe2 623
a875fbe8 624 if ($cd->{state} eq 'disabled') {
35cbb764
TL
625 # NOTE: do nothing here, the stop state is an exception as we do not
626 # process the LRM result here, thus the LRM always tries to stop the
627 # service (protection for the case no CRM is active)
e88469ba 628 return;
35cbb764 629 }
e88469ba 630
84c945e4 631 if ($ns->node_is_offline_delayed($sd->{node}) && $ns->get_node_state($sd->{node}) ne 'maintenance') {
af14d5f3
TL
632 &$change_service_state($self, $sid, 'fence');
633 return;
634 }
635
636 if ($cd->{state} eq 'stopped') {
637 # almost the same as 'disabled' state but the service will also get recovered
638 return;
639 }
640
bb07bd2c 641 if ($cd->{state} eq 'started') {
9da84a0d
TL
642 # simply mark it started, if it's on the wrong node
643 # next_state_started will fix that for us
644 &$change_service_state($self, $sid, 'started', node => $sd->{node});
e88469ba 645 return;
a875fbe8 646 }
e88469ba
DM
647
648 $haenv->log('err', "service '$sid' - unknown state '$cd->{state}' in service configuration");
a875fbe8
DM
649}
650
46139211 651sub record_service_failed_on_node {
57fe8e87 652 my ($self, $sid, $node) = @_;
46139211 653
57fe8e87
DM
654 if (!defined($self->{ss}->{$sid}->{failed_nodes})) {
655 $self->{ss}->{$sid}->{failed_nodes} = [];
656 }
46139211 657
57fe8e87 658 push @{$self->{ss}->{$sid}->{failed_nodes}}, $node;
46139211
TL
659}
660
a875fbe8 661sub next_state_started {
abc920b4 662 my ($self, $sid, $cd, $sd, $lrm_res) = @_;
a875fbe8
DM
663
664 my $haenv = $self->{haenv};
ea4443cc 665 my $master_status = $self->{ms};
a875fbe8
DM
666 my $ns = $self->{ns};
667
668 if (!$ns->node_is_online($sd->{node})) {
b0e9158d 669 if ($ns->node_is_offline_delayed($sd->{node})) {
5385a606
DM
670 &$change_service_state($self, $sid, 'fence');
671 }
99278e06
TL
672 if ($ns->get_node_state($sd->{node}) ne 'maintenance') {
673 return;
2167dd1e
TL
674 } else {
675 # save current node as fallback for when it comes out of
676 # maintenance
677 $sd->{maintenance_node} = $sd->{node};
99278e06 678 }
e88469ba 679 }
289e4784 680
af14d5f3 681 if ($cd->{state} eq 'disabled' || $cd->{state} eq 'stopped') {
e88469ba
DM
682 &$change_service_state($self, $sid, 'request_stop');
683 return;
684 }
685
bb07bd2c 686 if ($cd->{state} eq 'started') {
e88469ba
DM
687
688 if ($sd->{cmd}) {
21caf0db 689 my $cmd = shift @{$sd->{cmd}};
e88469ba 690
b0fdf86a 691 if ($cmd eq 'migrate' || $cmd eq 'relocate') {
21caf0db 692 my $target = shift @{$sd->{cmd}};
e88469ba 693 if (!$ns->node_is_online($target)) {
b0fdf86a 694 $haenv->log('err', "ignore service '$sid' $cmd request - node '$target' not online");
e88469ba 695 } elsif ($sd->{node} eq $target) {
b0fdf86a 696 $haenv->log('info', "ignore service '$sid' $cmd request - service already on node '$target'");
e88469ba 697 } else {
a3cb8dcb 698 $haenv->log('info', "$cmd service '$sid' to node '$target'");
b0fdf86a 699 &$change_service_state($self, $sid, $cmd, node => $sd->{node}, target => $target);
e88469ba 700 }
21caf0db
FE
701 } elsif ($cmd eq 'stop') {
702 my $timeout = shift @{$sd->{cmd}};
396eb6f0
TL
703 if ($timeout == 0) {
704 $haenv->log('info', "request immediate service hard-stop for service '$sid'");
705 } else {
706 $haenv->log('info', "request graceful stop with timeout '$timeout' for service '$sid'");
707 }
21caf0db
FE
708 &$change_service_state($self, $sid, 'request_stop', timeout => $timeout);
709 $haenv->update_service_config($sid, {'state' => 'stopped'});
a875fbe8 710 } else {
289e4784 711 $haenv->log('err', "unknown command '$cmd' for service '$sid'");
a875fbe8 712 }
21caf0db
FE
713
714 delete $sd->{cmd};
715
a875fbe8 716 } else {
b0fdf86a 717
abc920b4 718 my $try_next = 0;
46139211 719
ea4443cc 720 if ($lrm_res) {
46139211 721
e9e1cd68
TL
722 my $ec = $lrm_res->{exit_code};
723 if ($ec == SUCCESS) {
724
46139211 725 if (defined($sd->{failed_nodes})) {
81449997 726 $haenv->log('info', "relocation policy successful for '$sid' on node '$sd->{node}'," .
46139211
TL
727 " failed nodes: " . join(', ', @{$sd->{failed_nodes}}) );
728 }
729
730 delete $sd->{failed_nodes};
e9e1cd68 731
b47920fd
DM
732 # store flag to indicate successful start - only valid while state == 'started'
733 $sd->{running} = 1;
734
e9e1cd68 735 } elsif ($ec == ERROR) {
b47920fd
DM
736
737 delete $sd->{running};
738
e9e1cd68 739 # apply our relocate policy if we got ERROR from the LRM
46139211 740 $self->record_service_failed_on_node($sid, $sd->{node});
ea4443cc 741
46139211 742 if (scalar(@{$sd->{failed_nodes}}) <= $cd->{max_relocate}) {
ea4443cc 743
e9e1cd68
TL
744 # tell select_service_node to relocate if possible
745 $try_next = 1;
ea4443cc
TL
746
747 $haenv->log('warning', "starting service $sid on node".
748 " '$sd->{node}' failed, relocating service.");
ea4443cc
TL
749
750 } else {
751
46139211
TL
752 $haenv->log('err', "recovery policy for service $sid " .
753 "failed, entering error state. Failed nodes: ".
754 join(', ', @{$sd->{failed_nodes}}));
ea4443cc
TL
755 &$change_service_state($self, $sid, 'error');
756 return;
757
758 }
e9e1cd68 759 } else {
46139211
TL
760 $self->record_service_failed_on_node($sid, $sd->{node});
761
e9e1cd68
TL
762 $haenv->log('err', "service '$sid' got unrecoverable error" .
763 " (exit code $ec))");
764 # we have no save way out (yet) for other errors
765 &$change_service_state($self, $sid, 'error');
35cbb764 766 return;
ea4443cc 767 }
abc920b4
DM
768 }
769
2167dd1e
TL
770 my $node = select_service_node(
771 $self->{groups},
772 $self->{online_node_usage},
b2598576 773 $sid,
2167dd1e
TL
774 $cd,
775 $sd->{node},
776 $try_next,
777 $sd->{failed_nodes},
778 $sd->{maintenance_node},
779 );
abc920b4 780
b0fdf86a 781 if ($node && ($sd->{node} ne $node)) {
5d724d4d 782 $self->{online_node_usage}->add_service_usage_to_node($node, $sid, $sd->{node});
2167dd1e
TL
783
784 if (defined(my $fallback = $sd->{maintenance_node})) {
785 if ($node eq $fallback) {
786 $haenv->log('info', "moving service '$sid' back to '$fallback', node came back from maintenance.");
787 delete $sd->{maintenance_node};
788 } elsif ($sd->{node} ne $fallback) {
789 $haenv->log('info', "dropping maintenance fallback node '$fallback' for '$sid'");
790 delete $sd->{maintenance_node};
791 }
792 }
793
c0255b2c
TL
794 if ($cd->{type} eq 'vm') {
795 $haenv->log('info', "migrate service '$sid' to node '$node' (running)");
796 &$change_service_state($self, $sid, 'migrate', node => $sd->{node}, target => $node);
797 } else {
798 $haenv->log('info', "relocate service '$sid' to node '$node'");
799 &$change_service_state($self, $sid, 'relocate', node => $sd->{node}, target => $node);
800 }
b0fdf86a 801 } else {
e6eeb7dc
TL
802 if ($try_next && !defined($node)) {
803 $haenv->log('warning', "Start Error Recovery: Tried all available " .
804 " nodes for service '$sid', retry start on current node. " .
805 "Tried nodes: " . join(', ', @{$sd->{failed_nodes}}));
806 }
35cbb764 807 # ensure service get started again if it went unexpected down
bf2d8d74
TL
808 # but ensure also no LRM result gets lost
809 $sd->{uid} = compute_new_uuid($sd->{state}) if defined($lrm_res);
b0fdf86a 810 }
a875fbe8 811 }
e88469ba
DM
812
813 return;
35cbb764 814 }
e88469ba
DM
815
816 $haenv->log('err', "service '$sid' - unknown state '$cd->{state}' in service configuration");
a875fbe8 817}
c0bbd038 818
a2881965
TL
819sub next_state_error {
820 my ($self, $sid, $cd, $sd, $lrm_res) = @_;
821
822 my $ns = $self->{ns};
46139211 823 my $ms = $self->{ms};
a2881965
TL
824
825 if ($cd->{state} eq 'disabled') {
46139211
TL
826 # clean up on error recovery
827 delete $sd->{failed_nodes};
828
a2881965
TL
829 &$change_service_state($self, $sid, 'stopped');
830 return;
831 }
832
a2881965
TL
833}
834
c259b1a8
TL
835# after a node was fenced this recovers the service to a new node
836sub next_state_recovery {
837 my ($self, $sid, $cd, $sd, $lrm_res) = @_;
838
839 my ($haenv, $ss) = ($self->{haenv}, $self->{ss});
840 my $ns = $self->{ns};
841 my $ms = $self->{ms};
842
843 if ($sd->{state} ne 'recovery') { # should not happen
844 $haenv->log('err', "cannot recover service '$sid' from fencing, wrong state '$sd->{state}'");
845 return;
846 }
847
848 my $fenced_node = $sd->{node}; # for logging purpose
849
850 $self->recompute_online_node_usage(); # we want the most current node state
851
852 my $recovery_node = select_service_node(
853 $self->{groups},
854 $self->{online_node_usage},
b2598576 855 $sid,
c259b1a8
TL
856 $cd,
857 $sd->{node},
858 );
859
860 if ($recovery_node) {
90a24755
TL
861 my $msg = "recover service '$sid' from fenced node '$fenced_node' to node '$recovery_node'";
862 if ($recovery_node eq $fenced_node) {
863 # can happen if restriced groups and the node came up again OK
864 $msg = "recover service '$sid' to previous failed and fenced node '$fenced_node' again";
865 }
866 $haenv->log('info', "$msg");
c259b1a8
TL
867
868 $fence_recovery_cleanup->($self, $sid, $fenced_node);
869
870 $haenv->steal_service($sid, $sd->{node}, $recovery_node);
5d724d4d 871 $self->{online_node_usage}->add_service_usage_to_node($recovery_node, $sid, $recovery_node);
c259b1a8
TL
872
873 # NOTE: $sd *is normally read-only*, fencing is the exception
874 $cd->{node} = $sd->{node} = $recovery_node;
875 my $new_state = ($cd->{state} eq 'started') ? 'started' : 'request_stop';
876 $change_service_state->($self, $sid, $new_state, node => $recovery_node);
877 } else {
878 # no possible node found, cannot recover - but retry later, as we always try to make it available
879 $haenv->log('err', "recovering service '$sid' from fenced node '$fenced_node' failed, no recovery node found");
719883e9
TL
880
881 if ($cd->{state} eq 'disabled') {
882 # allow getting a service out of recovery manually if an admin disables it.
883 delete $sd->{failed_nodes}; # clean up on recovery to stopped
884 $change_service_state->($self, $sid, 'stopped'); # must NOT go through request_stop
885 return;
886 }
c259b1a8
TL
887 }
888}
889
c0bbd038 8901;