1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """Ganeti Remote API master script.
22
23 """
24
25
26
27
28
29 import logging
30 import optparse
31 import sys
32 import os
33 import os.path
34 import errno
35
36 try:
37 from pyinotify import pyinotify
38 except ImportError:
39 import pyinotify
40
41 from ganeti import asyncnotifier
42 from ganeti import constants
43 from ganeti import http
44 from ganeti import daemon
45 from ganeti import ssconf
46 from ganeti import luxi
47 from ganeti import serializer
48 from ganeti import compat
49 from ganeti import utils
50 from ganeti.rapi import connector
51
52 import ganeti.http.auth
53 import ganeti.http.server
54
55
56 -class RemoteApiRequestContext(object):
57 """Data structure for Remote API requests.
58
59 """
61 self.handler = None
62 self.handler_fn = None
63 self.handler_access = None
64 self.body_data = None
65
66
67 -class RemoteApiHandler(http.auth.HttpServerRequestAuthentication,
68 http.server.HttpServerHandler):
69 """REST Request Handler Class.
70
71 """
72 AUTH_REALM = "Ganeti Remote API"
73
74 - def __init__(self, user_fn, _client_cls=None):
89
90 @staticmethod
101
102 - def _GetRequestContext(self, req):
103 """Returns the context for a request.
104
105 The context is cached in the req.private variable.
106
107 """
108 if req.private is None:
109 (HandlerClass, items, args) = \
110 self._resmap.getController(req.request_path)
111
112 ctx = RemoteApiRequestContext()
113 ctx.handler = HandlerClass(items, args, req, _client_cls=self._client_cls)
114
115 method = req.request_method.upper()
116 try:
117 ctx.handler_fn = getattr(ctx.handler, method)
118 except AttributeError:
119 raise http.HttpNotImplemented("Method %s is unsupported for path %s" %
120 (method, req.request_path))
121
122 ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
123
124
125 if ctx.handler_access is None:
126 raise AssertionError("Permissions definition missing")
127
128
129 ctx.body_data = None
130
131 req.private = ctx
132
133
134 assert req.private.handler
135 assert req.private.handler_fn
136 assert req.private.handler_access is not None
137
138 return req.private
139
141 """Determine whether authentication is required.
142
143 """
144 return bool(self._GetRequestContext(req).handler_access)
145
147 """Checks whether a user can access a resource.
148
149 """
150 ctx = self._GetRequestContext(req)
151
152 user = self._user_fn(username)
153 if not (user and
154 self.VerifyBasicAuthPassword(req, username, password,
155 user.password)):
156
157 return False
158
159 if (not ctx.handler_access or
160 set(user.options).intersection(ctx.handler_access)):
161
162 return True
163
164
165 raise http.HttpForbidden()
166
201
205 """Initializes this class.
206
207 """
208 self._users = None
209
210 - def Get(self, username):
211 """Checks whether a user exists.
212
213 """
214 if self._users:
215 return self._users.get(username, None)
216 else:
217 return None
218
219 - def Load(self, filename):
220 """Loads a file containing users and passwords.
221
222 @type filename: string
223 @param filename: Path to file
224
225 """
226 logging.info("Reading users file at %s", filename)
227 try:
228 try:
229 contents = utils.ReadFile(filename)
230 except EnvironmentError, err:
231 self._users = None
232 if err.errno == errno.ENOENT:
233 logging.warning("No users file at %s", filename)
234 else:
235 logging.warning("Error while reading %s: %s", filename, err)
236 return False
237
238 users = http.auth.ParsePasswordFile(contents)
239
240 except Exception, err:
241
242 logging.error("Error while parsing %s: %s", filename, err)
243 return False
244
245 self._users = users
246
247 return True
248
252 """Initializes this class.
253
254 @param wm: Inotify watch manager
255 @type path: string
256 @param path: File path
257 @type cb: callable
258 @param cb: Function called on file change
259
260 """
261 asyncnotifier.FileEventHandlerBase.__init__(self, wm)
262
263 self._cb = cb
264 self._filename = os.path.basename(path)
265
266
267
268 mask = (pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"] |
269 pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"] |
270 pyinotify.EventsCodes.ALL_FLAGS["IN_MOVED_FROM"] |
271 pyinotify.EventsCodes.ALL_FLAGS["IN_MOVED_TO"])
272
273 self._handle = self.AddWatch(os.path.dirname(path), mask)
274
276 """Called upon inotify event.
277
278 """
279 if event.name == self._filename:
280 logging.debug("Received inotify event %s", event)
281 self._cb()
282
285 """Configures an inotify watcher for a file.
286
287 @type filename: string
288 @param filename: File to watch
289 @type cb: callable
290 @param cb: Function called on file change
291
292 """
293 wm = pyinotify.WatchManager()
294 handler = FileEventHandler(wm, filename, cb)
295 asyncnotifier.AsyncNotifier(wm, default_proc_fun=handler)
296
315
339
340
341 -def ExecRapi(options, args, prep_data):
342 """Main remote API function, executed with the PID file held.
343
344 """
345 (mainloop, server) = prep_data
346 try:
347 mainloop.Run()
348 finally:
349 server.Stop()
350
353 """Main function.
354
355 """
356 parser = optparse.OptionParser(description="Ganeti Remote API",
357 usage="%prog [-f] [-d] [-p port] [-b ADDRESS]",
358 version="%%prog (ganeti) %s" % constants.RELEASE_VERSION)
359
360 daemon.GenericMain(constants.RAPI, parser, CheckRapi, PrepRapi, ExecRapi,
361 default_ssl_cert=constants.RAPI_CERT_FILE,
362 default_ssl_key=constants.RAPI_CERT_FILE)
363