1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26
27 /*
28 * transition.c - Graph State Machine
29 *
30 * The graph state machine is implemented here, with a typical approach
31 * of a function per state. Separating the implementation allows more
32 * clarity into the actions taken on notification of state change, as well
33 * as a place for future expansion including hooks for configurable actions.
34 * All functions are called with dgraph_lock held.
35 *
36 * The start action for this state machine is not explicit. The states
37 * (ONLINE and DEGRADED) which need to know when they're entering the state
38 * due to a daemon restart implement this understanding by checking for
39 * transition from uninitialized. In the future, this would likely be better
40 * as an explicit start action instead of relying on an overloaded transition.
41 *
42 * All gt_enter functions use the same set of return codes.
43 * 0 success
44 * ECONNABORTED repository connection aborted
45 */
46
47 #include "startd.h"
48
49 static int
gt_running(restarter_instance_state_t state)50 gt_running(restarter_instance_state_t state)
51 {
52 if (state == RESTARTER_STATE_ONLINE ||
53 state == RESTARTER_STATE_DEGRADED)
54 return (1);
55
56 return (0);
57 }
58
59 static int
gt_enter_uninit(scf_handle_t * h,graph_vertex_t * v,restarter_instance_state_t old_state,restarter_error_t rerr)60 gt_enter_uninit(scf_handle_t *h, graph_vertex_t *v,
61 restarter_instance_state_t old_state, restarter_error_t rerr)
62 {
63 int err;
64 scf_instance_t *inst;
65
66 /* Initialize instance by refreshing it. */
67
68 err = libscf_fmri_get_instance(h, v->gv_name, &inst);
69 switch (err) {
70 case 0:
71 break;
72
73 case ECONNABORTED:
74 return (ECONNABORTED);
75
76 case ENOENT:
77 return (0);
78
79 case EINVAL:
80 case ENOTSUP:
81 default:
82 bad_error("libscf_fmri_get_instance", err);
83 }
84
85 err = refresh_vertex(v, inst);
86 if (err == 0)
87 graph_enable_by_vertex(v, v->gv_flags & GV_ENABLED, 0);
88
89 scf_instance_destroy(inst);
90
91 /* If the service was running, propagate a stop event. */
92 if (gt_running(old_state)) {
93 log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
94 v->gv_name);
95
96 graph_transition_propagate(v, PROPAGATE_STOP, rerr);
97 }
98
99 graph_transition_sulogin(RESTARTER_STATE_UNINIT, old_state);
100 return (0);
101 }
102
103 /* ARGSUSED */
104 static int
gt_enter_maint(scf_handle_t * h,graph_vertex_t * v,restarter_instance_state_t old_state,restarter_error_t rerr)105 gt_enter_maint(scf_handle_t *h, graph_vertex_t *v,
106 restarter_instance_state_t old_state, restarter_error_t rerr)
107 {
108 int to_offline = v->gv_flags & GV_TOOFFLINE;
109
110 /*
111 * If the service was running, propagate a stop event. If the
112 * service was not running the maintenance transition may satisfy
113 * optional dependencies and should be propagated to determine
114 * whether new dependents are satisfiable.
115 * Instances that transition to maintenance and have the GV_TOOFFLINE
116 * flag are special because they can expose new subtree leaves so
117 * propagate the offline to the instance dependencies.
118 */
119
120 /* instance transitioning to maintenance is considered disabled */
121 v->gv_flags &= ~GV_TODISABLE;
122 v->gv_flags &= ~GV_TOOFFLINE;
123
124 if (gt_running(old_state)) {
125 /*
126 * Handle state change during instance disabling.
127 * Propagate offline to the new exposed leaves.
128 */
129 if (to_offline) {
130 log_framework(LOG_DEBUG, "%s removed from subtree\n",
131 v->gv_name);
132
133 graph_offline_subtree_leaves(v, (void *)h);
134 }
135
136 log_framework(LOG_DEBUG, "Propagating maintenance (stop) of "
137 "%s.\n", v->gv_name);
138
139 graph_transition_propagate(v, PROPAGATE_STOP, rerr);
140 } else {
141 log_framework(LOG_DEBUG, "Propagating maintenance of %s.\n",
142 v->gv_name);
143
144 graph_transition_propagate(v, PROPAGATE_SAT, rerr);
145 }
146
147 graph_transition_sulogin(RESTARTER_STATE_MAINT, old_state);
148 return (0);
149 }
150
151 /* ARGSUSED */
152 static int
gt_enter_offline(scf_handle_t * h,graph_vertex_t * v,restarter_instance_state_t old_state,restarter_error_t rerr)153 gt_enter_offline(scf_handle_t *h, graph_vertex_t *v,
154 restarter_instance_state_t old_state, restarter_error_t rerr)
155 {
156 int to_offline = v->gv_flags & GV_TOOFFLINE;
157
158 v->gv_flags &= ~GV_TOOFFLINE;
159
160 /*
161 * If the instance should be enabled, see if we can start it.
162 * Otherwise send a disable command.
163 * If a instance has the GV_TOOFFLINE flag set then it must
164 * remains offline until the disable process completes.
165 */
166 if (v->gv_flags & GV_ENABLED) {
167 if (to_offline == 0)
168 graph_start_if_satisfied(v);
169 } else {
170 if (gt_running(old_state) && v->gv_post_disable_f)
171 v->gv_post_disable_f();
172
173 vertex_send_event(v, RESTARTER_EVENT_TYPE_DISABLE);
174 }
175
176 /*
177 * If the service was running, propagate a stop event. If the
178 * service was not running the offline transition may satisfy
179 * optional dependencies and should be propagated to determine
180 * whether new dependents are satisfiable.
181 * Instances that transition to offline and have the GV_TOOFFLINE flag
182 * are special because they can expose new subtree leaves so propagate
183 * the offline to the instance dependencies.
184 */
185 if (gt_running(old_state)) {
186 /*
187 * Handle state change during instance disabling.
188 * Propagate offline to the new exposed leaves.
189 */
190 if (to_offline) {
191 log_framework(LOG_DEBUG, "%s removed from subtree\n",
192 v->gv_name);
193
194 graph_offline_subtree_leaves(v, (void *)h);
195 }
196
197 log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
198 v->gv_name);
199
200 graph_transition_propagate(v, PROPAGATE_STOP, rerr);
201
202 /*
203 * The offline transition may satisfy require_any/restart
204 * dependencies and should be propagated to determine
205 * whether new dependents are satisfiable.
206 */
207 graph_transition_propagate(v, PROPAGATE_SAT, rerr);
208 } else {
209 log_framework(LOG_DEBUG, "Propagating offline of %s.\n",
210 v->gv_name);
211
212 graph_transition_propagate(v, PROPAGATE_SAT, rerr);
213 }
214
215 graph_transition_sulogin(RESTARTER_STATE_OFFLINE, old_state);
216 return (0);
217 }
218
219 /* ARGSUSED */
220 static int
gt_enter_disabled(scf_handle_t * h,graph_vertex_t * v,restarter_instance_state_t old_state,restarter_error_t rerr)221 gt_enter_disabled(scf_handle_t *h, graph_vertex_t *v,
222 restarter_instance_state_t old_state, restarter_error_t rerr)
223 {
224 int to_offline = v->gv_flags & GV_TOOFFLINE;
225
226 v->gv_flags &= ~GV_TODISABLE;
227 v->gv_flags &= ~GV_TOOFFLINE;
228
229 /*
230 * If the instance should be disabled, no problem. Otherwise,
231 * send an enable command, which should result in the instance
232 * moving to OFFLINE unless the instance is part of a subtree
233 * (non root) and in this case the result is unpredictable.
234 */
235 if (v->gv_flags & GV_ENABLED) {
236 vertex_send_event(v, RESTARTER_EVENT_TYPE_ENABLE);
237 } else if (gt_running(old_state) && v->gv_post_disable_f) {
238 v->gv_post_disable_f();
239 }
240
241 /*
242 * If the service was running, propagate this as a stop. If the
243 * service was not running the disabled transition may satisfy
244 * optional dependencies and should be propagated to determine
245 * whether new dependents are satisfiable.
246 */
247 if (gt_running(old_state)) {
248 /*
249 * We need to propagate the offline to new exposed leaves in
250 * case we've just disabled an instance that was part of a
251 * subtree.
252 */
253 if (to_offline) {
254 log_framework(LOG_DEBUG, "%s removed from subtree\n",
255 v->gv_name);
256
257 /*
258 * Handle state change during instance disabling.
259 * Propagate offline to the new exposed leaves.
260 */
261 graph_offline_subtree_leaves(v, (void *)h);
262 }
263
264
265 log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
266 v->gv_name);
267
268 graph_transition_propagate(v, PROPAGATE_STOP, rerr);
269
270 } else {
271 log_framework(LOG_DEBUG, "Propagating disable of %s.\n",
272 v->gv_name);
273
274 graph_transition_propagate(v, PROPAGATE_SAT, rerr);
275 }
276
277 graph_transition_sulogin(RESTARTER_STATE_DISABLED, old_state);
278 return (0);
279 }
280
281 static int
gt_internal_online_or_degraded(scf_handle_t * h,graph_vertex_t * v,restarter_instance_state_t old_state,restarter_error_t rerr)282 gt_internal_online_or_degraded(scf_handle_t *h, graph_vertex_t *v,
283 restarter_instance_state_t old_state, restarter_error_t rerr)
284 {
285 int r;
286
287 /*
288 * If the instance has just come up, update the start
289 * snapshot.
290 */
291 if (gt_running(old_state) == 0) {
292 /*
293 * Don't fire if we're just recovering state
294 * after a restart.
295 */
296 if (old_state != RESTARTER_STATE_UNINIT &&
297 v->gv_post_online_f)
298 v->gv_post_online_f();
299
300 r = libscf_snapshots_poststart(h, v->gv_name, B_TRUE);
301 switch (r) {
302 case 0:
303 case ENOENT:
304 /*
305 * If ENOENT, the instance must have been
306 * deleted. Pretend we were successful since
307 * we should get a delete event later.
308 */
309 break;
310
311 case ECONNABORTED:
312 return (ECONNABORTED);
313
314 case EACCES:
315 case ENOTSUP:
316 default:
317 bad_error("libscf_snapshots_poststart", r);
318 }
319 }
320
321 if (!(v->gv_flags & GV_ENABLED)) {
322 vertex_send_event(v, RESTARTER_EVENT_TYPE_DISABLE);
323 } else if (v->gv_flags & GV_TOOFFLINE) {
324 /*
325 * If the vertex has the GV_TOOFFLINE flag set then that's
326 * because the instance was transitioning from offline to
327 * online and the reverse disable algorithm doesn't offline
328 * those instances because it was already appearing offline.
329 * So do it now.
330 */
331 offline_vertex(v);
332 }
333
334 if (gt_running(old_state) == 0) {
335 log_framework(LOG_DEBUG, "Propagating start of %s.\n",
336 v->gv_name);
337
338 graph_transition_propagate(v, PROPAGATE_START, rerr);
339 } else if (rerr == RERR_REFRESH) {
340 /* For refresh we'll get a message sans state change */
341
342 log_framework(LOG_DEBUG, "Propagating refresh of %s.\n",
343 v->gv_name);
344
345 graph_transition_propagate(v, PROPAGATE_STOP, rerr);
346 }
347
348 return (0);
349 }
350
351 static int
gt_enter_online(scf_handle_t * h,graph_vertex_t * v,restarter_instance_state_t old_state,restarter_error_t rerr)352 gt_enter_online(scf_handle_t *h, graph_vertex_t *v,
353 restarter_instance_state_t old_state, restarter_error_t rerr)
354 {
355 int r;
356
357 r = gt_internal_online_or_degraded(h, v, old_state, rerr);
358 if (r != 0)
359 return (r);
360
361 graph_transition_sulogin(RESTARTER_STATE_ONLINE, old_state);
362 return (0);
363 }
364
365 static int
gt_enter_degraded(scf_handle_t * h,graph_vertex_t * v,restarter_instance_state_t old_state,restarter_error_t rerr)366 gt_enter_degraded(scf_handle_t *h, graph_vertex_t *v,
367 restarter_instance_state_t old_state, restarter_error_t rerr)
368 {
369 int r;
370
371 r = gt_internal_online_or_degraded(h, v, old_state, rerr);
372 if (r != 0)
373 return (r);
374
375 graph_transition_sulogin(RESTARTER_STATE_DEGRADED, old_state);
376 return (0);
377 }
378
379 /*
380 * gt_transition() implements the state transition for the graph
381 * state machine. It can return:
382 * 0 success
383 * ECONNABORTED repository connection aborted
384 *
385 * v->gv_state should be set to the state we're transitioning to before
386 * calling this function.
387 */
388 int
gt_transition(scf_handle_t * h,graph_vertex_t * v,restarter_error_t rerr,restarter_instance_state_t old_state)389 gt_transition(scf_handle_t *h, graph_vertex_t *v, restarter_error_t rerr,
390 restarter_instance_state_t old_state)
391 {
392 int err;
393 int lost_repository = 0;
394
395 /*
396 * If there's a common set of work to be done on exit from the
397 * old_state, include it as a separate set of functions here. For
398 * now there's no such work, so there are no gt_exit functions.
399 */
400
401 err = vertex_subgraph_dependencies_shutdown(h, v, old_state);
402 switch (err) {
403 case 0:
404 break;
405
406 case ECONNABORTED:
407 lost_repository = 1;
408 break;
409
410 default:
411 bad_error("vertex_subgraph_dependencies_shutdown", err);
412 }
413
414 /*
415 * Now call the appropriate gt_enter function for the new state.
416 */
417 switch (v->gv_state) {
418 case RESTARTER_STATE_UNINIT:
419 err = gt_enter_uninit(h, v, old_state, rerr);
420 break;
421
422 case RESTARTER_STATE_DISABLED:
423 err = gt_enter_disabled(h, v, old_state, rerr);
424 break;
425
426 case RESTARTER_STATE_OFFLINE:
427 err = gt_enter_offline(h, v, old_state, rerr);
428 break;
429
430 case RESTARTER_STATE_ONLINE:
431 err = gt_enter_online(h, v, old_state, rerr);
432 break;
433
434 case RESTARTER_STATE_DEGRADED:
435 err = gt_enter_degraded(h, v, old_state, rerr);
436 break;
437
438 case RESTARTER_STATE_MAINT:
439 err = gt_enter_maint(h, v, old_state, rerr);
440 break;
441
442 default:
443 /* Shouldn't be in an invalid state. */
444 #ifndef NDEBUG
445 uu_warn("%s:%d: Invalid state %d.\n", __FILE__, __LINE__,
446 v->gv_state);
447 #endif
448 abort();
449 }
450
451 switch (err) {
452 case 0:
453 break;
454
455 case ECONNABORTED:
456 lost_repository = 1;
457 break;
458
459 default:
460 #ifndef NDEBUG
461 uu_warn("%s:%d: "
462 "gt_enter_%s() failed with unexpected error %d.\n",
463 __FILE__, __LINE__, instance_state_str[v->gv_state], err);
464 #endif
465 abort();
466 }
467
468 return (lost_repository ? ECONNABORTED : 0);
469 }
470