3
from twisted.internet.defer import Deferred
4
from twisted.internet.protocol import ProcessProtocol
5
from twisted.internet.error import ProcessDone
7
from landscape.manager.manager import ManagerPlugin, SUCCEEDED, FAILED
10
class ShutdownFailedError(Exception):
11
"""Raised when a call to C{/sbin/shutdown} fails.
13
@ivar data: The data that the process printed before failing.
16
def __init__(self, data):
20
class ShutdownManager(ManagerPlugin):
22
def __init__(self, process_factory=None):
23
if process_factory is None:
24
from twisted.internet import reactor as process_factory
25
self._process_factory = process_factory
27
def register(self, registry):
28
"""Add this plugin to C{registry}.
30
The shutdown manager handles C{shutdown} activity messages broadcast
33
super(ShutdownManager, self).register(registry)
34
registry.register_message("shutdown", self.perform_shutdown)
36
def perform_shutdown(self, message):
37
"""Request a system restart or shutdown.
39
If the call to C{/sbin/shutdown} runs without errors the activity
40
specified in the message will be responded as succeeded. Otherwise,
41
it will be responded as failed.
43
operation_id = message["operation-id"]
44
reboot = reboot=message["reboot"]
45
protocol = ShutdownProcessProtocol()
46
protocol.set_timeout(self.registry.reactor)
47
protocol.result.addCallback(self._respond_success, operation_id)
48
protocol.result.addErrback(self._respond_failure, operation_id)
49
command, args = self._get_command_and_args(protocol, reboot)
50
self._process_factory.spawnProcess(protocol, command, args=args)
52
def _respond_success(self, data, operation_id):
53
logging.info("Shutdown request succeeded.")
54
return self._respond(SUCCEEDED, data, operation_id)
56
def _respond_failure(self, failure, operation_id):
57
logging.info("Shutdown request failed.")
58
return self._respond(FAILED, failure.value.data, operation_id)
60
def _respond(self, status, data, operation_id):
61
message = {"type": "operation-result",
64
"operation-id": operation_id}
65
return self.registry.broker.send_message(message, True)
67
def _get_command_and_args(self, protocol, reboot):
69
Returns a C{command, args} 2-tuple suitable for use with
70
L{IReactorProcess.spawnProcess}.
72
minutes = "+%d" % (protocol.delay // 60,)
74
args = ["/sbin/shutdown", "-r", minutes,
75
"Landscape is rebooting the system"]
77
args = ["/sbin/shutdown", "-h", minutes,
78
"Landscape is shutting down the system"]
79
return "/sbin/shutdown", args
82
class ShutdownProcessProtocol(ProcessProtocol):
83
"""A ProcessProtocol for calling C{/sbin/shutdown}.
85
C{shutdown} doesn't return immediately when a time specification is
86
provided. Failures are reported immediately after it starts and return a
87
non-zero exit code. The process protocol calls C{shutdown} and waits for
88
failures for C{timeout} seconds. If no failures are reported it fires
89
C{result}'s callback with whatever output was received from the process.
90
If failures are reported C{result}'s errback is fired.
92
@ivar result: A L{Deferred} fired when C{shutdown} fails or
94
@ivar reboot: A flag indicating whether a shutdown or reboot should be
95
performed. Default is C{False}.
96
@ivar delay: The time in seconds from now to schedule the shutdown.
97
Default is 240 seconds. The time will be converted to minutes using
98
integer division when passed to C{shutdown}.
101
def __init__(self, reboot=False, delay=240):
102
self.result = Deferred()
109
"""Get the data printed by the subprocess."""
110
return "".join(self._data)
112
def set_timeout(self, reactor, timeout=10):
114
Set the error checking timeout, after which C{result}'s callback will
117
reactor.call_later(timeout, self._succeed)
119
def childDataReceived(self, fd, data):
120
"""Some data was received from the child.
122
Add it to our buffer to pass to C{result} when it's fired.
125
self._data.append(data)
127
def processEnded(self, reason):
128
"""Fire back the C{result} L{Deferred}.
130
C{result}'s callback will be fired with the string of data received
131
from the subprocess, or if the subprocess failed C{result}'s errback
132
will be fired with the string of data received from the subprocess.
135
if reason.check(ProcessDone):
138
self.result.errback(ShutdownFailedError(self.get_data()))
139
self._waiting = False
142
"""Fire C{result}'s callback with data accumulated from the process."""
144
self.result.callback(self.get_data())
145
self._waiting = False