]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/rest/app/views/rpc_view.py
bump version to 12.0.3-pve3
[ceph.git] / ceph / src / pybind / mgr / rest / app / views / rpc_view.py
1
2
3 """
4 Helpers for writing django views and rest_framework ViewSets that get
5 their data from calls into the C++ side of ceph-mgr
6 """
7
8
9 from rest_framework import viewsets, status
10 from rest_framework.views import APIView
11
12 from rest_framework.response import Response
13
14 from rest.app.manager.osd_request_factory import OsdRequestFactory
15 from rest.app.manager.pool_request_factory import PoolRequestFactory
16 from rest.app.types import OsdMap, OSD, OSD_MAP, POOL, CRUSH_RULE, NotFound
17 from rest.module import global_instance as rest_plugin
18
19 from rest.logger import logger
20 log = logger()
21
22
23 class DataObject(object):
24 """
25 A convenience for converting dicts from the backend into
26 objects, because django_rest_framework expects objects
27 """
28 def __init__(self, data):
29 self.__dict__.update(data)
30
31
32 class MgrClient(object):
33 cluster_monitor = None
34
35 def __init__(self):
36 self._request_factories = {
37 OSD: OsdRequestFactory,
38 POOL: PoolRequestFactory
39 }
40
41 def get_sync_object(self, object_type, path=None):
42 return rest_plugin().get_sync_object(object_type, path)
43
44 def get_metadata(self, svc_type, svc_id):
45 return rest_plugin().get_metadata(svc_type, svc_id)
46
47 def get(self, object_type, object_id):
48 """
49 Get one object from a particular cluster.
50 """
51
52 if object_type == OSD:
53 return self._osd_resolve(object_id)
54 elif object_type == POOL:
55 return self._pool_resolve(object_id)
56 else:
57 raise NotImplementedError(object_type)
58
59 def get_valid_commands(self, object_type, object_ids):
60 """
61 Determine what commands can be run on OSD object_ids
62 """
63 if object_type != OSD:
64 raise NotImplementedError(object_type)
65
66 try:
67 valid_commands = self.get_request_factory(
68 object_type).get_valid_commands(object_ids)
69 except KeyError as e:
70 raise NotFound(object_type, str(e))
71
72 return valid_commands
73
74 def _osd_resolve(self, osd_id):
75 osdmap = self.get_sync_object(OsdMap)
76
77 try:
78 return osdmap.osds_by_id[osd_id]
79 except KeyError:
80 raise NotFound(OSD, osd_id)
81
82 def _pool_resolve(self, pool_id):
83 osdmap = self.get_sync_object(OsdMap)
84
85 try:
86 return osdmap.pools_by_id[pool_id]
87 except KeyError:
88 raise NotFound(POOL, pool_id)
89
90 def list_requests(self, filter_args):
91 state = filter_args.get('state', None)
92 fsid = filter_args.get('fsid', None)
93 requests = rest_plugin().requests.get_all()
94 return sorted([self._dump_request(r)
95 for r in requests
96 if (state is None or r.state == state) and (fsid is None or r.fsid == fsid)],
97 lambda a, b: cmp(b['requested_at'], a['requested_at']))
98
99 def _dump_request(self, request):
100 """UserRequest to JSON-serializable form"""
101 return {
102 'id': request.id,
103 'state': request.state,
104 'error': request.error,
105 'error_message': request.error_message,
106 'status': request.status,
107 'headline': request.headline,
108 'requested_at': request.requested_at,
109 'completed_at': request.completed_at
110 }
111
112 def get_request(self, request_id):
113 """
114 Get a JSON representation of a UserRequest
115 """
116 try:
117 return self._dump_request(rest_plugin().requests.get_by_id(request_id))
118 except KeyError:
119 raise NotFound('request', request_id)
120
121 def cancel_request(self, request_id):
122 try:
123 rest_plugin().requests.cancel(request_id)
124 return self.get_request(request_id)
125 except KeyError:
126 raise NotFound('request', request_id)
127
128 def list(self, object_type, list_filter):
129 """
130 Get many objects
131 """
132
133 osd_map = self.get_sync_object(OsdMap).data
134 if osd_map is None:
135 return []
136 if object_type == OSD:
137 result = osd_map['osds']
138 if 'id__in' in list_filter:
139 result = [o for o in result if o['osd'] in list_filter['id__in']]
140 if 'pool' in list_filter:
141 try:
142 osds_in_pool = self.get_sync_object(OsdMap).osds_by_pool[list_filter['pool']]
143 except KeyError:
144 raise NotFound("Pool {0} does not exist".format(list_filter['pool']))
145 else:
146 result = [o for o in result if o['osd'] in osds_in_pool]
147
148 return result
149 elif object_type == POOL:
150 return osd_map['pools']
151 elif object_type == CRUSH_RULE:
152 return osd_map['crush']['rules']
153 else:
154 raise NotImplementedError(object_type)
155
156 def request_delete(self, obj_type, obj_id):
157 return self._request('delete', obj_type, obj_id)
158
159 def request_create(self, obj_type, attributes):
160 return self._request('create', obj_type, attributes)
161
162 def request_update(self, command, obj_type, obj_id, attributes):
163 return self._request(command, obj_type, obj_id, attributes)
164
165 def request_apply(self, obj_type, obj_id, command):
166 return self._request(command, obj_type, obj_id)
167
168 def update(self, object_type, object_id, attributes):
169 """
170 Modify an object in a cluster.
171 """
172
173 if object_type == OSD:
174 # Run a resolve to throw exception if it's unknown
175 self._osd_resolve(object_id)
176 if 'id' not in attributes:
177 attributes['id'] = object_id
178
179 return self.request_update('update', OSD, object_id, attributes)
180 elif object_type == POOL:
181 self._pool_resolve(object_id)
182 if 'id' not in attributes:
183 attributes['id'] = object_id
184
185 return self.request_update('update', POOL, object_id, attributes)
186 elif object_type == OSD_MAP:
187 return self.request_update('update_config', OSD, object_id, attributes)
188
189 else:
190 raise NotImplementedError(object_type)
191
192 def get_request_factory(self, object_type):
193 try:
194 return self._request_factories[object_type]()
195 except KeyError:
196 raise ValueError("{0} is not one of {1}".format(object_type, self._request_factories.keys()))
197
198 def _request(self, method, obj_type, *args, **kwargs):
199 """
200 Create and submit UserRequest for an apply, create, update or delete.
201 """
202
203 request_factory = self.get_request_factory(obj_type)
204 request = getattr(request_factory, method)(*args, **kwargs)
205
206 if request:
207 # sleeps permitted during terminal phase of submitting, because we're
208 # doing I/O to the salt master to kick off
209 rest_plugin().requests.submit(request)
210 return {
211 'request_id': request.id
212 }
213 else:
214 return None
215
216 def server_get(self, fqdn):
217 return rest_plugin().get_server(fqdn)
218
219 def server_list(self):
220 return rest_plugin().list_servers()
221
222
223 from rest_framework.permissions import IsAuthenticated, BasePermission
224
225
226 class IsRoleAllowed(BasePermission):
227 def has_permission(self, request, view):
228 return True
229
230 # TODO: reinstate read vs. read/write limitations on API keys
231 # has_permission = False
232 # if request.user.groups.filter(name='readonly').exists():
233 # has_permission = request.method in SAFE_METHODS
234 # view.headers['Allow'] = ', '.join(SAFE_METHODS)
235 # elif request.user.groups.filter(name='read/write').exists():
236 # has_permission = True
237 # elif request.user.is_superuser:
238 # has_permission = True
239 #
240 # return has_permission
241
242
243 class RPCView(APIView):
244 serializer_class = None
245 log = log
246 permission_classes = [IsAuthenticated, IsRoleAllowed]
247
248 def get_authenticators(self):
249 return rest_plugin().get_authenticators()
250
251 def __init__(self, *args, **kwargs):
252 super(RPCView, self).__init__(*args, **kwargs)
253 self.client = MgrClient()
254
255 @property
256 def help(self):
257 return self.__doc__
258
259 @property
260 def help_summary(self):
261 return ""
262
263 def handle_exception(self, exc):
264 try:
265 return super(RPCView, self).handle_exception(exc)
266 except NotFound as e:
267 return Response(str(e), status=status.HTTP_404_NOT_FOUND)
268
269 def metadata(self, request):
270 ret = super(RPCView, self).metadata(request)
271
272 actions = {}
273 # TODO: get the fields marked up with whether they are:
274 # - [allowed|required|forbidden] during [creation|update] (6 possible kinds of field)
275 # e.g. on a pool
276 # id is forbidden during creation and update
277 # pg_num is required during create and optional during update
278 # pgp_num is optional during create or update
279 # nothing is required during update
280 if hasattr(self, 'update'):
281 if self.serializer_class:
282 actions['PATCH'] = self.serializer_class().metadata()
283 if hasattr(self, 'create'):
284 if self.serializer_class:
285 actions['POST'] = self.serializer_class().metadata()
286 ret['actions'] = actions
287
288 return ret
289
290
291 class RPCViewSet(viewsets.ViewSetMixin, RPCView):
292 pass