6 # We must share a global reference to this instance, because it is the
7 # gatekeeper to all accesses to data from the C++ side (e.g. the REST API
8 # request handlers need to see it)
9 _global_instance
= {'plugin': None}
10 def global_instance():
11 assert _global_instance
['plugin'] is not None
12 return _global_instance
['plugin']
24 from django
.core
.servers
.basehttp
import get_internal_wsgi_application
26 from mgr_module
import MgrModule
28 from rest
.app
.manager
.request_collection
import RequestCollection
29 from rest
.app
.types
import OsdMap
, NotFound
, Config
, FsMap
, MonMap
, \
30 PgSummary
, Health
, MonStatus
32 from logger
import logger
34 os
.environ
.setdefault("DJANGO_SETTINGS_MODULE", "rest.app.settings")
36 django_log
= logging
.getLogger("django.request")
37 django_log
.addHandler(logging
.StreamHandler())
38 django_log
.setLevel(logging
.DEBUG
)
41 def recurse_refs(root
, path
):
42 if isinstance(root
, dict):
43 for k
, v
in root
.items():
44 recurse_refs(v
, path
+ "->%s" % k
)
45 elif isinstance(root
, list):
46 for n
, i
in enumerate(root
):
47 recurse_refs(i
, path
+ "[%d]" % n
)
49 logger().info("%s %d (%s)" % (path
, sys
.getrefcount(root
), root
.__class
__))
52 class Module(MgrModule
):
56 "name=val,type=CephChoices,strings=true|false",
57 "desc": "Set whether to authenticate API access by key",
61 "cmd": "auth_key_create "
62 "name=key_name,type=CephString",
63 "desc": "Create an API key with this name",
67 "cmd": "auth_key_delete "
68 "name=key_name,type=CephString",
69 "desc": "Delete an API key with this name",
73 "cmd": "auth_key_list",
74 "desc": "List all API keys",
79 def __init__(self
, *args
, **kwargs
):
80 super(Module
, self
).__init
__(*args
, **kwargs
)
81 _global_instance
['plugin'] = self
82 self
.log
.info("Constructing module {0}: instance {1}".format(
83 __name__
, _global_instance
))
84 self
.requests
= RequestCollection()
87 self
.enable_auth
= True
89 def notify(self
, notify_type
, notify_id
):
90 # FIXME: don't bother going and get_sync_object'ing the map
91 # unless there is actually someone waiting for it (find out inside
93 self
.log
.info("Notify {0}".format(notify_type
))
94 if notify_type
== "command":
95 self
.requests
.on_completion(notify_id
)
96 elif notify_type
== "osd_map":
97 self
.requests
.on_map(OsdMap
, self
.get_sync_object(OsdMap
))
98 elif notify_type
== "mon_map":
99 self
.requests
.on_map(MonMap
, self
.get_sync_object(MonMap
))
100 elif notify_type
== "pg_summary":
101 self
.requests
.on_map(PgSummary
, self
.get_sync_object(PgSummary
))
103 self
.log
.warning("Unhandled notification type '{0}'".format(notify_type
))
105 def get_sync_object(self
, object_type
, path
=None):
106 if object_type
== OsdMap
:
107 data
= self
.get("osd_map")
109 assert data
is not None
111 data
['tree'] = self
.get("osd_map_tree")
112 data
['crush'] = self
.get("osd_map_crush")
113 data
['crush_map_text'] = self
.get("osd_map_crush_map_text")
114 data
['osd_metadata'] = self
.get("osd_metadata")
115 obj
= OsdMap(data
['epoch'], data
)
116 elif object_type
== Config
:
117 data
= self
.get("config")
118 obj
= Config(0, data
)
119 elif object_type
== MonMap
:
120 data
= self
.get("mon_map")
121 obj
= MonMap(data
['epoch'], data
)
122 elif object_type
== FsMap
:
123 data
= self
.get("fs_map")
124 obj
= FsMap(data
['epoch'], data
)
125 elif object_type
== PgSummary
:
126 data
= self
.get("pg_summary")
127 self
.log
.debug("JSON: {0}".format(data
))
128 obj
= PgSummary(0, data
)
129 elif object_type
== Health
:
130 data
= self
.get("health")
131 obj
= Health(0, json
.loads(data
['json']))
132 elif object_type
== MonStatus
:
133 data
= self
.get("mon_status")
134 obj
= MonStatus(0, json
.loads(data
['json']))
136 raise NotImplementedError(object_type
)
138 # TODO: move 'path' handling up into C++ land so that we only
139 # Pythonize the part we're interested in
143 if isinstance(obj
, dict):
146 obj
= getattr(obj
, part
)
147 except (AttributeError, KeyError):
148 raise NotFound(object_type
, path
)
152 def get_authenticators(self
):
154 For the benefit of django rest_framework APIView classes
156 return [self
._auth
_cls
()]
159 cherrypy
.engine
.exit()
162 self
.keys
= self
._load
_keys
()
163 self
.enable_auth
= self
.get_config_json("enable_auth")
164 if self
.enable_auth
is None:
165 self
.enable_auth
= True
167 app
= get_internal_wsgi_application()
169 from rest_framework
import authentication
171 class KeyUser(object):
172 def __init__(self
, username
):
173 self
.username
= username
175 def is_authenticated(self
):
178 # Take a local reference to use inside the APIKeyAuthentication
182 # Configure django.request logger
183 logging
.getLogger("django.request").handlers
= self
.log
.handlers
184 logging
.getLogger("django.request").setLevel(logging
.DEBUG
)
186 class APIKeyAuthentication(authentication
.BaseAuthentication
):
187 def authenticate(self
, request
):
188 if not global_instance().enable_auth
:
189 return KeyUser("anonymous"), None
191 username
= request
.META
.get('HTTP_X_USERNAME')
193 log
.warning("Rejecting: no X_USERNAME")
196 if username
not in global_instance().keys
:
197 log
.warning("Rejecting: username does not exist")
200 api_key
= request
.META
.get('HTTP_X_APIKEY')
201 expect_key
= global_instance().keys
[username
]
202 if api_key
!= expect_key
:
203 log
.warning("Rejecting: wrong API key")
206 log
.debug("Accepted for user {0}".format(username
))
207 return KeyUser(username
), None
209 self
._auth
_cls
= APIKeyAuthentication
211 cherrypy
.config
.update({
212 'server.socket_port': 8002,
213 'engine.autoreload.on': False
215 cherrypy
.tree
.graft(app
, '/')
217 cherrypy
.engine
.start()
218 cherrypy
.engine
.block()
220 def _generate_key(self
):
221 return uuid
.uuid4().__str
__()
223 def _load_keys(self
):
224 loaded_keys
= self
.get_config_json("keys")
225 self
.log
.debug("loaded_keys: {0}".format(loaded_keys
))
226 if loaded_keys
is None:
231 def _save_keys(self
):
232 self
.set_config_json("keys", self
.keys
)
234 def handle_command(self
, cmd
):
235 self
.log
.info("handle_command: {0}".format(json
.dumps(cmd
, indent
=2)))
236 prefix
= cmd
['prefix']
237 if prefix
== "enable_auth":
238 enable
= cmd
['val'] == "true"
239 self
.set_config_json("enable_auth", enable
)
240 self
.enable_auth
= enable
242 elif prefix
== "auth_key_create":
243 if cmd
['key_name'] in self
.keys
:
244 return 0, self
.keys
[cmd
['key_name']], ""
246 self
.keys
[cmd
['key_name']] = self
._generate
_key
()
249 return 0, self
.keys
[cmd
['key_name']], ""
250 elif prefix
== "auth_key_delete":
251 if cmd
['key_name'] in self
.keys
:
252 del self
.keys
[cmd
['key_name']]
256 elif prefix
== "auth_key_list":
257 return 0, json
.dumps(self
._load
_keys
(), indent
=2), ""
259 return -errno
.EINVAL
, "", "Command not found '{0}'".format(prefix
)