]>
Commit | Line | Data |
---|---|---|
11fdf7f2 | 1 | # -*- coding: utf-8 -*- |
aee94f69 TL |
2 | # pylint: disable=C0302 |
3 | # pylint: disable=too-many-branches | |
4 | # pylint: disable=too-many-lines | |
11fdf7f2 | 5 | |
9f95a23c | 6 | import ipaddress |
522d829b | 7 | import json |
f67539c2 | 8 | import logging |
1e59de90 | 9 | import os |
f67539c2 | 10 | import re |
9f95a23c | 11 | import xml.etree.ElementTree as ET # noqa: N814 |
f78120f9 | 12 | from enum import Enum |
522d829b TL |
13 | from subprocess import SubprocessError |
14 | ||
aee94f69 | 15 | from mgr_util import build_url, name_to_config_section |
f67539c2 TL |
16 | |
17 | from .. import mgr | |
11fdf7f2 | 18 | from ..awsauth import S3Auth |
9f95a23c | 19 | from ..exceptions import DashboardException |
f67539c2 TL |
20 | from ..rest_client import RequestException, RestClient |
21 | from ..settings import Settings | |
aee94f69 TL |
22 | from ..tools import dict_contains_path, dict_get, json_str_to_object, str_to_bool |
23 | from .ceph_service import CephService | |
9f95a23c TL |
24 | |
25 | try: | |
b3b6e05e | 26 | from typing import Any, Dict, List, Optional, Tuple, Union |
9f95a23c TL |
27 | except ImportError: |
28 | pass # For typing only | |
29 | ||
30 | logger = logging.getLogger('rgw_client') | |
11fdf7f2 | 31 | |
f78120f9 TL |
32 | _SYNC_GROUP_ID = 'dashboard_admin_group' |
33 | _SYNC_FLOW_ID = 'dashboard_admin_flow' | |
34 | _SYNC_PIPE_ID = 'dashboard_admin_pipe' | |
35 | ||
11fdf7f2 | 36 | |
f67539c2 TL |
37 | class NoRgwDaemonsException(Exception): |
38 | def __init__(self): | |
39 | super().__init__('No RGW service is running.') | |
40 | ||
41 | ||
522d829b | 42 | class NoCredentialsException(Exception): |
11fdf7f2 TL |
43 | def __init__(self): |
44 | super(NoCredentialsException, self).__init__( | |
45 | 'No RGW credentials found, ' | |
46 | 'please consult the documentation on how to enable RGW for ' | |
47 | 'the dashboard.') | |
48 | ||
49 | ||
522d829b TL |
50 | class RgwAdminException(Exception): |
51 | pass | |
52 | ||
53 | ||
f67539c2 TL |
54 | class RgwDaemon: |
55 | """Simple representation of a daemon.""" | |
56 | host: str | |
57 | name: str | |
58 | port: int | |
59 | ssl: bool | |
522d829b | 60 | realm_name: str |
f67539c2 | 61 | zonegroup_name: str |
f78120f9 | 62 | zonegroup_id: str |
522d829b | 63 | zone_name: str |
f67539c2 TL |
64 | |
65 | ||
66 | def _get_daemons() -> Dict[str, RgwDaemon]: | |
11fdf7f2 | 67 | """ |
f91f0fd5 | 68 | Retrieve RGW daemon info from MGR. |
11fdf7f2 TL |
69 | """ |
70 | service_map = mgr.get('service_map') | |
71 | if not dict_contains_path(service_map, ['services', 'rgw', 'daemons']): | |
f67539c2 | 72 | raise NoRgwDaemonsException |
11fdf7f2 | 73 | |
f67539c2 TL |
74 | daemons = {} |
75 | daemon_map = service_map['services']['rgw']['daemons'] | |
76 | for key in daemon_map.keys(): | |
77 | if dict_contains_path(daemon_map[key], ['metadata', 'frontend_config#0']): | |
78 | daemon = _determine_rgw_addr(daemon_map[key]) | |
79 | daemon.name = daemon_map[key]['metadata']['id'] | |
522d829b | 80 | daemon.realm_name = daemon_map[key]['metadata']['realm_name'] |
f67539c2 | 81 | daemon.zonegroup_name = daemon_map[key]['metadata']['zonegroup_name'] |
f78120f9 | 82 | daemon.zonegroup_id = daemon_map[key]['metadata']['zonegroup_id'] |
522d829b | 83 | daemon.zone_name = daemon_map[key]['metadata']['zone_name'] |
f67539c2 TL |
84 | daemons[daemon.name] = daemon |
85 | logger.info('Found RGW daemon with configuration: host=%s, port=%d, ssl=%s', | |
86 | daemon.host, daemon.port, str(daemon.ssl)) | |
87 | if not daemons: | |
88 | raise NoRgwDaemonsException | |
89 | ||
90 | return daemons | |
f91f0fd5 TL |
91 | |
92 | ||
f67539c2 | 93 | def _determine_rgw_addr(daemon_info: Dict[str, Any]) -> RgwDaemon: |
f91f0fd5 TL |
94 | """ |
95 | Parse RGW daemon info to determine the configured host (IP address) and port. | |
96 | """ | |
f67539c2 | 97 | daemon = RgwDaemon() |
aee94f69 TL |
98 | rgw_dns_name = CephService.send_command('mon', 'config get', |
99 | who=name_to_config_section('rgw.' + daemon_info['metadata']['id']), # noqa E501 #pylint: disable=line-too-long | |
100 | key='rgw_dns_name').rstrip() | |
101 | ||
f67539c2 | 102 | daemon.port, daemon.ssl = _parse_frontend_config(daemon_info['metadata']['frontend_config#0']) |
11fdf7f2 | 103 | |
aee94f69 TL |
104 | if rgw_dns_name: |
105 | daemon.host = rgw_dns_name | |
106 | elif daemon.ssl: | |
107 | daemon.host = daemon_info['metadata']['hostname'] | |
108 | else: | |
109 | daemon.host = _parse_addr(daemon_info['addr']) | |
110 | ||
f67539c2 | 111 | return daemon |
11fdf7f2 TL |
112 | |
113 | ||
adb31ebb | 114 | def _parse_addr(value) -> str: |
11fdf7f2 TL |
115 | """ |
116 | Get the IP address the RGW is running on. | |
117 | ||
118 | >>> _parse_addr('192.168.178.3:49774/1534999298') | |
119 | '192.168.178.3' | |
120 | ||
121 | >>> _parse_addr('[2001:db8:85a3::8a2e:370:7334]:49774/1534999298') | |
122 | '2001:db8:85a3::8a2e:370:7334' | |
123 | ||
124 | >>> _parse_addr('xyz') | |
125 | Traceback (most recent call last): | |
126 | ... | |
127 | LookupError: Failed to determine RGW address | |
128 | ||
129 | >>> _parse_addr('192.168.178.a:8080/123456789') | |
130 | Traceback (most recent call last): | |
131 | ... | |
132 | LookupError: Invalid RGW address '192.168.178.a' found | |
133 | ||
134 | >>> _parse_addr('[2001:0db8:1234]:443/123456789') | |
135 | Traceback (most recent call last): | |
136 | ... | |
137 | LookupError: Invalid RGW address '2001:0db8:1234' found | |
138 | ||
139 | >>> _parse_addr('2001:0db8::1234:49774/1534999298') | |
140 | Traceback (most recent call last): | |
141 | ... | |
142 | LookupError: Failed to determine RGW address | |
143 | ||
144 | :param value: The string to process. The syntax is '<HOST>:<PORT>/<NONCE>'. | |
145 | :type: str | |
146 | :raises LookupError if parsing fails to determine the IP address. | |
147 | :return: The IP address. | |
148 | :rtype: str | |
149 | """ | |
150 | match = re.search(r'^(\[)?(?(1)([^\]]+)\]|([^:]+)):\d+/\d+?', value) | |
151 | if match: | |
152 | # IPv4: | |
153 | # Group 0: 192.168.178.3:49774/1534999298 | |
154 | # Group 3: 192.168.178.3 | |
155 | # IPv6: | |
156 | # Group 0: [2001:db8:85a3::8a2e:370:7334]:49774/1534999298 | |
157 | # Group 1: [ | |
158 | # Group 2: 2001:db8:85a3::8a2e:370:7334 | |
159 | addr = match.group(3) if match.group(3) else match.group(2) | |
9f95a23c | 160 | try: |
f67539c2 | 161 | ipaddress.ip_address(addr) |
9f95a23c TL |
162 | return addr |
163 | except ValueError: | |
11fdf7f2 | 164 | raise LookupError('Invalid RGW address \'{}\' found'.format(addr)) |
11fdf7f2 TL |
165 | raise LookupError('Failed to determine RGW address') |
166 | ||
167 | ||
adb31ebb | 168 | def _parse_frontend_config(config) -> Tuple[int, bool]: |
11fdf7f2 TL |
169 | """ |
170 | Get the port the RGW is running on. Due the complexity of the | |
171 | syntax not all variations are supported. | |
172 | ||
9f95a23c TL |
173 | If there are multiple (ssl_)ports/(ssl_)endpoints options, then |
174 | the first found option will be returned. | |
175 | ||
11fdf7f2 | 176 | Get more details about the configuration syntax here: |
f67539c2 | 177 | http://docs.ceph.com/en/latest/radosgw/frontends/ |
11fdf7f2 TL |
178 | https://civetweb.github.io/civetweb/UserManual.html |
179 | ||
11fdf7f2 TL |
180 | :param config: The configuration string to parse. |
181 | :type config: str | |
182 | :raises LookupError if parsing fails to determine the port. | |
183 | :return: A tuple containing the port number and the information | |
184 | whether SSL is used. | |
185 | :rtype: (int, boolean) | |
186 | """ | |
9f95a23c | 187 | match = re.search(r'^(beast|civetweb)\s+.+$', config) |
11fdf7f2 | 188 | if match: |
9f95a23c TL |
189 | if match.group(1) == 'beast': |
190 | match = re.search(r'(port|ssl_port|endpoint|ssl_endpoint)=(.+)', | |
191 | config) | |
192 | if match: | |
193 | option_name = match.group(1) | |
194 | if option_name in ['port', 'ssl_port']: | |
195 | match = re.search(r'(\d+)', match.group(2)) | |
196 | if match: | |
197 | port = int(match.group(1)) | |
198 | ssl = option_name == 'ssl_port' | |
199 | return port, ssl | |
200 | if option_name in ['endpoint', 'ssl_endpoint']: | |
201 | match = re.search(r'([\d.]+|\[.+\])(:(\d+))?', | |
202 | match.group(2)) # type: ignore | |
203 | if match: | |
204 | port = int(match.group(3)) if \ | |
205 | match.group(2) is not None else 443 if \ | |
206 | option_name == 'ssl_endpoint' else \ | |
207 | 80 | |
208 | ssl = option_name == 'ssl_endpoint' | |
209 | return port, ssl | |
210 | if match.group(1) == 'civetweb': # type: ignore | |
211 | match = re.search(r'port=(.*:)?(\d+)(s)?', config) | |
212 | if match: | |
213 | port = int(match.group(2)) | |
214 | ssl = match.group(3) == 's' | |
215 | return port, ssl | |
216 | raise LookupError('Failed to determine RGW port from "{}"'.format(config)) | |
11fdf7f2 TL |
217 | |
218 | ||
522d829b TL |
219 | def _parse_secrets(user: str, data: dict) -> Tuple[str, str]: |
220 | for key in data.get('keys', []): | |
221 | if key.get('user') == user and data.get('system') in ['true', True]: | |
222 | access_key = key.get('access_key') | |
223 | secret_key = key.get('secret_key') | |
224 | return access_key, secret_key | |
225 | return '', '' | |
226 | ||
227 | ||
228 | def _get_user_keys(user: str, realm: Optional[str] = None) -> Tuple[str, str]: | |
229 | access_key = '' | |
230 | secret_key = '' | |
231 | rgw_user_info_cmd = ['user', 'info', '--uid', user] | |
232 | cmd_realm_option = ['--rgw-realm', realm] if realm else [] | |
233 | if realm: | |
234 | rgw_user_info_cmd += cmd_realm_option | |
235 | try: | |
236 | _, out, err = mgr.send_rgwadmin_command(rgw_user_info_cmd) | |
237 | if out: | |
238 | access_key, secret_key = _parse_secrets(user, out) | |
239 | if not access_key: | |
240 | rgw_create_user_cmd = [ | |
241 | 'user', 'create', | |
242 | '--uid', user, | |
243 | '--display-name', 'Ceph Dashboard', | |
244 | '--system', | |
245 | ] + cmd_realm_option | |
246 | _, out, err = mgr.send_rgwadmin_command(rgw_create_user_cmd) | |
247 | if out: | |
248 | access_key, secret_key = _parse_secrets(user, out) | |
249 | if not access_key: | |
250 | logger.error('Unable to create rgw user "%s": %s', user, err) | |
251 | except SubprocessError as error: | |
252 | logger.exception(error) | |
253 | ||
254 | return access_key, secret_key | |
255 | ||
256 | ||
257 | def configure_rgw_credentials(): | |
258 | logger.info('Configuring dashboard RGW credentials') | |
259 | user = 'dashboard' | |
260 | realms = [] | |
261 | access_key = '' | |
262 | secret_key = '' | |
263 | try: | |
264 | _, out, err = mgr.send_rgwadmin_command(['realm', 'list']) | |
265 | if out: | |
266 | realms = out.get('realms', []) | |
267 | if err: | |
268 | logger.error('Unable to list RGW realms: %s', err) | |
269 | if realms: | |
270 | realm_access_keys = {} | |
271 | realm_secret_keys = {} | |
272 | for realm in realms: | |
273 | realm_access_key, realm_secret_key = _get_user_keys(user, realm) | |
274 | if realm_access_key: | |
275 | realm_access_keys[realm] = realm_access_key | |
276 | realm_secret_keys[realm] = realm_secret_key | |
277 | if realm_access_keys: | |
278 | access_key = json.dumps(realm_access_keys) | |
279 | secret_key = json.dumps(realm_secret_keys) | |
280 | else: | |
281 | access_key, secret_key = _get_user_keys(user) | |
282 | ||
283 | assert access_key and secret_key | |
284 | Settings.RGW_API_ACCESS_KEY = access_key | |
285 | Settings.RGW_API_SECRET_KEY = secret_key | |
286 | except (AssertionError, SubprocessError) as error: | |
287 | logger.exception(error) | |
288 | raise NoCredentialsException | |
289 | ||
290 | ||
39ae355f | 291 | # pylint: disable=R0904 |
11fdf7f2 | 292 | class RgwClient(RestClient): |
11fdf7f2 TL |
293 | _host = None |
294 | _port = None | |
295 | _ssl = None | |
f67539c2 TL |
296 | _user_instances = {} # type: Dict[str, Dict[str, RgwClient]] |
297 | _config_instances = {} # type: Dict[str, RgwClient] | |
494da23a | 298 | _rgw_settings_snapshot = None |
f67539c2 TL |
299 | _daemons: Dict[str, RgwDaemon] = {} |
300 | daemon: RgwDaemon | |
301 | got_keys_from_config: bool | |
302 | userid: str | |
11fdf7f2 TL |
303 | |
304 | @staticmethod | |
f67539c2 TL |
305 | def _handle_response_status_code(status_code: int) -> int: |
306 | # Do not return auth error codes (so they are not handled as ceph API user auth errors). | |
307 | return 404 if status_code in [401, 403] else status_code | |
11fdf7f2 | 308 | |
f67539c2 TL |
309 | @staticmethod |
310 | def _get_daemon_connection_info(daemon_name: str) -> dict: | |
311 | try: | |
522d829b TL |
312 | realm_name = RgwClient._daemons[daemon_name].realm_name |
313 | access_key = Settings.RGW_API_ACCESS_KEY[realm_name] | |
314 | secret_key = Settings.RGW_API_SECRET_KEY[realm_name] | |
f67539c2 TL |
315 | except TypeError: |
316 | # Legacy string values. | |
317 | access_key = Settings.RGW_API_ACCESS_KEY | |
318 | secret_key = Settings.RGW_API_SECRET_KEY | |
319 | except KeyError as error: | |
320 | raise DashboardException(msg='Credentials not found for RGW Daemon: {}'.format(error), | |
321 | http_status_code=404, | |
322 | component='rgw') | |
11fdf7f2 | 323 | |
f67539c2 | 324 | return {'access_key': access_key, 'secret_key': secret_key} |
11fdf7f2 | 325 | |
9f95a23c TL |
326 | def _get_daemon_zone_info(self): # type: () -> dict |
327 | return json_str_to_object(self.proxy('GET', 'config?type=zone', None, None)) | |
328 | ||
e306af50 TL |
329 | def _get_realms_info(self): # type: () -> dict |
330 | return json_str_to_object(self.proxy('GET', 'realm?list', None, None)) | |
331 | ||
a4b75251 TL |
332 | def _get_realm_info(self, realm_id: str) -> Dict[str, Any]: |
333 | return json_str_to_object(self.proxy('GET', f'realm?id={realm_id}', None, None)) | |
334 | ||
494da23a TL |
335 | @staticmethod |
336 | def _rgw_settings(): | |
522d829b | 337 | return (Settings.RGW_API_ACCESS_KEY, |
494da23a TL |
338 | Settings.RGW_API_SECRET_KEY, |
339 | Settings.RGW_API_ADMIN_RESOURCE, | |
494da23a TL |
340 | Settings.RGW_API_SSL_VERIFY) |
341 | ||
11fdf7f2 | 342 | @staticmethod |
f67539c2 TL |
343 | def instance(userid: Optional[str] = None, |
344 | daemon_name: Optional[str] = None) -> 'RgwClient': | |
345 | # pylint: disable=too-many-branches | |
346 | ||
347 | RgwClient._daemons = _get_daemons() | |
348 | ||
349 | # The API access key and secret key are mandatory for a minimal configuration. | |
350 | if not (Settings.RGW_API_ACCESS_KEY and Settings.RGW_API_SECRET_KEY): | |
522d829b | 351 | configure_rgw_credentials() |
f67539c2 | 352 | |
f78120f9 | 353 | daemon_keys = RgwClient._daemons.keys() |
f67539c2 | 354 | if not daemon_name: |
f78120f9 TL |
355 | if len(daemon_keys) > 1: |
356 | try: | |
357 | multiiste = RgwMultisite() | |
358 | default_zonegroup = multiiste.get_all_zonegroups_info()['default_zonegroup'] | |
359 | ||
360 | # Iterate through _daemons.values() to find the daemon with the | |
361 | # matching zonegroup_id | |
362 | for daemon in RgwClient._daemons.values(): | |
363 | if daemon.zonegroup_id == default_zonegroup: | |
364 | daemon_name = daemon.name | |
365 | break | |
366 | except Exception: # pylint: disable=broad-except | |
367 | daemon_name = next(iter(daemon_keys)) | |
368 | else: | |
369 | # Handle the case where there is only one or no key in _daemons | |
370 | daemon_name = next(iter(daemon_keys)) | |
f67539c2 | 371 | |
494da23a TL |
372 | # Discard all cached instances if any rgw setting has changed |
373 | if RgwClient._rgw_settings_snapshot != RgwClient._rgw_settings(): | |
374 | RgwClient._rgw_settings_snapshot = RgwClient._rgw_settings() | |
adb31ebb | 375 | RgwClient.drop_instance() |
494da23a | 376 | |
f67539c2 | 377 | if daemon_name not in RgwClient._config_instances: |
f78120f9 TL |
378 | connection_info = RgwClient._get_daemon_connection_info(daemon_name) # type: ignore |
379 | RgwClient._config_instances[daemon_name] = RgwClient(connection_info['access_key'], # type: ignore # noqa E501 #pylint: disable=line-too-long | |
f67539c2 | 380 | connection_info['secret_key'], |
f78120f9 | 381 | daemon_name) # type: ignore |
11fdf7f2 | 382 | |
f78120f9 TL |
383 | if not userid or userid == RgwClient._config_instances[daemon_name].userid: # type: ignore |
384 | return RgwClient._config_instances[daemon_name] # type: ignore | |
11fdf7f2 | 385 | |
f67539c2 TL |
386 | if daemon_name not in RgwClient._user_instances \ |
387 | or userid not in RgwClient._user_instances[daemon_name]: | |
11fdf7f2 | 388 | # Get the access and secret keys for the specified user. |
f78120f9 | 389 | keys = RgwClient._config_instances[daemon_name].get_user_keys(userid) # type: ignore |
11fdf7f2 TL |
390 | if not keys: |
391 | raise RequestException( | |
392 | "User '{}' does not have any keys configured.".format( | |
393 | userid)) | |
f67539c2 TL |
394 | instance = RgwClient(keys['access_key'], |
395 | keys['secret_key'], | |
f78120f9 | 396 | daemon_name, # type: ignore |
f67539c2 | 397 | userid) |
f78120f9 | 398 | RgwClient._user_instances.update({daemon_name: {userid: instance}}) # type: ignore |
11fdf7f2 | 399 | |
f78120f9 | 400 | return RgwClient._user_instances[daemon_name][userid] # type: ignore |
11fdf7f2 TL |
401 | |
402 | @staticmethod | |
f67539c2 TL |
403 | def admin_instance(daemon_name: Optional[str] = None) -> 'RgwClient': |
404 | return RgwClient.instance(daemon_name=daemon_name) | |
11fdf7f2 | 405 | |
adb31ebb | 406 | @staticmethod |
f67539c2 | 407 | def drop_instance(instance: Optional['RgwClient'] = None): |
adb31ebb | 408 | """ |
f67539c2 | 409 | Drop a cached instance or all. |
adb31ebb | 410 | """ |
f67539c2 TL |
411 | if instance: |
412 | if instance.got_keys_from_config: | |
413 | del RgwClient._config_instances[instance.daemon.name] | |
414 | else: | |
415 | del RgwClient._user_instances[instance.daemon.name][instance.userid] | |
adb31ebb | 416 | else: |
f67539c2 | 417 | RgwClient._config_instances.clear() |
adb31ebb TL |
418 | RgwClient._user_instances.clear() |
419 | ||
11fdf7f2 | 420 | def _reset_login(self): |
f67539c2 | 421 | if self.got_keys_from_config: |
11fdf7f2 TL |
422 | raise RequestException('Authentication failed for the "{}" user: wrong credentials' |
423 | .format(self.userid), status_code=401) | |
f67539c2 TL |
424 | logger.info("Fetching new keys for user: %s", self.userid) |
425 | keys = RgwClient.admin_instance(daemon_name=self.daemon.name).get_user_keys(self.userid) | |
426 | self.auth = S3Auth(keys['access_key'], keys['secret_key'], | |
427 | service_url=self.service_url) | |
11fdf7f2 | 428 | |
f67539c2 | 429 | def __init__(self, |
522d829b TL |
430 | access_key: str, |
431 | secret_key: str, | |
432 | daemon_name: str, | |
433 | user_id: Optional[str] = None) -> None: | |
f67539c2 TL |
434 | try: |
435 | daemon = RgwClient._daemons[daemon_name] | |
436 | except KeyError as error: | |
437 | raise DashboardException(msg='RGW Daemon not found: {}'.format(error), | |
438 | http_status_code=404, | |
439 | component='rgw') | |
11fdf7f2 | 440 | ssl_verify = Settings.RGW_API_SSL_VERIFY |
f67539c2 TL |
441 | self.admin_path = Settings.RGW_API_ADMIN_RESOURCE |
442 | self.service_url = build_url(host=daemon.host, port=daemon.port) | |
443 | ||
444 | self.auth = S3Auth(access_key, secret_key, service_url=self.service_url) | |
445 | super(RgwClient, self).__init__(daemon.host, | |
446 | daemon.port, | |
447 | 'RGW', | |
448 | daemon.ssl, | |
449 | self.auth, | |
450 | ssl_verify=ssl_verify) | |
451 | self.got_keys_from_config = not user_id | |
452 | try: | |
453 | self.userid = self._get_user_id(self.admin_path) if self.got_keys_from_config \ | |
454 | else user_id | |
455 | except RequestException as error: | |
522d829b | 456 | logger.exception(error) |
f67539c2 TL |
457 | msg = 'Error connecting to Object Gateway' |
458 | if error.status_code == 404: | |
459 | msg = '{}: {}'.format(msg, str(error)) | |
460 | raise DashboardException(msg=msg, | |
461 | http_status_code=error.status_code, | |
462 | component='rgw') | |
463 | self.daemon = daemon | |
11fdf7f2 | 464 | |
f67539c2 TL |
465 | logger.info("Created new connection: daemon=%s, host=%s, port=%s, ssl=%d, sslverify=%d", |
466 | daemon.name, daemon.host, daemon.port, daemon.ssl, ssl_verify) | |
11fdf7f2 TL |
467 | |
468 | @RestClient.api_get('/', resp_structure='[0] > (ID & DisplayName)') | |
f67539c2 | 469 | def is_service_online(self, request=None) -> bool: |
11fdf7f2 TL |
470 | """ |
471 | Consider the service as online if the response contains the | |
472 | specified keys. Nothing more is checked here. | |
473 | """ | |
474 | _ = request({'format': 'json'}) | |
475 | return True | |
476 | ||
477 | @RestClient.api_get('/{admin_path}/metadata/user?myself', | |
478 | resp_structure='data > user_id') | |
479 | def _get_user_id(self, admin_path, request=None): | |
480 | # pylint: disable=unused-argument | |
481 | """ | |
482 | Get the user ID of the user that is used to communicate with the | |
483 | RGW Admin Ops API. | |
484 | :rtype: str | |
485 | :return: The user ID of the user that is used to sign the | |
486 | RGW Admin Ops API calls. | |
487 | """ | |
488 | response = request() | |
489 | return response['data']['user_id'] | |
490 | ||
491 | @RestClient.api_get('/{admin_path}/metadata/user', resp_structure='[+]') | |
492 | def _user_exists(self, admin_path, user_id, request=None): | |
493 | # pylint: disable=unused-argument | |
494 | response = request() | |
495 | if user_id: | |
496 | return user_id in response | |
497 | return self.userid in response | |
498 | ||
499 | def user_exists(self, user_id=None): | |
500 | return self._user_exists(self.admin_path, user_id) | |
501 | ||
502 | @RestClient.api_get('/{admin_path}/metadata/user?key={userid}', | |
503 | resp_structure='data > system') | |
f67539c2 | 504 | def _is_system_user(self, admin_path, userid, request=None) -> bool: |
11fdf7f2 TL |
505 | # pylint: disable=unused-argument |
506 | response = request() | |
1e59de90 | 507 | return response['data']['system'] |
11fdf7f2 | 508 | |
f67539c2 | 509 | def is_system_user(self) -> bool: |
11fdf7f2 TL |
510 | return self._is_system_user(self.admin_path, self.userid) |
511 | ||
512 | @RestClient.api_get( | |
513 | '/{admin_path}/user', | |
514 | resp_structure='tenant & user_id & email & keys[*] > ' | |
515 | ' (user & access_key & secret_key)') | |
516 | def _admin_get_user_keys(self, admin_path, userid, request=None): | |
517 | # pylint: disable=unused-argument | |
518 | colon_idx = userid.find(':') | |
519 | user = userid if colon_idx == -1 else userid[:colon_idx] | |
520 | response = request({'uid': user}) | |
521 | for key in response['keys']: | |
522 | if key['user'] == userid: | |
523 | return { | |
524 | 'access_key': key['access_key'], | |
525 | 'secret_key': key['secret_key'] | |
526 | } | |
527 | return None | |
528 | ||
529 | def get_user_keys(self, userid): | |
530 | return self._admin_get_user_keys(self.admin_path, userid) | |
531 | ||
532 | @RestClient.api('/{admin_path}/{path}') | |
9f95a23c TL |
533 | def _proxy_request( |
534 | self, # pylint: disable=too-many-arguments | |
535 | admin_path, | |
536 | path, | |
537 | method, | |
538 | params, | |
539 | data, | |
540 | request=None): | |
11fdf7f2 | 541 | # pylint: disable=unused-argument |
9f95a23c TL |
542 | return request(method=method, params=params, data=data, |
543 | raw_content=True) | |
11fdf7f2 TL |
544 | |
545 | def proxy(self, method, path, params, data): | |
9f95a23c TL |
546 | logger.debug("proxying method=%s path=%s params=%s data=%s", |
547 | method, path, params, data) | |
548 | return self._proxy_request(self.admin_path, path, method, | |
549 | params, data) | |
11fdf7f2 TL |
550 | |
551 | @RestClient.api_get('/', resp_structure='[1][*] > Name') | |
552 | def get_buckets(self, request=None): | |
553 | """ | |
554 | Get a list of names from all existing buckets of this user. | |
555 | :return: Returns a list of bucket names. | |
556 | """ | |
557 | response = request({'format': 'json'}) | |
558 | return [bucket['Name'] for bucket in response[1]] | |
559 | ||
560 | @RestClient.api_get('/{bucket_name}') | |
561 | def bucket_exists(self, bucket_name, userid, request=None): | |
562 | """ | |
563 | Check if the specified bucket exists for this user. | |
564 | :param bucket_name: The name of the bucket. | |
565 | :return: Returns True if the bucket exists, otherwise False. | |
566 | """ | |
567 | # pylint: disable=unused-argument | |
568 | try: | |
569 | request() | |
570 | my_buckets = self.get_buckets() | |
571 | if bucket_name not in my_buckets: | |
572 | raise RequestException( | |
573 | 'Bucket "{}" belongs to other user'.format(bucket_name), | |
574 | 403) | |
575 | return True | |
576 | except RequestException as e: | |
577 | if e.status_code == 404: | |
578 | return False | |
579 | ||
580 | raise e | |
581 | ||
582 | @RestClient.api_put('/{bucket_name}') | |
9f95a23c TL |
583 | def create_bucket(self, bucket_name, zonegroup=None, |
584 | placement_target=None, lock_enabled=False, | |
585 | request=None): | |
586 | logger.info("Creating bucket: %s, zonegroup: %s, placement_target: %s", | |
587 | bucket_name, zonegroup, placement_target) | |
588 | data = None | |
589 | if zonegroup and placement_target: | |
590 | create_bucket_configuration = ET.Element('CreateBucketConfiguration') | |
591 | location_constraint = ET.SubElement(create_bucket_configuration, 'LocationConstraint') | |
592 | location_constraint.text = '{}:{}'.format(zonegroup, placement_target) | |
593 | data = ET.tostring(create_bucket_configuration, encoding='unicode') | |
594 | ||
595 | headers = None # type: Optional[dict] | |
596 | if lock_enabled: | |
597 | headers = {'x-amz-bucket-object-lock-enabled': 'true'} | |
598 | ||
599 | return request(data=data, headers=headers) | |
600 | ||
601 | def get_placement_targets(self): # type: () -> dict | |
602 | zone = self._get_daemon_zone_info() | |
9f95a23c TL |
603 | placement_targets = [] # type: List[Dict] |
604 | for placement_pool in zone['placement_pools']: | |
605 | placement_targets.append( | |
606 | { | |
607 | 'name': placement_pool['key'], | |
608 | 'data_pool': placement_pool['val']['storage_classes']['STANDARD']['data_pool'] | |
609 | } | |
610 | ) | |
611 | ||
f67539c2 TL |
612 | return {'zonegroup': self.daemon.zonegroup_name, |
613 | 'placement_targets': placement_targets} | |
9f95a23c | 614 | |
e306af50 TL |
615 | def get_realms(self): # type: () -> List |
616 | realms_info = self._get_realms_info() | |
617 | if 'realms' in realms_info and realms_info['realms']: | |
618 | return realms_info['realms'] | |
e306af50 TL |
619 | return [] |
620 | ||
aee94f69 | 621 | def get_default_realm(self): |
a4b75251 TL |
622 | realms_info = self._get_realms_info() |
623 | if 'default_info' in realms_info and realms_info['default_info']: | |
624 | realm_info = self._get_realm_info(realms_info['default_info']) | |
625 | if 'name' in realm_info and realm_info['name']: | |
626 | return realm_info['name'] | |
aee94f69 | 627 | return None |
a4b75251 | 628 | |
f78120f9 TL |
629 | def get_default_zonegroup(self): |
630 | return self.daemon.zonegroup_name | |
631 | ||
9f95a23c TL |
632 | @RestClient.api_get('/{bucket_name}?versioning') |
633 | def get_bucket_versioning(self, bucket_name, request=None): | |
634 | """ | |
635 | Get bucket versioning. | |
636 | :param str bucket_name: the name of the bucket. | |
637 | :return: versioning info | |
638 | :rtype: Dict | |
639 | """ | |
640 | # pylint: disable=unused-argument | |
641 | result = request() | |
642 | if 'Status' not in result: | |
643 | result['Status'] = 'Suspended' | |
644 | if 'MfaDelete' not in result: | |
645 | result['MfaDelete'] = 'Disabled' | |
646 | return result | |
647 | ||
648 | @RestClient.api_put('/{bucket_name}?versioning') | |
649 | def set_bucket_versioning(self, bucket_name, versioning_state, mfa_delete, | |
650 | mfa_token_serial, mfa_token_pin, request=None): | |
651 | """ | |
652 | Set bucket versioning. | |
653 | :param str bucket_name: the name of the bucket. | |
654 | :param str versioning_state: | |
655 | https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTVersioningStatus.html | |
656 | :param str mfa_delete: MFA Delete state. | |
657 | :param str mfa_token_serial: | |
658 | https://docs.ceph.com/docs/master/radosgw/mfa/ | |
659 | :param str mfa_token_pin: value of a TOTP token at a certain time (auth code) | |
660 | :return: None | |
661 | """ | |
662 | # pylint: disable=unused-argument | |
663 | versioning_configuration = ET.Element('VersioningConfiguration') | |
664 | status_element = ET.SubElement(versioning_configuration, 'Status') | |
665 | status_element.text = versioning_state | |
666 | ||
667 | headers = {} | |
668 | if mfa_delete and mfa_token_serial and mfa_token_pin: | |
669 | headers['x-amz-mfa'] = '{} {}'.format(mfa_token_serial, mfa_token_pin) | |
670 | mfa_delete_element = ET.SubElement(versioning_configuration, 'MfaDelete') | |
671 | mfa_delete_element.text = mfa_delete | |
672 | ||
673 | data = ET.tostring(versioning_configuration, encoding='unicode') | |
674 | ||
675 | try: | |
676 | request(data=data, headers=headers) | |
677 | except RequestException as error: | |
678 | msg = str(error) | |
f67539c2 TL |
679 | if mfa_delete and mfa_token_serial and mfa_token_pin \ |
680 | and 'AccessDenied' in error.content.decode(): | |
9f95a23c | 681 | msg = 'Bad MFA credentials: {}'.format(msg) |
9f95a23c | 682 | raise DashboardException(msg=msg, |
f67539c2 | 683 | http_status_code=error.status_code, |
9f95a23c TL |
684 | component='rgw') |
685 | ||
f38dd50b TL |
686 | @RestClient.api_get('/{bucket_name}?acl') |
687 | def get_acl(self, bucket_name, request=None): | |
688 | # pylint: disable=unused-argument | |
689 | try: | |
690 | result = request(raw_content=True) # type: ignore | |
691 | return result.decode("utf-8") | |
692 | except RequestException as error: | |
693 | msg = 'Error getting ACLs' | |
694 | if error.status_code == 404: | |
695 | msg = '{}: {}'.format(msg, str(error)) | |
696 | raise DashboardException(msg=msg, | |
697 | http_status_code=error.status_code, | |
698 | component='rgw') | |
699 | ||
700 | @RestClient.api_put('/{bucket_name}?acl') | |
701 | def set_acl(self, bucket_name, acl, request=None): | |
702 | # pylint: disable=unused-argument | |
703 | headers = {'x-amz-acl': acl} | |
704 | try: | |
705 | result = request(headers=headers) # type: ignore | |
706 | except RequestException as e: | |
707 | raise DashboardException(msg=str(e), component='rgw') | |
708 | return result | |
709 | ||
39ae355f TL |
710 | @RestClient.api_get('/{bucket_name}?encryption') |
711 | def get_bucket_encryption(self, bucket_name, request=None): | |
712 | # pylint: disable=unused-argument | |
713 | try: | |
714 | result = request() # type: ignore | |
715 | result['Status'] = 'Enabled' | |
716 | return result | |
717 | except RequestException as e: | |
718 | if e.content: | |
719 | content = json_str_to_object(e.content) | |
720 | if content.get( | |
721 | 'Code') == 'ServerSideEncryptionConfigurationNotFoundError': | |
722 | return { | |
723 | 'Status': 'Disabled', | |
724 | } | |
725 | raise e | |
726 | ||
727 | @RestClient.api_delete('/{bucket_name}?encryption') | |
728 | def delete_bucket_encryption(self, bucket_name, request=None): | |
729 | # pylint: disable=unused-argument | |
730 | result = request() # type: ignore | |
731 | return result | |
732 | ||
733 | @RestClient.api_put('/{bucket_name}?encryption') | |
734 | def set_bucket_encryption(self, bucket_name, key_id, | |
735 | sse_algorithm, request: Optional[object] = None): | |
736 | # pylint: disable=unused-argument | |
737 | encryption_configuration = ET.Element('ServerSideEncryptionConfiguration') | |
738 | rule_element = ET.SubElement(encryption_configuration, 'Rule') | |
739 | default_encryption_element = ET.SubElement(rule_element, | |
740 | 'ApplyServerSideEncryptionByDefault') | |
741 | sse_algo_element = ET.SubElement(default_encryption_element, | |
742 | 'SSEAlgorithm') | |
743 | sse_algo_element.text = sse_algorithm | |
744 | if sse_algorithm == 'aws:kms': | |
745 | kms_master_key_element = ET.SubElement(default_encryption_element, | |
746 | 'KMSMasterKeyID') | |
747 | kms_master_key_element.text = key_id | |
748 | data = ET.tostring(encryption_configuration, encoding='unicode') | |
749 | try: | |
750 | _ = request(data=data) # type: ignore | |
751 | except RequestException as e: | |
752 | raise DashboardException(msg=str(e), component='rgw') | |
753 | ||
f38dd50b TL |
754 | @RestClient.api_put('/{bucket_name}?tagging') |
755 | def set_tags(self, bucket_name, tags, request=None): | |
756 | # pylint: disable=unused-argument | |
757 | try: | |
758 | ET.fromstring(tags) | |
759 | except ET.ParseError: | |
760 | return "Data must be properly formatted" | |
761 | try: | |
762 | result = request(data=tags) # type: ignore | |
763 | except RequestException as e: | |
764 | raise DashboardException(msg=str(e), component='rgw') | |
765 | return result | |
766 | ||
f78120f9 TL |
767 | @RestClient.api_get('/{bucket_name}?lifecycle') |
768 | def get_lifecycle(self, bucket_name, request=None): | |
769 | # pylint: disable=unused-argument | |
770 | try: | |
771 | result = request() # type: ignore | |
772 | result = {'LifecycleConfiguration': result} | |
773 | except RequestException as e: | |
774 | if e.content: | |
775 | content = json_str_to_object(e.content) | |
776 | if content.get( | |
777 | 'Code') == 'NoSuchLifecycleConfiguration': | |
778 | return None | |
779 | raise DashboardException(msg=str(e), component='rgw') | |
780 | return result | |
781 | ||
782 | @staticmethod | |
783 | def dict_to_xml(data): | |
784 | if not data or data == '{}': | |
785 | return '' | |
786 | if isinstance(data, str): | |
787 | try: | |
788 | data = json.loads(data) | |
789 | except json.JSONDecodeError: | |
790 | raise DashboardException('Could not load json string') | |
791 | ||
792 | def transform(data): | |
793 | xml: str = '' | |
794 | if isinstance(data, dict): | |
795 | for key, value in data.items(): | |
796 | if isinstance(value, list): | |
797 | for item in value: | |
798 | if key == 'Rules': | |
799 | key = 'Rule' | |
800 | xml += f'<{key}>\n{transform(item)}</{key}>\n' | |
801 | elif isinstance(value, dict): | |
802 | xml += f'<{key}>\n{transform(value)}</{key}>\n' | |
803 | else: | |
804 | xml += f'<{key}>{str(value)}</{key}>\n' | |
805 | ||
806 | elif isinstance(data, list): | |
807 | for item in data: | |
808 | xml += transform(item) | |
809 | else: | |
810 | xml += f'{data}' | |
811 | ||
812 | return xml | |
813 | ||
814 | return transform(data) | |
815 | ||
816 | @RestClient.api_put('/{bucket_name}?lifecycle') | |
817 | def set_lifecycle(self, bucket_name, lifecycle, request=None): | |
818 | # pylint: disable=unused-argument | |
819 | lifecycle = lifecycle.strip() | |
820 | if lifecycle.startswith('{'): | |
821 | lifecycle = RgwClient.dict_to_xml(lifecycle) | |
822 | try: | |
823 | if lifecycle and '<LifecycleConfiguration>' not in str(lifecycle): | |
824 | lifecycle = f'<LifecycleConfiguration>{lifecycle}</LifecycleConfiguration>' | |
825 | result = request(data=lifecycle) # type: ignore | |
826 | except RequestException as e: | |
827 | if e.content: | |
828 | content = json_str_to_object(e.content) | |
829 | if content.get("Code") == "MalformedXML": | |
830 | msg = "Invalid Lifecycle document" | |
831 | raise DashboardException(msg=msg, component='rgw') | |
832 | raise DashboardException(msg=str(e), component='rgw') | |
833 | return result | |
834 | ||
835 | @RestClient.api_delete('/{bucket_name}?lifecycle') | |
836 | def delete_lifecycle(self, bucket_name, request=None): | |
837 | # pylint: disable=unused-argument | |
838 | try: | |
839 | result = request() | |
840 | except RequestException as e: | |
841 | raise DashboardException(msg=str(e), component='rgw') | |
842 | return result | |
843 | ||
9f95a23c TL |
844 | @RestClient.api_get('/{bucket_name}?object-lock') |
845 | def get_bucket_locking(self, bucket_name, request=None): | |
846 | # type: (str, Optional[object]) -> dict | |
847 | """ | |
848 | Gets the locking configuration for a bucket. The locking | |
849 | configuration will be applied by default to every new object | |
850 | placed in the specified bucket. | |
851 | :param bucket_name: The name of the bucket. | |
852 | :type bucket_name: str | |
853 | :return: The locking configuration. | |
854 | :rtype: Dict | |
855 | """ | |
856 | # pylint: disable=unused-argument | |
857 | ||
858 | # Try to get the Object Lock configuration. If there is none, | |
859 | # then return default values. | |
860 | try: | |
861 | result = request() # type: ignore | |
862 | return { | |
863 | 'lock_enabled': dict_get(result, 'ObjectLockEnabled') == 'Enabled', | |
864 | 'lock_mode': dict_get(result, 'Rule.DefaultRetention.Mode'), | |
865 | 'lock_retention_period_days': dict_get(result, 'Rule.DefaultRetention.Days', 0), | |
866 | 'lock_retention_period_years': dict_get(result, 'Rule.DefaultRetention.Years', 0) | |
867 | } | |
868 | except RequestException as e: | |
869 | if e.content: | |
870 | content = json_str_to_object(e.content) | |
871 | if content.get( | |
872 | 'Code') == 'ObjectLockConfigurationNotFoundError': | |
873 | return { | |
874 | 'lock_enabled': False, | |
875 | 'lock_mode': 'compliance', | |
876 | 'lock_retention_period_days': None, | |
877 | 'lock_retention_period_years': None | |
878 | } | |
879 | raise e | |
880 | ||
881 | @RestClient.api_put('/{bucket_name}?object-lock') | |
882 | def set_bucket_locking(self, | |
b3b6e05e TL |
883 | bucket_name: str, |
884 | mode: str, | |
885 | retention_period_days: Optional[Union[int, str]] = None, | |
886 | retention_period_years: Optional[Union[int, str]] = None, | |
887 | request: Optional[object] = None) -> None: | |
9f95a23c TL |
888 | """ |
889 | Places the locking configuration on the specified bucket. The | |
890 | locking configuration will be applied by default to every new | |
891 | object placed in the specified bucket. | |
892 | :param bucket_name: The name of the bucket. | |
893 | :type bucket_name: str | |
894 | :param mode: The lock mode, e.g. `COMPLIANCE` or `GOVERNANCE`. | |
895 | :type mode: str | |
896 | :param retention_period_days: | |
897 | :type retention_period_days: int | |
898 | :param retention_period_years: | |
899 | :type retention_period_years: int | |
900 | :rtype: None | |
901 | """ | |
902 | # pylint: disable=unused-argument | |
903 | ||
1e59de90 TL |
904 | retention_period_days, retention_period_years = self.perform_validations( |
905 | retention_period_days, retention_period_years, mode) | |
9f95a23c TL |
906 | |
907 | # Generate the XML data like this: | |
908 | # <ObjectLockConfiguration> | |
909 | # <ObjectLockEnabled>string</ObjectLockEnabled> | |
910 | # <Rule> | |
911 | # <DefaultRetention> | |
912 | # <Days>integer</Days> | |
913 | # <Mode>string</Mode> | |
914 | # <Years>integer</Years> | |
915 | # </DefaultRetention> | |
916 | # </Rule> | |
917 | # </ObjectLockConfiguration> | |
918 | locking_configuration = ET.Element('ObjectLockConfiguration') | |
919 | enabled_element = ET.SubElement(locking_configuration, | |
920 | 'ObjectLockEnabled') | |
921 | enabled_element.text = 'Enabled' # Locking can't be disabled. | |
922 | rule_element = ET.SubElement(locking_configuration, 'Rule') | |
923 | default_retention_element = ET.SubElement(rule_element, | |
924 | 'DefaultRetention') | |
925 | mode_element = ET.SubElement(default_retention_element, 'Mode') | |
926 | mode_element.text = mode.upper() | |
927 | if retention_period_days: | |
928 | days_element = ET.SubElement(default_retention_element, 'Days') | |
929 | days_element.text = str(retention_period_days) | |
930 | if retention_period_years: | |
931 | years_element = ET.SubElement(default_retention_element, 'Years') | |
932 | years_element.text = str(retention_period_years) | |
933 | ||
934 | data = ET.tostring(locking_configuration, encoding='unicode') | |
935 | ||
936 | try: | |
937 | _ = request(data=data) # type: ignore | |
938 | except RequestException as e: | |
939 | raise DashboardException(msg=str(e), component='rgw') | |
1e59de90 TL |
940 | |
941 | def list_roles(self) -> List[Dict[str, Any]]: | |
942 | rgw_list_roles_command = ['role', 'list'] | |
943 | code, roles, err = mgr.send_rgwadmin_command(rgw_list_roles_command) | |
944 | if code < 0: | |
945 | logger.warning('Error listing roles with code %d: %s', code, err) | |
946 | return [] | |
947 | ||
f38dd50b TL |
948 | for role in roles: |
949 | if 'PermissionPolicies' not in role: | |
950 | role['PermissionPolicies'] = [] | |
1e59de90 TL |
951 | return roles |
952 | ||
953 | def create_role(self, role_name: str, role_path: str, role_assume_policy_doc: str) -> None: | |
954 | try: | |
955 | json.loads(role_assume_policy_doc) | |
956 | except: # noqa: E722 | |
957 | raise DashboardException('Assume role policy document is not a valid json') | |
958 | ||
959 | # valid values: | |
960 | # pylint: disable=C0301 | |
961 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-path # noqa: E501 | |
962 | if len(role_name) > 64: | |
963 | raise DashboardException( | |
964 | f'Role name "{role_name}" is invalid. Should be 64 characters or less') | |
965 | ||
966 | role_name_regex = '[0-9a-zA-Z_+=,.@-]+' | |
967 | if not re.fullmatch(role_name_regex, role_name): | |
968 | raise DashboardException( | |
969 | f'Role name "{role_name}" is invalid. Valid characters are "{role_name_regex}"') | |
970 | ||
971 | if not os.path.isabs(role_path): | |
972 | raise DashboardException( | |
973 | f'Role path "{role_path}" is invalid. It should be an absolute path') | |
974 | if role_path[-1] != '/': | |
975 | raise DashboardException( | |
976 | f'Role path "{role_path}" is invalid. It should start and end with a slash') | |
977 | path_regex = '(\u002F)|(\u002F[\u0021-\u007E]+\u002F)' | |
978 | if not re.fullmatch(path_regex, role_path): | |
979 | raise DashboardException( | |
980 | (f'Role path "{role_path}" is invalid.' | |
981 | f'Role path should follow the pattern "{path_regex}"')) | |
982 | ||
983 | rgw_create_role_command = ['role', 'create', '--role-name', role_name, '--path', role_path] | |
984 | if role_assume_policy_doc: | |
985 | rgw_create_role_command += ['--assume-role-policy-doc', f"{role_assume_policy_doc}"] | |
986 | ||
987 | code, _roles, _err = mgr.send_rgwadmin_command(rgw_create_role_command, | |
988 | stdout_as_json=False) | |
989 | if code != 0: | |
990 | # pylint: disable=C0301 | |
991 | link = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-path' # noqa: E501 | |
992 | msg = (f'Error creating role with code {code}: ' | |
993 | 'Looks like the document has a wrong format.' | |
994 | f' For more information about the format look at {link}') | |
995 | raise DashboardException(msg=msg, component='rgw') | |
996 | ||
f38dd50b TL |
997 | def get_role(self, role_name: str): |
998 | rgw_get_role_command = ['role', 'get', '--role-name', role_name] | |
999 | code, role, _err = mgr.send_rgwadmin_command(rgw_get_role_command) | |
1000 | if code != 0: | |
1001 | raise DashboardException(msg=f'Error getting role with code {code}: {_err}', | |
1002 | component='rgw') | |
1003 | return role | |
1004 | ||
1005 | def update_role(self, role_name: str, max_session_duration: str): | |
1006 | rgw_update_role_command = ['role', 'update', '--role-name', | |
1007 | role_name, '--max_session_duration', max_session_duration] | |
1008 | code, _, _err = mgr.send_rgwadmin_command(rgw_update_role_command, | |
1009 | stdout_as_json=False) | |
1010 | if code != 0: | |
1011 | raise DashboardException(msg=f'Error updating role with code {code}: {_err}', | |
1012 | component='rgw') | |
1013 | ||
1014 | def delete_role(self, role_name: str) -> None: | |
1015 | rgw_delete_role_command = ['role', 'delete', '--role-name', role_name] | |
1016 | code, _, _err = mgr.send_rgwadmin_command(rgw_delete_role_command, | |
1017 | stdout_as_json=False) | |
1018 | if code != 0: | |
1019 | raise DashboardException(msg=f'Error deleting role with code {code}: {_err}', | |
1020 | component='rgw') | |
1021 | ||
1022 | @RestClient.api_get('/{bucket_name}?policy') | |
1023 | def get_bucket_policy(self, bucket_name: str, request=None): | |
1024 | """ | |
1025 | Gets the bucket policy for a bucket. | |
1026 | :param bucket_name: The name of the bucket. | |
1027 | :type bucket_name: str | |
1028 | :rtype: None | |
1029 | """ | |
1030 | # pylint: disable=unused-argument | |
1031 | ||
1032 | try: | |
1033 | request = request() | |
1034 | return request | |
1035 | except RequestException as e: | |
1036 | if e.content: | |
1037 | content = json_str_to_object(e.content) | |
1038 | if content.get( | |
1039 | 'Code') == 'NoSuchBucketPolicy': | |
1040 | return None | |
1041 | raise e | |
1042 | ||
1043 | @RestClient.api_put('/{bucket_name}?policy') | |
1044 | def set_bucket_policy(self, bucket_name: str, policy: str, request=None): | |
1045 | """ | |
1046 | Sets the bucket policy for a bucket. | |
1047 | :param bucket_name: The name of the bucket. | |
1048 | :type bucket_name: str | |
1049 | :param policy: The bucket policy. | |
1050 | :type policy: JSON Structured Document | |
1051 | :return: The bucket policy. | |
1052 | :rtype: Dict | |
1053 | """ | |
1054 | # pylint: disable=unused-argument | |
1055 | try: | |
1056 | request = request(data=policy) | |
1057 | except RequestException as e: | |
1058 | if e.content: | |
1059 | content = json_str_to_object(e.content) | |
1060 | if content.get("Code") == "InvalidArgument": | |
1061 | msg = "Invalid JSON document" | |
1062 | raise DashboardException(msg=msg, component='rgw') | |
1063 | raise DashboardException(e) | |
1064 | ||
1e59de90 TL |
1065 | def perform_validations(self, retention_period_days, retention_period_years, mode): |
1066 | try: | |
1067 | retention_period_days = int(retention_period_days) if retention_period_days else 0 | |
1068 | retention_period_years = int(retention_period_years) if retention_period_years else 0 | |
1069 | if retention_period_days < 0 or retention_period_years < 0: | |
1070 | raise ValueError | |
1071 | except (TypeError, ValueError): | |
1072 | msg = "Retention period must be a positive integer." | |
1073 | raise DashboardException(msg=msg, component='rgw') | |
1074 | if retention_period_days and retention_period_years: | |
1075 | # https://docs.aws.amazon.com/AmazonS3/latest/API/archive-RESTBucketPUTObjectLockConfiguration.html | |
1076 | msg = "Retention period requires either Days or Years. "\ | |
1077 | "You can't specify both at the same time." | |
1078 | raise DashboardException(msg=msg, component='rgw') | |
1079 | if not retention_period_days and not retention_period_years: | |
1080 | msg = "Retention period requires either Days or Years. "\ | |
1081 | "You must specify at least one." | |
1082 | raise DashboardException(msg=msg, component='rgw') | |
1083 | if not isinstance(mode, str) or mode.upper() not in ['COMPLIANCE', 'GOVERNANCE']: | |
1084 | msg = "Retention mode must be either COMPLIANCE or GOVERNANCE." | |
1085 | raise DashboardException(msg=msg, component='rgw') | |
1086 | return retention_period_days, retention_period_years | |
aee94f69 | 1087 | |
f78120f9 TL |
1088 | @RestClient.api_put('/{bucket_name}?replication') |
1089 | def set_bucket_replication(self, bucket_name, replication: bool, request=None): | |
1090 | # pGenerate the minimum replication configuration | |
1091 | # required for enabling the replication | |
1092 | root = ET.Element('ReplicationConfiguration', | |
1093 | xmlns='http://s3.amazonaws.com/doc/2006-03-01/') | |
1094 | role = ET.SubElement(root, 'Role') | |
1095 | role.text = f'{bucket_name}_replication_role' | |
1096 | ||
1097 | rule = ET.SubElement(root, 'Rule') | |
1098 | rule_id = ET.SubElement(rule, 'ID') | |
1099 | rule_id.text = _SYNC_PIPE_ID | |
1100 | ||
1101 | status = ET.SubElement(rule, 'Status') | |
1102 | status.text = 'Enabled' if replication else 'Disabled' | |
1103 | ||
1104 | filter_elem = ET.SubElement(rule, 'Filter') | |
1105 | prefix = ET.SubElement(filter_elem, 'Prefix') | |
1106 | prefix.text = '' | |
1107 | ||
1108 | destination = ET.SubElement(rule, 'Destination') | |
1109 | ||
1110 | bucket = ET.SubElement(destination, 'Bucket') | |
1111 | bucket.text = bucket_name | |
1112 | ||
1113 | replication_config = ET.tostring(root, encoding='utf-8', method='xml').decode() | |
1114 | ||
1115 | try: | |
1116 | request = request(data=replication_config) | |
1117 | except RequestException as e: | |
1118 | raise DashboardException(msg=str(e), component='rgw') | |
1119 | ||
1120 | @RestClient.api_get('/{bucket_name}?replication') | |
1121 | def get_bucket_replication(self, bucket_name, request=None): | |
1122 | # pylint: disable=unused-argument | |
1123 | try: | |
1124 | result = request() | |
1125 | return result | |
1126 | except RequestException as e: | |
1127 | if e.content: | |
1128 | content = json_str_to_object(e.content) | |
1129 | if content.get('Code') == 'ReplicationConfigurationNotFoundError': | |
1130 | return None | |
1131 | raise e | |
1132 | ||
1133 | ||
1134 | class SyncStatus(Enum): | |
1135 | enabled = 'enabled' | |
1136 | allowed = 'allowed' | |
1137 | forbidden = 'forbidden' | |
1138 | ||
1139 | ||
1140 | class SyncFlowTypes(Enum): | |
1141 | directional = 'directional' | |
1142 | symmetrical = 'symmetrical' | |
1143 | ||
aee94f69 TL |
1144 | |
1145 | class RgwMultisite: | |
1146 | def migrate_to_multisite(self, realm_name: str, zonegroup_name: str, zone_name: str, | |
1147 | zonegroup_endpoints: str, zone_endpoints: str, access_key: str, | |
1148 | secret_key: str): | |
1149 | rgw_realm_create_cmd = ['realm', 'create', '--rgw-realm', realm_name, '--default'] | |
1150 | try: | |
1151 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_create_cmd, False) | |
1152 | if exit_code > 0: | |
1153 | raise DashboardException(e=err, msg='Unable to create realm', | |
1154 | http_status_code=500, component='rgw') | |
1155 | except SubprocessError as error: | |
1156 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1157 | ||
1158 | rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', 'default', | |
1159 | '--zonegroup-new-name', zonegroup_name] | |
1160 | try: | |
1161 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_edit_cmd, False) | |
1162 | if exit_code > 0: | |
1163 | raise DashboardException(e=err, msg='Unable to rename zonegroup to {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1164 | http_status_code=500, component='rgw') | |
1165 | except SubprocessError as error: | |
1166 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1167 | ||
1168 | rgw_zone_edit_cmd = ['zone', 'rename', '--rgw-zone', | |
1169 | 'default', '--zone-new-name', zone_name, | |
1170 | '--rgw-zonegroup', zonegroup_name] | |
1171 | try: | |
1172 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_edit_cmd, False) | |
1173 | if exit_code > 0: | |
1174 | raise DashboardException(e=err, msg='Unable to rename zone to {}'.format(zone_name), # noqa E501 #pylint: disable=line-too-long | |
1175 | http_status_code=500, component='rgw') | |
1176 | except SubprocessError as error: | |
1177 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1178 | ||
1179 | rgw_zonegroup_modify_cmd = ['zonegroup', 'modify', | |
1180 | '--rgw-realm', realm_name, | |
1181 | '--rgw-zonegroup', zonegroup_name] | |
1182 | if zonegroup_endpoints: | |
1183 | rgw_zonegroup_modify_cmd.append('--endpoints') | |
1184 | rgw_zonegroup_modify_cmd.append(zonegroup_endpoints) | |
1185 | rgw_zonegroup_modify_cmd.append('--master') | |
1186 | rgw_zonegroup_modify_cmd.append('--default') | |
1187 | try: | |
1188 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd) | |
1189 | if exit_code > 0: | |
1190 | raise DashboardException(e=err, msg='Unable to modify zonegroup {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1191 | http_status_code=500, component='rgw') | |
1192 | except SubprocessError as error: | |
1193 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1194 | ||
1195 | rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-realm', realm_name, | |
1196 | '--rgw-zonegroup', zonegroup_name, | |
1197 | '--rgw-zone', zone_name] | |
1198 | if zone_endpoints: | |
1199 | rgw_zone_modify_cmd.append('--endpoints') | |
1200 | rgw_zone_modify_cmd.append(zone_endpoints) | |
1201 | rgw_zone_modify_cmd.append('--master') | |
1202 | rgw_zone_modify_cmd.append('--default') | |
1203 | try: | |
1204 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd) | |
1205 | if exit_code > 0: | |
1206 | raise DashboardException(e=err, msg='Unable to modify zone', | |
1207 | http_status_code=500, component='rgw') | |
1208 | except SubprocessError as error: | |
1209 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1210 | ||
1211 | if access_key and secret_key: | |
1212 | rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zone', zone_name, | |
1213 | '--access-key', access_key, '--secret', secret_key] | |
1214 | try: | |
1215 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd) | |
1216 | if exit_code > 0: | |
1217 | raise DashboardException(e=err, msg='Unable to modify zone', | |
1218 | http_status_code=500, component='rgw') | |
1219 | except SubprocessError as error: | |
1220 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1221 | ||
1222 | def create_realm(self, realm_name: str, default: bool): | |
1223 | rgw_realm_create_cmd = ['realm', 'create'] | |
1224 | cmd_create_realm_options = ['--rgw-realm', realm_name] | |
f38dd50b | 1225 | if default: |
aee94f69 TL |
1226 | cmd_create_realm_options.append('--default') |
1227 | rgw_realm_create_cmd += cmd_create_realm_options | |
1228 | try: | |
1229 | exit_code, _, _ = mgr.send_rgwadmin_command(rgw_realm_create_cmd) | |
1230 | if exit_code > 0: | |
1231 | raise DashboardException(msg='Unable to create realm', | |
1232 | http_status_code=500, component='rgw') | |
1233 | except SubprocessError as error: | |
1234 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1235 | ||
1236 | def list_realms(self): | |
1237 | rgw_realm_list = {} | |
1238 | rgw_realm_list_cmd = ['realm', 'list'] | |
1239 | try: | |
1240 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_realm_list_cmd) | |
1241 | if exit_code > 0: | |
1242 | raise DashboardException(msg='Unable to fetch realm list', | |
1243 | http_status_code=500, component='rgw') | |
1244 | rgw_realm_list = out | |
1245 | except SubprocessError as error: | |
1246 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1247 | return rgw_realm_list | |
1248 | ||
1249 | def get_realm(self, realm_name: str): | |
1250 | realm_info = {} | |
1251 | rgw_realm_info_cmd = ['realm', 'get', '--rgw-realm', realm_name] | |
1252 | try: | |
1253 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_realm_info_cmd) | |
1254 | if exit_code > 0: | |
1255 | raise DashboardException('Unable to get realm info', | |
1256 | http_status_code=500, component='rgw') | |
1257 | realm_info = out | |
1258 | except SubprocessError as error: | |
1259 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1260 | return realm_info | |
1261 | ||
1262 | def get_all_realms_info(self): | |
1263 | all_realms_info = {} | |
1264 | realms_info = [] | |
1265 | rgw_realm_list = self.list_realms() | |
1266 | if 'realms' in rgw_realm_list: | |
1267 | if rgw_realm_list['realms'] != []: | |
1268 | for rgw_realm in rgw_realm_list['realms']: | |
1269 | realm_info = self.get_realm(rgw_realm) | |
1270 | realms_info.append(realm_info) | |
1271 | all_realms_info['realms'] = realms_info # type: ignore | |
1272 | else: | |
1273 | all_realms_info['realms'] = [] # type: ignore | |
1274 | if 'default_info' in rgw_realm_list and rgw_realm_list['default_info'] != '': | |
1275 | all_realms_info['default_realm'] = rgw_realm_list['default_info'] # type: ignore | |
1276 | else: | |
1277 | all_realms_info['default_realm'] = '' # type: ignore | |
1278 | return all_realms_info | |
1279 | ||
1280 | def edit_realm(self, realm_name: str, new_realm_name: str, default: str = ''): | |
1281 | rgw_realm_edit_cmd = [] | |
1282 | if new_realm_name != realm_name: | |
1283 | rgw_realm_edit_cmd = ['realm', 'rename', '--rgw-realm', | |
1284 | realm_name, '--realm-new-name', new_realm_name] | |
1285 | try: | |
1286 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_edit_cmd, False) | |
1287 | if exit_code > 0: | |
1288 | raise DashboardException(e=err, msg='Unable to edit realm', | |
1289 | http_status_code=500, component='rgw') | |
1290 | except SubprocessError as error: | |
1291 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1292 | if default and str_to_bool(default): | |
1293 | rgw_realm_edit_cmd = ['realm', 'default', '--rgw-realm', new_realm_name] | |
1294 | try: | |
1295 | exit_code, _, _ = mgr.send_rgwadmin_command(rgw_realm_edit_cmd, False) | |
1296 | if exit_code > 0: | |
1297 | raise DashboardException(msg='Unable to set {} as default realm'.format(new_realm_name), # noqa E501 #pylint: disable=line-too-long | |
1298 | http_status_code=500, component='rgw') | |
1299 | except SubprocessError as error: | |
1300 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1301 | ||
1302 | def delete_realm(self, realm_name: str): | |
1303 | rgw_delete_realm_cmd = ['realm', 'rm', '--rgw-realm', realm_name] | |
1304 | try: | |
1305 | exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_realm_cmd) | |
1306 | if exit_code > 0: | |
1307 | raise DashboardException(msg='Unable to delete realm', | |
1308 | http_status_code=500, component='rgw') | |
1309 | except SubprocessError as error: | |
1310 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1311 | ||
1312 | def create_zonegroup(self, realm_name: str, zonegroup_name: str, | |
1313 | default: bool, master: bool, endpoints: str): | |
1314 | rgw_zonegroup_create_cmd = ['zonegroup', 'create'] | |
1315 | cmd_create_zonegroup_options = ['--rgw-zonegroup', zonegroup_name] | |
1316 | if realm_name != 'null': | |
1317 | cmd_create_zonegroup_options.append('--rgw-realm') | |
1318 | cmd_create_zonegroup_options.append(realm_name) | |
1319 | if default != 'false': | |
1320 | cmd_create_zonegroup_options.append('--default') | |
1321 | if master != 'false': | |
1322 | cmd_create_zonegroup_options.append('--master') | |
1323 | if endpoints: | |
1324 | cmd_create_zonegroup_options.append('--endpoints') | |
1325 | cmd_create_zonegroup_options.append(endpoints) | |
1326 | rgw_zonegroup_create_cmd += cmd_create_zonegroup_options | |
1327 | try: | |
1328 | exit_code, out, err = mgr.send_rgwadmin_command(rgw_zonegroup_create_cmd) | |
1329 | if exit_code > 0: | |
1330 | raise DashboardException(e=err, msg='Unable to get realm info', | |
1331 | http_status_code=500, component='rgw') | |
1332 | except SubprocessError as error: | |
1333 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1334 | return out | |
1335 | ||
1336 | def list_zonegroups(self): | |
1337 | rgw_zonegroup_list = {} | |
1338 | rgw_zonegroup_list_cmd = ['zonegroup', 'list'] | |
1339 | try: | |
1340 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_list_cmd) | |
1341 | if exit_code > 0: | |
1342 | raise DashboardException(msg='Unable to fetch zonegroup list', | |
1343 | http_status_code=500, component='rgw') | |
1344 | rgw_zonegroup_list = out | |
1345 | except SubprocessError as error: | |
1346 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1347 | return rgw_zonegroup_list | |
1348 | ||
1349 | def get_zonegroup(self, zonegroup_name: str): | |
1350 | zonegroup_info = {} | |
1351 | if zonegroup_name != 'default': | |
1352 | rgw_zonegroup_info_cmd = ['zonegroup', 'get', '--rgw-zonegroup', zonegroup_name] | |
1353 | else: | |
1354 | rgw_zonegroup_info_cmd = ['zonegroup', 'get', '--rgw-zonegroup', | |
1355 | zonegroup_name, '--rgw-realm', 'default'] | |
1356 | try: | |
1357 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_info_cmd) | |
1358 | if exit_code > 0: | |
1359 | raise DashboardException('Unable to get zonegroup info', | |
1360 | http_status_code=500, component='rgw') | |
1361 | zonegroup_info = out | |
1362 | except SubprocessError as error: | |
1363 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1364 | return zonegroup_info | |
1365 | ||
1366 | def get_all_zonegroups_info(self): | |
1367 | all_zonegroups_info = {} | |
1368 | zonegroups_info = [] | |
1369 | rgw_zonegroup_list = self.list_zonegroups() | |
1370 | if 'zonegroups' in rgw_zonegroup_list: | |
1371 | if rgw_zonegroup_list['zonegroups'] != []: | |
1372 | for rgw_zonegroup in rgw_zonegroup_list['zonegroups']: | |
1373 | zonegroup_info = self.get_zonegroup(rgw_zonegroup) | |
1374 | zonegroups_info.append(zonegroup_info) | |
1375 | all_zonegroups_info['zonegroups'] = zonegroups_info # type: ignore | |
1376 | else: | |
1377 | all_zonegroups_info['zonegroups'] = [] # type: ignore | |
1378 | if 'default_info' in rgw_zonegroup_list and rgw_zonegroup_list['default_info'] != '': | |
1379 | all_zonegroups_info['default_zonegroup'] = rgw_zonegroup_list['default_info'] | |
1380 | else: | |
1381 | all_zonegroups_info['default_zonegroup'] = '' # type: ignore | |
1382 | return all_zonegroups_info | |
1383 | ||
1384 | def delete_zonegroup(self, zonegroup_name: str, delete_pools: str, pools: List[str]): | |
1385 | if delete_pools == 'true': | |
1386 | zonegroup_info = self.get_zonegroup(zonegroup_name) | |
1387 | rgw_delete_zonegroup_cmd = ['zonegroup', 'delete', '--rgw-zonegroup', zonegroup_name] | |
1388 | try: | |
1389 | exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_zonegroup_cmd) | |
1390 | if exit_code > 0: | |
1391 | raise DashboardException(msg='Unable to delete zonegroup', | |
1392 | http_status_code=500, component='rgw') | |
1393 | except SubprocessError as error: | |
1394 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1395 | self.update_period() | |
1396 | if delete_pools == 'true': | |
1397 | for zone in zonegroup_info['zones']: | |
1398 | self.delete_zone(zone['name'], 'true', pools) | |
1399 | ||
1400 | def modify_zonegroup(self, realm_name: str, zonegroup_name: str, default: str, master: str, | |
1401 | endpoints: str): | |
1402 | ||
1403 | rgw_zonegroup_modify_cmd = ['zonegroup', 'modify', | |
1404 | '--rgw-realm', realm_name, | |
1405 | '--rgw-zonegroup', zonegroup_name] | |
1406 | if endpoints: | |
1407 | rgw_zonegroup_modify_cmd.append('--endpoints') | |
1408 | rgw_zonegroup_modify_cmd.append(endpoints) | |
1409 | if master and str_to_bool(master): | |
1410 | rgw_zonegroup_modify_cmd.append('--master') | |
1411 | if default and str_to_bool(default): | |
1412 | rgw_zonegroup_modify_cmd.append('--default') | |
1413 | try: | |
1414 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd) | |
1415 | if exit_code > 0: | |
1416 | raise DashboardException(e=err, msg='Unable to modify zonegroup {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1417 | http_status_code=500, component='rgw') | |
1418 | except SubprocessError as error: | |
1419 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1420 | self.update_period() | |
1421 | ||
1422 | def add_or_remove_zone(self, zonegroup_name: str, zone_name: str, action: str): | |
1423 | if action == 'add': | |
1424 | rgw_zonegroup_add_zone_cmd = ['zonegroup', 'add', '--rgw-zonegroup', | |
1425 | zonegroup_name, '--rgw-zone', zone_name] | |
1426 | try: | |
1427 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_add_zone_cmd) | |
1428 | if exit_code > 0: | |
1429 | raise DashboardException(e=err, msg='Unable to add zone {} to zonegroup {}'.format(zone_name, zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1430 | http_status_code=500, component='rgw') | |
1431 | except SubprocessError as error: | |
1432 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1433 | self.update_period() | |
1434 | if action == 'remove': | |
1435 | rgw_zonegroup_rm_zone_cmd = ['zonegroup', 'remove', | |
1436 | '--rgw-zonegroup', zonegroup_name, '--rgw-zone', zone_name] | |
1437 | try: | |
1438 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_rm_zone_cmd) | |
1439 | if exit_code > 0: | |
1440 | raise DashboardException(e=err, msg='Unable to remove zone {} from zonegroup {}'.format(zone_name, zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1441 | http_status_code=500, component='rgw') | |
1442 | except SubprocessError as error: | |
1443 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1444 | self.update_period() | |
1445 | ||
1446 | def get_placement_targets_by_zonegroup(self, zonegroup_name: str): | |
1447 | rgw_get_placement_cmd = ['zonegroup', 'placement', | |
1448 | 'list', '--rgw-zonegroup', zonegroup_name] | |
1449 | try: | |
1450 | exit_code, out, err = mgr.send_rgwadmin_command(rgw_get_placement_cmd) | |
1451 | if exit_code > 0: | |
1452 | raise DashboardException(e=err, msg='Unable to get placement targets', | |
1453 | http_status_code=500, component='rgw') | |
1454 | except SubprocessError as error: | |
1455 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1456 | return out | |
1457 | ||
1458 | def add_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]): | |
1459 | rgw_add_placement_cmd = ['zonegroup', 'placement', 'add'] | |
1460 | for placement_target in placement_targets: | |
1461 | cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name, | |
1462 | '--placement-id', placement_target['placement_id']] | |
1463 | if placement_target['tags']: | |
1464 | cmd_add_placement_options += ['--tags', placement_target['tags']] | |
1465 | rgw_add_placement_cmd += cmd_add_placement_options | |
1466 | try: | |
1467 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd) | |
1468 | if exit_code > 0: | |
1469 | raise DashboardException(e=err, | |
1470 | msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1471 | http_status_code=500, component='rgw') | |
1472 | except SubprocessError as error: | |
1473 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1474 | self.update_period() | |
1475 | storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else [] # noqa E501 #pylint: disable=line-too-long | |
1476 | if storage_classes: | |
1477 | for sc in storage_classes: | |
1478 | cmd_add_placement_options = ['--storage-class', sc] | |
1479 | try: | |
1480 | exit_code, _, err = mgr.send_rgwadmin_command( | |
1481 | rgw_add_placement_cmd + cmd_add_placement_options) | |
1482 | if exit_code > 0: | |
1483 | raise DashboardException(e=err, | |
1484 | msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1485 | http_status_code=500, component='rgw') | |
1486 | except SubprocessError as error: | |
1487 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1488 | self.update_period() | |
1489 | ||
1490 | def modify_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]): | |
1491 | rgw_add_placement_cmd = ['zonegroup', 'placement', 'modify'] | |
1492 | for placement_target in placement_targets: | |
1493 | cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name, | |
1494 | '--placement-id', placement_target['placement_id']] | |
1495 | if placement_target['tags']: | |
1496 | cmd_add_placement_options += ['--tags', placement_target['tags']] | |
1497 | rgw_add_placement_cmd += cmd_add_placement_options | |
1498 | storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else [] # noqa E501 #pylint: disable=line-too-long | |
1499 | if storage_classes: | |
1500 | for sc in storage_classes: | |
1501 | cmd_add_placement_options = [] | |
1502 | cmd_add_placement_options = ['--storage-class', sc] | |
1503 | try: | |
1504 | exit_code, _, err = mgr.send_rgwadmin_command( | |
1505 | rgw_add_placement_cmd + cmd_add_placement_options) | |
1506 | if exit_code > 0: | |
1507 | raise DashboardException(e=err, | |
1508 | msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1509 | http_status_code=500, component='rgw') | |
1510 | except SubprocessError as error: | |
1511 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1512 | self.update_period() | |
1513 | else: | |
1514 | try: | |
1515 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd) | |
1516 | if exit_code > 0: | |
1517 | raise DashboardException(e=err, | |
1518 | msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1519 | http_status_code=500, component='rgw') | |
1520 | except SubprocessError as error: | |
1521 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1522 | self.update_period() | |
1523 | ||
1524 | # pylint: disable=W0102 | |
1525 | def edit_zonegroup(self, realm_name: str, zonegroup_name: str, new_zonegroup_name: str, | |
1526 | default: str = '', master: str = '', endpoints: str = '', | |
1527 | add_zones: List[str] = [], remove_zones: List[str] = [], | |
1528 | placement_targets: List[Dict[str, str]] = []): | |
1529 | rgw_zonegroup_edit_cmd = [] | |
1530 | if new_zonegroup_name != zonegroup_name: | |
1531 | rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', zonegroup_name, | |
1532 | '--zonegroup-new-name', new_zonegroup_name] | |
1533 | try: | |
1534 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_edit_cmd, False) | |
1535 | if exit_code > 0: | |
1536 | raise DashboardException(e=err, msg='Unable to rename zonegroup to {}'.format(new_zonegroup_name), # noqa E501 #pylint: disable=line-too-long | |
1537 | http_status_code=500, component='rgw') | |
1538 | except SubprocessError as error: | |
1539 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1540 | self.update_period() | |
1541 | self.modify_zonegroup(realm_name, new_zonegroup_name, default, master, endpoints) | |
1542 | if add_zones: | |
1543 | for zone_name in add_zones: | |
1544 | self.add_or_remove_zone(new_zonegroup_name, zone_name, 'add') | |
1545 | if remove_zones: | |
1546 | for zone_name in remove_zones: | |
1547 | self.add_or_remove_zone(new_zonegroup_name, zone_name, 'remove') | |
1548 | existing_placement_targets = self.get_placement_targets_by_zonegroup(new_zonegroup_name) | |
1549 | existing_placement_targets_ids = [pt['key'] for pt in existing_placement_targets] | |
1550 | if placement_targets: | |
1551 | for pt in placement_targets: | |
1552 | if pt['placement_id'] in existing_placement_targets_ids: | |
1553 | self.modify_placement_targets(new_zonegroup_name, placement_targets) | |
1554 | else: | |
1555 | self.add_placement_targets(new_zonegroup_name, placement_targets) | |
1556 | ||
1557 | def update_period(self): | |
1558 | rgw_update_period_cmd = ['period', 'update', '--commit'] | |
1559 | try: | |
1560 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_update_period_cmd) | |
1561 | if exit_code > 0: | |
1562 | raise DashboardException(e=err, msg='Unable to update period', | |
1563 | http_status_code=500, component='rgw') | |
1564 | except SubprocessError as error: | |
1565 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1566 | ||
1567 | def create_zone(self, zone_name, zonegroup_name, default, master, endpoints, access_key, | |
1568 | secret_key): | |
1569 | rgw_zone_create_cmd = ['zone', 'create'] | |
1570 | cmd_create_zone_options = ['--rgw-zone', zone_name] | |
1571 | if zonegroup_name != 'null': | |
1572 | cmd_create_zone_options.append('--rgw-zonegroup') | |
1573 | cmd_create_zone_options.append(zonegroup_name) | |
1574 | if default != 'false': | |
1575 | cmd_create_zone_options.append('--default') | |
1576 | if master != 'false': | |
1577 | cmd_create_zone_options.append('--master') | |
1578 | if endpoints != 'null': | |
1579 | cmd_create_zone_options.append('--endpoints') | |
1580 | cmd_create_zone_options.append(endpoints) | |
1581 | if access_key is not None: | |
1582 | cmd_create_zone_options.append('--access-key') | |
1583 | cmd_create_zone_options.append(access_key) | |
1584 | if secret_key is not None: | |
1585 | cmd_create_zone_options.append('--secret') | |
1586 | cmd_create_zone_options.append(secret_key) | |
1587 | rgw_zone_create_cmd += cmd_create_zone_options | |
1588 | try: | |
1589 | exit_code, out, err = mgr.send_rgwadmin_command(rgw_zone_create_cmd) | |
1590 | if exit_code > 0: | |
1591 | raise DashboardException(e=err, msg='Unable to create zone', | |
1592 | http_status_code=500, component='rgw') | |
1593 | except SubprocessError as error: | |
1594 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1595 | ||
1596 | self.update_period() | |
1597 | return out | |
1598 | ||
1599 | def parse_secrets(self, user, data): | |
1600 | for key in data.get('keys', []): | |
1601 | if key.get('user') == user: | |
1602 | access_key = key.get('access_key') | |
1603 | secret_key = key.get('secret_key') | |
1604 | return access_key, secret_key | |
1605 | return '', '' | |
1606 | ||
1607 | def modify_zone(self, zone_name: str, zonegroup_name: str, default: str, master: str, | |
1608 | endpoints: str, access_key: str, secret_key: str): | |
1609 | rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zonegroup', | |
1610 | zonegroup_name, '--rgw-zone', zone_name] | |
1611 | if endpoints: | |
1612 | rgw_zone_modify_cmd.append('--endpoints') | |
1613 | rgw_zone_modify_cmd.append(endpoints) | |
1614 | if default and str_to_bool(default): | |
1615 | rgw_zone_modify_cmd.append('--default') | |
1616 | if master and str_to_bool(master): | |
1617 | rgw_zone_modify_cmd.append('--master') | |
1618 | if access_key is not None: | |
1619 | rgw_zone_modify_cmd.append('--access-key') | |
1620 | rgw_zone_modify_cmd.append(access_key) | |
1621 | if secret_key is not None: | |
1622 | rgw_zone_modify_cmd.append('--secret') | |
1623 | rgw_zone_modify_cmd.append(secret_key) | |
1624 | try: | |
1625 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd) | |
1626 | if exit_code > 0: | |
1627 | raise DashboardException(e=err, msg='Unable to modify zone', | |
1628 | http_status_code=500, component='rgw') | |
1629 | except SubprocessError as error: | |
1630 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1631 | self.update_period() | |
1632 | ||
1633 | def add_placement_targets_zone(self, zone_name: str, placement_target: str, data_pool: str, | |
1634 | index_pool: str, data_extra_pool: str): | |
1635 | rgw_zone_add_placement_cmd = ['zone', 'placement', 'add', '--rgw-zone', zone_name, | |
1636 | '--placement-id', placement_target, '--data-pool', data_pool, | |
1637 | '--index-pool', index_pool, | |
1638 | '--data-extra-pool', data_extra_pool] | |
1639 | try: | |
1640 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_add_placement_cmd) | |
1641 | if exit_code > 0: | |
1642 | raise DashboardException(e=err, msg='Unable to add placement target {} to zone {}'.format(placement_target, zone_name), # noqa E501 #pylint: disable=line-too-long | |
1643 | http_status_code=500, component='rgw') | |
1644 | except SubprocessError as error: | |
1645 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1646 | self.update_period() | |
1647 | ||
1648 | def add_storage_class_zone(self, zone_name: str, placement_target: str, storage_class: str, | |
1649 | data_pool: str, compression: str): | |
1650 | rgw_zone_add_storage_class_cmd = ['zone', 'placement', 'add', '--rgw-zone', zone_name, | |
1651 | '--placement-id', placement_target, | |
1652 | '--storage-class', storage_class, | |
1653 | '--data-pool', data_pool, | |
1654 | '--compression', compression] | |
1655 | try: | |
1656 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_add_storage_class_cmd) | |
1657 | if exit_code > 0: | |
1658 | raise DashboardException(e=err, msg='Unable to add storage class {} to zone {}'.format(storage_class, zone_name), # noqa E501 #pylint: disable=line-too-long | |
1659 | http_status_code=500, component='rgw') | |
1660 | except SubprocessError as error: | |
1661 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1662 | self.update_period() | |
1663 | ||
1664 | def edit_zone(self, zone_name: str, new_zone_name: str, zonegroup_name: str, default: str = '', | |
1665 | master: str = '', endpoints: str = '', access_key: str = '', secret_key: str = '', | |
1666 | placement_target: str = '', data_pool: str = '', index_pool: str = '', | |
1667 | data_extra_pool: str = '', storage_class: str = '', data_pool_class: str = '', | |
1668 | compression: str = ''): | |
1669 | if new_zone_name != zone_name: | |
1670 | rgw_zone_rename_cmd = ['zone', 'rename', '--rgw-zone', | |
1671 | zone_name, '--zone-new-name', new_zone_name] | |
1672 | try: | |
1673 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_rename_cmd, False) | |
1674 | if exit_code > 0: | |
1675 | raise DashboardException(e=err, msg='Unable to rename zone to {}'.format(new_zone_name), # noqa E501 #pylint: disable=line-too-long | |
1676 | http_status_code=500, component='rgw') | |
1677 | except SubprocessError as error: | |
1678 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1679 | self.update_period() | |
1680 | self.modify_zone(new_zone_name, zonegroup_name, default, master, endpoints, access_key, | |
1681 | secret_key) | |
1682 | self.add_placement_targets_zone(new_zone_name, placement_target, | |
1683 | data_pool, index_pool, data_extra_pool) | |
1684 | self.add_storage_class_zone(new_zone_name, placement_target, storage_class, | |
1685 | data_pool_class, compression) | |
1686 | ||
1687 | def list_zones(self): | |
1688 | rgw_zone_list = {} | |
1689 | rgw_zone_list_cmd = ['zone', 'list'] | |
1690 | try: | |
1691 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zone_list_cmd) | |
1692 | if exit_code > 0: | |
1693 | raise DashboardException(msg='Unable to fetch zone list', | |
1694 | http_status_code=500, component='rgw') | |
1695 | rgw_zone_list = out | |
1696 | except SubprocessError as error: | |
1697 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1698 | return rgw_zone_list | |
1699 | ||
1700 | def get_zone(self, zone_name: str): | |
1701 | zone_info = {} | |
1702 | rgw_zone_info_cmd = ['zone', 'get', '--rgw-zone', zone_name] | |
1703 | try: | |
1704 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zone_info_cmd) | |
1705 | if exit_code > 0: | |
1706 | raise DashboardException('Unable to get zone info', | |
1707 | http_status_code=500, component='rgw') | |
1708 | zone_info = out | |
1709 | except SubprocessError as error: | |
1710 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1711 | return zone_info | |
1712 | ||
1713 | def get_all_zones_info(self): | |
1714 | all_zones_info = {} | |
1715 | zones_info = [] | |
1716 | rgw_zone_list = self.list_zones() | |
1717 | if 'zones' in rgw_zone_list: | |
1718 | if rgw_zone_list['zones'] != []: | |
1719 | for rgw_zone in rgw_zone_list['zones']: | |
1720 | zone_info = self.get_zone(rgw_zone) | |
1721 | zones_info.append(zone_info) | |
1722 | all_zones_info['zones'] = zones_info # type: ignore | |
1723 | else: | |
1724 | all_zones_info['zones'] = [] | |
1725 | if 'default_info' in rgw_zone_list and rgw_zone_list['default_info'] != '': | |
1726 | all_zones_info['default_zone'] = rgw_zone_list['default_info'] # type: ignore | |
1727 | else: | |
1728 | all_zones_info['default_zone'] = '' # type: ignore | |
1729 | return all_zones_info | |
1730 | ||
1731 | def delete_zone(self, zone_name: str, delete_pools: str, pools: List[str], | |
1732 | zonegroup_name: str = '',): | |
1733 | rgw_remove_zone_from_zonegroup_cmd = ['zonegroup', 'remove', '--rgw-zonegroup', | |
1734 | zonegroup_name, '--rgw-zone', zone_name] | |
1735 | rgw_delete_zone_cmd = ['zone', 'delete', '--rgw-zone', zone_name] | |
1736 | if zonegroup_name: | |
1737 | try: | |
1738 | exit_code, _, _ = mgr.send_rgwadmin_command(rgw_remove_zone_from_zonegroup_cmd) | |
1739 | if exit_code > 0: | |
1740 | raise DashboardException(msg='Unable to remove zone from zonegroup', | |
1741 | http_status_code=500, component='rgw') | |
1742 | except SubprocessError as error: | |
1743 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1744 | self.update_period() | |
1745 | try: | |
1746 | exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_zone_cmd) | |
1747 | if exit_code > 0: | |
1748 | raise DashboardException(msg='Unable to delete zone', | |
1749 | http_status_code=500, component='rgw') | |
1750 | except SubprocessError as error: | |
1751 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1752 | self.update_period() | |
1753 | if delete_pools == 'true': | |
1754 | self.delete_pools(pools) | |
1755 | ||
1756 | def delete_pools(self, pools): | |
1757 | for pool in pools: | |
1758 | if mgr.rados.pool_exists(pool): | |
1759 | mgr.rados.delete_pool(pool) | |
1760 | ||
1761 | def create_system_user(self, userName: str, zoneName: str): | |
1762 | rgw_user_create_cmd = ['user', 'create', '--uid', userName, | |
1763 | '--display-name', userName, '--rgw-zone', zoneName, '--system'] | |
1764 | try: | |
1765 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_user_create_cmd) | |
1766 | if exit_code > 0: | |
1767 | raise DashboardException(msg='Unable to create system user', | |
1768 | http_status_code=500, component='rgw') | |
1769 | return out | |
1770 | except SubprocessError as error: | |
1771 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1772 | ||
1773 | def get_user_list(self, zoneName: str): | |
1774 | all_users_info = [] | |
1775 | user_list = [] | |
1776 | rgw_user_list_cmd = ['user', 'list', '--rgw-zone', zoneName] | |
1777 | try: | |
1778 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_user_list_cmd) | |
1779 | if exit_code > 0: | |
1780 | raise DashboardException('Unable to get user list', | |
1781 | http_status_code=500, component='rgw') | |
1782 | user_list = out | |
1783 | except SubprocessError as error: | |
1784 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1785 | ||
1786 | if len(user_list) > 0: | |
1787 | for user_name in user_list: | |
1788 | rgw_user_info_cmd = ['user', 'info', '--uid', user_name, '--rgw-zone', zoneName] | |
1789 | try: | |
1790 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_user_info_cmd) | |
1791 | if exit_code > 0: | |
1792 | raise DashboardException('Unable to get user info', | |
1793 | http_status_code=500, component='rgw') | |
1794 | all_users_info.append(out) | |
1795 | except SubprocessError as error: | |
1796 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1797 | return all_users_info | |
1798 | ||
1799 | def get_multisite_status(self): | |
1800 | is_multisite_configured = True | |
1801 | rgw_realm_list = self.list_realms() | |
1802 | rgw_zonegroup_list = self.list_zonegroups() | |
1803 | rgw_zone_list = self.list_zones() | |
f78120f9 TL |
1804 | if len(rgw_realm_list['realms']) < 1 and len(rgw_zonegroup_list['zonegroups']) <= 1 \ |
1805 | and len(rgw_zone_list['zones']) <= 1: | |
aee94f69 TL |
1806 | is_multisite_configured = False |
1807 | return is_multisite_configured | |
1808 | ||
1809 | def get_multisite_sync_status(self): | |
1810 | rgw_multisite_sync_status_cmd = ['sync', 'status'] | |
1811 | try: | |
1812 | exit_code, out, _ = mgr.send_rgwadmin_command(rgw_multisite_sync_status_cmd, False) | |
1813 | if exit_code > 0: | |
1814 | raise DashboardException('Unable to get sync status', | |
1815 | http_status_code=500, component='rgw') | |
1816 | if out: | |
1817 | return self.process_data(out) | |
1818 | except SubprocessError as error: | |
1819 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1820 | return {} | |
1821 | ||
1822 | def process_data(self, data): | |
1823 | primary_zone_data, metadata_sync_data = self.extract_metadata_and_primary_zone_data(data) | |
1824 | replica_zones_info = [] | |
1825 | if metadata_sync_data != {}: | |
1826 | datasync_info = self.extract_datasync_info(data) | |
1827 | replica_zones_info = [self.extract_replica_zone_data(item) for item in datasync_info] | |
1828 | ||
1829 | replica_zones_info_object = { | |
1830 | 'metadataSyncInfo': metadata_sync_data, | |
1831 | 'dataSyncInfo': replica_zones_info, | |
1832 | 'primaryZoneData': primary_zone_data | |
1833 | } | |
1834 | ||
1835 | return replica_zones_info_object | |
1836 | ||
1837 | def extract_metadata_and_primary_zone_data(self, data): | |
1838 | primary_zone_info, metadata_sync_infoormation = self.extract_zones_data(data) | |
1839 | ||
1840 | primary_zone_tree = primary_zone_info.split('\n') if primary_zone_info else [] | |
1841 | realm = self.get_primary_zonedata(primary_zone_tree[0]) | |
1842 | zonegroup = self.get_primary_zonedata(primary_zone_tree[1]) | |
1843 | zone = self.get_primary_zonedata(primary_zone_tree[2]) | |
1844 | ||
1845 | primary_zone_data = [realm, zonegroup, zone] | |
1846 | zonegroup_info = self.get_zonegroup(zonegroup) | |
1847 | metadata_sync_data = {} | |
1848 | if len(zonegroup_info['zones']) > 1: | |
1849 | metadata_sync_data = self.extract_metadata_sync_data(metadata_sync_infoormation) | |
1850 | ||
1851 | return primary_zone_data, metadata_sync_data | |
1852 | ||
1853 | def extract_zones_data(self, data): | |
1854 | result = data | |
1855 | primary_zone_info = result.split('metadata sync')[0] if 'metadata sync' in result else None | |
1856 | metadata_sync_infoormation = result.split('metadata sync')[1] if 'metadata sync' in result else None # noqa E501 #pylint: disable=line-too-long | |
1857 | return primary_zone_info, metadata_sync_infoormation | |
1858 | ||
1859 | def extract_metadata_sync_data(self, metadata_sync_infoormation): | |
1860 | metadata_sync_info = metadata_sync_infoormation.split('data sync source')[0].strip() if 'data sync source' in metadata_sync_infoormation else None # noqa E501 #pylint: disable=line-too-long | |
1861 | ||
1862 | if metadata_sync_info == 'no sync (zone is master)': | |
1863 | return metadata_sync_info | |
1864 | ||
1865 | metadata_sync_data = {} | |
1866 | metadata_sync_info_array = metadata_sync_info.split('\n') if metadata_sync_info else [] | |
1867 | metadata_sync_data['syncstatus'] = metadata_sync_info_array[0].strip() if len(metadata_sync_info_array) > 0 else None # noqa E501 #pylint: disable=line-too-long | |
1868 | ||
1869 | for item in metadata_sync_info_array: | |
1870 | self.extract_metadata_sync_info(metadata_sync_data, item) | |
1871 | ||
1872 | metadata_sync_data['fullSyncStatus'] = metadata_sync_info_array | |
1873 | return metadata_sync_data | |
1874 | ||
1875 | def extract_metadata_sync_info(self, metadata_sync_data, item): | |
1876 | if 'oldest incremental change not applied:' in item: | |
1877 | metadata_sync_data['timestamp'] = item.split('applied:')[1].split()[0].strip() | |
1878 | ||
1879 | def extract_datasync_info(self, data): | |
1880 | metadata_sync_infoormation = data.split('metadata sync')[1] if 'metadata sync' in data else None # noqa E501 #pylint: disable=line-too-long | |
1881 | if 'data sync source' in metadata_sync_infoormation: | |
1882 | datasync_info = metadata_sync_infoormation.split('data sync source')[1].split('source:') | |
1883 | return datasync_info | |
1884 | return [] | |
1885 | ||
1886 | def extract_replica_zone_data(self, datasync_item): | |
1887 | replica_zone_data = {} | |
1888 | datasync_info_array = datasync_item.split('\n') | |
1889 | replica_zone_name = self.get_primary_zonedata(datasync_info_array[0]) | |
1890 | replica_zone_data['name'] = replica_zone_name.strip() | |
1891 | replica_zone_data['syncstatus'] = datasync_info_array[1].strip() | |
1892 | replica_zone_data['fullSyncStatus'] = datasync_info_array | |
1893 | for item in datasync_info_array: | |
1894 | self.extract_metadata_sync_info(replica_zone_data, item) | |
1895 | return replica_zone_data | |
1896 | ||
1897 | def get_primary_zonedata(self, data): | |
1898 | regex = r'\(([^)]+)\)' | |
1899 | match = re.search(regex, data) | |
1900 | ||
1901 | if match and match.group(1): | |
1902 | return match.group(1) | |
1903 | ||
1904 | return '' | |
f78120f9 TL |
1905 | |
1906 | def get_sync_policy(self, bucket_name: str = '', zonegroup_name: str = ''): | |
1907 | rgw_sync_policy_cmd = ['sync', 'policy', 'get'] | |
1908 | if bucket_name: | |
1909 | rgw_sync_policy_cmd += ['--bucket', bucket_name] | |
1910 | if zonegroup_name: | |
1911 | rgw_sync_policy_cmd += ['--rgw-zonegroup', zonegroup_name] | |
1912 | try: | |
1913 | exit_code, out, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) | |
1914 | if exit_code > 0: | |
1915 | raise DashboardException(f'Unable to get sync policy: {err}', | |
1916 | http_status_code=500, component='rgw') | |
1917 | return out | |
1918 | except SubprocessError as error: | |
1919 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1920 | ||
1921 | def get_sync_policy_group(self, group_id: str, bucket_name: str = '', | |
1922 | zonegroup_name: str = ''): | |
1923 | rgw_sync_policy_cmd = ['sync', 'group', 'get', '--group-id', group_id] | |
1924 | if bucket_name: | |
1925 | rgw_sync_policy_cmd += ['--bucket', bucket_name] | |
1926 | if zonegroup_name: | |
1927 | rgw_sync_policy_cmd += ['--rgw-zonegroup', zonegroup_name] | |
1928 | try: | |
1929 | exit_code, out, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) | |
1930 | if exit_code > 0: | |
1931 | raise DashboardException(f'Unable to get sync policy group: {err}', | |
1932 | http_status_code=500, component='rgw') | |
1933 | return out | |
1934 | except SubprocessError as error: | |
1935 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1936 | ||
1937 | def create_sync_policy_group(self, group_id: str, status: str, bucket_name: str = ''): | |
1938 | rgw_sync_policy_cmd = ['sync', 'group', 'create', '--group-id', group_id, | |
1939 | '--status', SyncStatus[status].value] | |
1940 | if bucket_name: | |
1941 | rgw_sync_policy_cmd += ['--bucket', bucket_name] | |
1942 | try: | |
1943 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) | |
1944 | if exit_code > 0: | |
1945 | raise DashboardException(f'Unable to create sync policy group: {err}', | |
1946 | http_status_code=500, component='rgw') | |
1947 | except SubprocessError as error: | |
1948 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1949 | ||
1950 | def update_sync_policy_group(self, group_id: str, status: str, bucket_name: str = ''): | |
1951 | rgw_sync_policy_cmd = ['sync', 'group', 'modify', '--group-id', group_id, | |
1952 | '--status', SyncStatus[status].value] | |
1953 | if bucket_name: | |
1954 | rgw_sync_policy_cmd += ['--bucket', bucket_name] | |
1955 | try: | |
1956 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) | |
1957 | if exit_code > 0: | |
1958 | raise DashboardException(f'Unable to update sync policy group: {err}', | |
1959 | http_status_code=500, component='rgw') | |
1960 | except SubprocessError as error: | |
1961 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1962 | ||
1963 | def remove_sync_policy_group(self, group_id: str, bucket_name=''): | |
1964 | rgw_sync_policy_cmd = ['sync', 'group', 'remove', '--group-id', group_id] | |
1965 | if bucket_name: | |
1966 | rgw_sync_policy_cmd += ['--bucket', bucket_name] | |
1967 | try: | |
1968 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) | |
1969 | if exit_code > 0: | |
1970 | raise DashboardException(f'Unable to remove sync policy group: {err}', | |
1971 | http_status_code=500, component='rgw') | |
1972 | except SubprocessError as error: | |
1973 | raise DashboardException(error, http_status_code=500, component='rgw') | |
1974 | ||
1975 | def create_sync_flow(self, group_id: str, flow_id: str, flow_type: str, | |
1976 | zones: Optional[List[str]] = None, bucket_name: str = '', | |
1977 | source_zone: Optional[List[str]] = None, | |
1978 | destination_zone: Optional[List[str]] = None): | |
1979 | rgw_sync_policy_cmd = ['sync', 'group', 'flow', 'create', '--group-id', group_id, | |
1980 | '--flow-id', flow_id, '--flow-type', SyncFlowTypes[flow_type].value] | |
1981 | ||
1982 | if SyncFlowTypes[flow_type].value == 'directional': | |
1983 | if source_zone is not None: | |
1984 | rgw_sync_policy_cmd += ['--source-zone', ','.join(source_zone)] | |
1985 | if destination_zone is not None: | |
1986 | rgw_sync_policy_cmd += ['--dest-zone', ','.join(destination_zone)] | |
1987 | else: | |
1988 | if zones: | |
1989 | rgw_sync_policy_cmd += ['--zones', ','.join(zones)] | |
1990 | ||
1991 | if bucket_name: | |
1992 | rgw_sync_policy_cmd += ['--bucket', bucket_name] | |
1993 | ||
1994 | try: | |
1995 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) | |
1996 | if exit_code > 0: | |
1997 | raise DashboardException(f'Unable to create sync flow: {err}', | |
1998 | http_status_code=500, component='rgw') | |
1999 | except SubprocessError as error: | |
2000 | raise DashboardException(error, http_status_code=500, component='rgw') | |
2001 | ||
2002 | def remove_sync_flow(self, group_id: str, flow_id: str, flow_type: str, | |
2003 | source_zone='', destination_zone='', | |
2004 | zones: Optional[List[str]] = None, bucket_name: str = ''): | |
2005 | rgw_sync_policy_cmd = ['sync', 'group', 'flow', 'remove', '--group-id', group_id, | |
2006 | '--flow-id', flow_id, '--flow-type', SyncFlowTypes[flow_type].value] | |
2007 | ||
2008 | if SyncFlowTypes[flow_type].value == 'directional': | |
2009 | rgw_sync_policy_cmd += ['--source-zone', source_zone, '--dest-zone', destination_zone] | |
2010 | else: | |
2011 | if zones: | |
2012 | rgw_sync_policy_cmd += ['--zones', ','.join(zones)] | |
2013 | ||
2014 | if bucket_name: | |
2015 | rgw_sync_policy_cmd += ['--bucket', bucket_name] | |
2016 | ||
2017 | try: | |
2018 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) | |
2019 | if exit_code > 0: | |
2020 | raise DashboardException(f'Unable to remove sync flow: {err}', | |
2021 | http_status_code=500, component='rgw') | |
2022 | except SubprocessError as error: | |
2023 | raise DashboardException(error, http_status_code=500, component='rgw') | |
2024 | ||
2025 | def create_sync_pipe(self, group_id: str, pipe_id: str, | |
2026 | source_zones: Optional[List[str]] = None, | |
2027 | destination_zones: Optional[List[str]] = None, | |
2028 | source_bucket: str = '', | |
2029 | destination_bucket: str = '', | |
2030 | bucket_name: str = ''): | |
2031 | rgw_sync_policy_cmd = ['sync', 'group', 'pipe', 'create', | |
2032 | '--group-id', group_id, '--pipe-id', pipe_id] | |
2033 | ||
2034 | if bucket_name: | |
2035 | rgw_sync_policy_cmd += ['--bucket', bucket_name] | |
2036 | ||
2037 | if source_zones: | |
2038 | rgw_sync_policy_cmd += ['--source-zones', ','.join(source_zones)] | |
2039 | ||
2040 | if destination_zones: | |
2041 | rgw_sync_policy_cmd += ['--dest-zones', ','.join(destination_zones)] | |
2042 | ||
2043 | if source_bucket: | |
2044 | rgw_sync_policy_cmd += ['--source-bucket', source_bucket] | |
2045 | ||
2046 | if destination_bucket: | |
2047 | rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket] | |
2048 | ||
2049 | try: | |
2050 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) | |
2051 | if exit_code > 0: | |
2052 | raise DashboardException(f'Unable to create sync pipe: {err}', | |
2053 | http_status_code=500, component='rgw') | |
2054 | except SubprocessError as error: | |
2055 | raise DashboardException(error, http_status_code=500, component='rgw') | |
2056 | ||
2057 | def remove_sync_pipe(self, group_id: str, pipe_id: str, | |
2058 | source_zones: Optional[List[str]] = None, | |
2059 | destination_zones: Optional[List[str]] = None, | |
2060 | destination_bucket: str = '', bucket_name: str = ''): | |
2061 | rgw_sync_policy_cmd = ['sync', 'group', 'pipe', 'remove', | |
2062 | '--group-id', group_id, '--pipe-id', pipe_id] | |
2063 | ||
2064 | if bucket_name: | |
2065 | rgw_sync_policy_cmd += ['--bucket', bucket_name] | |
2066 | ||
2067 | if source_zones: | |
2068 | rgw_sync_policy_cmd += ['--source-zones', ','.join(source_zones)] | |
2069 | ||
2070 | if destination_zones: | |
2071 | rgw_sync_policy_cmd += ['--dest-zones', ','.join(destination_zones)] | |
2072 | ||
2073 | if destination_bucket: | |
2074 | rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket] | |
2075 | ||
2076 | try: | |
2077 | exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) | |
2078 | if exit_code > 0: | |
2079 | raise DashboardException(f'Unable to remove sync pipe: {err}', | |
2080 | http_status_code=500, component='rgw') | |
2081 | except SubprocessError as error: | |
2082 | raise DashboardException(error, http_status_code=500, component='rgw') | |
2083 | ||
2084 | def create_dashboard_admin_sync_group(self, zonegroup_name: str = ''): | |
2085 | ||
2086 | zonegroup_info = self.get_zonegroup(zonegroup_name) | |
2087 | zone_names = [] | |
2088 | for zones in zonegroup_info['zones']: | |
2089 | zone_names.append(zones['name']) | |
2090 | ||
2091 | # create a sync policy group with status allowed | |
2092 | self.create_sync_policy_group(_SYNC_GROUP_ID, SyncStatus.allowed.value) | |
2093 | # create a sync flow with source and destination zones | |
2094 | self.create_sync_flow(_SYNC_GROUP_ID, _SYNC_FLOW_ID, | |
2095 | SyncFlowTypes.symmetrical.value, | |
2096 | zones=zone_names) | |
2097 | # create a sync pipe with source and destination zones | |
2098 | self.create_sync_pipe(_SYNC_GROUP_ID, _SYNC_PIPE_ID, source_zones=['*'], | |
2099 | destination_zones=['*'], source_bucket='*', destination_bucket='*') | |
2100 | # period update --commit | |
2101 | self.update_period() | |
2102 | ||
2103 | def policy_group_exists(self, group_name: str, zonegroup_name: str): | |
2104 | try: | |
2105 | _ = self.get_sync_policy_group( | |
2106 | group_id=group_name, zonegroup_name=zonegroup_name) | |
2107 | return True | |
2108 | except DashboardException: | |
2109 | return False |