]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/volumes/fs/subvolume.py
2 Copyright (C) 2019 Red Hat, Inc.
4 LGPL2.1. See file COPYING.
13 from .subvolspec
import SubvolumeSpec
14 from .exception
import VolumeException
16 log
= logging
.getLogger(__name__
)
18 class SubVolume(object):
20 Combine libcephfs and librados interfaces to implement a
21 'Subvolume' concept implemented as a cephfs directory.
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.
27 Refer to subvolumes with SubvolumePath, which specifies the
28 subvolume and group IDs (both strings). The group ID may
31 In general, functions in this class are allowed raise rados.Error
32 or cephfs.Error exceptions in unexpected situations.
36 def __init__(self
, mgr
, fs_handle
):
38 self
.rados
= mgr
.rados
40 def _get_single_dir_entry(self
, dir_path
, exclude
=[]):
42 Return a directory entry in a given directory excluding passed
45 exclude
.extend((b
".", b
".."))
47 with self
.fs
.opendir(dir_path
) as d
:
48 entry
= self
.fs
.readdir(d
)
50 if entry
.d_name
not in exclude
and entry
.is_dir():
52 entry
= self
.fs
.readdir(d
)
54 except cephfs
.Error
as e
:
55 raise VolumeException(-e
.args
[0], e
.args
[1])
58 ### basic subvolume operations
60 def create_subvolume(self
, spec
, size
=None, namespace_isolated
=True, mode
=0o755, pool
=None):
62 Set up metadata, pools and auth for a subvolume.
64 This function is idempotent. It is safe to call this again
65 for an already-created subvolume, even if it is in use.
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
73 subvolpath
= spec
.subvolume_path
74 log
.info("creating subvolume with path: {0}".format(subvolpath
))
76 self
.fs
.mkdirs(subvolpath
, mode
)
81 self
.fs
.setxattr(subvolpath
, 'ceph.quota.max_bytes', str(size
).encode('utf-8'), 0)
82 except cephfs
.InvalidValue
as e
:
83 raise VolumeException(-errno
.EINVAL
, "Invalid size: '{0}'".format(size
))
86 self
.fs
.setxattr(subvolpath
, 'ceph.dir.layout.pool', pool
.encode('utf-8'), 0)
87 except cephfs
.InvalidValue
:
88 raise VolumeException(-errno
.EINVAL
,
89 "Invalid pool layout '{0}'. It must be a valid data pool".format(pool
))
91 xattr_key
= xattr_val
= None
92 if namespace_isolated
:
93 # enforce security isolation, use separate namespace for this subvolume
94 xattr_key
= 'ceph.dir.layout.pool_namespace'
95 xattr_val
= spec
.fs_namespace
97 # If subvolume's namespace layout is not set, then the subvolume's pool
98 # layout remains unset and will undesirably change with ancestor's
99 # pool layout changes.
100 xattr_key
= 'ceph.dir.layout.pool'
101 xattr_val
= self
._get
_ancestor
_xattr
(subvolpath
, "ceph.dir.layout.pool")
102 # TODO: handle error...
103 self
.fs
.setxattr(subvolpath
, xattr_key
, xattr_val
.encode('utf-8'), 0)
104 except Exception as e
:
106 # cleanup subvol path on best effort basis
107 log
.debug("cleaning up subvolume with path: {0}".format(subvolpath
))
108 self
.fs
.rmdir(subvolpath
)
110 log
.debug("failed to clean up subvolume with path: {0}".format(subvolpath
))
115 def remove_subvolume(self
, spec
, force
):
117 Make a subvolume inaccessible to guests. This function is idempotent.
118 This is the fast part of tearing down a subvolume. The subvolume will
119 get purged in the background.
121 :param spec: subvolume path specification
122 :param force: flag to ignore non-existent path (never raise exception)
126 subvolpath
= spec
.subvolume_path
127 log
.info("deleting subvolume with path: {0}".format(subvolpath
))
129 # Create the trash directory if it doesn't already exist
130 trashdir
= spec
.trash_dir
131 self
.fs
.mkdirs(trashdir
, 0o700)
133 # mangle the trash directroy entry to a random string so that subsequent
134 # subvolume create and delete with same name moves the subvolume directory
135 # to a unique trash dir (else, rename() could fail if the trash dir exist).
136 trashpath
= spec
.unique_trash_path
138 self
.fs
.rename(subvolpath
, trashpath
)
139 except cephfs
.ObjectNotFound
:
141 raise VolumeException(
142 -errno
.ENOENT
, "Subvolume '{0}' not found, cannot remove it".format(spec
.subvolume_id
))
143 except cephfs
.Error
as e
:
144 raise VolumeException(-e
.args
[0], e
.args
[1])
146 def purge_subvolume(self
, spec
, should_cancel
):
148 Finish clearing up a subvolume from the trash directory.
151 def rmtree(root_path
):
152 log
.debug("rmtree {0}".format(root_path
))
154 dir_handle
= self
.fs
.opendir(root_path
)
155 except cephfs
.ObjectNotFound
:
157 except cephfs
.Error
as e
:
158 raise VolumeException(-e
.args
[0], e
.args
[1])
159 d
= self
.fs
.readdir(dir_handle
)
160 while d
and not should_cancel():
161 if d
.d_name
not in (b
".", b
".."):
162 d_full
= os
.path
.join(root_path
, d
.d_name
)
166 self
.fs
.unlink(d_full
)
168 d
= self
.fs
.readdir(dir_handle
)
169 self
.fs
.closedir(dir_handle
)
170 # remove the directory only if we were not asked to cancel
171 # (else we would fail to remove this anyway)
172 if not should_cancel():
173 self
.fs
.rmdir(root_path
)
175 trashpath
= spec
.trash_path
176 # catch any unlink errors
179 except cephfs
.Error
as e
:
180 raise VolumeException(-e
.args
[0], e
.args
[1])
182 def get_subvolume_path(self
, spec
):
183 path
= spec
.subvolume_path
186 except cephfs
.ObjectNotFound
:
188 except cephfs
.Error
as e
:
189 raise VolumeException(-e
.args
[0], e
.args
[1])
192 def get_dir_entries(self
, path
):
194 Get the directory names in a given path
195 :param path: the given path
196 :return: the list of directory names
200 with self
.fs
.opendir(path
) as dir_handle
:
201 d
= self
.fs
.readdir(dir_handle
)
203 if (d
.d_name
not in (b
".", b
"..")) and d
.is_dir():
204 dirs
.append(d
.d_name
)
205 d
= self
.fs
.readdir(dir_handle
)
206 except cephfs
.ObjectNotFound
:
207 # When the given path is not found, we just return an empty list
209 except cephfs
.Error
as e
:
210 raise VolumeException(-e
.args
[0], e
.args
[1])
215 def create_group(self
, spec
, mode
=0o755, pool
=None):
216 path
= spec
.group_path
217 self
.fs
.mkdirs(path
, mode
)
220 pool
= self
._get
_ancestor
_xattr
(path
, "ceph.dir.layout.pool")
222 self
.fs
.setxattr(path
, 'ceph.dir.layout.pool', pool
.encode('utf-8'), 0)
223 except cephfs
.InvalidValue
:
224 raise VolumeException(-errno
.EINVAL
,
225 "Invalid pool layout '{0}'. It must be a valid data pool".format(pool
))
226 except Exception as e
:
228 # cleanup group path on best effort basis
229 log
.debug("cleaning up subvolumegroup with path: {0}".format(path
))
232 log
.debug("failed to clean up subvolumegroup with path: {0}".format(path
))
237 def remove_group(self
, spec
, force
):
238 path
= spec
.group_path
241 except cephfs
.ObjectNotFound
:
243 raise VolumeException(-errno
.ENOENT
, "Subvolume group '{0}' not found".format(spec
.group_id
))
244 except cephfs
.Error
as e
:
245 raise VolumeException(-e
.args
[0], e
.args
[1])
247 def get_group_path(self
, spec
):
248 path
= spec
.group_path
251 except cephfs
.ObjectNotFound
:
255 def _get_ancestor_xattr(self
, path
, attr
):
257 Helper for reading layout information: if this xattr is missing
258 on the requested path, keep checking parents until we find it.
261 return self
.fs
.getxattr(path
, attr
).decode('utf-8')
262 except cephfs
.NoData
:
266 return self
._get
_ancestor
_xattr
(os
.path
.split(path
)[0], attr
)
268 ### snapshot operations
270 def _snapshot_create(self
, snappath
, mode
=0o755):
272 Create a snapshot, or do nothing if it already exists.
275 self
.fs
.stat(snappath
)
276 except cephfs
.ObjectNotFound
:
277 self
.fs
.mkdir(snappath
, mode
)
278 except cephfs
.Error
as e
:
279 raise VolumeException(-e
.args
[0], e
.args
[1])
281 log
.warn("Snapshot '{0}' already exists".format(snappath
))
283 def _snapshot_delete(self
, snappath
, force
):
285 Remove a snapshot, or do nothing if it doesn't exist.
288 self
.fs
.stat(snappath
)
289 self
.fs
.rmdir(snappath
)
290 except cephfs
.ObjectNotFound
:
292 raise VolumeException(-errno
.ENOENT
, "Snapshot '{0}' not found, cannot remove it".format(snappath
))
293 except cephfs
.Error
as e
:
294 raise VolumeException(-e
.args
[0], e
.args
[1])
296 def create_subvolume_snapshot(self
, spec
, snapname
, mode
=0o755):
297 snappath
= spec
.make_subvol_snap_path(self
.rados
.conf_get('client_snapdir'), snapname
)
298 self
._snapshot
_create
(snappath
, mode
)
300 def remove_subvolume_snapshot(self
, spec
, snapname
, force
):
301 snappath
= spec
.make_subvol_snap_path(self
.rados
.conf_get('client_snapdir'), snapname
)
302 self
._snapshot
_delete
(snappath
, force
)
304 def create_group_snapshot(self
, spec
, snapname
, mode
=0o755):
305 snappath
= spec
.make_group_snap_path(self
.rados
.conf_get('client_snapdir'), snapname
)
306 self
._snapshot
_create
(snappath
, mode
)
308 def remove_group_snapshot(self
, spec
, snapname
, force
):
309 snappath
= spec
.make_group_snap_path(self
.rados
.conf_get('client_snapdir'), snapname
)
310 return self
._snapshot
_delete
(snappath
, force
)
312 def get_trash_entry(self
, spec
, exclude
):
314 trashdir
= spec
.trash_dir
315 return self
._get
_single
_dir
_entry
(trashdir
, exclude
)
316 except VolumeException
as ve
:
317 if ve
.errno
== -errno
.ENOENT
:
318 # trash dir does not exist yet, signal success
322 ### context manager routines
327 def __exit__(self
, exc_type
, exc_val
, exc_tb
):