]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/controllers/rgw.py
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / rgw.py
1 # -*- coding: utf-8 -*-
2 from __future__ import absolute_import
3
4 import json
5
6 import cherrypy
7
8 from . import ApiController, BaseController, RESTController, Endpoint, \
9 ReadPermission
10 from .. import logger
11 from ..exceptions import DashboardException
12 from ..rest_client import RequestException
13 from ..security import Scope
14 from ..services.ceph_service import CephService
15 from ..services.rgw_client import RgwClient
16
17
18 @ApiController('/rgw', Scope.RGW)
19 class Rgw(BaseController):
20
21 @Endpoint()
22 @ReadPermission
23 def status(self):
24 status = {'available': False, 'message': None}
25 try:
26 instance = RgwClient.admin_instance()
27 # Check if the service is online.
28 if not instance.is_service_online():
29 msg = 'Failed to connect to the Object Gateway\'s Admin Ops API.'
30 raise RequestException(msg)
31 # Ensure the API user ID is known by the RGW.
32 if not instance.user_exists():
33 msg = 'The user "{}" is unknown to the Object Gateway.'.format(
34 instance.userid)
35 raise RequestException(msg)
36 # Ensure the system flag is set for the API user ID.
37 if not instance.is_system_user():
38 msg = 'The system flag is not set for user "{}".'.format(
39 instance.userid)
40 raise RequestException(msg)
41 status['available'] = True
42 except (RequestException, LookupError) as ex:
43 status['message'] = str(ex)
44 return status
45
46
47 @ApiController('/rgw/daemon', Scope.RGW)
48 class RgwDaemon(RESTController):
49
50 def list(self):
51 daemons = []
52 for hostname, server in CephService.get_service_map('rgw').items():
53 for service in server['services']:
54 metadata = service['metadata']
55
56 # extract per-daemon service data and health
57 daemon = {
58 'id': service['id'],
59 'version': metadata['ceph_version'],
60 'server_hostname': hostname
61 }
62
63 daemons.append(daemon)
64
65 return sorted(daemons, key=lambda k: k['id'])
66
67 def get(self, svc_id):
68 daemon = {
69 'rgw_metadata': [],
70 'rgw_id': svc_id,
71 'rgw_status': []
72 }
73 service = CephService.get_service('rgw', svc_id)
74 if not service:
75 raise cherrypy.NotFound('Service rgw {} is not available'.format(svc_id))
76
77 metadata = service['metadata']
78 status = service['status']
79 if 'json' in status:
80 try:
81 status = json.loads(status['json'])
82 except ValueError:
83 logger.warning('%s had invalid status json', service['id'])
84 status = {}
85 else:
86 logger.warning('%s has no key "json" in status', service['id'])
87
88 daemon['rgw_metadata'] = metadata
89 daemon['rgw_status'] = status
90 return daemon
91
92
93 class RgwRESTController(RESTController):
94
95 def proxy(self, method, path, params=None, json_response=True):
96 try:
97 instance = RgwClient.admin_instance()
98 result = instance.proxy(method, path, params, None)
99 if json_response and result != '':
100 result = json.loads(result.decode('utf-8'))
101 return result
102 except (DashboardException, RequestException) as e:
103 raise DashboardException(e, http_status_code=500, component='rgw')
104
105
106 @ApiController('/rgw/bucket', Scope.RGW)
107 class RgwBucket(RgwRESTController):
108
109 def _append_bid(self, bucket):
110 """
111 Append the bucket identifier that looks like [<tenant>/]<bucket>.
112 See http://docs.ceph.com/docs/nautilus/radosgw/multitenancy/ for
113 more information.
114 :param bucket: The bucket parameters.
115 :type bucket: dict
116 :return: The modified bucket parameters including the 'bid' parameter.
117 :rtype: dict
118 """
119 if isinstance(bucket, dict):
120 bucket['bid'] = '{}/{}'.format(bucket['tenant'], bucket['bucket']) \
121 if bucket['tenant'] else bucket['bucket']
122 return bucket
123
124 def list(self):
125 return self.proxy('GET', 'bucket')
126
127 def get(self, bucket):
128 result = self.proxy('GET', 'bucket', {'bucket': bucket})
129 return self._append_bid(result)
130
131 def create(self, bucket, uid):
132 try:
133 rgw_client = RgwClient.instance(uid)
134 return rgw_client.create_bucket(bucket)
135 except RequestException as e:
136 raise DashboardException(e, http_status_code=500, component='rgw')
137
138 def set(self, bucket, bucket_id, uid):
139 result = self.proxy('PUT', 'bucket', {
140 'bucket': bucket,
141 'bucket-id': bucket_id,
142 'uid': uid
143 }, json_response=False)
144 return self._append_bid(result)
145
146 def delete(self, bucket, purge_objects='true'):
147 return self.proxy('DELETE', 'bucket', {
148 'bucket': bucket,
149 'purge-objects': purge_objects
150 }, json_response=False)
151
152
153 @ApiController('/rgw/user', Scope.RGW)
154 class RgwUser(RgwRESTController):
155
156 def _append_uid(self, user):
157 """
158 Append the user identifier that looks like [<tenant>$]<user>.
159 See http://docs.ceph.com/docs/jewel/radosgw/multitenancy/ for
160 more information.
161 :param user: The user parameters.
162 :type user: dict
163 :return: The modified user parameters including the 'uid' parameter.
164 :rtype: dict
165 """
166 if isinstance(user, dict):
167 user['uid'] = '{}${}'.format(user['tenant'], user['user_id']) \
168 if user['tenant'] else user['user_id']
169 return user
170
171 def list(self):
172 users = []
173 marker = None
174 while True:
175 params = {}
176 if marker:
177 params['marker'] = marker
178 result = self.proxy('GET', 'user?list', params)
179 users.extend(result['keys'])
180 if not result['truncated']:
181 break
182 # Make sure there is a marker.
183 assert result['marker']
184 # Make sure the marker has changed.
185 assert marker != result['marker']
186 marker = result['marker']
187 return users
188
189 def get(self, uid):
190 result = self.proxy('GET', 'user', {'uid': uid})
191 return self._append_uid(result)
192
193 @Endpoint()
194 @ReadPermission
195 def get_emails(self):
196 emails = []
197 for uid in json.loads(self.list()):
198 user = json.loads(self.get(uid))
199 if user["email"]:
200 emails.append(user["email"])
201 return emails
202
203 def create(self, uid, display_name, email=None, max_buckets=None,
204 suspended=None, generate_key=None, access_key=None,
205 secret_key=None):
206 params = {'uid': uid}
207 if display_name is not None:
208 params['display-name'] = display_name
209 if email is not None:
210 params['email'] = email
211 if max_buckets is not None:
212 params['max-buckets'] = max_buckets
213 if suspended is not None:
214 params['suspended'] = suspended
215 if generate_key is not None:
216 params['generate-key'] = generate_key
217 if access_key is not None:
218 params['access-key'] = access_key
219 if secret_key is not None:
220 params['secret-key'] = secret_key
221 result = self.proxy('PUT', 'user', params)
222 return self._append_uid(result)
223
224 def set(self, uid, display_name=None, email=None, max_buckets=None,
225 suspended=None):
226 params = {'uid': uid}
227 if display_name is not None:
228 params['display-name'] = display_name
229 if email is not None:
230 params['email'] = email
231 if max_buckets is not None:
232 params['max-buckets'] = max_buckets
233 if suspended is not None:
234 params['suspended'] = suspended
235 result = self.proxy('POST', 'user', params)
236 return self._append_uid(result)
237
238 def delete(self, uid):
239 try:
240 instance = RgwClient.admin_instance()
241 # Ensure the user is not configured to access the RGW Object Gateway.
242 if instance.userid == uid:
243 raise DashboardException(msg='Unable to delete "{}" - this user '
244 'account is required for managing the '
245 'Object Gateway'.format(uid))
246 # Finally redirect request to the RGW proxy.
247 return self.proxy('DELETE', 'user', {'uid': uid}, json_response=False)
248 except (DashboardException, RequestException) as e:
249 raise DashboardException(e, component='rgw')
250
251 # pylint: disable=redefined-builtin
252 @RESTController.Resource(method='POST', path='/capability', status=201)
253 def create_cap(self, uid, type, perm):
254 return self.proxy('PUT', 'user?caps', {
255 'uid': uid,
256 'user-caps': '{}={}'.format(type, perm)
257 })
258
259 # pylint: disable=redefined-builtin
260 @RESTController.Resource(method='DELETE', path='/capability', status=204)
261 def delete_cap(self, uid, type, perm):
262 return self.proxy('DELETE', 'user?caps', {
263 'uid': uid,
264 'user-caps': '{}={}'.format(type, perm)
265 })
266
267 @RESTController.Resource(method='POST', path='/key', status=201)
268 def create_key(self, uid, key_type='s3', subuser=None, generate_key='true',
269 access_key=None, secret_key=None):
270 params = {'uid': uid, 'key-type': key_type, 'generate-key': generate_key}
271 if subuser is not None:
272 params['subuser'] = subuser
273 if access_key is not None:
274 params['access-key'] = access_key
275 if secret_key is not None:
276 params['secret-key'] = secret_key
277 return self.proxy('PUT', 'user?key', params)
278
279 @RESTController.Resource(method='DELETE', path='/key', status=204)
280 def delete_key(self, uid, key_type='s3', subuser=None, access_key=None):
281 params = {'uid': uid, 'key-type': key_type}
282 if subuser is not None:
283 params['subuser'] = subuser
284 if access_key is not None:
285 params['access-key'] = access_key
286 return self.proxy('DELETE', 'user?key', params, json_response=False)
287
288 @RESTController.Resource(method='GET', path='/quota')
289 def get_quota(self, uid):
290 return self.proxy('GET', 'user?quota', {'uid': uid})
291
292 @RESTController.Resource(method='PUT', path='/quota')
293 def set_quota(self, uid, quota_type, enabled, max_size_kb, max_objects):
294 return self.proxy('PUT', 'user?quota', {
295 'uid': uid,
296 'quota-type': quota_type,
297 'enabled': enabled,
298 'max-size-kb': max_size_kb,
299 'max-objects': max_objects
300 }, json_response=False)
301
302 @RESTController.Resource(method='POST', path='/subuser', status=201)
303 def create_subuser(self, uid, subuser, access, key_type='s3',
304 generate_secret='true', access_key=None,
305 secret_key=None):
306 return self.proxy('PUT', 'user', {
307 'uid': uid,
308 'subuser': subuser,
309 'key-type': key_type,
310 'access': access,
311 'generate-secret': generate_secret,
312 'access-key': access_key,
313 'secret-key': secret_key
314 })
315
316 @RESTController.Resource(method='DELETE', path='/subuser/{subuser}', status=204)
317 def delete_subuser(self, uid, subuser, purge_keys='true'):
318 """
319 :param purge_keys: Set to False to do not purge the keys.
320 Note, this only works for s3 subusers.
321 """
322 return self.proxy('DELETE', 'user', {
323 'uid': uid,
324 'subuser': subuser,
325 'purge-keys': purge_keys
326 }, json_response=False)