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