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