]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/volumes/fs/operations/group.py
69e3959d7aa3d2ab684967854c9613382fe4d933
[ceph.git] / ceph / src / pybind / mgr / volumes / fs / operations / group.py
1 import os
2 import errno
3 import logging
4 from contextlib import contextmanager
5
6 import cephfs
7
8 from .snapshot_util import mksnap, rmsnap
9 from .pin_util import pin
10 from .template import GroupTemplate
11 from ..fs_util import listdir, listsnaps, get_ancestor_xattr, create_base_dir
12 from ..exception import VolumeException
13
14 log = logging.getLogger(__name__)
15
16 class Group(GroupTemplate):
17 # Reserved subvolume group name which we use in paths for subvolumes
18 # that are not assigned to a group (i.e. created with group=None)
19 NO_GROUP_NAME = "_nogroup"
20
21 def __init__(self, fs, vol_spec, groupname):
22 assert groupname != Group.NO_GROUP_NAME
23 self.fs = fs
24 self.user_id = None
25 self.group_id = None
26 self.vol_spec = vol_spec
27 self.groupname = groupname if groupname else Group.NO_GROUP_NAME
28
29 @property
30 def path(self):
31 return os.path.join(self.vol_spec.base_dir.encode('utf-8'), self.groupname.encode('utf-8'))
32
33 @property
34 def group_name(self):
35 return self.groupname
36
37 @property
38 def uid(self):
39 return self.user_id
40
41 @uid.setter
42 def uid(self, val):
43 self.user_id = val
44
45 @property
46 def gid(self):
47 return self.group_id
48
49 @gid.setter
50 def gid(self, val):
51 self.group_id = val
52
53 def is_default_group(self):
54 return self.groupname == Group.NO_GROUP_NAME
55
56 def list_subvolumes(self):
57 try:
58 return listdir(self.fs, self.path)
59 except VolumeException as ve:
60 # listing a default group when it's not yet created
61 if ve.errno == -errno.ENOENT and self.is_default_group():
62 return []
63 raise
64
65 def pin(self, pin_type, pin_setting):
66 return pin(self.fs, self.path, pin_type, pin_setting)
67
68 def create_snapshot(self, snapname):
69 snappath = os.path.join(self.path,
70 self.vol_spec.snapshot_dir_prefix.encode('utf-8'),
71 snapname.encode('utf-8'))
72 mksnap(self.fs, snappath)
73
74 def remove_snapshot(self, snapname):
75 snappath = os.path.join(self.path,
76 self.vol_spec.snapshot_dir_prefix.encode('utf-8'),
77 snapname.encode('utf-8'))
78 rmsnap(self.fs, snappath)
79
80 def list_snapshots(self):
81 try:
82 dirpath = os.path.join(self.path,
83 self.vol_spec.snapshot_dir_prefix.encode('utf-8'))
84 return listsnaps(self.fs, self.vol_spec, dirpath, filter_inherited_snaps=True)
85 except VolumeException as ve:
86 if ve.errno == -errno.ENOENT:
87 return []
88 raise
89
90 def create_group(fs, vol_spec, groupname, pool, mode, uid, gid):
91 """
92 create a subvolume group.
93
94 :param fs: ceph filesystem handle
95 :param vol_spec: volume specification
96 :param groupname: subvolume group name
97 :param pool: the RADOS pool where the data objects of the subvolumes will be stored
98 :param mode: the user permissions
99 :param uid: the user identifier
100 :param gid: the group identifier
101 :return: None
102 """
103 group = Group(fs, vol_spec, groupname)
104 path = group.path
105 vol_spec_base_dir = group.vol_spec.base_dir.encode('utf-8')
106
107 # create vol_spec base directory with default mode(0o755) if it doesn't exist
108 create_base_dir(fs, vol_spec_base_dir, vol_spec.DEFAULT_MODE)
109 fs.mkdir(path, mode)
110 try:
111 if not pool:
112 pool = get_ancestor_xattr(fs, path, "ceph.dir.layout.pool")
113 try:
114 fs.setxattr(path, 'ceph.dir.layout.pool', pool.encode('utf-8'), 0)
115 except cephfs.InvalidValue:
116 raise VolumeException(-errno.EINVAL,
117 "Invalid pool layout '{0}'. It must be a valid data pool".format(pool))
118 if uid is None:
119 uid = 0
120 else:
121 try:
122 uid = int(uid)
123 if uid < 0:
124 raise ValueError
125 except ValueError:
126 raise VolumeException(-errno.EINVAL, "invalid UID")
127 if gid is None:
128 gid = 0
129 else:
130 try:
131 gid = int(gid)
132 if gid < 0:
133 raise ValueError
134 except ValueError:
135 raise VolumeException(-errno.EINVAL, "invalid GID")
136 fs.chown(path, uid, gid)
137 except (cephfs.Error, VolumeException) as e:
138 try:
139 # cleanup group path on best effort basis
140 log.debug("cleaning up subvolume group path: {0}".format(path))
141 fs.rmdir(path)
142 except cephfs.Error as ce:
143 log.debug("failed to clean up subvolume group {0} with path: {1} ({2})".format(groupname, path, ce))
144 if isinstance(e, cephfs.Error):
145 e = VolumeException(-e.args[0], e.args[1])
146 raise e
147
148 def remove_group(fs, vol_spec, groupname):
149 """
150 remove a subvolume group.
151
152 :param fs: ceph filesystem handle
153 :param vol_spec: volume specification
154 :param groupname: subvolume group name
155 :return: None
156 """
157 group = Group(fs, vol_spec, groupname)
158 try:
159 fs.rmdir(group.path)
160 except cephfs.Error as e:
161 if e.args[0] == errno.ENOENT:
162 raise VolumeException(-errno.ENOENT, "subvolume group '{0}' does not exist".format(groupname))
163 raise VolumeException(-e.args[0], e.args[1])
164
165 @contextmanager
166 def open_group(fs, vol_spec, groupname):
167 """
168 open a subvolume group. This API is to be used as a context manager.
169
170 :param fs: ceph filesystem handle
171 :param vol_spec: volume specification
172 :param groupname: subvolume group name
173 :return: yields a group object (subclass of GroupTemplate)
174 """
175 group = Group(fs, vol_spec, groupname)
176 try:
177 st = fs.stat(group.path)
178 group.uid = int(st.st_uid)
179 group.gid = int(st.st_gid)
180 except cephfs.Error as e:
181 if e.args[0] == errno.ENOENT:
182 if not group.is_default_group():
183 raise VolumeException(-errno.ENOENT, "subvolume group '{0}' does not exist".format(groupname))
184 else:
185 raise VolumeException(-e.args[0], e.args[1])
186 yield group
187
188 @contextmanager
189 def open_group_unique(fs, vol_spec, groupname, c_group, c_groupname):
190 if groupname == c_groupname:
191 yield c_group
192 else:
193 with open_group(fs, vol_spec, groupname) as group:
194 yield group