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
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
83
84
85 -class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
86 http.server.HttpServer):
87 """REST Request Handler Class.
88
89 """
90 AUTH_REALM = "Ganeti Remote API"
91
99
101 """Loads a file containing users and passwords.
102
103 @type filename: string
104 @param filename: Path to file
105
106 """
107 logging.info("Reading users file at %s", filename)
108 try:
109 try:
110 contents = utils.ReadFile(filename)
111 except EnvironmentError, err:
112 self._users = None
113 if err.errno == errno.ENOENT:
114 logging.warning("No users file at %s", filename)
115 else:
116 logging.warning("Error while reading %s: %s", filename, err)
117 return False
118
119 users = http.auth.ParsePasswordFile(contents)
120
121 except Exception, err:
122
123 logging.error("Error while parsing %s: %s", filename, err)
124 return False
125
126 self._users = users
127
128 return True
129
130 - def _GetRequestContext(self, req):
131 """Returns the context for a request.
132
133 The context is cached in the req.private variable.
134
135 """
136 if req.private is None:
137 (HandlerClass, items, args) = \
138 self._resmap.getController(req.request_path)
139
140 ctx = RemoteApiRequestContext()
141 ctx.handler = HandlerClass(items, args, req)
142
143 method = req.request_method.upper()
144 try:
145 ctx.handler_fn = getattr(ctx.handler, method)
146 except AttributeError:
147 raise http.HttpNotImplemented("Method %s is unsupported for path %s" %
148 (method, req.request_path))
149
150 ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
151
152
153 if ctx.handler_access is None:
154 raise AssertionError("Permissions definition missing")
155
156
157 ctx.body_data = None
158
159 req.private = ctx
160
161
162 assert req.private.handler
163 assert req.private.handler_fn
164 assert req.private.handler_access is not None
165
166 return req.private
167
169 """Determine whether authentication is required.
170
171 """
172 return bool(self._GetRequestContext(req).handler_access)
173
175 """Checks whether a user can access a resource.
176
177 """
178 ctx = self._GetRequestContext(req)
179
180
181 valid_user = False
182 if self._users:
183 user = self._users.get(username, None)
184 if user and self.VerifyBasicAuthPassword(req, username, password,
185 user.password):
186 valid_user = True
187
188 if not valid_user:
189
190 return False
191
192 if (not ctx.handler_access or
193 set(user.options).intersection(ctx.handler_access)):
194
195 return True
196
197
198 raise http.HttpForbidden()
199
238
239
242 """Initializes this class.
243
244 @param wm: Inotify watch manager
245 @type path: string
246 @param path: File path
247 @type cb: callable
248 @param cb: Function called on file change
249
250 """
251 asyncnotifier.FileEventHandlerBase.__init__(self, wm)
252
253 self._cb = cb
254 self._filename = os.path.basename(path)
255
256
257
258 mask = (pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"] |
259 pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"] |
260 pyinotify.EventsCodes.ALL_FLAGS["IN_MOVED_FROM"] |
261 pyinotify.EventsCodes.ALL_FLAGS["IN_MOVED_TO"])
262
263 self._handle = self.AddWatch(os.path.dirname(path), mask)
264
266 """Called upon inotify event.
267
268 """
269 if event.name == self._filename:
270 logging.debug("Received inotify event %s", event)
271 self._cb()
272
273
275 """Configures an inotify watcher for a file.
276
277 @type filename: string
278 @param filename: File to watch
279 @type cb: callable
280 @param cb: Function called on file change
281
282 """
283 wm = pyinotify.WatchManager()
284 handler = FileEventHandler(wm, filename, cb)
285 asyncnotifier.AsyncNotifier(wm, default_proc_fun=handler)
286
287
305
306
329
330
332 """Main remote API function, executed with the PID file held.
333
334 """
335 (mainloop, server) = prep_data
336 try:
337 mainloop.Run()
338 finally:
339 server.Stop()
340
341
343 """Main function.
344
345 """
346 parser = optparse.OptionParser(description="Ganeti Remote API",
347 usage="%prog [-f] [-d] [-p port] [-b ADDRESS]",
348 version="%%prog (ganeti) %s" % constants.RELEASE_VERSION)
349
350 daemon.GenericMain(constants.RAPI, parser, CheckRapi, PrepRapi, ExecRapi,
351 default_ssl_cert=constants.RAPI_CERT_FILE,
352 default_ssl_key=constants.RAPI_CERT_FILE)
353