]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | |
2 | from rest.app.manager.request_factory import RequestFactory | |
3 | from rest.app.types import OsdMap, Config | |
4 | from rest.app.manager.user_request import OsdMapModifyingRequest, \ | |
5 | PgCreatingRequest, PoolCreatingRequest | |
6 | ||
7 | from rest.module import global_instance as rest_plugin | |
8 | ||
9 | from rest.logger import logger | |
10 | log = logger() | |
11 | ||
12 | # Valid values for the 'var' argument to 'ceph osd pool set' | |
13 | POOL_PROPERTIES = ["size", "min_size", "crash_replay_interval", "pg_num", | |
14 | "pgp_num", "crush_ruleset", "hashpspool"] | |
15 | ||
16 | # In Ceph versions before mon_osd_max_split_count, assume it is set to this | |
17 | LEGACY_MON_OSD_MAX_SPLIT_COUNT = "32" | |
18 | ||
19 | ||
20 | class PoolRequestFactory(RequestFactory): | |
21 | def _resolve_pool(self, pool_id): | |
22 | osd_map = rest_plugin().get_sync_object(OsdMap) | |
23 | return osd_map.pools_by_id[pool_id] | |
24 | ||
25 | def _pool_attribute_commands(self, pool_name, attributes): | |
26 | commands = [] | |
27 | for var in POOL_PROPERTIES: | |
28 | if var in attributes: | |
29 | val = attributes[var] | |
30 | ||
31 | # Special case for hashpspool, accepts 'true' from firefly | |
32 | # onwards but requires 0 or 1 for dumpling, so just use the | |
33 | # old style. | |
34 | if isinstance(val, bool): | |
35 | val = 1 if val else 0 | |
36 | ||
37 | commands.append(('osd pool set', { | |
38 | 'pool': pool_name, | |
39 | 'var': var, | |
40 | 'val': val | |
41 | })) | |
42 | ||
43 | # Quota setting ('osd pool set-quota') is separate to the main 'set' | |
44 | # operation | |
45 | for attr_name, set_name in [('quota_max_bytes', 'max_bytes'), | |
46 | ('quota_max_objects', 'max_objects')]: | |
47 | if attr_name in attributes: | |
48 | commands.append(('osd pool set-quota', { | |
49 | 'pool': pool_name, | |
50 | 'field': set_name, | |
51 | # set-quota wants a string in case it has units in | |
52 | 'val': attributes[attr_name].__str__() | |
53 | })) | |
54 | ||
55 | # Renames come last (the preceeding commands reference the pool by its | |
56 | # old name) | |
57 | if 'name' in attributes: | |
58 | commands.append(('osd pool rename', { | |
59 | "srcpool": pool_name, | |
60 | "destpool": attributes['name'] | |
61 | })) | |
62 | ||
63 | return commands | |
64 | ||
65 | def delete(self, pool_id): | |
66 | # Resolve pool ID to name | |
67 | pool_name = self._resolve_pool(pool_id)['pool_name'] | |
68 | ||
69 | # TODO: perhaps the REST API should have something in the body to | |
70 | # make it slightly harder to accidentally delete a pool, to respect | |
71 | # the severity of this operation since we're hiding the | |
72 | # --yes-i-really-really-want-to stuff here | |
73 | # TODO: handle errors in a way that caller can show to a user, e.g. | |
74 | # if the name is wrong we should be sending a structured errors dict | |
75 | # that they can use to associate the complaint with the 'name' field. | |
76 | commands = [ | |
77 | ('osd pool delete', {'pool': pool_name, 'pool2': pool_name, | |
78 | 'sure': '--yes-i-really-really-mean-it'})] | |
79 | return OsdMapModifyingRequest( | |
80 | "Deleting pool '{name}'".format(name=pool_name), | |
81 | commands) | |
82 | ||
83 | def update(self, pool_id, attributes): | |
84 | osd_map = rest_plugin().get_sync_object(OsdMap) | |
85 | pool = self._resolve_pool(pool_id) | |
86 | pool_name = pool['pool_name'] | |
87 | ||
88 | if 'pg_num' in attributes: | |
89 | # Special case when setting pg_num: have to do some extra work | |
90 | # to wait for PG creation between setting these two fields. | |
91 | final_pg_count = attributes['pg_num'] | |
92 | ||
93 | if 'pgp_num' in attributes: | |
94 | pgp_num = attributes['pgp_num'] | |
95 | del attributes['pgp_num'] | |
96 | else: | |
97 | pgp_num = attributes['pg_num'] | |
98 | del attributes['pg_num'] | |
99 | ||
100 | pre_create_commands = self._pool_attribute_commands(pool_name, | |
101 | attributes) | |
102 | ||
103 | # This setting is new in Ceph Firefly, where it defaults to 32. | |
104 | # For older revisions, we simply pretend that the setting exists | |
105 | # with a default setting. | |
106 | mon_osd_max_split_count = int(rest_plugin().get_sync_object(Config).data.get( | |
107 | 'mon_osd_max_split_count', LEGACY_MON_OSD_MAX_SPLIT_COUNT)) | |
108 | initial_pg_count = pool['pg_num'] | |
109 | n_osds = min(initial_pg_count, len(osd_map.osds_by_id)) | |
110 | # The rules about creating PGs: | |
111 | # where N_osds = min(old_pg_count, osd_count) | |
112 | # the number of new PGs divided by N_osds may not be greater | |
113 | # than mon_osd_max_split_count | |
114 | block_size = mon_osd_max_split_count * n_osds | |
115 | ||
116 | return PgCreatingRequest( | |
117 | "Growing pool '{name}' to {size} PGs".format( | |
118 | name=pool_name, size=final_pg_count), | |
119 | pre_create_commands, | |
120 | pool_id, pool_name, pgp_num, | |
121 | initial_pg_count, final_pg_count, block_size) | |
122 | else: | |
123 | commands = self._pool_attribute_commands(pool_name, attributes) | |
124 | if not commands: | |
125 | raise NotImplementedError(attributes) | |
126 | ||
127 | # TODO: provide some machine-readable indication of which objects | |
128 | # are affected by a particular request. | |
129 | # Perhaps subclass Request for each type of object, and have that | |
130 | # subclass provide both the patches->commands mapping and the | |
131 | # human readable and machine readable descriptions of it? | |
132 | ||
133 | # Objects may be decorated with 'id' from use in a bulk PATCH, but | |
134 | # we don't want anything | |
135 | # from this point onwards to see that. | |
136 | if 'id' in attributes: | |
137 | del attributes['id'] | |
138 | return OsdMapModifyingRequest( | |
139 | "Modifying pool '{name}' ({attrs})".format( | |
140 | name=pool_name, attrs=", ".join( | |
141 | "%s=%s" % (k, v) for k, v in attributes.items()) | |
142 | ), commands) | |
143 | ||
144 | def create(self, attributes): | |
145 | commands = [('osd pool create', {'pool': attributes['name'], | |
146 | 'pg_num': attributes['pg_num']})] | |
147 | ||
148 | # Which attributes must we set after the initial create? | |
149 | post_create_attrs = attributes.copy() | |
150 | del post_create_attrs['name'] | |
151 | del post_create_attrs['pg_num'] | |
152 | if 'pgp_num' in post_create_attrs: | |
153 | del post_create_attrs['pgp_num'] | |
154 | ||
155 | commands.extend(self._pool_attribute_commands( | |
156 | attributes['name'], | |
157 | post_create_attrs | |
158 | )) | |
159 | ||
160 | log.debug("Post-create attributes: %s" % post_create_attrs) | |
161 | log.debug("Commands: %s" % post_create_attrs) | |
162 | ||
163 | return PoolCreatingRequest( | |
164 | "Creating pool '{name}'".format(name=attributes['name']), | |
165 | attributes['name'], commands) |