Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-masterd @ ee6c7b94

History | View | Annotate | Download (9.3 kB)

1 685ee993 Iustin Pop
#!/usr/bin/python -u
2 ffeffa1d Iustin Pop
#
3 ffeffa1d Iustin Pop
4 ffeffa1d Iustin Pop
# Copyright (C) 2006, 2007 Google Inc.
5 ffeffa1d Iustin Pop
#
6 ffeffa1d Iustin Pop
# This program is free software; you can redistribute it and/or modify
7 ffeffa1d Iustin Pop
# it under the terms of the GNU General Public License as published by
8 ffeffa1d Iustin Pop
# the Free Software Foundation; either version 2 of the License, or
9 ffeffa1d Iustin Pop
# (at your option) any later version.
10 ffeffa1d Iustin Pop
#
11 ffeffa1d Iustin Pop
# This program is distributed in the hope that it will be useful, but
12 ffeffa1d Iustin Pop
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 ffeffa1d Iustin Pop
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 ffeffa1d Iustin Pop
# General Public License for more details.
15 ffeffa1d Iustin Pop
#
16 ffeffa1d Iustin Pop
# You should have received a copy of the GNU General Public License
17 ffeffa1d Iustin Pop
# along with this program; if not, write to the Free Software
18 ffeffa1d Iustin Pop
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 ffeffa1d Iustin Pop
# 02110-1301, USA.
20 ffeffa1d Iustin Pop
21 ffeffa1d Iustin Pop
22 ffeffa1d Iustin Pop
"""Master daemon program.
23 ffeffa1d Iustin Pop
24 ffeffa1d Iustin Pop
Some classes deviates from the standard style guide since the
25 ffeffa1d Iustin Pop
inheritance from parent classes requires it.
26 ffeffa1d Iustin Pop
27 ffeffa1d Iustin Pop
"""
28 ffeffa1d Iustin Pop
29 ffeffa1d Iustin Pop
30 c1f2901b Iustin Pop
import sys
31 ffeffa1d Iustin Pop
import SocketServer
32 ffeffa1d Iustin Pop
import time
33 ffeffa1d Iustin Pop
import collections
34 ffeffa1d Iustin Pop
import Queue
35 ffeffa1d Iustin Pop
import random
36 ffeffa1d Iustin Pop
import signal
37 ffeffa1d Iustin Pop
import simplejson
38 96cb3986 Michael Hanselmann
import logging
39 ffeffa1d Iustin Pop
40 ffeffa1d Iustin Pop
from cStringIO import StringIO
41 c1f2901b Iustin Pop
from optparse import OptionParser
42 ffeffa1d Iustin Pop
43 39dcf2ef Guido Trotter
from ganeti import config
44 ffeffa1d Iustin Pop
from ganeti import constants
45 ffeffa1d Iustin Pop
from ganeti import mcpu
46 ffeffa1d Iustin Pop
from ganeti import opcodes
47 ffeffa1d Iustin Pop
from ganeti import jqueue
48 39dcf2ef Guido Trotter
from ganeti import locking
49 ffeffa1d Iustin Pop
from ganeti import luxi
50 ffeffa1d Iustin Pop
from ganeti import utils
51 c1f2901b Iustin Pop
from ganeti import errors
52 c1f2901b Iustin Pop
from ganeti import ssconf
53 96cb3986 Michael Hanselmann
from ganeti import logger
54 23e50d39 Michael Hanselmann
from ganeti import workerpool
55 b1b6ea87 Iustin Pop
from ganeti import rpc
56 c1f2901b Iustin Pop
57 c1f2901b Iustin Pop
58 23e50d39 Michael Hanselmann
CLIENT_REQUEST_WORKERS = 16
59 23e50d39 Michael Hanselmann
60 c1f2901b Iustin Pop
EXIT_NOTMASTER = constants.EXIT_NOTMASTER
61 c1f2901b Iustin Pop
EXIT_NODESETUP_ERROR = constants.EXIT_NODESETUP_ERROR
62 ffeffa1d Iustin Pop
63 ffeffa1d Iustin Pop
64 23e50d39 Michael Hanselmann
class ClientRequestWorker(workerpool.BaseWorker):
65 23e50d39 Michael Hanselmann
  def RunTask(self, server, request, client_address):
66 23e50d39 Michael Hanselmann
    """Process the request.
67 23e50d39 Michael Hanselmann
68 23e50d39 Michael Hanselmann
    This is copied from the code in ThreadingMixIn.
69 23e50d39 Michael Hanselmann
70 23e50d39 Michael Hanselmann
    """
71 23e50d39 Michael Hanselmann
    try:
72 23e50d39 Michael Hanselmann
      server.finish_request(request, client_address)
73 23e50d39 Michael Hanselmann
      server.close_request(request)
74 23e50d39 Michael Hanselmann
    except:
75 23e50d39 Michael Hanselmann
      server.handle_error(request, client_address)
76 23e50d39 Michael Hanselmann
      server.close_request(request)
77 23e50d39 Michael Hanselmann
78 23e50d39 Michael Hanselmann
79 ffeffa1d Iustin Pop
class IOServer(SocketServer.UnixStreamServer):
80 ffeffa1d Iustin Pop
  """IO thread class.
81 ffeffa1d Iustin Pop
82 ffeffa1d Iustin Pop
  This class takes care of initializing the other threads, setting
83 ffeffa1d Iustin Pop
  signal handlers (which are processed only in this thread), and doing
84 ffeffa1d Iustin Pop
  cleanup at shutdown.
85 ffeffa1d Iustin Pop
86 ffeffa1d Iustin Pop
  """
87 39dcf2ef Guido Trotter
  def __init__(self, address, rqhandler, context):
88 ce862cd5 Guido Trotter
    """IOServer constructor
89 ce862cd5 Guido Trotter
90 ce862cd5 Guido Trotter
    Args:
91 ce862cd5 Guido Trotter
      address: the address to bind this IOServer to
92 ce862cd5 Guido Trotter
      rqhandler: RequestHandler type object
93 39dcf2ef Guido Trotter
      context: Context Object common to all worker threads
94 ce862cd5 Guido Trotter
95 ce862cd5 Guido Trotter
    """
96 ffeffa1d Iustin Pop
    SocketServer.UnixStreamServer.__init__(self, address, rqhandler)
97 39dcf2ef Guido Trotter
    self.context = context
98 50a3fbb2 Michael Hanselmann
99 50a3fbb2 Michael Hanselmann
    # We'll only start threads once we've forked.
100 50a3fbb2 Michael Hanselmann
    self.jobqueue = None
101 23e50d39 Michael Hanselmann
    self.request_workers = None
102 50a3fbb2 Michael Hanselmann
103 50a3fbb2 Michael Hanselmann
  def setup_queue(self):
104 50a3fbb2 Michael Hanselmann
    self.jobqueue = jqueue.JobQueue(self.context)
105 23e50d39 Michael Hanselmann
    self.request_workers = workerpool.WorkerPool(CLIENT_REQUEST_WORKERS,
106 23e50d39 Michael Hanselmann
                                                 ClientRequestWorker)
107 ffeffa1d Iustin Pop
108 ffeffa1d Iustin Pop
  def process_request(self, request, client_address):
109 23e50d39 Michael Hanselmann
    """Add task to workerpool to process request.
110 ffeffa1d Iustin Pop
111 ffeffa1d Iustin Pop
    """
112 23e50d39 Michael Hanselmann
    self.request_workers.AddTask(self, request, client_address)
113 ffeffa1d Iustin Pop
114 ffeffa1d Iustin Pop
  def serve_forever(self):
115 ffeffa1d Iustin Pop
    """Handle one request at a time until told to quit."""
116 610bc9ee Michael Hanselmann
    sighandler = utils.SignalHandler([signal.SIGINT, signal.SIGTERM])
117 610bc9ee Michael Hanselmann
    try:
118 610bc9ee Michael Hanselmann
      while not sighandler.called:
119 610bc9ee Michael Hanselmann
        self.handle_request()
120 610bc9ee Michael Hanselmann
    finally:
121 610bc9ee Michael Hanselmann
      sighandler.Reset()
122 c1f2901b Iustin Pop
123 c1f2901b Iustin Pop
  def server_cleanup(self):
124 c1f2901b Iustin Pop
    """Cleanup the server.
125 c1f2901b Iustin Pop
126 c1f2901b Iustin Pop
    This involves shutting down the processor threads and the master
127 c1f2901b Iustin Pop
    socket.
128 c1f2901b Iustin Pop
129 c1f2901b Iustin Pop
    """
130 50a3fbb2 Michael Hanselmann
    try:
131 50a3fbb2 Michael Hanselmann
      self.server_close()
132 50a3fbb2 Michael Hanselmann
      utils.RemoveFile(constants.MASTER_SOCKET)
133 50a3fbb2 Michael Hanselmann
    finally:
134 23e50d39 Michael Hanselmann
      if self.request_workers:
135 36088c4c Michael Hanselmann
        self.request_workers.TerminateWorkers()
136 50a3fbb2 Michael Hanselmann
      if self.jobqueue:
137 50a3fbb2 Michael Hanselmann
        self.jobqueue.Shutdown()
138 ffeffa1d Iustin Pop
139 ffeffa1d Iustin Pop
140 ffeffa1d Iustin Pop
class ClientRqHandler(SocketServer.BaseRequestHandler):
141 ffeffa1d Iustin Pop
  """Client handler"""
142 ffeffa1d Iustin Pop
  EOM = '\3'
143 ffeffa1d Iustin Pop
  READ_SIZE = 4096
144 ffeffa1d Iustin Pop
145 ffeffa1d Iustin Pop
  def setup(self):
146 ffeffa1d Iustin Pop
    self._buffer = ""
147 ffeffa1d Iustin Pop
    self._msgs = collections.deque()
148 ffeffa1d Iustin Pop
    self._ops = ClientOps(self.server)
149 ffeffa1d Iustin Pop
150 ffeffa1d Iustin Pop
  def handle(self):
151 ffeffa1d Iustin Pop
    while True:
152 ffeffa1d Iustin Pop
      msg = self.read_message()
153 ffeffa1d Iustin Pop
      if msg is None:
154 3d8548c4 Michael Hanselmann
        logging.info("client closed connection")
155 ffeffa1d Iustin Pop
        break
156 3d8548c4 Michael Hanselmann
157 ffeffa1d Iustin Pop
      request = simplejson.loads(msg)
158 3d8548c4 Michael Hanselmann
      logging.debug("request: %s", request)
159 ffeffa1d Iustin Pop
      if not isinstance(request, dict):
160 3d8548c4 Michael Hanselmann
        logging.error("wrong request received: %s", msg)
161 ffeffa1d Iustin Pop
        break
162 3d8548c4 Michael Hanselmann
163 3d8548c4 Michael Hanselmann
      method = request.get(luxi.KEY_METHOD, None)
164 3d8548c4 Michael Hanselmann
      args = request.get(luxi.KEY_ARGS, None)
165 3d8548c4 Michael Hanselmann
      if method is None or args is None:
166 3d8548c4 Michael Hanselmann
        logging.error("no method or args in request")
167 ffeffa1d Iustin Pop
        break
168 3d8548c4 Michael Hanselmann
169 3d8548c4 Michael Hanselmann
      success = False
170 3d8548c4 Michael Hanselmann
      try:
171 3d8548c4 Michael Hanselmann
        result = self._ops.handle_request(method, args)
172 3d8548c4 Michael Hanselmann
        success = True
173 3d8548c4 Michael Hanselmann
      except:
174 3d8548c4 Michael Hanselmann
        logging.error("Unexpected exception", exc_info=True)
175 3d8548c4 Michael Hanselmann
        err = sys.exc_info()
176 3d8548c4 Michael Hanselmann
        result = "Caught exception: %s" % str(err[1])
177 3d8548c4 Michael Hanselmann
178 3d8548c4 Michael Hanselmann
      response = {
179 3d8548c4 Michael Hanselmann
        luxi.KEY_SUCCESS: success,
180 3d8548c4 Michael Hanselmann
        luxi.KEY_RESULT: result,
181 3d8548c4 Michael Hanselmann
        }
182 3d8548c4 Michael Hanselmann
      logging.debug("response: %s", response)
183 3d8548c4 Michael Hanselmann
      self.send_message(simplejson.dumps(response))
184 ffeffa1d Iustin Pop
185 ffeffa1d Iustin Pop
  def read_message(self):
186 ffeffa1d Iustin Pop
    while not self._msgs:
187 ffeffa1d Iustin Pop
      data = self.request.recv(self.READ_SIZE)
188 ffeffa1d Iustin Pop
      if not data:
189 ffeffa1d Iustin Pop
        return None
190 ffeffa1d Iustin Pop
      new_msgs = (self._buffer + data).split(self.EOM)
191 ffeffa1d Iustin Pop
      self._buffer = new_msgs.pop()
192 ffeffa1d Iustin Pop
      self._msgs.extend(new_msgs)
193 ffeffa1d Iustin Pop
    return self._msgs.popleft()
194 ffeffa1d Iustin Pop
195 ffeffa1d Iustin Pop
  def send_message(self, msg):
196 ffeffa1d Iustin Pop
    #print "sending", msg
197 ffeffa1d Iustin Pop
    self.request.sendall(msg + self.EOM)
198 ffeffa1d Iustin Pop
199 ffeffa1d Iustin Pop
200 ffeffa1d Iustin Pop
class ClientOps:
201 ffeffa1d Iustin Pop
  """Class holding high-level client operations."""
202 ffeffa1d Iustin Pop
  def __init__(self, server):
203 ffeffa1d Iustin Pop
    self.server = server
204 ffeffa1d Iustin Pop
205 0bbe448c Michael Hanselmann
  def handle_request(self, method, args):
206 0bbe448c Michael Hanselmann
    queue = self.server.jobqueue
207 0bbe448c Michael Hanselmann
208 0bbe448c Michael Hanselmann
    # TODO: Parameter validation
209 0bbe448c Michael Hanselmann
210 0bbe448c Michael Hanselmann
    if method == luxi.REQ_SUBMIT_JOB:
211 0bbe448c Michael Hanselmann
      ops = [opcodes.OpCode.LoadOpCode(state) for state in args]
212 c3f0a12f Iustin Pop
      # we need to compute the node list here, since from now on all
213 c3f0a12f Iustin Pop
      # operations require locks on the queue or the storage, and we
214 c3f0a12f Iustin Pop
      # shouldn't get another lock
215 c3f0a12f Iustin Pop
      node_list = self.server.context.cfg.GetNodeList()
216 c3f0a12f Iustin Pop
      return queue.SubmitJob(ops, node_list)
217 ffeffa1d Iustin Pop
218 0bbe448c Michael Hanselmann
    elif method == luxi.REQ_CANCEL_JOB:
219 3a2c7775 Michael Hanselmann
      job_id = args
220 0bbe448c Michael Hanselmann
      return queue.CancelJob(job_id)
221 ffeffa1d Iustin Pop
222 0bbe448c Michael Hanselmann
    elif method == luxi.REQ_ARCHIVE_JOB:
223 3a2c7775 Michael Hanselmann
      job_id = args
224 0bbe448c Michael Hanselmann
      return queue.ArchiveJob(job_id)
225 0bbe448c Michael Hanselmann
226 0bbe448c Michael Hanselmann
    elif method == luxi.REQ_QUERY_JOBS:
227 0bbe448c Michael Hanselmann
      (job_ids, fields) = args
228 0bbe448c Michael Hanselmann
      return queue.QueryJobs(job_ids, fields)
229 0bbe448c Michael Hanselmann
230 ee6c7b94 Michael Hanselmann
    elif method == luxi.REQ_QUERY_INSTANCES:
231 ee6c7b94 Michael Hanselmann
      (names, fields) = args
232 ee6c7b94 Michael Hanselmann
      op = opcodes.OpQueryInstances(names=names, output_fields=fields)
233 ee6c7b94 Michael Hanselmann
      return self._Query(op)
234 ee6c7b94 Michael Hanselmann
235 0bbe448c Michael Hanselmann
    else:
236 0bbe448c Michael Hanselmann
      raise ValueError("Invalid operation")
237 ffeffa1d Iustin Pop
238 ee6c7b94 Michael Hanselmann
  def _DummyLog(self, *args):
239 ee6c7b94 Michael Hanselmann
    pass
240 ee6c7b94 Michael Hanselmann
241 ee6c7b94 Michael Hanselmann
  def _Query(self, op):
242 ee6c7b94 Michael Hanselmann
    """Runs the specified opcode and returns the result.
243 ee6c7b94 Michael Hanselmann
244 ee6c7b94 Michael Hanselmann
    """
245 ee6c7b94 Michael Hanselmann
    proc = mcpu.Processor(self.server.context)
246 ee6c7b94 Michael Hanselmann
    # TODO: Where should log messages go?
247 ee6c7b94 Michael Hanselmann
    return proc.ExecOpCode(op, self._DummyLog)
248 ee6c7b94 Michael Hanselmann
249 ffeffa1d Iustin Pop
250 39dcf2ef Guido Trotter
class GanetiContext(object):
251 39dcf2ef Guido Trotter
  """Context common to all ganeti threads.
252 39dcf2ef Guido Trotter
253 39dcf2ef Guido Trotter
  This class creates and holds common objects shared by all threads.
254 39dcf2ef Guido Trotter
255 39dcf2ef Guido Trotter
  """
256 39dcf2ef Guido Trotter
  _instance = None
257 39dcf2ef Guido Trotter
258 39dcf2ef Guido Trotter
  def __init__(self):
259 39dcf2ef Guido Trotter
    """Constructs a new GanetiContext object.
260 39dcf2ef Guido Trotter
261 39dcf2ef Guido Trotter
    There should be only a GanetiContext object at any time, so this
262 39dcf2ef Guido Trotter
    function raises an error if this is not the case.
263 39dcf2ef Guido Trotter
264 39dcf2ef Guido Trotter
    """
265 39dcf2ef Guido Trotter
    assert self.__class__._instance is None, "double GanetiContext instance"
266 39dcf2ef Guido Trotter
267 39dcf2ef Guido Trotter
    # Create a ConfigWriter...
268 39dcf2ef Guido Trotter
    self.cfg = config.ConfigWriter()
269 39dcf2ef Guido Trotter
    # And a GanetiLockingManager...
270 984f7c32 Guido Trotter
    self.glm = locking.GanetiLockManager(
271 39dcf2ef Guido Trotter
                self.cfg.GetNodeList(),
272 39dcf2ef Guido Trotter
                self.cfg.GetInstanceList())
273 39dcf2ef Guido Trotter
274 39dcf2ef Guido Trotter
    # setting this also locks the class against attribute modifications
275 39dcf2ef Guido Trotter
    self.__class__._instance = self
276 39dcf2ef Guido Trotter
277 39dcf2ef Guido Trotter
  def __setattr__(self, name, value):
278 39dcf2ef Guido Trotter
    """Setting GanetiContext attributes is forbidden after initialization.
279 39dcf2ef Guido Trotter
280 39dcf2ef Guido Trotter
    """
281 39dcf2ef Guido Trotter
    assert self.__class__._instance is None, "Attempt to modify Ganeti Context"
282 39dcf2ef Guido Trotter
    object.__setattr__(self, name, value)
283 39dcf2ef Guido Trotter
284 39dcf2ef Guido Trotter
285 c1f2901b Iustin Pop
def ParseOptions():
286 c1f2901b Iustin Pop
  """Parse the command line options.
287 c1f2901b Iustin Pop
288 c1f2901b Iustin Pop
  Returns:
289 c1f2901b Iustin Pop
    (options, args) as from OptionParser.parse_args()
290 c1f2901b Iustin Pop
291 c1f2901b Iustin Pop
  """
292 c1f2901b Iustin Pop
  parser = OptionParser(description="Ganeti master daemon",
293 c1f2901b Iustin Pop
                        usage="%prog [-f] [-d]",
294 c1f2901b Iustin Pop
                        version="%%prog (ganeti) %s" %
295 c1f2901b Iustin Pop
                        constants.RELEASE_VERSION)
296 c1f2901b Iustin Pop
297 c1f2901b Iustin Pop
  parser.add_option("-f", "--foreground", dest="fork",
298 c1f2901b Iustin Pop
                    help="Don't detach from the current terminal",
299 c1f2901b Iustin Pop
                    default=True, action="store_false")
300 c1f2901b Iustin Pop
  parser.add_option("-d", "--debug", dest="debug",
301 c1f2901b Iustin Pop
                    help="Enable some debug messages",
302 c1f2901b Iustin Pop
                    default=False, action="store_true")
303 c1f2901b Iustin Pop
  options, args = parser.parse_args()
304 c1f2901b Iustin Pop
  return options, args
305 c1f2901b Iustin Pop
306 c1f2901b Iustin Pop
307 ffeffa1d Iustin Pop
def main():
308 ffeffa1d Iustin Pop
  """Main function"""
309 ffeffa1d Iustin Pop
310 c1f2901b Iustin Pop
  options, args = ParseOptions()
311 c1f2901b Iustin Pop
  utils.debug = options.debug
312 b74159ee Iustin Pop
  utils.no_fork = True
313 c1f2901b Iustin Pop
314 5675cd1f Iustin Pop
  ssconf.CheckMaster(options.debug)
315 c1f2901b Iustin Pop
316 39dcf2ef Guido Trotter
  master = IOServer(constants.MASTER_SOCKET, ClientRqHandler, GanetiContext())
317 ffeffa1d Iustin Pop
318 c1f2901b Iustin Pop
  # become a daemon
319 c1f2901b Iustin Pop
  if options.fork:
320 c1f2901b Iustin Pop
    utils.Daemonize(logfile=constants.LOG_MASTERDAEMON,
321 c1f2901b Iustin Pop
                    noclose_fds=[master.fileno()])
322 c1f2901b Iustin Pop
323 99e88451 Iustin Pop
  utils.WritePidFile(constants.MASTERD_PID)
324 8feda3ad Guido Trotter
325 59f187eb Iustin Pop
  logger.SetupLogging(constants.LOG_MASTERDAEMON, debug=options.debug,
326 59f187eb Iustin Pop
                      stderr_logging=not options.fork)
327 3b316acb Iustin Pop
328 d4fa5c23 Iustin Pop
  logging.info("ganeti master daemon startup")
329 3b316acb Iustin Pop
330 b1b6ea87 Iustin Pop
  # activate ip
331 b1b6ea87 Iustin Pop
  master_node = ssconf.SimpleStore().GetMasterNode()
332 b1b6ea87 Iustin Pop
  if not rpc.call_node_start_master(master_node, False):
333 b1b6ea87 Iustin Pop
    logging.error("Can't activate master IP address")
334 b1b6ea87 Iustin Pop
335 d4fa5c23 Iustin Pop
  master.setup_queue()
336 c1f2901b Iustin Pop
  try:
337 d4fa5c23 Iustin Pop
    master.serve_forever()
338 a4af651e Iustin Pop
  finally:
339 d4fa5c23 Iustin Pop
    master.server_cleanup()
340 99e88451 Iustin Pop
    utils.RemovePidFile(constants.MASTERD_PID)
341 a4af651e Iustin Pop
342 ffeffa1d Iustin Pop
343 ffeffa1d Iustin Pop
if __name__ == "__main__":
344 ffeffa1d Iustin Pop
  main()