]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/rest/app/views/rpc_view.py
4 Helpers for writing django views and rest_framework ViewSets that get
5 their data from calls into the C++ side of ceph-mgr
9 from rest_framework
import viewsets
, status
10 from rest_framework
.views
import APIView
12 from rest_framework
.response
import Response
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
19 from rest
.logger
import logger
23 class DataObject(object):
25 A convenience for converting dicts from the backend into
26 objects, because django_rest_framework expects objects
28 def __init__(self
, data
):
29 self
.__dict
__.update(data
)
32 class MgrClient(object):
33 cluster_monitor
= None
36 self
._request
_factories
= {
37 OSD
: OsdRequestFactory
,
38 POOL
: PoolRequestFactory
41 def get_sync_object(self
, object_type
, path
=None):
42 return rest_plugin().get_sync_object(object_type
, path
)
44 def get_metadata(self
, svc_type
, svc_id
):
45 return rest_plugin().get_metadata(svc_type
, svc_id
)
47 def get(self
, object_type
, object_id
):
49 Get one object from a particular cluster.
52 if object_type
== OSD
:
53 return self
._osd
_resolve
(object_id
)
54 elif object_type
== POOL
:
55 return self
._pool
_resolve
(object_id
)
57 raise NotImplementedError(object_type
)
59 def get_valid_commands(self
, object_type
, object_ids
):
61 Determine what commands can be run on OSD object_ids
63 if object_type
!= OSD
:
64 raise NotImplementedError(object_type
)
67 valid_commands
= self
.get_request_factory(
68 object_type
).get_valid_commands(object_ids
)
70 raise NotFound(object_type
, str(e
))
74 def _osd_resolve(self
, osd_id
):
75 osdmap
= self
.get_sync_object(OsdMap
)
78 return osdmap
.osds_by_id
[osd_id
]
80 raise NotFound(OSD
, osd_id
)
82 def _pool_resolve(self
, pool_id
):
83 osdmap
= self
.get_sync_object(OsdMap
)
86 return osdmap
.pools_by_id
[pool_id
]
88 raise NotFound(POOL
, pool_id
)
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
)
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']))
99 def _dump_request(self
, request
):
100 """UserRequest to JSON-serializable form"""
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
112 def get_request(self
, request_id
):
114 Get a JSON representation of a UserRequest
117 return self
._dump
_request
(rest_plugin().requests
.get_by_id(request_id
))
119 raise NotFound('request', request_id
)
121 def cancel_request(self
, request_id
):
123 rest_plugin().requests
.cancel(request_id
)
124 return self
.get_request(request_id
)
126 raise NotFound('request', request_id
)
128 def list(self
, object_type
, list_filter
):
133 osd_map
= self
.get_sync_object(OsdMap
).data
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
:
142 osds_in_pool
= self
.get_sync_object(OsdMap
).osds_by_pool
[list_filter
['pool']]
144 raise NotFound("Pool {0} does not exist".format(list_filter
['pool']))
146 result
= [o
for o
in result
if o
['osd'] in osds_in_pool
]
149 elif object_type
== POOL
:
150 return osd_map
['pools']
151 elif object_type
== CRUSH_RULE
:
152 return osd_map
['crush']['rules']
154 raise NotImplementedError(object_type
)
156 def request_delete(self
, obj_type
, obj_id
):
157 return self
._request
('delete', obj_type
, obj_id
)
159 def request_create(self
, obj_type
, attributes
):
160 return self
._request
('create', obj_type
, attributes
)
162 def request_update(self
, command
, obj_type
, obj_id
, attributes
):
163 return self
._request
(command
, obj_type
, obj_id
, attributes
)
165 def request_apply(self
, obj_type
, obj_id
, command
):
166 return self
._request
(command
, obj_type
, obj_id
)
168 def update(self
, object_type
, object_id
, attributes
):
170 Modify an object in a cluster.
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
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
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
)
190 raise NotImplementedError(object_type
)
192 def get_request_factory(self
, object_type
):
194 return self
._request
_factories
[object_type
]()
196 raise ValueError("{0} is not one of {1}".format(object_type
, self
._request
_factories
.keys()))
198 def _request(self
, method
, obj_type
, *args
, **kwargs
):
200 Create and submit UserRequest for an apply, create, update or delete.
203 request_factory
= self
.get_request_factory(obj_type
)
204 request
= getattr(request_factory
, method
)(*args
, **kwargs
)
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
)
211 'request_id': request
.id
216 def server_get(self
, fqdn
):
217 return rest_plugin().get_server(fqdn
)
219 def server_list(self
):
220 return rest_plugin().list_servers()
223 from rest_framework
.permissions
import IsAuthenticated
, BasePermission
226 class IsRoleAllowed(BasePermission
):
227 def has_permission(self
, request
, view
):
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
240 # return has_permission
243 class RPCView(APIView
):
244 serializer_class
= None
246 permission_classes
= [IsAuthenticated
, IsRoleAllowed
]
248 def get_authenticators(self
):
249 return rest_plugin().get_authenticators()
251 def __init__(self
, *args
, **kwargs
):
252 super(RPCView
, self
).__init
__(*args
, **kwargs
)
253 self
.client
= MgrClient()
260 def help_summary(self
):
263 def handle_exception(self
, exc
):
265 return super(RPCView
, self
).handle_exception(exc
)
266 except NotFound
as e
:
267 return Response(str(e
), status
=status
.HTTP_404_NOT_FOUND
)
269 def metadata(self
, request
):
270 ret
= super(RPCView
, self
).metadata(request
)
273 # TODO: get the fields marked up with whether they are:
274 # - [allowed|required|forbidden] during [creation|update] (6 possible kinds of field)
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
291 class RPCViewSet(viewsets
.ViewSetMixin
, RPCView
):