~oddbloke/cloud-init/lp1506187

« back to all changes in this revision

Viewing changes to cloudinit/config/cc_power_state_change.py

  • Committer: Scott Moser
  • Date: 2015-09-08 20:53:59 UTC
  • Revision ID: smoser@ubuntu.com-20150908205359-smh83m5eg2ic3qbd
  power_state: support 'condition' argument
  
  if 'condition' is provided to config in power_state, then
  consult it before powering off.
  
  This allows the user to shut down only if a condition is met, and
  leave the system in a debuggable state otherwise.
  
  An example is as simple as:
   power_state:
     mode: poweroff
     condition: ['sh', '-c', '[ -f /disable-poweroff ]']

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
import errno
23
23
import os
24
24
import re
 
25
import six
25
26
import subprocess
26
27
import time
27
28
 
48
49
        return None
49
50
 
50
51
 
 
52
def check_condition(cond, log=None):
 
53
    if isinstance(cond, bool):
 
54
        if log:
 
55
            log.debug("Static Condition: %s" % cond)
 
56
        return cond
 
57
 
 
58
    pre = "check_condition command (%s): " % cond
 
59
    try:
 
60
        proc = subprocess.Popen(cond, shell=not isinstance(cond, list))
 
61
        proc.communicate()
 
62
        ret = proc.returncode
 
63
        if ret == 0:
 
64
            if log:
 
65
                log.debug(pre + "exited 0. condition met.")
 
66
            return True
 
67
        elif ret == 1:
 
68
            if log:
 
69
                log.debug(pre + "exited 1. condition not met.")
 
70
            return False
 
71
        else:
 
72
            if log:
 
73
                log.warn(pre + "unexpected exit %s. " % ret +
 
74
                         "do not apply change.")
 
75
            return False
 
76
    except Exception as e:
 
77
        if log:
 
78
            log.warn(pre + "Unexpected error: %s" % e)
 
79
        return False
 
80
 
 
81
 
51
82
def handle(_name, cfg, _cloud, log, _args):
52
83
 
53
84
    try:
54
 
        (args, timeout) = load_power_state(cfg)
 
85
        (args, timeout, condition) = load_power_state(cfg)
55
86
        if args is None:
56
87
            log.debug("no power_state provided. doing nothing")
57
88
            return
59
90
        log.warn("%s Not performing power state change!" % str(e))
60
91
        return
61
92
 
 
93
    if condition is False:
 
94
        log.debug("Condition was false. Will not perform state change.")
 
95
        return
 
96
 
62
97
    mypid = os.getpid()
63
98
 
64
99
    cmdline = givecmdline(mypid)
70
105
 
71
106
    log.debug("After pid %s ends, will execute: %s" % (mypid, ' '.join(args)))
72
107
 
73
 
    util.fork_cb(run_after_pid_gone, mypid, cmdline, timeout, log, execmd,
74
 
                 [args, devnull_fp])
 
108
    util.fork_cb(run_after_pid_gone, mypid, cmdline, timeout, log, 
 
109
                 condition, execmd, [args, devnull_fp])
75
110
 
76
111
 
77
112
def load_power_state(cfg):
80
115
    pstate = cfg.get('power_state')
81
116
 
82
117
    if pstate is None:
83
 
        return (None, None)
 
118
        return (None, None, None)
84
119
 
85
120
    if not isinstance(pstate, dict):
86
121
        raise TypeError("power_state is not a dict.")
115
150
        raise ValueError("failed to convert timeout '%s' to float." %
116
151
                         pstate['timeout'])
117
152
 
118
 
    return (args, timeout)
 
153
    condition = pstate.get("condition", True)
 
154
    if not isinstance(condition, six.string_types + (list, bool)):
 
155
        raise TypeError("condition type %s invalid. must be list, bool, str")
 
156
    return (args, timeout, condition)
119
157
 
120
158
 
121
159
def doexit(sysexit):
133
171
    doexit(ret)
134
172
 
135
173
 
136
 
def run_after_pid_gone(pid, pidcmdline, timeout, log, func, args):
 
174
def run_after_pid_gone(pid, pidcmdline, timeout, log, condition, func, args):
137
175
    # wait until pid, with /proc/pid/cmdline contents of pidcmdline
138
176
    # is no longer alive.  After it is gone, or timeout has passed
139
177
    # execute func(args)
175
213
 
176
214
    if log:
177
215
        log.debug(msg)
 
216
 
 
217
    try:
 
218
        if not check_condition(condition, log):
 
219
            return
 
220
    except Exception as e:
 
221
        fatal("Unexpected Exception when checking condition: %s" % e)
 
222
 
178
223
    func(*args)