]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | # -*- coding: utf-8 -*- |
2 | from __future__ import absolute_import | |
3 | ||
4 | import json | |
f67539c2 | 5 | import logging |
11fdf7f2 TL |
6 | |
7 | import cherrypy | |
f67539c2 | 8 | |
11fdf7f2 TL |
9 | from ..exceptions import DashboardException |
10 | from ..rest_client import RequestException | |
f67539c2 | 11 | from ..security import Permission, Scope |
9f95a23c | 12 | from ..services.auth import AuthManager, JwtManager |
11fdf7f2 | 13 | from ..services.ceph_service import CephService |
f67539c2 | 14 | from ..services.rgw_client import NoRgwDaemonsException, RgwClient |
9f95a23c | 15 | from ..tools import json_str_to_object, str_to_bool |
f67539c2 TL |
16 | from . import ApiController, BaseController, ControllerDoc, Endpoint, \ |
17 | EndpointDoc, ReadPermission, RESTController, allow_empty_body | |
9f95a23c TL |
18 | |
19 | try: | |
f67539c2 | 20 | from typing import Any, List, Optional |
f6b5b4d7 | 21 | except ImportError: # pragma: no cover |
9f95a23c TL |
22 | pass # Just for type checking |
23 | ||
f67539c2 TL |
24 | logger = logging.getLogger("controllers.rgw") |
25 | ||
26 | RGW_SCHEMA = { | |
27 | "available": (bool, "Is RGW available?"), | |
28 | "message": (str, "Descriptions") | |
29 | } | |
30 | ||
31 | RGW_DAEMON_SCHEMA = { | |
32 | "id": (str, "Daemon ID"), | |
33 | "version": (str, "Ceph Version"), | |
34 | "server_hostname": (str, ""), | |
35 | "zonegroup_name": (str, "Zone Group"), | |
36 | "zone_name": (str, "Zone") | |
37 | } | |
38 | ||
39 | RGW_USER_SCHEMA = { | |
40 | "list_of_users": ([str], "list of rgw users") | |
41 | } | |
11fdf7f2 TL |
42 | |
43 | ||
44 | @ApiController('/rgw', Scope.RGW) | |
f67539c2 | 45 | @ControllerDoc("RGW Management API", "Rgw") |
11fdf7f2 | 46 | class Rgw(BaseController): |
11fdf7f2 TL |
47 | @Endpoint() |
48 | @ReadPermission | |
f67539c2 TL |
49 | @EndpointDoc("Display RGW Status", |
50 | responses={200: RGW_SCHEMA}) | |
51 | def status(self) -> dict: | |
11fdf7f2 TL |
52 | status = {'available': False, 'message': None} |
53 | try: | |
54 | instance = RgwClient.admin_instance() | |
55 | # Check if the service is online. | |
adb31ebb | 56 | try: |
f67539c2 | 57 | is_online = instance.is_service_online() |
adb31ebb TL |
58 | except RequestException as e: |
59 | # Drop this instance because the RGW client seems not to | |
60 | # exist anymore (maybe removed via orchestrator). Removing | |
61 | # the instance from the cache will result in the correct | |
62 | # error message next time when the backend tries to | |
63 | # establish a new connection (-> 'No RGW found' instead | |
64 | # of 'RGW REST API failed request ...'). | |
65 | # Note, this only applies to auto-detected RGW clients. | |
f67539c2 | 66 | RgwClient.drop_instance(instance) |
adb31ebb TL |
67 | raise e |
68 | if not is_online: | |
11fdf7f2 TL |
69 | msg = 'Failed to connect to the Object Gateway\'s Admin Ops API.' |
70 | raise RequestException(msg) | |
11fdf7f2 | 71 | # Ensure the system flag is set for the API user ID. |
f6b5b4d7 | 72 | if not instance.is_system_user(): # pragma: no cover - no complexity there |
11fdf7f2 TL |
73 | msg = 'The system flag is not set for user "{}".'.format( |
74 | instance.userid) | |
75 | raise RequestException(msg) | |
76 | status['available'] = True | |
f67539c2 | 77 | except (DashboardException, RequestException, NoRgwDaemonsException) as ex: |
9f95a23c | 78 | status['message'] = str(ex) # type: ignore |
11fdf7f2 TL |
79 | return status |
80 | ||
81 | ||
82 | @ApiController('/rgw/daemon', Scope.RGW) | |
f67539c2 | 83 | @ControllerDoc("RGW Daemon Management API", "RgwDaemon") |
11fdf7f2 | 84 | class RgwDaemon(RESTController): |
f67539c2 TL |
85 | @EndpointDoc("Display RGW Daemons", |
86 | responses={200: [RGW_DAEMON_SCHEMA]}) | |
87 | def list(self) -> List[dict]: | |
88 | daemons: List[dict] = [] | |
89 | try: | |
90 | instance = RgwClient.admin_instance() | |
91 | except NoRgwDaemonsException: | |
92 | return daemons | |
93 | ||
11fdf7f2 TL |
94 | for hostname, server in CephService.get_service_map('rgw').items(): |
95 | for service in server['services']: | |
96 | metadata = service['metadata'] | |
97 | ||
98 | # extract per-daemon service data and health | |
99 | daemon = { | |
f67539c2 | 100 | 'id': metadata['id'], |
11fdf7f2 | 101 | 'version': metadata['ceph_version'], |
f67539c2 TL |
102 | 'server_hostname': hostname, |
103 | 'zonegroup_name': metadata['zonegroup_name'], | |
104 | 'zone_name': metadata['zone_name'], | |
105 | 'default': instance.daemon.name == metadata['id'] | |
11fdf7f2 TL |
106 | } |
107 | ||
108 | daemons.append(daemon) | |
109 | ||
110 | return sorted(daemons, key=lambda k: k['id']) | |
111 | ||
112 | def get(self, svc_id): | |
9f95a23c | 113 | # type: (str) -> dict |
11fdf7f2 TL |
114 | daemon = { |
115 | 'rgw_metadata': [], | |
116 | 'rgw_id': svc_id, | |
117 | 'rgw_status': [] | |
118 | } | |
119 | service = CephService.get_service('rgw', svc_id) | |
120 | if not service: | |
121 | raise cherrypy.NotFound('Service rgw {} is not available'.format(svc_id)) | |
122 | ||
123 | metadata = service['metadata'] | |
124 | status = service['status'] | |
125 | if 'json' in status: | |
126 | try: | |
127 | status = json.loads(status['json']) | |
128 | except ValueError: | |
129 | logger.warning('%s had invalid status json', service['id']) | |
130 | status = {} | |
131 | else: | |
132 | logger.warning('%s has no key "json" in status', service['id']) | |
133 | ||
134 | daemon['rgw_metadata'] = metadata | |
135 | daemon['rgw_status'] = status | |
136 | return daemon | |
137 | ||
138 | ||
139 | class RgwRESTController(RESTController): | |
f67539c2 | 140 | def proxy(self, daemon_name, method, path, params=None, json_response=True): |
11fdf7f2 | 141 | try: |
f67539c2 | 142 | instance = RgwClient.admin_instance(daemon_name=daemon_name) |
11fdf7f2 | 143 | result = instance.proxy(method, path, params, None) |
9f95a23c TL |
144 | if json_response: |
145 | result = json_str_to_object(result) | |
11fdf7f2 TL |
146 | return result |
147 | except (DashboardException, RequestException) as e: | |
f67539c2 TL |
148 | http_status_code = e.status if isinstance(e, DashboardException) else 500 |
149 | raise DashboardException(e, http_status_code=http_status_code, component='rgw') | |
11fdf7f2 TL |
150 | |
151 | ||
9f95a23c | 152 | @ApiController('/rgw/site', Scope.RGW) |
f67539c2 | 153 | @ControllerDoc("RGW Site Management API", "RgwSite") |
9f95a23c | 154 | class RgwSite(RgwRESTController): |
f67539c2 | 155 | def list(self, query=None, daemon_name=None): |
9f95a23c | 156 | if query == 'placement-targets': |
f67539c2 TL |
157 | return RgwClient.admin_instance(daemon_name=daemon_name).get_placement_targets() |
158 | if query == 'realms': | |
159 | return RgwClient.admin_instance(daemon_name=daemon_name).get_realms() | |
9f95a23c | 160 | |
f67539c2 TL |
161 | # @TODO: for multisite: by default, retrieve cluster topology/map. |
162 | raise DashboardException(http_status_code=501, component='rgw', msg='Not Implemented') | |
9f95a23c TL |
163 | |
164 | ||
11fdf7f2 | 165 | @ApiController('/rgw/bucket', Scope.RGW) |
f67539c2 | 166 | @ControllerDoc("RGW Bucket Management API", "RgwBucket") |
11fdf7f2 | 167 | class RgwBucket(RgwRESTController): |
11fdf7f2 TL |
168 | def _append_bid(self, bucket): |
169 | """ | |
170 | Append the bucket identifier that looks like [<tenant>/]<bucket>. | |
171 | See http://docs.ceph.com/docs/nautilus/radosgw/multitenancy/ for | |
172 | more information. | |
173 | :param bucket: The bucket parameters. | |
174 | :type bucket: dict | |
175 | :return: The modified bucket parameters including the 'bid' parameter. | |
176 | :rtype: dict | |
177 | """ | |
178 | if isinstance(bucket, dict): | |
179 | bucket['bid'] = '{}/{}'.format(bucket['tenant'], bucket['bucket']) \ | |
180 | if bucket['tenant'] else bucket['bucket'] | |
181 | return bucket | |
182 | ||
f67539c2 TL |
183 | def _get_versioning(self, owner, daemon_name, bucket_name): |
184 | rgw_client = RgwClient.instance(owner, daemon_name) | |
9f95a23c TL |
185 | return rgw_client.get_bucket_versioning(bucket_name) |
186 | ||
f67539c2 | 187 | def _set_versioning(self, owner, daemon_name, bucket_name, versioning_state, mfa_delete, |
9f95a23c | 188 | mfa_token_serial, mfa_token_pin): |
f67539c2 | 189 | bucket_versioning = self._get_versioning(owner, daemon_name, bucket_name) |
9f95a23c TL |
190 | if versioning_state != bucket_versioning['Status']\ |
191 | or (mfa_delete and mfa_delete != bucket_versioning['MfaDelete']): | |
f67539c2 | 192 | rgw_client = RgwClient.instance(owner, daemon_name) |
9f95a23c TL |
193 | rgw_client.set_bucket_versioning(bucket_name, versioning_state, mfa_delete, |
194 | mfa_token_serial, mfa_token_pin) | |
195 | ||
f67539c2 TL |
196 | def _get_locking(self, owner, daemon_name, bucket_name): |
197 | rgw_client = RgwClient.instance(owner, daemon_name) | |
9f95a23c TL |
198 | return rgw_client.get_bucket_locking(bucket_name) |
199 | ||
f67539c2 | 200 | def _set_locking(self, owner, daemon_name, bucket_name, mode, |
9f95a23c | 201 | retention_period_days, retention_period_years): |
f67539c2 | 202 | rgw_client = RgwClient.instance(owner, daemon_name) |
9f95a23c | 203 | return rgw_client.set_bucket_locking(bucket_name, mode, |
b3b6e05e TL |
204 | retention_period_days, |
205 | retention_period_years) | |
9f95a23c | 206 | |
eafe8130 | 207 | @staticmethod |
9f95a23c TL |
208 | def strip_tenant_from_bucket_name(bucket_name): |
209 | # type (str) -> str | |
eafe8130 | 210 | """ |
9f95a23c | 211 | >>> RgwBucket.strip_tenant_from_bucket_name('tenant/bucket-name') |
eafe8130 | 212 | 'bucket-name' |
9f95a23c | 213 | >>> RgwBucket.strip_tenant_from_bucket_name('bucket-name') |
eafe8130 TL |
214 | 'bucket-name' |
215 | """ | |
9f95a23c | 216 | return bucket_name[bucket_name.find('/') + 1:] |
eafe8130 | 217 | |
9f95a23c TL |
218 | @staticmethod |
219 | def get_s3_bucket_name(bucket_name, tenant=None): | |
220 | # type (str, str) -> str | |
221 | """ | |
222 | >>> RgwBucket.get_s3_bucket_name('bucket-name', 'tenant') | |
223 | 'tenant:bucket-name' | |
224 | >>> RgwBucket.get_s3_bucket_name('tenant/bucket-name', 'tenant') | |
225 | 'tenant:bucket-name' | |
226 | >>> RgwBucket.get_s3_bucket_name('bucket-name') | |
227 | 'bucket-name' | |
228 | """ | |
229 | bucket_name = RgwBucket.strip_tenant_from_bucket_name(bucket_name) | |
230 | if tenant: | |
231 | bucket_name = '{}:{}'.format(tenant, bucket_name) | |
eafe8130 TL |
232 | return bucket_name |
233 | ||
f67539c2 TL |
234 | def list(self, stats=False, daemon_name=None): |
235 | # type: (bool, Optional[str]) -> List[Any] | |
f91f0fd5 | 236 | query_params = '?stats' if stats else '' |
f67539c2 | 237 | result = self.proxy(daemon_name, 'GET', 'bucket{}'.format(query_params)) |
f91f0fd5 TL |
238 | |
239 | if stats: | |
240 | result = [self._append_bid(bucket) for bucket in result] | |
241 | ||
242 | return result | |
11fdf7f2 | 243 | |
f67539c2 TL |
244 | def get(self, bucket, daemon_name=None): |
245 | # type: (str, Optional[str]) -> dict | |
246 | result = self.proxy(daemon_name, 'GET', 'bucket', {'bucket': bucket}) | |
9f95a23c TL |
247 | bucket_name = RgwBucket.get_s3_bucket_name(result['bucket'], |
248 | result['tenant']) | |
249 | ||
250 | # Append the versioning configuration. | |
f67539c2 | 251 | versioning = self._get_versioning(result['owner'], daemon_name, bucket_name) |
9f95a23c TL |
252 | result['versioning'] = versioning['Status'] |
253 | result['mfa_delete'] = versioning['MfaDelete'] | |
254 | ||
255 | # Append the locking configuration. | |
f67539c2 | 256 | locking = self._get_locking(result['owner'], daemon_name, bucket_name) |
9f95a23c TL |
257 | result.update(locking) |
258 | ||
11fdf7f2 TL |
259 | return self._append_bid(result) |
260 | ||
f91f0fd5 | 261 | @allow_empty_body |
9f95a23c TL |
262 | def create(self, bucket, uid, zonegroup=None, placement_target=None, |
263 | lock_enabled='false', lock_mode=None, | |
264 | lock_retention_period_days=None, | |
f67539c2 | 265 | lock_retention_period_years=None, daemon_name=None): |
9f95a23c | 266 | lock_enabled = str_to_bool(lock_enabled) |
11fdf7f2 | 267 | try: |
f67539c2 | 268 | rgw_client = RgwClient.instance(uid, daemon_name) |
9f95a23c TL |
269 | result = rgw_client.create_bucket(bucket, zonegroup, |
270 | placement_target, | |
271 | lock_enabled) | |
272 | if lock_enabled: | |
f67539c2 | 273 | self._set_locking(uid, daemon_name, bucket, lock_mode, |
9f95a23c TL |
274 | lock_retention_period_days, |
275 | lock_retention_period_years) | |
276 | return result | |
f6b5b4d7 | 277 | except RequestException as e: # pragma: no cover - handling is too obvious |
11fdf7f2 TL |
278 | raise DashboardException(e, http_status_code=500, component='rgw') |
279 | ||
f91f0fd5 | 280 | @allow_empty_body |
9f95a23c TL |
281 | def set(self, bucket, bucket_id, uid, versioning_state=None, |
282 | mfa_delete=None, mfa_token_serial=None, mfa_token_pin=None, | |
283 | lock_mode=None, lock_retention_period_days=None, | |
f67539c2 | 284 | lock_retention_period_years=None, daemon_name=None): |
9f95a23c TL |
285 | # When linking a non-tenant-user owned bucket to a tenanted user, we |
286 | # need to prefix bucket name with '/'. e.g. photos -> /photos | |
287 | if '$' in uid and '/' not in bucket: | |
288 | bucket = '/{}'.format(bucket) | |
289 | ||
290 | # Link bucket to new user: | |
f67539c2 TL |
291 | result = self.proxy(daemon_name, |
292 | 'PUT', | |
9f95a23c TL |
293 | 'bucket', { |
294 | 'bucket': bucket, | |
295 | 'bucket-id': bucket_id, | |
296 | 'uid': uid | |
297 | }, | |
298 | json_response=False) | |
299 | ||
300 | uid_tenant = uid[:uid.find('$')] if uid.find('$') >= 0 else None | |
301 | bucket_name = RgwBucket.get_s3_bucket_name(bucket, uid_tenant) | |
302 | ||
b3b6e05e | 303 | locking = self._get_locking(uid, daemon_name, bucket_name) |
9f95a23c | 304 | if versioning_state: |
b3b6e05e TL |
305 | if versioning_state == 'Suspended' and locking['lock_enabled']: |
306 | raise DashboardException(msg='Bucket versioning cannot be disabled/suspended ' | |
307 | 'on buckets with object lock enabled ', | |
308 | http_status_code=409, component='rgw') | |
f67539c2 | 309 | self._set_versioning(uid, daemon_name, bucket_name, versioning_state, |
9f95a23c TL |
310 | mfa_delete, mfa_token_serial, mfa_token_pin) |
311 | ||
312 | # Update locking if it is enabled. | |
9f95a23c | 313 | if locking['lock_enabled']: |
f67539c2 | 314 | self._set_locking(uid, daemon_name, bucket_name, lock_mode, |
9f95a23c TL |
315 | lock_retention_period_days, |
316 | lock_retention_period_years) | |
317 | ||
11fdf7f2 TL |
318 | return self._append_bid(result) |
319 | ||
f67539c2 TL |
320 | def delete(self, bucket, purge_objects='true', daemon_name=None): |
321 | return self.proxy(daemon_name, 'DELETE', 'bucket', { | |
11fdf7f2 TL |
322 | 'bucket': bucket, |
323 | 'purge-objects': purge_objects | |
324 | }, json_response=False) | |
325 | ||
326 | ||
327 | @ApiController('/rgw/user', Scope.RGW) | |
f67539c2 | 328 | @ControllerDoc("RGW User Management API", "RgwUser") |
11fdf7f2 | 329 | class RgwUser(RgwRESTController): |
11fdf7f2 TL |
330 | def _append_uid(self, user): |
331 | """ | |
332 | Append the user identifier that looks like [<tenant>$]<user>. | |
333 | See http://docs.ceph.com/docs/jewel/radosgw/multitenancy/ for | |
334 | more information. | |
335 | :param user: The user parameters. | |
336 | :type user: dict | |
337 | :return: The modified user parameters including the 'uid' parameter. | |
338 | :rtype: dict | |
339 | """ | |
340 | if isinstance(user, dict): | |
341 | user['uid'] = '{}${}'.format(user['tenant'], user['user_id']) \ | |
342 | if user['tenant'] else user['user_id'] | |
343 | return user | |
344 | ||
9f95a23c TL |
345 | @staticmethod |
346 | def _keys_allowed(): | |
347 | permissions = AuthManager.get_user(JwtManager.get_username()).permissions_dict() | |
348 | edit_permissions = [Permission.CREATE, Permission.UPDATE, Permission.DELETE] | |
349 | return Scope.RGW in permissions and Permission.READ in permissions[Scope.RGW] \ | |
350 | and len(set(edit_permissions).intersection(set(permissions[Scope.RGW]))) > 0 | |
351 | ||
f67539c2 TL |
352 | @EndpointDoc("Display RGW Users", |
353 | responses={200: RGW_USER_SCHEMA}) | |
354 | def list(self, daemon_name=None): | |
355 | # type: (Optional[str]) -> List[str] | |
9f95a23c | 356 | users = [] # type: List[str] |
11fdf7f2 TL |
357 | marker = None |
358 | while True: | |
9f95a23c | 359 | params = {} # type: dict |
11fdf7f2 TL |
360 | if marker: |
361 | params['marker'] = marker | |
f67539c2 | 362 | result = self.proxy(daemon_name, 'GET', 'user?list', params) |
11fdf7f2 TL |
363 | users.extend(result['keys']) |
364 | if not result['truncated']: | |
365 | break | |
366 | # Make sure there is a marker. | |
367 | assert result['marker'] | |
368 | # Make sure the marker has changed. | |
369 | assert marker != result['marker'] | |
370 | marker = result['marker'] | |
371 | return users | |
372 | ||
f67539c2 TL |
373 | def get(self, uid, daemon_name=None, stats=True) -> dict: |
374 | query_params = '?stats' if stats else '' | |
375 | result = self.proxy(daemon_name, 'GET', 'user{}'.format(query_params), | |
376 | {'uid': uid, 'stats': stats}) | |
9f95a23c TL |
377 | if not self._keys_allowed(): |
378 | del result['keys'] | |
379 | del result['swift_keys'] | |
11fdf7f2 TL |
380 | return self._append_uid(result) |
381 | ||
382 | @Endpoint() | |
383 | @ReadPermission | |
f67539c2 TL |
384 | def get_emails(self, daemon_name=None): |
385 | # type: (Optional[str]) -> List[str] | |
11fdf7f2 | 386 | emails = [] |
f67539c2 TL |
387 | for uid in json.loads(self.list(daemon_name)): # type: ignore |
388 | user = json.loads(self.get(uid, daemon_name)) # type: ignore | |
11fdf7f2 TL |
389 | if user["email"]: |
390 | emails.append(user["email"]) | |
391 | return emails | |
392 | ||
f91f0fd5 | 393 | @allow_empty_body |
11fdf7f2 TL |
394 | def create(self, uid, display_name, email=None, max_buckets=None, |
395 | suspended=None, generate_key=None, access_key=None, | |
f67539c2 | 396 | secret_key=None, daemon_name=None): |
11fdf7f2 TL |
397 | params = {'uid': uid} |
398 | if display_name is not None: | |
399 | params['display-name'] = display_name | |
400 | if email is not None: | |
401 | params['email'] = email | |
402 | if max_buckets is not None: | |
403 | params['max-buckets'] = max_buckets | |
404 | if suspended is not None: | |
405 | params['suspended'] = suspended | |
406 | if generate_key is not None: | |
407 | params['generate-key'] = generate_key | |
408 | if access_key is not None: | |
409 | params['access-key'] = access_key | |
410 | if secret_key is not None: | |
411 | params['secret-key'] = secret_key | |
f67539c2 | 412 | result = self.proxy(daemon_name, 'PUT', 'user', params) |
11fdf7f2 TL |
413 | return self._append_uid(result) |
414 | ||
f91f0fd5 | 415 | @allow_empty_body |
11fdf7f2 | 416 | def set(self, uid, display_name=None, email=None, max_buckets=None, |
f67539c2 | 417 | suspended=None, daemon_name=None): |
11fdf7f2 TL |
418 | params = {'uid': uid} |
419 | if display_name is not None: | |
420 | params['display-name'] = display_name | |
421 | if email is not None: | |
422 | params['email'] = email | |
423 | if max_buckets is not None: | |
424 | params['max-buckets'] = max_buckets | |
425 | if suspended is not None: | |
426 | params['suspended'] = suspended | |
f67539c2 | 427 | result = self.proxy(daemon_name, 'POST', 'user', params) |
11fdf7f2 TL |
428 | return self._append_uid(result) |
429 | ||
f67539c2 | 430 | def delete(self, uid, daemon_name=None): |
11fdf7f2 | 431 | try: |
f67539c2 | 432 | instance = RgwClient.admin_instance(daemon_name=daemon_name) |
11fdf7f2 TL |
433 | # Ensure the user is not configured to access the RGW Object Gateway. |
434 | if instance.userid == uid: | |
435 | raise DashboardException(msg='Unable to delete "{}" - this user ' | |
436 | 'account is required for managing the ' | |
437 | 'Object Gateway'.format(uid)) | |
438 | # Finally redirect request to the RGW proxy. | |
f67539c2 | 439 | return self.proxy(daemon_name, 'DELETE', 'user', {'uid': uid}, json_response=False) |
f6b5b4d7 | 440 | except (DashboardException, RequestException) as e: # pragma: no cover |
11fdf7f2 TL |
441 | raise DashboardException(e, component='rgw') |
442 | ||
443 | # pylint: disable=redefined-builtin | |
444 | @RESTController.Resource(method='POST', path='/capability', status=201) | |
f91f0fd5 | 445 | @allow_empty_body |
f67539c2 TL |
446 | def create_cap(self, uid, type, perm, daemon_name=None): |
447 | return self.proxy(daemon_name, 'PUT', 'user?caps', { | |
11fdf7f2 TL |
448 | 'uid': uid, |
449 | 'user-caps': '{}={}'.format(type, perm) | |
450 | }) | |
451 | ||
452 | # pylint: disable=redefined-builtin | |
453 | @RESTController.Resource(method='DELETE', path='/capability', status=204) | |
f67539c2 TL |
454 | def delete_cap(self, uid, type, perm, daemon_name=None): |
455 | return self.proxy(daemon_name, 'DELETE', 'user?caps', { | |
11fdf7f2 TL |
456 | 'uid': uid, |
457 | 'user-caps': '{}={}'.format(type, perm) | |
458 | }) | |
459 | ||
460 | @RESTController.Resource(method='POST', path='/key', status=201) | |
f91f0fd5 | 461 | @allow_empty_body |
11fdf7f2 | 462 | def create_key(self, uid, key_type='s3', subuser=None, generate_key='true', |
f67539c2 | 463 | access_key=None, secret_key=None, daemon_name=None): |
11fdf7f2 TL |
464 | params = {'uid': uid, 'key-type': key_type, 'generate-key': generate_key} |
465 | if subuser is not None: | |
466 | params['subuser'] = subuser | |
467 | if access_key is not None: | |
468 | params['access-key'] = access_key | |
469 | if secret_key is not None: | |
470 | params['secret-key'] = secret_key | |
f67539c2 | 471 | return self.proxy(daemon_name, 'PUT', 'user?key', params) |
11fdf7f2 TL |
472 | |
473 | @RESTController.Resource(method='DELETE', path='/key', status=204) | |
f67539c2 | 474 | def delete_key(self, uid, key_type='s3', subuser=None, access_key=None, daemon_name=None): |
11fdf7f2 TL |
475 | params = {'uid': uid, 'key-type': key_type} |
476 | if subuser is not None: | |
477 | params['subuser'] = subuser | |
478 | if access_key is not None: | |
479 | params['access-key'] = access_key | |
f67539c2 | 480 | return self.proxy(daemon_name, 'DELETE', 'user?key', params, json_response=False) |
11fdf7f2 TL |
481 | |
482 | @RESTController.Resource(method='GET', path='/quota') | |
f67539c2 TL |
483 | def get_quota(self, uid, daemon_name=None): |
484 | return self.proxy(daemon_name, 'GET', 'user?quota', {'uid': uid}) | |
11fdf7f2 TL |
485 | |
486 | @RESTController.Resource(method='PUT', path='/quota') | |
f91f0fd5 | 487 | @allow_empty_body |
f67539c2 TL |
488 | def set_quota(self, uid, quota_type, enabled, max_size_kb, max_objects, daemon_name=None): |
489 | return self.proxy(daemon_name, 'PUT', 'user?quota', { | |
11fdf7f2 TL |
490 | 'uid': uid, |
491 | 'quota-type': quota_type, | |
492 | 'enabled': enabled, | |
493 | 'max-size-kb': max_size_kb, | |
494 | 'max-objects': max_objects | |
495 | }, json_response=False) | |
496 | ||
497 | @RESTController.Resource(method='POST', path='/subuser', status=201) | |
f91f0fd5 | 498 | @allow_empty_body |
11fdf7f2 TL |
499 | def create_subuser(self, uid, subuser, access, key_type='s3', |
500 | generate_secret='true', access_key=None, | |
f67539c2 TL |
501 | secret_key=None, daemon_name=None): |
502 | return self.proxy(daemon_name, 'PUT', 'user', { | |
11fdf7f2 TL |
503 | 'uid': uid, |
504 | 'subuser': subuser, | |
505 | 'key-type': key_type, | |
506 | 'access': access, | |
507 | 'generate-secret': generate_secret, | |
508 | 'access-key': access_key, | |
509 | 'secret-key': secret_key | |
510 | }) | |
511 | ||
512 | @RESTController.Resource(method='DELETE', path='/subuser/{subuser}', status=204) | |
f67539c2 | 513 | def delete_subuser(self, uid, subuser, purge_keys='true', daemon_name=None): |
11fdf7f2 TL |
514 | """ |
515 | :param purge_keys: Set to False to do not purge the keys. | |
516 | Note, this only works for s3 subusers. | |
517 | """ | |
f67539c2 | 518 | return self.proxy(daemon_name, 'DELETE', 'user', { |
11fdf7f2 TL |
519 | 'uid': uid, |
520 | 'subuser': subuser, | |
521 | 'purge-keys': purge_keys | |
522 | }, json_response=False) |