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