]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/volumes/fs/operations/versions/auth_metadata.py
import quincy beta 17.1.0
[ceph.git] / ceph / src / pybind / mgr / volumes / fs / operations / versions / auth_metadata.py
CommitLineData
cd265ab1
TL
1from contextlib import contextmanager
2import os
3import fcntl
4import json
5import logging
6import struct
7import uuid
8
9import cephfs
10
11from ..group import Group
12
13log = logging.getLogger(__name__)
14
20effc67 15
cd265ab1
TL
16class AuthMetadataError(Exception):
17 pass
18
20effc67 19
cd265ab1
TL
20class AuthMetadataManager(object):
21
22 # Current version
23 version = 6
24
25 # Filename extensions for meta files.
26 META_FILE_EXT = ".meta"
27 DEFAULT_VOL_PREFIX = "/volumes"
28
29 def __init__(self, fs):
30 self.fs = fs
31 self._id = struct.unpack(">Q", uuid.uuid1().bytes[0:8])[0]
32 self.volume_prefix = self.DEFAULT_VOL_PREFIX
33
34 def _to_bytes(self, param):
35 '''
36 Helper method that returns byte representation of the given parameter.
37 '''
38 if isinstance(param, str):
39 return param.encode('utf-8')
40 elif param is None:
41 return param
42 else:
43 return str(param).encode('utf-8')
44
45 def _subvolume_metadata_path(self, group_name, subvol_name):
46 return os.path.join(self.volume_prefix, "_{0}:{1}{2}".format(
47 group_name if group_name != Group.NO_GROUP_NAME else "",
48 subvol_name,
49 self.META_FILE_EXT))
50
51 def _check_compat_version(self, compat_version):
52 if self.version < compat_version:
53 msg = ("The current version of AuthMetadataManager, version {0} "
54 "does not support the required feature. Need version {1} "
55 "or greater".format(self.version, compat_version)
20effc67 56 )
cd265ab1
TL
57 log.error(msg)
58 raise AuthMetadataError(msg)
59
60 def _metadata_get(self, path):
61 """
62 Return a deserialized JSON object, or None
63 """
64 fd = self.fs.open(path, "r")
65 # TODO iterate instead of assuming file < 4MB
66 read_bytes = self.fs.read(fd, 0, 4096 * 1024)
67 self.fs.close(fd)
68 if read_bytes:
69 return json.loads(read_bytes.decode())
70 else:
71 return None
72
73 def _metadata_set(self, path, data):
74 serialized = json.dumps(data)
75 fd = self.fs.open(path, "w")
76 try:
77 self.fs.write(fd, self._to_bytes(serialized), 0)
78 self.fs.fsync(fd, 0)
79 finally:
80 self.fs.close(fd)
81
82 def _lock(self, path):
83 @contextmanager
84 def fn():
85 while(1):
86 fd = self.fs.open(path, os.O_CREAT, 0o755)
87 self.fs.flock(fd, fcntl.LOCK_EX, self._id)
88
89 # The locked file will be cleaned up sometime. It could be
90 # unlinked by consumer e.g., an another manila-share service
91 # instance, before lock was applied on it. Perform checks to
92 # ensure that this does not happen.
93 try:
94 statbuf = self.fs.stat(path)
95 except cephfs.ObjectNotFound:
96 self.fs.close(fd)
97 continue
98
99 fstatbuf = self.fs.fstat(fd)
100 if statbuf.st_ino == fstatbuf.st_ino:
101 break
102
103 try:
104 yield
105 finally:
106 self.fs.flock(fd, fcntl.LOCK_UN, self._id)
107 self.fs.close(fd)
108
109 return fn()
110
111 def _auth_metadata_path(self, auth_id):
112 return os.path.join(self.volume_prefix, "${0}{1}".format(
113 auth_id, self.META_FILE_EXT))
114
115 def auth_lock(self, auth_id):
116 return self._lock(self._auth_metadata_path(auth_id))
117
118 def auth_metadata_get(self, auth_id):
119 """
120 Call me with the metadata locked!
121
122 Check whether a auth metadata structure can be decoded by the current
123 version of AuthMetadataManager.
124
125 Return auth metadata that the current version of AuthMetadataManager
126 can decode.
127 """
128 auth_metadata = self._metadata_get(self._auth_metadata_path(auth_id))
129
130 if auth_metadata:
131 self._check_compat_version(auth_metadata['compat_version'])
132
133 return auth_metadata
134
135 def auth_metadata_set(self, auth_id, data):
136 """
137 Call me with the metadata locked!
138
139 Fsync the auth metadata.
140
141 Add two version attributes to the auth metadata,
142 'compat_version', the minimum AuthMetadataManager version that can
143 decode the metadata, and 'version', the AuthMetadataManager version
144 that encoded the metadata.
145 """
146 data['compat_version'] = 6
147 data['version'] = self.version
148 return self._metadata_set(self._auth_metadata_path(auth_id), data)
149
150 def create_subvolume_metadata_file(self, group_name, subvol_name):
151 """
152 Create a subvolume metadata file, if it does not already exist, to store
153 data about auth ids having access to the subvolume
154 """
155 fd = self.fs.open(self._subvolume_metadata_path(group_name, subvol_name),
156 os.O_CREAT, 0o755)
157 self.fs.close(fd)
158
159 def delete_subvolume_metadata_file(self, group_name, subvol_name):
160 vol_meta_path = self._subvolume_metadata_path(group_name, subvol_name)
161 try:
162 self.fs.unlink(vol_meta_path)
163 except cephfs.ObjectNotFound:
164 pass
165
166 def subvol_metadata_lock(self, group_name, subvol_name):
167 """
168 Return a ContextManager which locks the authorization metadata for
169 a particular subvolume, and persists a flag to the metadata indicating
170 that it is currently locked, so that we can detect dirty situations
171 during recovery.
172
173 This lock isn't just to make access to the metadata safe: it's also
174 designed to be used over the two-step process of checking the
175 metadata and then responding to an authorization request, to
176 ensure that at the point we respond the metadata hasn't changed
177 in the background. It's key to how we avoid security holes
178 resulting from races during that problem ,
179 """
180 return self._lock(self._subvolume_metadata_path(group_name, subvol_name))
181
182 def subvol_metadata_get(self, group_name, subvol_name):
183 """
184 Call me with the metadata locked!
185
186 Check whether a subvolume metadata structure can be decoded by the current
187 version of AuthMetadataManager.
188
189 Return a subvolume_metadata structure that the current version of
190 AuthMetadataManager can decode.
191 """
192 subvolume_metadata = self._metadata_get(self._subvolume_metadata_path(group_name, subvol_name))
193
194 if subvolume_metadata:
195 self._check_compat_version(subvolume_metadata['compat_version'])
196
197 return subvolume_metadata
198
199 def subvol_metadata_set(self, group_name, subvol_name, data):
200 """
201 Call me with the metadata locked!
202
203 Add two version attributes to the subvolume metadata,
204 'compat_version', the minimum AuthMetadataManager version that can
205 decode the metadata and 'version', the AuthMetadataManager version
206 that encoded the metadata.
207 """
208 data['compat_version'] = 1
209 data['version'] = self.version
210 return self._metadata_set(self._subvolume_metadata_path(group_name, subvol_name), data)