]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/volumes/fs/volume.py
8cc7ef26fa05883c8bb093097228e6266ddab736
[ceph.git] / ceph / src / pybind / mgr / volumes / fs / volume.py
1 import json
2 import errno
3 import logging
4
5 import cephfs
6 import orchestrator
7
8 from .subvolspec import SubvolumeSpec
9 from .subvolume import SubVolume
10 from .exception import VolumeException
11
12 log = logging.getLogger(__name__)
13
14 class VolumeClient(object):
15 def __init__(self, mgr):
16 self.mgr = mgr
17
18 def gen_pool_names(self, volname):
19 """
20 return metadata and data pool name (from a filesystem/volume name) as a tuple
21 """
22 return "cephfs.{}.meta".format(volname), "cephfs.{}.data".format(volname)
23
24 def get_fs(self, fs_name):
25 fs_map = self.mgr.get('fs_map')
26 for fs in fs_map['filesystems']:
27 if fs['mdsmap']['fs_name'] == fs_name:
28 return fs
29 return None
30
31 def get_mds_names(self, fs_name):
32 fs = self.get_fs(fs_name)
33 if fs is None:
34 return []
35 return [mds['name'] for mds in fs['mdsmap']['info'].values()]
36
37 def volume_exists(self, volname):
38 return self.get_fs(volname) is not None
39
40 def volume_exception_to_retval(self, ve):
41 """
42 return a tuple representation from a volume exception
43 """
44 return ve.to_tuple()
45
46 def create_pool(self, pool_name, pg_num, pg_num_min=None, pg_autoscale_factor=None):
47 # create the given pool
48 command = {'prefix': 'osd pool create', 'pool': pool_name, 'pg_num': pg_num}
49 if pg_num_min:
50 command['pg_num_min'] = pg_num_min
51 r, outb, outs = self.mgr.mon_command(command)
52 if r != 0:
53 return r, outb, outs
54
55 # set pg autoscale if needed
56 if pg_autoscale_factor:
57 command = {'prefix': 'osd pool set', 'pool': pool_name, 'var': 'pg_autoscale_bias',
58 'val': str(pg_autoscale_factor)}
59 r, outb, outs = self.mgr.mon_command(command)
60 return r, outb, outs
61
62 def remove_pool(self, pool_name):
63 command = {'prefix': 'osd pool rm', 'pool': pool_name, 'pool2': pool_name,
64 'yes_i_really_really_mean_it': True}
65 return self.mgr.mon_command(command)
66
67 def create_filesystem(self, fs_name, metadata_pool, data_pool):
68 command = {'prefix': 'fs new', 'fs_name': fs_name, 'metadata': metadata_pool,
69 'data': data_pool}
70 return self.mgr.mon_command(command)
71
72 def remove_filesystem(self, fs_name):
73 command = {'prefix': 'fs rm', 'fs_name': fs_name, 'yes_i_really_mean_it': True}
74 return self.mgr.mon_command(command)
75
76 def create_mds(self, fs_name):
77 spec = orchestrator.StatelessServiceSpec()
78 spec.name = fs_name
79 try:
80 completion = self.mgr.add_stateless_service("mds", spec)
81 self.mgr._orchestrator_wait([completion])
82 orchestrator.raise_if_exception(completion)
83 except (ImportError, orchestrator.OrchestratorError):
84 return 0, "", "Volume created successfully (no MDS daemons created)"
85 except Exception as e:
86 # Don't let detailed orchestrator exceptions (python backtraces)
87 # bubble out to the user
88 log.exception("Failed to create MDS daemons")
89 return -errno.EINVAL, "", str(e)
90 return 0, "", ""
91
92 def set_mds_down(self, fs_name):
93 command = {'prefix': 'fs set', 'fs_name': fs_name, 'var': 'cluster_down', 'val': 'true'}
94 r, outb, outs = self.mgr.mon_command(command)
95 if r != 0:
96 return r, outb, outs
97 for mds in self.get_mds_names(fs_name):
98 command = {'prefix': 'mds fail', 'role_or_gid': mds}
99 r, outb, outs = self.mgr.mon_command(command)
100 if r != 0:
101 return r, outb, outs
102 return 0, "", ""
103
104 ### volume operations -- create, rm, ls
105
106 def create_volume(self, volname, size=None):
107 """
108 create volume (pool, filesystem and mds)
109 """
110 metadata_pool, data_pool = self.gen_pool_names(volname)
111 # create pools
112 r, outs, outb = self.create_pool(metadata_pool, 16, pg_num_min=16, pg_autoscale_factor=4.0)
113 if r != 0:
114 return r, outb, outs
115 r, outb, outs = self.create_pool(data_pool, 8)
116 if r != 0:
117 return r, outb, outs
118 # create filesystem
119 r, outb, outs = self.create_filesystem(volname, metadata_pool, data_pool)
120 if r != 0:
121 log.error("Filesystem creation error: {0} {1} {2}".format(r, outb, outs))
122 return r, outb, outs
123 # create mds
124 return self.create_mds(volname)
125
126 def delete_volume(self, volname):
127 """
128 delete the given module (tear down mds, remove filesystem)
129 """
130 # Tear down MDS daemons
131 try:
132 completion = self.mgr.remove_stateless_service("mds", volname)
133 self.mgr._orchestrator_wait([completion])
134 orchestrator.raise_if_exception(completion)
135 except (ImportError, orchestrator.OrchestratorError):
136 log.warning("OrchestratorError, not tearing down MDS daemons")
137 except Exception as e:
138 # Don't let detailed orchestrator exceptions (python backtraces)
139 # bubble out to the user
140 log.exception("Failed to tear down MDS daemons")
141 return -errno.EINVAL, "", str(e)
142
143 # In case orchestrator didn't tear down MDS daemons cleanly, or
144 # there was no orchestrator, we force the daemons down.
145 if self.volume_exists(volname):
146 r, outb, outs = self.set_mds_down(volname)
147 if r != 0:
148 return r, outb, outs
149 r, outb, outs = self.remove_filesystem(volname)
150 if r != 0:
151 return r, outb, outs
152 else:
153 log.warning("Filesystem already gone for volume '{0}'".format(volname))
154 metadata_pool, data_pool = self.gen_pool_names(volname)
155 r, outb, outs = self.remove_pool(metadata_pool)
156 if r != 0:
157 return r, outb, outs
158 return self.remove_pool(data_pool)
159
160 def list_volumes(self):
161 result = []
162 fs_map = self.mgr.get("fs_map")
163 for f in fs_map['filesystems']:
164 result.append({'name': f['mdsmap']['fs_name']})
165 return 0, json.dumps(result, indent=2), ""
166
167 def group_exists(self, sv, spec):
168 # default group need not be explicitly created (as it gets created
169 # at the time of subvolume, snapshot and other create operations).
170 return spec.is_default_group() or sv.get_group_path(spec)
171
172 @staticmethod
173 def octal_str_to_decimal_int(mode):
174 try:
175 return int(mode, 8)
176 except ValueError:
177 raise VolumeException(-errno.EINVAL, "Invalid mode '{0}'".format(mode))
178
179 ### subvolume operations
180
181 def create_subvolume(self, volname, subvolname, groupname, size, mode='755', pool=None):
182 ret = 0, "", ""
183 try:
184 if not self.volume_exists(volname):
185 raise VolumeException(
186 -errno.ENOENT, "Volume '{0}' not found, create it with `ceph fs " \
187 "volume create` before trying to create subvolumes".format(volname))
188 with SubVolume(self.mgr, fs_name=volname) as sv:
189 spec = SubvolumeSpec(subvolname, groupname)
190 if not self.group_exists(sv, spec):
191 raise VolumeException(
192 -errno.ENOENT, "Subvolume group '{0}' not found, create it with " \
193 "`ceph fs subvolumegroup create` before creating subvolumes".format(groupname))
194 sv.create_subvolume(spec, size, pool=pool, mode=self.octal_str_to_decimal_int(mode))
195 except VolumeException as ve:
196 ret = self.volume_exception_to_retval(ve)
197 return ret
198
199 def remove_subvolume(self, volname, subvolname, groupname, force):
200 ret = 0, "", ""
201 try:
202 fs = self.get_fs(volname)
203 if fs:
204 with SubVolume(self.mgr, fs_name=volname) as sv:
205 spec = SubvolumeSpec(subvolname, groupname)
206 if self.group_exists(sv, spec):
207 sv.remove_subvolume(spec, force)
208 sv.purge_subvolume(spec)
209 elif not force:
210 raise VolumeException(
211 -errno.ENOENT, "Subvolume group '{0}' not found, cannot remove " \
212 "subvolume '{1}'".format(groupname, subvolname))
213 elif not force:
214 raise VolumeException(
215 -errno.ENOENT, "Volume '{0}' not found, cannot remove subvolume " \
216 "'{1}'".format(volname, subvolname))
217 except VolumeException as ve:
218 ret = self.volume_exception_to_retval(ve)
219 return ret
220
221 def subvolume_getpath(self, volname, subvolname, groupname):
222 ret = None
223 try:
224 if not self.volume_exists(volname):
225 raise VolumeException(
226 -errno.ENOENT, "Volume '{0}' not found".format(volname))
227
228 with SubVolume(self.mgr, fs_name=volname) as sv:
229 spec = SubvolumeSpec(subvolname, groupname)
230 if not self.group_exists(sv, spec):
231 raise VolumeException(
232 -errno.ENOENT, "Subvolume group '{0}' not found".format(groupname))
233 path = sv.get_subvolume_path(spec)
234 if not path:
235 raise VolumeException(
236 -errno.ENOENT, "Subvolume '{0}' not found".format(subvolname))
237 ret = 0, path, ""
238 except VolumeException as ve:
239 ret = self.volume_exception_to_retval(ve)
240 return ret
241
242 ### subvolume snapshot
243
244 def create_subvolume_snapshot(self, volname, subvolname, snapname, groupname):
245 ret = 0, "", ""
246 try:
247 if not self.volume_exists(volname):
248 raise VolumeException(
249 -errno.ENOENT, "Volume '{0}' not found, cannot create snapshot " \
250 "'{1}'".format(volname, snapname))
251
252 with SubVolume(self.mgr, fs_name=volname) as sv:
253 spec = SubvolumeSpec(subvolname, groupname)
254 if not self.group_exists(sv, spec):
255 raise VolumeException(
256 -errno.ENOENT, "Subvolume group '{0}' not found, cannot create " \
257 "snapshot '{1}'".format(groupname, snapname))
258 if not sv.get_subvolume_path(spec):
259 raise VolumeException(
260 -errno.ENOENT, "Subvolume '{0}' not found, cannot create snapshot " \
261 "'{1}'".format(subvolname, snapname))
262 sv.create_subvolume_snapshot(spec, snapname)
263 except VolumeException as ve:
264 ret = self.volume_exception_to_retval(ve)
265 return ret
266
267 def remove_subvolume_snapshot(self, volname, subvolname, snapname, groupname, force):
268 ret = 0, "", ""
269 try:
270 if self.volume_exists(volname):
271 with SubVolume(self.mgr, fs_name=volname) as sv:
272 spec = SubvolumeSpec(subvolname, groupname)
273 if self.group_exists(sv, spec):
274 if sv.get_subvolume_path(spec):
275 sv.remove_subvolume_snapshot(spec, snapname, force)
276 elif not force:
277 raise VolumeException(
278 -errno.ENOENT, "Subvolume '{0}' not found, cannot remove " \
279 "subvolume snapshot '{1}'".format(subvolname, snapname))
280 elif not force:
281 raise VolumeException(
282 -errno.ENOENT, "Subvolume group '{0}' already removed, cannot " \
283 "remove subvolume snapshot '{1}'".format(groupname, snapname))
284 elif not force:
285 raise VolumeException(
286 -errno.ENOENT, "Volume '{0}' not found, cannot remove subvolumegroup " \
287 "snapshot '{1}'".format(volname, snapname))
288 except VolumeException as ve:
289 ret = self.volume_exception_to_retval(ve)
290 return ret
291
292 ### group operations
293
294 def create_subvolume_group(self, volname, groupname, mode='755', pool=None):
295 ret = 0, "", ""
296 try:
297 if not self.volume_exists(volname):
298 raise VolumeException(
299 -errno.ENOENT, "Volume '{0}' not found, create it with `ceph fs " \
300 "volume create` before trying to create subvolume groups".format(volname))
301
302 # TODO: validate that subvol size fits in volume size
303 with SubVolume(self.mgr, fs_name=volname) as sv:
304 spec = SubvolumeSpec("", groupname)
305 sv.create_group(spec, pool=pool, mode=self.octal_str_to_decimal_int(mode))
306 except VolumeException as ve:
307 ret = self.volume_exception_to_retval(ve)
308 return ret
309
310 def remove_subvolume_group(self, volname, groupname, force):
311 ret = 0, "", ""
312 try:
313 if self.volume_exists(volname):
314 with SubVolume(self.mgr, fs_name=volname) as sv:
315 # TODO: check whether there are no subvolumes in the group
316 spec = SubvolumeSpec("", groupname)
317 sv.remove_group(spec, force)
318 elif not force:
319 raise VolumeException(
320 -errno.ENOENT, "Volume '{0}' not found, cannot remove subvolume " \
321 "group '{0}'".format(volname, groupname))
322 except VolumeException as ve:
323 ret = self.volume_exception_to_retval(ve)
324 return ret
325
326 ### group snapshot
327
328 def create_subvolume_group_snapshot(self, volname, groupname, snapname):
329 ret = 0, "", ""
330 try:
331 if not self.volume_exists(volname):
332 raise VolumeException(
333 -errno.ENOENT, "Volume '{0}' not found, cannot create snapshot " \
334 "'{1}'".format(volname, snapname))
335
336 with SubVolume(self.mgr, fs_name=volname) as sv:
337 spec = SubvolumeSpec("", groupname)
338 if not self.group_exists(sv, spec):
339 raise VolumeException(
340 -errno.ENOENT, "Subvolume group '{0}' not found, cannot create " \
341 "snapshot '{1}'".format(groupname, snapname))
342 sv.create_group_snapshot(spec, snapname)
343 except VolumeException as ve:
344 ret = self.volume_exception_to_retval(ve)
345 return ret
346
347 def remove_subvolume_group_snapshot(self, volname, groupname, snapname, force):
348 ret = 0, "", ""
349 try:
350 if self.volume_exists(volname):
351 with SubVolume(self.mgr, fs_name=volname) as sv:
352 spec = SubvolumeSpec("", groupname)
353 if self.group_exists(sv, spec):
354 sv.remove_group_snapshot(spec, snapname, force)
355 elif not force:
356 raise VolumeException(
357 -errno.ENOENT, "Subvolume group '{0}' not found, cannot " \
358 "remove it".format(groupname))
359 elif not force:
360 raise VolumeException(
361 -errno.ENOENT, "Volume '{0}' not found, cannot remove subvolumegroup " \
362 "snapshot '{1}'".format(volname, snapname))
363 except VolumeException as ve:
364 ret = self.volume_exception_to_retval(ve)
365 return ret