]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | import os |
2 | import stat | |
3 | import uuid | |
4 | import errno | |
5 | import logging | |
e306af50 | 6 | from datetime import datetime |
92f5a8d4 TL |
7 | |
8 | import cephfs | |
9 | ||
10 | from .metadata_manager import MetadataManager | |
11 | from .subvolume_base import SubvolumeBase | |
12 | from ..op_sm import OpSm | |
13 | from ..template import SubvolumeTemplate | |
14 | from ..snapshot_util import mksnap, rmsnap | |
15 | from ...exception import IndexException, OpSmException, VolumeException, MetadataMgrException | |
16 | from ...fs_util import listdir | |
17 | ||
18 | from ..clone_index import open_clone_index, create_clone_index | |
19 | ||
20 | log = logging.getLogger(__name__) | |
21 | ||
22 | class 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") |