~ahasenack/landscape-client/landscape-client-1.5.5-0ubuntu0.9.04.0

« back to all changes in this revision

Viewing changes to landscape/manager/shutdownmanager.py

  • Committer: Bazaar Package Importer
  • Author(s): Rick Clark
  • Date: 2008-09-08 16:35:57 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080908163557-l3ixzj5dxz37wnw2
Tags: 1.0.18-0ubuntu1
New upstream release 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import logging
 
2
 
 
3
from twisted.internet.defer import Deferred
 
4
from twisted.internet.protocol import ProcessProtocol
 
5
from twisted.internet.error import ProcessDone
 
6
 
 
7
from landscape.manager.manager import ManagerPlugin, SUCCEEDED, FAILED
 
8
 
 
9
 
 
10
class ShutdownFailedError(Exception):
 
11
    """Raised when a call to C{/sbin/shutdown} fails.
 
12
 
 
13
    @ivar data: The data that the process printed before failing.
 
14
    """
 
15
 
 
16
    def __init__(self, data):
 
17
        self.data = data
 
18
 
 
19
 
 
20
class ShutdownManager(ManagerPlugin):
 
21
 
 
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
 
26
 
 
27
    def register(self, registry):
 
28
        """Add this plugin to C{registry}.
 
29
 
 
30
        The shutdown manager handles C{shutdown} activity messages broadcast
 
31
        from the server.
 
32
        """
 
33
        super(ShutdownManager, self).register(registry)
 
34
        registry.register_message("shutdown", self.perform_shutdown)
 
35
 
 
36
    def perform_shutdown(self, message):
 
37
        """Request a system restart or shutdown.
 
38
 
 
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.
 
42
        """
 
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)
 
51
 
 
52
    def _respond_success(self, data, operation_id):
 
53
        logging.info("Shutdown request succeeded.")
 
54
        return self._respond(SUCCEEDED, data, operation_id)
 
55
 
 
56
    def _respond_failure(self, failure, operation_id):
 
57
        logging.info("Shutdown request failed.")
 
58
        return self._respond(FAILED, failure.value.data, operation_id)
 
59
 
 
60
    def _respond(self, status, data, operation_id):
 
61
        message = {"type": "operation-result",
 
62
                   "status": status,
 
63
                   "result-text": data,
 
64
                   "operation-id": operation_id}
 
65
        return self.registry.broker.send_message(message, True)
 
66
 
 
67
    def _get_command_and_args(self, protocol, reboot):
 
68
        """
 
69
        Returns a C{command, args} 2-tuple suitable for use with
 
70
        L{IReactorProcess.spawnProcess}.
 
71
        """
 
72
        minutes = "+%d" % (protocol.delay // 60,)
 
73
        if reboot:
 
74
            args = ["/sbin/shutdown", "-r", minutes,
 
75
                    "Landscape is rebooting the system"]
 
76
        else:
 
77
            args = ["/sbin/shutdown", "-h", minutes,
 
78
                    "Landscape is shutting down the system"]
 
79
        return "/sbin/shutdown", args
 
80
 
 
81
 
 
82
class ShutdownProcessProtocol(ProcessProtocol):
 
83
    """A ProcessProtocol for calling C{/sbin/shutdown}.
 
84
 
 
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.
 
91
 
 
92
    @ivar result: A L{Deferred} fired when C{shutdown} fails or
 
93
        succeeds.
 
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}.
 
99
    """
 
100
 
 
101
    def __init__(self, reboot=False, delay=240):
 
102
        self.result = Deferred()
 
103
        self.reboot = reboot
 
104
        self.delay = delay
 
105
        self._data = []
 
106
        self._waiting = True
 
107
 
 
108
    def get_data(self):
 
109
        """Get the data printed by the subprocess."""
 
110
        return "".join(self._data)
 
111
 
 
112
    def set_timeout(self, reactor, timeout=10):
 
113
        """
 
114
        Set the error checking timeout, after which C{result}'s callback will
 
115
        be fired.
 
116
        """
 
117
        reactor.call_later(timeout, self._succeed)
 
118
 
 
119
    def childDataReceived(self, fd, data):
 
120
        """Some data was received from the child.
 
121
 
 
122
        Add it to our buffer to pass to C{result} when it's fired.
 
123
        """
 
124
        if self._waiting:
 
125
            self._data.append(data)
 
126
 
 
127
    def processEnded(self, reason):
 
128
        """Fire back the C{result} L{Deferred}.
 
129
 
 
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.
 
133
        """
 
134
        if self._waiting:
 
135
            if reason.check(ProcessDone):
 
136
                self._succeed()
 
137
            else:
 
138
                self.result.errback(ShutdownFailedError(self.get_data()))
 
139
                self._waiting = False
 
140
 
 
141
    def _succeed(self):
 
142
        """Fire C{result}'s callback with data accumulated from the process."""
 
143
        if self._waiting:
 
144
            self.result.callback(self.get_data())
 
145
            self._waiting = False