]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/volumes/fs/subvolume.py
import 14.2.4 nautilus point release
[ceph.git] / ceph / src / pybind / mgr / volumes / fs / subvolume.py
1 """
2 Copyright (C) 2019 Red Hat, Inc.
3
4 LGPL2.1. See file COPYING.
5 """
6
7 import logging
8 import os
9 import errno
10
11 import cephfs
12
13 from .subvolspec import SubvolumeSpec
14 from .exception import VolumeException
15
16 log = logging.getLogger(__name__)
17
18 class SubVolume(object):
19 """
20 Combine libcephfs and librados interfaces to implement a
21 'Subvolume' concept implemented as a cephfs directory.
22
23 Additionally, subvolumes may be in a 'Group'. Conveniently,
24 subvolumes are a lot like manila shares, and groups are a lot
25 like manila consistency groups.
26
27 Refer to subvolumes with SubvolumePath, which specifies the
28 subvolume and group IDs (both strings). The group ID may
29 be None.
30
31 In general, functions in this class are allowed raise rados.Error
32 or cephfs.Error exceptions in unexpected situations.
33 """
34
35
36 def __init__(self, mgr, fs_handle):
37 self.fs = fs_handle
38 self.rados = mgr.rados
39
40 def _get_single_dir_entry(self, dir_path, exclude=[]):
41 """
42 Return a directory entry in a given directory excluding passed
43 in entries.
44 """
45 exclude.extend((b".", b".."))
46 try:
47 with self.fs.opendir(dir_path) as d:
48 entry = self.fs.readdir(d)
49 while entry:
50 if entry.d_name not in exclude and entry.is_dir():
51 return entry.d_name
52 entry = self.fs.readdir(d)
53 return None
54 except cephfs.Error as e:
55 raise VolumeException(-e.args[0], e.args[1])
56
57
58 ### basic subvolume operations
59
60 def create_subvolume(self, spec, size=None, namespace_isolated=True, mode=0o755, pool=None):
61 """
62 Set up metadata, pools and auth for a subvolume.
63
64 This function is idempotent. It is safe to call this again
65 for an already-created subvolume, even if it is in use.
66
67 :param spec: subvolume path specification
68 :param size: In bytes, or None for no size limit
69 :param namespace_isolated: If true, use separate RADOS namespace for this subvolume
70 :param pool: the RADOS pool where the data objects of the subvolumes will be stored
71 :return: None
72 """
73 subvolpath = spec.subvolume_path
74 log.info("creating subvolume with path: {0}".format(subvolpath))
75
76 self.fs.mkdirs(subvolpath, mode)
77
78 if size is not None:
79 self.fs.setxattr(subvolpath, 'ceph.quota.max_bytes', str(size).encode('utf-8'), 0)
80
81 if pool:
82 self.fs.setxattr(subvolpath, 'ceph.dir.layout.pool', pool.encode('utf-8'), 0)
83
84 xattr_key = xattr_val = None
85 if namespace_isolated:
86 # enforce security isolation, use separate namespace for this subvolume
87 xattr_key = 'ceph.dir.layout.pool_namespace'
88 xattr_val = spec.fs_namespace
89 elif not pool:
90 # If subvolume's namespace layout is not set, then the subvolume's pool
91 # layout remains unset and will undesirably change with ancestor's
92 # pool layout changes.
93 xattr_key = 'ceph.dir.layout.pool'
94 xattr_val = self._get_ancestor_xattr(subvolpath, "ceph.dir.layout.pool")
95 # TODO: handle error...
96 self.fs.setxattr(subvolpath, xattr_key, xattr_val.encode('utf-8'), 0)
97
98 def remove_subvolume(self, spec, force):
99 """
100 Make a subvolume inaccessible to guests. This function is idempotent.
101 This is the fast part of tearing down a subvolume. The subvolume will
102 get purged in the background.
103
104 :param spec: subvolume path specification
105 :param force: flag to ignore non-existent path (never raise exception)
106 :return: None
107 """
108
109 subvolpath = spec.subvolume_path
110 log.info("deleting subvolume with path: {0}".format(subvolpath))
111
112 # Create the trash directory if it doesn't already exist
113 trashdir = spec.trash_dir
114 self.fs.mkdirs(trashdir, 0o700)
115
116 # mangle the trash directroy entry to a random string so that subsequent
117 # subvolume create and delete with same name moves the subvolume directory
118 # to a unique trash dir (else, rename() could fail if the trash dir exist).
119 trashpath = spec.unique_trash_path
120 try:
121 self.fs.rename(subvolpath, trashpath)
122 except cephfs.ObjectNotFound:
123 if not force:
124 raise VolumeException(
125 -errno.ENOENT, "Subvolume '{0}' not found, cannot remove it".format(spec.subvolume_id))
126 except cephfs.Error as e:
127 raise VolumeException(-e.args[0], e.args[1])
128
129 def purge_subvolume(self, spec, should_cancel):
130 """
131 Finish clearing up a subvolume from the trash directory.
132 """
133
134 def rmtree(root_path):
135 log.debug("rmtree {0}".format(root_path))
136 try:
137 dir_handle = self.fs.opendir(root_path)
138 except cephfs.ObjectNotFound:
139 return
140 except cephfs.Error as e:
141 raise VolumeException(-e.args[0], e.args[1])
142 d = self.fs.readdir(dir_handle)
143 while d and not should_cancel():
144 if d.d_name not in (b".", b".."):
145 d_full = os.path.join(root_path, d.d_name)
146 if d.is_dir():
147 rmtree(d_full)
148 else:
149 self.fs.unlink(d_full)
150
151 d = self.fs.readdir(dir_handle)
152 self.fs.closedir(dir_handle)
153 # remove the directory only if we were not asked to cancel
154 # (else we would fail to remove this anyway)
155 if not should_cancel():
156 self.fs.rmdir(root_path)
157
158 trashpath = spec.trash_path
159 # catch any unlink errors
160 try:
161 rmtree(trashpath)
162 except cephfs.Error as e:
163 raise VolumeException(-e.args[0], e.args[1])
164
165 def get_subvolume_path(self, spec):
166 path = spec.subvolume_path
167 try:
168 self.fs.stat(path)
169 except cephfs.ObjectNotFound:
170 return None
171 except cephfs.Error as e:
172 raise VolumeException(-e.args[0], e.args[1])
173 return path
174
175 ### group operations
176
177 def create_group(self, spec, mode=0o755, pool=None):
178 path = spec.group_path
179 self.fs.mkdirs(path, mode)
180 if not pool:
181 pool = self._get_ancestor_xattr(path, "ceph.dir.layout.pool")
182 self.fs.setxattr(path, 'ceph.dir.layout.pool', pool.encode('utf-8'), 0)
183
184 def remove_group(self, spec, force):
185 path = spec.group_path
186 try:
187 self.fs.rmdir(path)
188 except cephfs.ObjectNotFound:
189 if not force:
190 raise VolumeException(-errno.ENOENT, "Subvolume group '{0}' not found".format(spec.group_id))
191 except cephfs.Error as e:
192 raise VolumeException(-e.args[0], e.args[1])
193
194 def get_group_path(self, spec):
195 path = spec.group_path
196 try:
197 self.fs.stat(path)
198 except cephfs.ObjectNotFound:
199 return None
200 return path
201
202 def _get_ancestor_xattr(self, path, attr):
203 """
204 Helper for reading layout information: if this xattr is missing
205 on the requested path, keep checking parents until we find it.
206 """
207 try:
208 return self.fs.getxattr(path, attr).decode('utf-8')
209 except cephfs.NoData:
210 if path == "/":
211 raise
212 else:
213 return self._get_ancestor_xattr(os.path.split(path)[0], attr)
214
215 ### snapshot operations
216
217 def _snapshot_create(self, snappath, mode=0o755):
218 """
219 Create a snapshot, or do nothing if it already exists.
220 """
221 try:
222 self.fs.stat(snappath)
223 except cephfs.ObjectNotFound:
224 self.fs.mkdir(snappath, mode)
225 except cephfs.Error as e:
226 raise VolumeException(-e.args[0], e.args[1])
227 else:
228 log.warn("Snapshot '{0}' already exists".format(snappath))
229
230 def _snapshot_delete(self, snappath, force):
231 """
232 Remove a snapshot, or do nothing if it doesn't exist.
233 """
234 try:
235 self.fs.stat(snappath)
236 self.fs.rmdir(snappath)
237 except cephfs.ObjectNotFound:
238 if not force:
239 raise VolumeException(-errno.ENOENT, "Snapshot '{0}' not found, cannot remove it".format(snappath))
240 except cephfs.Error as e:
241 raise VolumeException(-e.args[0], e.args[1])
242
243 def create_subvolume_snapshot(self, spec, snapname, mode=0o755):
244 snappath = spec.make_subvol_snap_path(self.rados.conf_get('client_snapdir'), snapname)
245 self._snapshot_create(snappath, mode)
246
247 def remove_subvolume_snapshot(self, spec, snapname, force):
248 snappath = spec.make_subvol_snap_path(self.rados.conf_get('client_snapdir'), snapname)
249 self._snapshot_delete(snappath, force)
250
251 def create_group_snapshot(self, spec, snapname, mode=0o755):
252 snappath = spec.make_group_snap_path(self.rados.conf_get('client_snapdir'), snapname)
253 self._snapshot_create(snappath, mode)
254
255 def remove_group_snapshot(self, spec, snapname, force):
256 snappath = spec.make_group_snap_path(self.rados.conf_get('client_snapdir'), snapname)
257 return self._snapshot_delete(snappath, force)
258
259 def get_trash_entry(self, spec, exclude):
260 try:
261 trashdir = spec.trash_dir
262 return self._get_single_dir_entry(trashdir, exclude)
263 except VolumeException as ve:
264 if ve.errno == -errno.ENOENT:
265 # trash dir does not exist yet, signal success
266 return None
267 raise
268
269 ### context manager routines
270
271 def __enter__(self):
272 return self
273
274 def __exit__(self, exc_type, exc_val, exc_tb):
275 pass