]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/controllers/pool.py
import ceph quincy 17.2.4
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / pool.py
CommitLineData
11fdf7f2 1# -*- coding: utf-8 -*-
11fdf7f2 2
eafe8130 3import time
f67539c2
TL
4from typing import Any, Dict, Iterable, List, Optional, Union, cast
5
11fdf7f2
TL
6import cherrypy
7
11fdf7f2
TL
8from .. import mgr
9from ..security import Scope
10from ..services.ceph_service import CephService
11fdf7f2 11from ..services.exception import handle_send_command_error
f67539c2
TL
12from ..services.rbd import RbdConfiguration
13from ..tools import TaskManager, str_to_bool
a4b75251
TL
14from . import APIDoc, APIRouter, Endpoint, EndpointDoc, ReadPermission, \
15 RESTController, Task, UIRouter
f67539c2
TL
16
17POOL_SCHEMA = ([{
18 "pool": (int, "pool id"),
19 "pool_name": (str, "pool name"),
20 "flags": (int, ""),
21 "flags_names": (str, "flags name"),
22 "type": (str, "type of pool"),
23 "size": (int, "pool size"),
24 "min_size": (int, ""),
25 "crush_rule": (str, ""),
26 "object_hash": (int, ""),
27 "pg_autoscale_mode": (str, ""),
28 "pg_num": (int, ""),
29 "pg_placement_num": (int, ""),
30 "pg_placement_num_target": (int, ""),
31 "pg_num_target": (int, ""),
32 "pg_num_pending": (int, ""),
33 "last_pg_merge_meta": ({
34 "ready_epoch": (int, ""),
35 "last_epoch_started": (int, ""),
36 "last_epoch_clean": (int, ""),
37 "source_pgid": (str, ""),
38 "source_version": (str, ""),
39 "target_version": (str, ""),
40 }, ""),
41 "auid": (int, ""),
42 "snap_mode": (str, ""),
43 "snap_seq": (int, ""),
44 "snap_epoch": (int, ""),
45 "pool_snaps": ([str], ""),
46 "quota_max_bytes": (int, ""),
47 "quota_max_objects": (int, ""),
48 "tiers": ([str], ""),
49 "tier_of": (int, ""),
50 "read_tier": (int, ""),
51 "write_tier": (int, ""),
52 "cache_mode": (str, ""),
53 "target_max_bytes": (int, ""),
54 "target_max_objects": (int, ""),
55 "cache_target_dirty_ratio_micro": (int, ""),
56 "cache_target_dirty_high_ratio_micro": (int, ""),
57 "cache_target_full_ratio_micro": (int, ""),
58 "cache_min_flush_age": (int, ""),
59 "cache_min_evict_age": (int, ""),
60 "erasure_code_profile": (str, ""),
61 "hit_set_params": ({
62 "type": (str, "")
63 }, ""),
64 "hit_set_period": (int, ""),
65 "hit_set_count": (int, ""),
66 "use_gmt_hitset": (bool, ""),
67 "min_read_recency_for_promote": (int, ""),
68 "min_write_recency_for_promote": (int, ""),
69 "hit_set_grade_decay_rate": (int, ""),
70 "hit_set_search_last_n": (int, ""),
71 "grade_table": ([str], ""),
72 "stripe_width": (int, ""),
73 "expected_num_objects": (int, ""),
74 "fast_read": (bool, ""),
75 "options": ({
20effc67
TL
76 "pg_num_min": (int, ""),
77 "pg_num_max": (int, "")
f67539c2
TL
78 }, ""),
79 "application_metadata": ([str], ""),
80 "create_time": (str, ""),
81 "last_change": (str, ""),
82 "last_force_op_resend": (str, ""),
83 "last_force_op_resend_prenautilus": (str, ""),
84 "last_force_op_resend_preluminous": (str, ""),
85 "removed_snaps": ([str], "")
86}])
11fdf7f2
TL
87
88
89def pool_task(name, metadata, wait_for=2.0):
90 return Task("pool/{}".format(name), metadata, wait_for)
91
92
a4b75251
TL
93@APIRouter('/pool', Scope.POOL)
94@APIDoc("Get pool details by pool name", "Pool")
11fdf7f2
TL
95class Pool(RESTController):
96
97 @staticmethod
98 def _serialize_pool(pool, attrs):
99 if not attrs or not isinstance(attrs, list):
100 attrs = pool.keys()
101
102 crush_rules = {r['rule_id']: r["rule_name"] for r in mgr.get('osd_map_crush')['rules']}
103
f6b5b4d7 104 res: Dict[Union[int, str], Union[str, List[Any]]] = {}
11fdf7f2
TL
105 for attr in attrs:
106 if attr not in pool:
107 continue
108 if attr == 'type':
109 res[attr] = {1: 'replicated', 3: 'erasure'}[pool[attr]]
110 elif attr == 'crush_rule':
111 res[attr] = crush_rules[pool[attr]]
112 elif attr == 'application_metadata':
113 res[attr] = list(pool[attr].keys())
114 else:
115 res[attr] = pool[attr]
116
117 # pool_name is mandatory
118 res['pool_name'] = pool['pool_name']
119 return res
120
eafe8130
TL
121 @classmethod
122 def _pool_list(cls, attrs=None, stats=False):
11fdf7f2
TL
123 if attrs:
124 attrs = attrs.split(',')
125
126 if str_to_bool(stats):
127 pools = CephService.get_pool_list_with_stats()
128 else:
129 pools = CephService.get_pool_list()
130
eafe8130 131 return [cls._serialize_pool(pool, attrs) for pool in pools]
11fdf7f2 132
f67539c2
TL
133 @EndpointDoc("Display Pool List",
134 parameters={
135 'attrs': (str, 'Pool Attributes'),
136 'stats': (bool, 'Pool Stats')
137 },
138 responses={200: POOL_SCHEMA})
11fdf7f2
TL
139 def list(self, attrs=None, stats=False):
140 return self._pool_list(attrs, stats)
141
eafe8130 142 @classmethod
f6b5b4d7 143 def _get(cls, pool_name: str, attrs: Optional[str] = None, stats: bool = False) -> dict:
eafe8130 144 pools = cls._pool_list(attrs, stats)
9f95a23c 145 pool = [p for p in pools if p['pool_name'] == pool_name]
11fdf7f2
TL
146 if not pool:
147 raise cherrypy.NotFound('No such pool')
148 return pool[0]
149
f6b5b4d7 150 def get(self, pool_name: str, attrs: Optional[str] = None, stats: bool = False) -> dict:
11fdf7f2
TL
151 pool = self._get(pool_name, attrs, stats)
152 pool['configuration'] = RbdConfiguration(pool_name).list()
153 return pool
154
155 @pool_task('delete', ['{pool_name}'])
156 @handle_send_command_error('pool')
157 def delete(self, pool_name):
158 return CephService.send_command('mon', 'osd pool delete', pool=pool_name, pool2=pool_name,
159 yes_i_really_really_mean_it=True)
160
161 @pool_task('edit', ['{pool_name}'])
162 def set(self, pool_name, flags=None, application_metadata=None, configuration=None, **kwargs):
163 self._set_pool_values(pool_name, application_metadata, flags, True, kwargs)
9f95a23c
TL
164 if kwargs.get('pool'):
165 pool_name = kwargs['pool']
11fdf7f2 166 RbdConfiguration(pool_name).set_configuration(configuration)
eafe8130 167 self._wait_for_pgs(pool_name)
11fdf7f2
TL
168
169 @pool_task('create', {'pool_name': '{pool}'})
170 @handle_send_command_error('pool')
171 def create(self, pool, pg_num, pool_type, erasure_code_profile=None, flags=None,
172 application_metadata=None, rule_name=None, configuration=None, **kwargs):
173 ecp = erasure_code_profile if erasure_code_profile else None
174 CephService.send_command('mon', 'osd pool create', pool=pool, pg_num=int(pg_num),
175 pgp_num=int(pg_num), pool_type=pool_type, erasure_code_profile=ecp,
176 rule=rule_name)
177 self._set_pool_values(pool, application_metadata, flags, False, kwargs)
178 RbdConfiguration(pool).set_configuration(configuration)
eafe8130 179 self._wait_for_pgs(pool)
11fdf7f2
TL
180
181 def _set_pool_values(self, pool, application_metadata, flags, update_existing, kwargs):
182 update_name = False
92f5a8d4
TL
183 current_pool = self._get(pool)
184 if update_existing and kwargs.get('compression_mode') == 'unset':
185 self._prepare_compression_removal(current_pool.get('options'), kwargs)
11fdf7f2
TL
186 if flags and 'ec_overwrites' in flags:
187 CephService.send_command('mon', 'osd pool set', pool=pool, var='allow_ec_overwrites',
188 val='true')
189 if application_metadata is not None:
190 def set_app(what, app):
191 CephService.send_command('mon', 'osd pool application ' + what, pool=pool, app=app,
192 yes_i_really_mean_it=True)
193 if update_existing:
194 original_app_metadata = set(
f6b5b4d7 195 cast(Iterable[Any], current_pool.get('application_metadata')))
11fdf7f2
TL
196 else:
197 original_app_metadata = set()
198
199 for app in original_app_metadata - set(application_metadata):
200 set_app('disable', app)
201 for app in set(application_metadata) - original_app_metadata:
202 set_app('enable', app)
203
204 def set_key(key, value):
205 CephService.send_command('mon', 'osd pool set', pool=pool, var=key, val=str(value))
206
9f95a23c
TL
207 quotas = {}
208 quotas['max_objects'] = kwargs.pop('quota_max_objects', None)
209 quotas['max_bytes'] = kwargs.pop('quota_max_bytes', None)
210 self._set_quotas(pool, quotas)
211
11fdf7f2
TL
212 for key, value in kwargs.items():
213 if key == 'pool':
214 update_name = True
215 destpool = value
216 else:
217 set_key(key, value)
218 if key == 'pg_num':
219 set_key('pgp_num', value)
220 if update_name:
221 CephService.send_command('mon', 'osd pool rename', srcpool=pool, destpool=destpool)
222
9f95a23c
TL
223 def _set_quotas(self, pool, quotas):
224 for field, value in quotas.items():
225 if value is not None:
226 CephService.send_command('mon', 'osd pool set-quota',
227 pool=pool, field=field, val=str(value))
228
92f5a8d4
TL
229 def _prepare_compression_removal(self, options, kwargs):
230 """
231 Presets payload with values to remove compression attributes in case they are not
232 needed anymore.
233
234 In case compression is not needed the dashboard will send 'compression_mode' with the
235 value 'unset'.
236
237 :param options: All set options for the current pool.
238 :param kwargs: Payload of the PUT / POST call
239 """
240 if options is not None:
11fdf7f2
TL
241 def reset_arg(arg, value):
242 if options.get(arg):
243 kwargs[arg] = value
244 for arg in ['compression_min_blob_size', 'compression_max_blob_size',
245 'compression_required_ratio']:
246 reset_arg(arg, '0')
247 reset_arg('compression_algorithm', 'unset')
248
eafe8130
TL
249 @classmethod
250 def _wait_for_pgs(cls, pool_name):
251 """
252 Keep the task waiting for until all pg changes are complete
253 :param pool_name: The name of the pool.
254 :type pool_name: string
255 """
256 current_pool = cls._get(pool_name)
257 initial_pgs = int(current_pool['pg_placement_num']) + int(current_pool['pg_num'])
258 cls._pg_wait_loop(current_pool, initial_pgs)
259
260 @classmethod
261 def _pg_wait_loop(cls, pool, initial_pgs):
262 """
263 Compares if all pg changes are completed, if not it will call itself
264 until all changes are completed.
265 :param pool: The dict that represents a pool.
266 :type pool: dict
267 :param initial_pgs: The pg and pg_num count before any change happened.
268 :type initial_pgs: int
269 """
270 if 'pg_num_target' in pool:
271 target = int(pool['pg_num_target']) + int(pool['pg_placement_num_target'])
272 current = int(pool['pg_placement_num']) + int(pool['pg_num'])
273 if current != target:
274 max_diff = abs(target - initial_pgs)
275 diff = max_diff - abs(target - current)
276 percentage = int(round(diff / float(max_diff) * 100))
277 TaskManager.current_task().set_progress(percentage)
278 time.sleep(4)
279 cls._pg_wait_loop(cls._get(pool['pool_name']), initial_pgs)
280
11fdf7f2
TL
281 @RESTController.Resource()
282 @ReadPermission
283 def configuration(self, pool_name):
284 return RbdConfiguration(pool_name).list()
285
9f95a23c 286
a4b75251
TL
287@UIRouter('/pool', Scope.POOL)
288@APIDoc("Dashboard UI helper function; not part of the public API", "PoolUi")
9f95a23c 289class PoolUi(Pool):
11fdf7f2
TL
290 @Endpoint()
291 @ReadPermission
9f95a23c 292 def info(self):
11fdf7f2 293 """Used by the create-pool dialog"""
9f95a23c
TL
294 osd_map_crush = mgr.get('osd_map_crush')
295 options = mgr.get('config_options')['options']
11fdf7f2
TL
296
297 def rules(pool_type):
298 return [r
9f95a23c 299 for r in osd_map_crush['rules']
11fdf7f2
TL
300 if r['type'] == pool_type]
301
302 def all_bluestore():
303 return all(o['osd_objectstore'] == 'bluestore'
304 for o in mgr.get('osd_metadata').values())
305
9f95a23c 306 def get_config_option_enum(conf_name):
11fdf7f2 307 return [[v for v in o['enum_values'] if len(v) > 0]
9f95a23c 308 for o in options
11fdf7f2
TL
309 if o['name'] == conf_name][0]
310
e306af50 311 profiles = CephService.get_erasure_code_profiles()
f6b5b4d7
TL
312 used_rules: Dict[str, List[str]] = {}
313 used_profiles: Dict[str, List[str]] = {}
9f95a23c
TL
314 pool_names = []
315 for p in self._pool_list():
316 name = p['pool_name']
9f95a23c 317 pool_names.append(name)
e306af50 318 rule = p['crush_rule']
9f95a23c
TL
319 if rule in used_rules:
320 used_rules[rule].append(name)
321 else:
322 used_rules[rule] = [name]
e306af50
TL
323 profile = p['erasure_code_profile']
324 if profile in used_profiles:
325 used_profiles[profile].append(name)
326 else:
327 used_profiles[profile] = [name]
9f95a23c
TL
328
329 mgr_config = mgr.get('config')
330 return {
331 "pool_names": pool_names,
11fdf7f2
TL
332 "crush_rules_replicated": rules(1),
333 "crush_rules_erasure": rules(3),
334 "is_all_bluestore": all_bluestore(),
335 "osd_count": len(mgr.get('osd_map')['osds']),
9f95a23c
TL
336 "bluestore_compression_algorithm": mgr_config['bluestore_compression_algorithm'],
337 "compression_algorithms": get_config_option_enum('bluestore_compression_algorithm'),
338 "compression_modes": get_config_option_enum('bluestore_compression_mode'),
339 "pg_autoscale_default_mode": mgr_config['osd_pool_default_pg_autoscale_mode'],
340 "pg_autoscale_modes": get_config_option_enum('osd_pool_default_pg_autoscale_mode'),
e306af50
TL
341 "erasure_code_profiles": profiles,
342 "used_rules": used_rules,
343 "used_profiles": used_profiles,
f6b5b4d7 344 'nodes': mgr.get('osd_map_tree')['nodes']
11fdf7f2 345 }
2a845540
TL
346
347
348class RBDPool(Pool):
349 def create(self, pool='rbd-mirror'): # pylint: disable=arguments-differ
350 super().create(pool, pg_num=1, pool_type='replicated',
351 rule_name='replicated_rule', application_metadata=['rbd'])