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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * transition.c - Graph State Machine 30 * 31 * The graph state machine is implemented here, with a typical approach 32 * of a function per state. Separating the implementation allows more 33 * clarity into the actions taken on notification of state change, as well 34 * as a place for future expansion including hooks for configurable actions. 35 * All functions are called with dgraph_lock held. 36 * 37 * The start action for this state machine is not explicit. The states 38 * (ONLINE and DEGRADED) which need to know when they're entering the state 39 * due to a daemon restart implement this understanding by checking for 40 * transition from uninitialized. In the future, this would likely be better 41 * as an explicit start action instead of relying on an overloaded transition. 42 * 43 * All gt_enter functions use the same set of return codes. 44 * 0 success 45 * ECONNABORTED repository connection aborted 46 */ 47 48 #include "startd.h" 49 50 static int 51 gt_running(restarter_instance_state_t state) 52 { 53 if (state == RESTARTER_STATE_ONLINE || 54 state == RESTARTER_STATE_DEGRADED) 55 return (1); 56 57 return (0); 58 } 59 60 static int 61 gt_enter_uninit(scf_handle_t *h, graph_vertex_t *v, 62 restarter_instance_state_t old_state, restarter_error_t rerr) 63 { 64 int err; 65 scf_instance_t *inst; 66 67 /* Initialize instance by refreshing it. */ 68 69 err = libscf_fmri_get_instance(h, v->gv_name, &inst); 70 switch (err) { 71 case 0: 72 break; 73 74 case ECONNABORTED: 75 return (ECONNABORTED); 76 77 case ENOENT: 78 return (0); 79 80 case EINVAL: 81 case ENOTSUP: 82 default: 83 bad_error("libscf_fmri_get_instance", err); 84 } 85 86 err = refresh_vertex(v, inst); 87 if (err == 0) 88 graph_enable_by_vertex(v, v->gv_flags & GV_ENABLED, 0); 89 90 scf_instance_destroy(inst); 91 92 /* If the service was running, propagate a stop event. */ 93 if (gt_running(old_state)) { 94 log_framework(LOG_DEBUG, "Propagating stop of %s.\n", 95 v->gv_name); 96 97 graph_transition_propagate(v, PROPAGATE_STOP, rerr); 98 } 99 100 graph_transition_sulogin(RESTARTER_STATE_UNINIT, old_state); 101 return (0); 102 } 103 104 /* ARGSUSED */ 105 static int 106 gt_enter_maint(scf_handle_t *h, graph_vertex_t *v, 107 restarter_instance_state_t old_state, restarter_error_t rerr) 108 { 109 /* 110 * If the service was running, propagate a stop event. If the 111 * service was not running the maintenance transition may satisfy 112 * optional dependencies and should be propagated to determine 113 * whether new dependents are satisfiable. 114 */ 115 if (gt_running(old_state)) { 116 log_framework(LOG_DEBUG, "Propagating maintenance (stop) of " 117 "%s.\n", v->gv_name); 118 119 graph_transition_propagate(v, PROPAGATE_STOP, rerr); 120 } else { 121 log_framework(LOG_DEBUG, "Propagating maintenance of %s.\n", 122 v->gv_name); 123 124 graph_transition_propagate(v, PROPAGATE_SAT, rerr); 125 } 126 127 graph_transition_sulogin(RESTARTER_STATE_MAINT, old_state); 128 return (0); 129 } 130 131 /* ARGSUSED */ 132 static int 133 gt_enter_offline(scf_handle_t *h, graph_vertex_t *v, 134 restarter_instance_state_t old_state, restarter_error_t rerr) 135 { 136 /* 137 * If the instance should be enabled, see if we can start it. 138 * Otherwise send a disable command. 139 */ 140 if (v->gv_flags & GV_ENABLED) { 141 graph_start_if_satisfied(v); 142 } else { 143 if (gt_running(old_state) && v->gv_post_disable_f) 144 v->gv_post_disable_f(); 145 vertex_send_event(v, RESTARTER_EVENT_TYPE_DISABLE); 146 } 147 148 /* 149 * If the service was running, propagate a stop event. If the 150 * service was not running the offline transition may satisfy 151 * optional dependencies and should be propagated to determine 152 * whether new dependents are satisfiable. 153 */ 154 if (gt_running(old_state)) { 155 log_framework(LOG_DEBUG, "Propagating stop of %s.\n", 156 v->gv_name); 157 158 graph_transition_propagate(v, PROPAGATE_STOP, rerr); 159 } else { 160 log_framework(LOG_DEBUG, "Propagating offline of %s.\n", 161 v->gv_name); 162 163 graph_transition_propagate(v, PROPAGATE_SAT, rerr); 164 } 165 166 graph_transition_sulogin(RESTARTER_STATE_OFFLINE, old_state); 167 return (0); 168 } 169 170 /* ARGSUSED */ 171 static int 172 gt_enter_disabled(scf_handle_t *h, graph_vertex_t *v, 173 restarter_instance_state_t old_state, restarter_error_t rerr) 174 { 175 /* 176 * If the instance should be disabled, no problem. Otherwise, 177 * send an enable command, which should result in the instance 178 * moving to OFFLINE. 179 */ 180 if (v->gv_flags & GV_ENABLED) { 181 vertex_send_event(v, RESTARTER_EVENT_TYPE_ENABLE); 182 } else if (gt_running(old_state) && v->gv_post_disable_f) { 183 v->gv_post_disable_f(); 184 } 185 186 /* 187 * If the service was running, propagate this as a stop. If the 188 * service was not running the disabled transition may satisfy 189 * optional dependencies and should be propagated to determine 190 * whether new dependents are satisfiable. 191 */ 192 if (gt_running(old_state)) { 193 log_framework(LOG_DEBUG, "Propagating stop of %s.\n", 194 v->gv_name); 195 196 graph_transition_propagate(v, PROPAGATE_STOP, rerr); 197 198 } else { 199 log_framework(LOG_DEBUG, "Propagating disable of %s.\n", 200 v->gv_name); 201 202 graph_transition_propagate(v, PROPAGATE_SAT, rerr); 203 } 204 205 graph_transition_sulogin(RESTARTER_STATE_DISABLED, old_state); 206 return (0); 207 } 208 209 static int 210 gt_internal_online_or_degraded(scf_handle_t *h, graph_vertex_t *v, 211 restarter_instance_state_t old_state, restarter_error_t rerr) 212 { 213 int r; 214 215 /* 216 * If the instance has just come up, update the start 217 * snapshot. 218 */ 219 if (gt_running(old_state) == 0) { 220 /* 221 * Don't fire if we're just recovering state 222 * after a restart. 223 */ 224 if (old_state != RESTARTER_STATE_UNINIT && 225 v->gv_post_online_f) 226 v->gv_post_online_f(); 227 228 r = libscf_snapshots_poststart(h, v->gv_name, B_TRUE); 229 switch (r) { 230 case 0: 231 case ENOENT: 232 /* 233 * If ENOENT, the instance must have been 234 * deleted. Pretend we were successful since 235 * we should get a delete event later. 236 */ 237 break; 238 239 case ECONNABORTED: 240 return (ECONNABORTED); 241 242 case EACCES: 243 case ENOTSUP: 244 default: 245 bad_error("libscf_snapshots_poststart", r); 246 } 247 } 248 if (!(v->gv_flags & GV_ENABLED)) 249 vertex_send_event(v, RESTARTER_EVENT_TYPE_DISABLE); 250 251 if (gt_running(old_state) == 0) { 252 log_framework(LOG_DEBUG, "Propagating start of %s.\n", 253 v->gv_name); 254 255 graph_transition_propagate(v, PROPAGATE_START, rerr); 256 } else if (rerr == RERR_REFRESH) { 257 /* For refresh we'll get a message sans state change */ 258 259 log_framework(LOG_DEBUG, "Propagating refresh of %s.\n", 260 v->gv_name); 261 262 graph_transition_propagate(v, PROPAGATE_STOP, rerr); 263 } 264 265 return (0); 266 } 267 268 static int 269 gt_enter_online(scf_handle_t *h, graph_vertex_t *v, 270 restarter_instance_state_t old_state, restarter_error_t rerr) 271 { 272 int r; 273 274 r = gt_internal_online_or_degraded(h, v, old_state, rerr); 275 if (r != 0) 276 return (r); 277 278 graph_transition_sulogin(RESTARTER_STATE_ONLINE, old_state); 279 return (0); 280 } 281 282 static int 283 gt_enter_degraded(scf_handle_t *h, graph_vertex_t *v, 284 restarter_instance_state_t old_state, restarter_error_t rerr) 285 { 286 int r; 287 288 r = gt_internal_online_or_degraded(h, v, old_state, rerr); 289 if (r != 0) 290 return (r); 291 292 graph_transition_sulogin(RESTARTER_STATE_DEGRADED, old_state); 293 return (0); 294 } 295 296 /* 297 * gt_transition() implements the state transition for the graph 298 * state machine. It can return: 299 * 0 success 300 * ECONNABORTED repository connection aborted 301 * 302 * v->gv_state should be set to the state we're transitioning to before 303 * calling this function. 304 */ 305 int 306 gt_transition(scf_handle_t *h, graph_vertex_t *v, restarter_error_t rerr, 307 restarter_instance_state_t old_state) 308 { 309 int err; 310 int lost_repository = 0; 311 312 /* 313 * If there's a common set of work to be done on exit from the 314 * old_state, include it as a separate set of functions here. For 315 * now there's no such work, so there are no gt_exit functions. 316 */ 317 318 err = vertex_subgraph_dependencies_shutdown(h, v, old_state); 319 switch (err) { 320 case 0: 321 break; 322 323 case ECONNABORTED: 324 lost_repository = 1; 325 break; 326 327 default: 328 bad_error("vertex_subgraph_dependencies_shutdown", err); 329 } 330 331 /* 332 * Now call the appropriate gt_enter function for the new state. 333 */ 334 switch (v->gv_state) { 335 case RESTARTER_STATE_UNINIT: 336 err = gt_enter_uninit(h, v, old_state, rerr); 337 break; 338 339 case RESTARTER_STATE_DISABLED: 340 err = gt_enter_disabled(h, v, old_state, rerr); 341 break; 342 343 case RESTARTER_STATE_OFFLINE: 344 err = gt_enter_offline(h, v, old_state, rerr); 345 break; 346 347 case RESTARTER_STATE_ONLINE: 348 err = gt_enter_online(h, v, old_state, rerr); 349 break; 350 351 case RESTARTER_STATE_DEGRADED: 352 err = gt_enter_degraded(h, v, old_state, rerr); 353 break; 354 355 case RESTARTER_STATE_MAINT: 356 err = gt_enter_maint(h, v, old_state, rerr); 357 break; 358 359 default: 360 /* Shouldn't be in an invalid state. */ 361 #ifndef NDEBUG 362 uu_warn("%s:%d: Invalid state %d.\n", __FILE__, __LINE__, 363 v->gv_state); 364 #endif 365 abort(); 366 } 367 368 switch (err) { 369 case 0: 370 break; 371 372 case ECONNABORTED: 373 lost_repository = 1; 374 break; 375 376 default: 377 #ifndef NDEBUG 378 uu_warn("%s:%d: " 379 "gt_enter_%s() failed with unexpected error %d.\n", 380 __FILE__, __LINE__, instance_state_str[v->gv_state], err); 381 #endif 382 abort(); 383 } 384 385 return (lost_repository ? ECONNABORTED : 0); 386 } 387