]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py
import 15.2.4
[ceph.git] / ceph / src / pybind / mgr / volumes / fs / operations / versions / subvolume_v1.py
CommitLineData
92f5a8d4
TL
1import os
2import stat
3import uuid
4import errno
5import logging
e306af50 6from datetime import datetime
92f5a8d4
TL
7
8import cephfs
9
10from .metadata_manager import MetadataManager
11from .subvolume_base import SubvolumeBase
12from ..op_sm import OpSm
13from ..template import SubvolumeTemplate
14from ..snapshot_util import mksnap, rmsnap
15from ...exception import IndexException, OpSmException, VolumeException, MetadataMgrException
16from ...fs_util import listdir
17
18from ..clone_index import open_clone_index, create_clone_index
19
20log = logging.getLogger(__name__)
21
22class SubvolumeV1(SubvolumeBase, SubvolumeTemplate):
23 VERSION = 1
24
25 @staticmethod
26 def version():
27 return SubvolumeV1.VERSION
28
29 @property
30 def path(self):
31 try:
32 # no need to stat the path -- open() does that
33 return self.metadata_mgr.get_global_option('path').encode('utf-8')
34 except MetadataMgrException as me:
35 raise VolumeException(-errno.EINVAL, "error fetching subvolume metadata")
36
37 def create(self, size, isolate_nspace, pool, mode, uid, gid):
38 subvolume_type = SubvolumeBase.SUBVOLUME_TYPE_NORMAL
39 try:
40 initial_state = OpSm.get_init_state(subvolume_type)
41 except OpSmException as oe:
42 raise VolumeException(-errno.EINVAL, "subvolume creation failed: internal error")
43
44 subvol_path = os.path.join(self.base_path, str(uuid.uuid4()).encode('utf-8'))
45 try:
46 # create directory and set attributes
47 self.fs.mkdirs(subvol_path, mode)
e306af50 48 self.set_attrs(subvol_path, size, isolate_nspace, pool, uid, gid)
92f5a8d4
TL
49
50 # persist subvolume metadata
51 qpath = subvol_path.decode('utf-8')
52 self.init_config(SubvolumeV1.VERSION, subvolume_type, qpath, initial_state)
53 except (VolumeException, MetadataMgrException, cephfs.Error) as e:
54 try:
55 log.info("cleaning up subvolume with path: {0}".format(self.subvolname))
56 self.remove()
57 except VolumeException as ve:
58 log.info("failed to cleanup subvolume '{0}' ({1})".format(self.subvolname, ve))
59
60 if isinstance(e, MetadataMgrException):
61 log.error("metadata manager exception: {0}".format(e))
62 e = VolumeException(-errno.EINVAL, "exception in subvolume metadata")
63 elif isinstance(e, cephfs.Error):
64 e = VolumeException(-e.args[0], e.args[1])
65 raise e
66
67 def add_clone_source(self, volname, subvolume, snapname, flush=False):
68 self.metadata_mgr.add_section("source")
69 self.metadata_mgr.update_section("source", "volume", volname)
70 if not subvolume.group.is_default_group():
71 self.metadata_mgr.update_section("source", "group", subvolume.group_name)
72 self.metadata_mgr.update_section("source", "subvolume", subvolume.subvol_name)
73 self.metadata_mgr.update_section("source", "snapshot", snapname)
74 if flush:
75 self.metadata_mgr.flush()
76
77 def remove_clone_source(self, flush=False):
78 self.metadata_mgr.remove_section("source")
79 if flush:
80 self.metadata_mgr.flush()
81
82 def create_clone(self, pool, source_volname, source_subvolume, snapname):
83 subvolume_type = SubvolumeBase.SUBVOLUME_TYPE_CLONE
84 try:
85 initial_state = OpSm.get_init_state(subvolume_type)
86 except OpSmException as oe:
87 raise VolumeException(-errno.EINVAL, "clone failed: internal error")
88
89 subvol_path = os.path.join(self.base_path, str(uuid.uuid4()).encode('utf-8'))
90 try:
91 # create directory and set attributes
92 self.fs.mkdirs(subvol_path, source_subvolume.mode)
e306af50 93 self.set_attrs(subvol_path, None, None, pool, source_subvolume.uid, source_subvolume.gid)
92f5a8d4
TL
94
95 # persist subvolume metadata and clone source
96 qpath = subvol_path.decode('utf-8')
97 self.metadata_mgr.init(SubvolumeV1.VERSION, subvolume_type, qpath, initial_state)
98 self.add_clone_source(source_volname, source_subvolume, snapname)
99 self.metadata_mgr.flush()
100 except (VolumeException, MetadataMgrException, cephfs.Error) as e:
101 try:
102 log.info("cleaning up subvolume with path: {0}".format(self.subvolname))
103 self.remove()
104 except VolumeException as ve:
105 log.info("failed to cleanup subvolume '{0}' ({1})".format(self.subvolname, ve))
106
107 if isinstance(e, MetadataMgrException):
108 log.error("metadata manager exception: {0}".format(e))
109 e = VolumeException(-errno.EINVAL, "exception in subvolume metadata")
110 elif isinstance(e, cephfs.Error):
111 e = VolumeException(-e.args[0], e.args[1])
112 raise e
113
114 def open(self, need_complete=True, expected_types=[]):
115 try:
116 self.metadata_mgr.refresh()
117 subvol_path = self.path
118 log.debug("refreshed metadata, checking subvolume path '{0}'".format(subvol_path))
119 st = self.fs.stat(subvol_path)
120 etype = self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_TYPE)
121 if len(expected_types) and not etype in expected_types:
122 raise VolumeException(-errno.ENOTSUP, "subvolume '{0}' is not {1}".format(
123 self.subvolname, "a {0}".format(expected_types[0]) if len(expected_types) == 1 else \
124 "one of types ({0})".format(",".join(expected_types))))
125 if need_complete:
126 estate = self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_STATE)
127 if not OpSm.is_final_state(estate):
128 raise VolumeException(-errno.EAGAIN, "subvolume '{0}' is not ready for use".format(self.subvolname))
129 self.uid = int(st.st_uid)
130 self.gid = int(st.st_gid)
131 self.mode = int(st.st_mode & ~stat.S_IFMT(st.st_mode))
132 except MetadataMgrException as me:
133 if me.errno == -errno.ENOENT:
134 raise VolumeException(-errno.ENOENT, "subvolume '{0}' does not exist".format(self.subvolname))
135 raise VolumeException(me.args[0], me.args[1])
136 except cephfs.ObjectNotFound:
137 log.debug("missing subvolume path '{0}' for subvolume '{1}'".format(subvol_path, self.subvolname))
138 raise VolumeException(-errno.ENOENT, "mount path missing for subvolume '{0}'".format(self.subvolname))
139 except cephfs.Error as e:
140 raise VolumeException(-e.args[0], e.args[1])
141
142 def _get_clone_source(self):
143 try:
144 clone_source = {
145 'volume' : self.metadata_mgr.get_option("source", "volume"),
146 'subvolume': self.metadata_mgr.get_option("source", "subvolume"),
147 'snapshot' : self.metadata_mgr.get_option("source", "snapshot"),
148 }
149
150 try:
151 clone_source["group"] = self.metadata_mgr.get_option("source", "group")
152 except MetadataMgrException as me:
153 if me.errno == -errno.ENOENT:
154 pass
155 else:
156 raise
157 except MetadataMgrException as me:
158 raise VolumeException(-errno.EINVAL, "error fetching subvolume metadata")
159 return clone_source
160
161 @property
162 def status(self):
163 state = self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_STATE)
164 subvolume_type = self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_TYPE)
165 subvolume_status = {
166 'state' : state
167 }
168 if not OpSm.is_final_state(state) and subvolume_type == SubvolumeBase.SUBVOLUME_TYPE_CLONE:
169 subvolume_status["source"] = self._get_clone_source()
170 return subvolume_status
171
172 @property
173 def state(self):
174 return self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_STATE)
175
176 @state.setter
177 def state(self, val):
178 state = val[0]
179 flush = val[1]
180 self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_STATE, state)
181 if flush:
182 self.metadata_mgr.flush()
183
184 def remove(self):
185 self.trash_base_dir()
186
187 def resize(self, newsize, noshrink):
188 subvol_path = self.path
189 return self._resize(subvol_path, newsize, noshrink)
190
191 def snapshot_path(self, snapname):
192 return os.path.join(self.path,
193 self.vol_spec.snapshot_dir_prefix.encode('utf-8'),
194 snapname.encode('utf-8'))
195
196 def create_snapshot(self, snapname):
197 snappath = self.snapshot_path(snapname)
198 mksnap(self.fs, snappath)
199
200 def is_snapshot_protected(self, snapname):
201 try:
202 self.metadata_mgr.get_option('protected snaps', snapname)
203 except MetadataMgrException as me:
204 if me.errno == -errno.ENOENT:
205 return False
206 else:
e306af50 207 log.warning("error checking protected snap {0} ({1})".format(snapname, me))
92f5a8d4
TL
208 raise VolumeException(-errno.EINVAL, "snapshot protection check failed")
209 else:
210 return True
211
212 def has_pending_clones(self, snapname):
213 try:
214 return self.metadata_mgr.section_has_item('clone snaps', snapname)
215 except MetadataMgrException as me:
216 if me.errno == -errno.ENOENT:
217 return False
218 raise
219
220 def remove_snapshot(self, snapname):
221 if self.is_snapshot_protected(snapname):
222 raise VolumeException(-errno.EINVAL, "snapshot '{0}' is protected".format(snapname))
223 snappath = self.snapshot_path(snapname)
224 rmsnap(self.fs, snappath)
225
e306af50
TL
226 def snapshot_info(self, snapname):
227 snappath = self.snapshot_path(snapname)
228 snap_info = {}
229 try:
230 snap_attrs = {'created_at':'ceph.snap.btime', 'size':'ceph.dir.rbytes',
231 'data_pool':'ceph.dir.layout.pool'}
232 for key, val in snap_attrs.items():
233 snap_info[key] = self.fs.getxattr(snappath, val)
234 return {'size': int(snap_info['size']),
235 'created_at': str(datetime.fromtimestamp(float(snap_info['created_at']))),
236 'data_pool': snap_info['data_pool'].decode('utf-8'),
237 'protected': "yes" if self.is_snapshot_protected(snapname) else "no",
238 'has_pending_clones': "yes" if self.has_pending_clones(snapname) else "no"}
239 except cephfs.Error as e:
240 if e.errno == errno.ENOENT:
241 raise VolumeException(-errno.ENOENT,
242 "snapshot '{0}' doesnot exist".format(snapname))
243 raise VolumeException(-e.args[0], e.args[1])
244
92f5a8d4
TL
245 def list_snapshots(self):
246 try:
247 dirpath = os.path.join(self.path,
248 self.vol_spec.snapshot_dir_prefix.encode('utf-8'))
249 return listdir(self.fs, dirpath)
250 except VolumeException as ve:
251 if ve.errno == -errno.ENOENT:
252 return []
253 raise
254
255 def _protect_snapshot(self, snapname):
256 try:
257 self.metadata_mgr.add_section("protected snaps")
258 self.metadata_mgr.update_section("protected snaps", snapname, "1")
259 self.metadata_mgr.flush()
260 except MetadataMgrException as me:
e306af50 261 log.warning("error updating protected snap list ({0})".format(me))
92f5a8d4
TL
262 raise VolumeException(-errno.EINVAL, "error protecting snapshot")
263
264 def _unprotect_snapshot(self, snapname):
265 try:
266 self.metadata_mgr.remove_option("protected snaps", snapname)
267 self.metadata_mgr.flush()
268 except MetadataMgrException as me:
e306af50 269 log.warning("error updating protected snap list ({0})".format(me))
92f5a8d4
TL
270 raise VolumeException(-errno.EINVAL, "error unprotecting snapshot")
271
272 def protect_snapshot(self, snapname):
273 if not snapname.encode('utf-8') in self.list_snapshots():
274 raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname))
275 if self.is_snapshot_protected(snapname):
276 raise VolumeException(-errno.EEXIST, "snapshot '{0}' is already protected".format(snapname))
277 self._protect_snapshot(snapname)
278
279 def unprotect_snapshot(self, snapname):
280 if not snapname.encode('utf-8') in self.list_snapshots():
281 raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname))
282 if not self.is_snapshot_protected(snapname):
283 raise VolumeException(-errno.EEXIST, "snapshot '{0}' is not protected".format(snapname))
284 if self.has_pending_clones(snapname):
285 raise VolumeException(-errno.EEXIST, "snapshot '{0}' has pending clones".format(snapname))
286 self._unprotect_snapshot(snapname)
287
288 def _add_snap_clone(self, track_id, snapname):
289 self.metadata_mgr.add_section("clone snaps")
290 self.metadata_mgr.update_section("clone snaps", track_id, snapname)
291 self.metadata_mgr.flush()
292
293 def _remove_snap_clone(self, track_id):
294 self.metadata_mgr.remove_option("clone snaps", track_id)
295 self.metadata_mgr.flush()
296
297 def attach_snapshot(self, snapname, tgt_subvolume):
298 if not snapname.encode('utf-8') in self.list_snapshots():
299 raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname))
300 if not self.is_snapshot_protected(snapname):
301 raise VolumeException(-errno.EINVAL, "snapshot '{0}' is not protected".format(snapname))
302 try:
303 create_clone_index(self.fs, self.vol_spec)
304 with open_clone_index(self.fs, self.vol_spec) as index:
305 track_idx = index.track(tgt_subvolume.base_path)
306 self._add_snap_clone(track_idx, snapname)
307 except (IndexException, MetadataMgrException) as e:
e306af50 308 log.warning("error creating clone index: {0}".format(e))
92f5a8d4
TL
309 raise VolumeException(-errno.EINVAL, "error cloning subvolume")
310
311 def detach_snapshot(self, snapname, track_id):
312 if not snapname.encode('utf-8') in self.list_snapshots():
313 raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname))
314 try:
315 with open_clone_index(self.fs, self.vol_spec) as index:
316 index.untrack(track_id)
317 self._remove_snap_clone(track_id)
318 except (IndexException, MetadataMgrException) as e:
e306af50 319 log.warning("error delining snapshot from clone: {0}".format(e))
92f5a8d4 320 raise VolumeException(-errno.EINVAL, "error delinking snapshot from clone")