28
29
frequency = PER_INSTANCE
31
def handle(_name, cfg, cloud, log, _args):
33
finalcmds = cfg.get("finalcmd")
36
log.debug("No final commands")
32
def handle(_name, cfg, _cloud, log, _args):
35
(args, timeout) = load_power_state(cfg)
37
log.debug("no power_state provided. doing nothing")
39
except Exception as e:
40
log.warn("%s Not performing power state change!" % str(e))
39
43
mypid = os.getpid()
40
44
cmdline = util.load_file("/proc/%s/cmdline")
43
log.warn("Failed to get cmdline of current process")
47
log.warn("power_state: failed to get cmdline of current process")
50
devnull_fp = open("/dev/null", "w")
52
log.debug("After pid %s ends, will execute: %s" % (mypid, ' '.join(args)))
54
util.fork_cb(run_after_pid_gone, mypid, cmdline, timeout, log, execmd,
58
def load_power_state(cfg):
59
# returns a tuple of shutdown_command, timeout
60
# shutdown_command is None if no config found
61
pstate = cfg.get('power_state')
66
if not isinstance(pstate, dict):
67
raise TypeError("power_state is not a dict.")
69
opt_map = {'halt': '-H', 'poweroff': '-P', 'reboot': '-r'}
71
mode = pstate.get("mode")
72
if mode not in opt_map:
73
raise TypeError("power_state[mode] required, must be one of: %s." %
74
','.join(opt_map.keys()))
76
delay = pstate.get("delay", "now")
77
if delay != "now" and not re.match("\+[0-9]+", delay):
78
raise TypeError("power_state[delay] must be 'now' or '+m' (minutes).")
80
args = ["shutdown", opt_map[mode], delay]
81
if pstate.get("message"):
82
args.append(pstate.get("message"))
47
timeout = float(cfg.get("finalcmd_timeout", 30.0))
85
timeout = float(pstate.get('timeout', 30.0))
49
log.warn("failed to convert finalcmd_timeout '%s' to float" %
50
cfg.get("finalcmd_timeout", 30.0))
53
devnull_fp = open("/dev/null", "w")
55
shellcode = util.shellify(finalcmds)
57
# note, after the fork, we do not use any of cloud-init's functions
58
# that would attempt to log. The primary reason for that is
59
# to allow the 'finalcmd' the ability to do just about anything
60
# and not depend on syslog services.
61
# Basically, it should "just work" to have finalcmd of:
64
finalcmd_d = os.path.join(cloud.get_ipath_cur(), "finalcmds")
66
util.fork_cb(run_after_pid_gone, mypid, cmdline, timeout,
67
runfinal, (shellcode, finalcmd_d, devnull_fp))
70
def execmd(exe_args, data_in=None, output=None):
87
raise ValueError("failed to convert timeout '%s' to float." %
90
return (args, timeout)
93
def execmd(exe_args, output=None, data_in=None):
72
95
proc = subprocess.Popen(exe_args, stdin=subprocess.PIPE,
73
96
stdout=output, stderr=subprocess.STDOUT)
74
97
proc.communicate(data_in)
77
return proc.returncode()
80
def runfinal(shellcode, finalcmd_d, output=None):
81
ret = execmd(["/bin/sh"], data_in=shellcode, output=output)
82
if not (finalcmd_d and os.path.isdir(finalcmd_d)):
89
# now runparts the final command dir
90
for exe_name in sorted(os.listdir(finalcmd_d)):
91
exe_path = os.path.join(finalcmd_d, exe_name)
92
if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
93
ret = execmd([exe_path], data_in=None, output=output)
99
def run_after_pid_gone(pid, pidcmdline, timeout, func, args):
100
sys.exit(proc.returncode())
103
def run_after_pid_gone(pid, pidcmdline, timeout, log, func, args):
100
104
# wait until pid, with /proc/pid/cmdline contents of pidcmdline
101
105
# is no longer alive. After it is gone, or timeout has passed
102
106
# execute func(args)
103
msg = "ERROR: Uncaught error"
104
108
end_time = time.time() + timeout
106
110
cmdline_f = "/proc/%s/cmdline" % pid
109
118
if time.time() > end_time:
110
119
msg = "timeout reached before %s ended" % pid