]>
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
)
79 self
.fs
.setxattr(subvolpath
, 'ceph.quota.max_bytes', str(size
).encode('utf-8'), 0)
82 self
.fs
.setxattr(subvolpath
, 'ceph.dir.layout.pool', pool
.encode('utf-8'), 0)
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
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)
98 def remove_subvolume(self
, spec
, force
):
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.
104 :param spec: subvolume path specification
105 :param force: flag to ignore non-existent path (never raise exception)
109 subvolpath
= spec
.subvolume_path
110 log
.info("deleting subvolume with path: {0}".format(subvolpath
))
112 # Create the trash directory if it doesn't already exist
113 trashdir
= spec
.trash_dir
114 self
.fs
.mkdirs(trashdir
, 0o700)
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
121 self
.fs
.rename(subvolpath
, trashpath
)
122 except cephfs
.ObjectNotFound
:
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])
129 def purge_subvolume(self
, spec
, should_cancel
):
131 Finish clearing up a subvolume from the trash directory.
134 def rmtree(root_path
):
135 log
.debug("rmtree {0}".format(root_path
))
137 dir_handle
= self
.fs
.opendir(root_path
)
138 except cephfs
.ObjectNotFound
:
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
)
149 self
.fs
.unlink(d_full
)
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
)
158 trashpath
= spec
.trash_path
159 # catch any unlink errors
162 except cephfs
.Error
as e
:
163 raise VolumeException(-e
.args
[0], e
.args
[1])
165 def get_subvolume_path(self
, spec
):
166 path
= spec
.subvolume_path
169 except cephfs
.ObjectNotFound
:
171 except cephfs
.Error
as e
:
172 raise VolumeException(-e
.args
[0], e
.args
[1])
177 def create_group(self
, spec
, mode
=0o755, pool
=None):
178 path
= spec
.group_path
179 self
.fs
.mkdirs(path
, mode
)
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)
184 def remove_group(self
, spec
, force
):
185 path
= spec
.group_path
188 except cephfs
.ObjectNotFound
:
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])
194 def get_group_path(self
, spec
):
195 path
= spec
.group_path
198 except cephfs
.ObjectNotFound
:
202 def _get_ancestor_xattr(self
, path
, attr
):
204 Helper for reading layout information: if this xattr is missing
205 on the requested path, keep checking parents until we find it.
208 return self
.fs
.getxattr(path
, attr
).decode('utf-8')
209 except cephfs
.NoData
:
213 return self
._get
_ancestor
_xattr
(os
.path
.split(path
)[0], attr
)
215 ### snapshot operations
217 def _snapshot_create(self
, snappath
, mode
=0o755):
219 Create a snapshot, or do nothing if it already exists.
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])
228 log
.warn("Snapshot '{0}' already exists".format(snappath
))
230 def _snapshot_delete(self
, snappath
, force
):
232 Remove a snapshot, or do nothing if it doesn't exist.
235 self
.fs
.stat(snappath
)
236 self
.fs
.rmdir(snappath
)
237 except cephfs
.ObjectNotFound
:
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])
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
)
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
)
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
)
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
)
259 def get_trash_entry(self
, spec
, exclude
):
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
269 ### context manager routines
274 def __exit__(self
, exc_type
, exc_val
, exc_tb
):