6 from hashlib
import md5
7 from typing
import Dict
, Union
11 from ..pin_util
import pin
12 from .subvolume_attrs
import SubvolumeTypes
, SubvolumeStates
13 from .metadata_manager
import MetadataManager
14 from ..trash
import create_trashcan
, open_trashcan
15 from ...fs_util
import get_ancestor_xattr
16 from ...exception
import MetadataMgrException
, VolumeException
17 from .op_sm
import SubvolumeOpSm
19 log
= logging
.getLogger(__name__
)
21 class SubvolumeBase(object):
22 LEGACY_CONF_DIR
= "_legacy"
24 def __init__(self
, fs
, vol_spec
, group
, subvolname
, legacy
=False):
29 self
.vol_spec
= vol_spec
31 self
.subvolname
= subvolname
32 self
.legacy_mode
= legacy
61 return os
.path
.join(self
.group
.path
, self
.subvolname
.encode('utf-8'))
64 def config_path(self
):
65 return os
.path
.join(self
.base_path
, b
".meta")
69 return os
.path
.join(self
.vol_spec
.base_dir
.encode('utf-8'), SubvolumeBase
.LEGACY_CONF_DIR
.encode('utf-8'))
72 def legacy_config_path(self
):
74 m
.update(self
.base_path
)
75 meta_config
= "{0}.meta".format(m
.digest().hex())
76 return os
.path
.join(self
.legacy_dir
, meta_config
.encode('utf-8'))
80 return "{0}{1}".format(self
.vol_spec
.fs_namespace
, self
.subvolname
)
84 return self
.group
.group_name
87 def subvol_name(self
):
88 return self
.subvolname
91 def legacy_mode(self
):
95 def legacy_mode(self
, mode
):
100 """ Path to subvolume data directory """
101 raise NotImplementedError
105 """ List of features supported by the subvolume, containing items from SubvolumeFeatures """
106 raise NotImplementedError
110 """ Subvolume state, one of SubvolumeStates """
111 raise NotImplementedError
114 def subvol_type(self
):
115 return SubvolumeTypes
.from_value(self
.metadata_mgr
.get_global_option(MetadataManager
.GLOBAL_META_KEY_TYPE
))
119 """ Boolean declaring if subvolume can be purged """
120 raise NotImplementedError
122 def load_config(self
):
124 self
.metadata_mgr
= MetadataManager(self
.fs
, self
.legacy_config_path
, 0o640)
126 self
.metadata_mgr
= MetadataManager(self
.fs
, self
.config_path
, 0o640)
128 def get_attrs(self
, pathname
):
129 # get subvolume attributes
130 attrs
= {} # type: Dict[str, Union[int, str, None]]
131 stx
= self
.fs
.statx(pathname
,
132 cephfs
.CEPH_STATX_UID | cephfs
.CEPH_STATX_GID | cephfs
.CEPH_STATX_MODE
,
133 cephfs
.AT_SYMLINK_NOFOLLOW
)
135 attrs
["uid"] = int(stx
["uid"])
136 attrs
["gid"] = int(stx
["gid"])
137 attrs
["mode"] = int(int(stx
["mode"]) & ~stat
.S_IFMT(stx
["mode"]))
140 attrs
["data_pool"] = self
.fs
.getxattr(pathname
, 'ceph.dir.layout.pool').decode('utf-8')
141 except cephfs
.NoData
:
142 attrs
["data_pool"] = None
145 attrs
["pool_namespace"] = self
.fs
.getxattr(pathname
, 'ceph.dir.layout.pool_namespace').decode('utf-8')
146 except cephfs
.NoData
:
147 attrs
["pool_namespace"] = None
150 attrs
["quota"] = int(self
.fs
.getxattr(pathname
, 'ceph.quota.max_bytes').decode('utf-8'))
151 except cephfs
.NoData
:
152 attrs
["quota"] = None
156 def set_attrs(self
, path
, attrs
):
157 # set subvolume attributes
159 quota
= attrs
.get("quota")
160 if quota
is not None:
162 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes', str(quota
).encode('utf-8'), 0)
163 except cephfs
.InvalidValue
as e
:
164 raise VolumeException(-errno
.EINVAL
, "invalid size specified: '{0}'".format(quota
))
165 except cephfs
.Error
as e
:
166 raise VolumeException(-e
.args
[0], e
.args
[1])
169 data_pool
= attrs
.get("data_pool")
170 if data_pool
is not None:
172 self
.fs
.setxattr(path
, 'ceph.dir.layout.pool', data_pool
.encode('utf-8'), 0)
173 except cephfs
.InvalidValue
:
174 raise VolumeException(-errno
.EINVAL
,
175 "invalid pool layout '{0}' -- need a valid data pool".format(data_pool
))
176 except cephfs
.Error
as e
:
177 raise VolumeException(-e
.args
[0], e
.args
[1])
180 xattr_key
= xattr_val
= None
181 pool_namespace
= attrs
.get("pool_namespace")
182 if pool_namespace
is not None:
183 # enforce security isolation, use separate namespace for this subvolume
184 xattr_key
= 'ceph.dir.layout.pool_namespace'
185 xattr_val
= pool_namespace
187 # If subvolume's namespace layout is not set, then the subvolume's pool
188 # layout remains unset and will undesirably change with ancestor's
189 # pool layout changes.
190 xattr_key
= 'ceph.dir.layout.pool'
193 self
.fs
.getxattr(path
, 'ceph.dir.layout.pool').decode('utf-8')
194 except cephfs
.NoData
as e
:
195 xattr_val
= get_ancestor_xattr(self
.fs
, os
.path
.split(path
)[0], "ceph.dir.layout.pool")
196 if xattr_key
and xattr_val
:
198 self
.fs
.setxattr(path
, xattr_key
, xattr_val
.encode('utf-8'), 0)
199 except cephfs
.Error
as e
:
200 raise VolumeException(-e
.args
[0], e
.args
[1])
203 uid
= attrs
.get("uid")
211 raise VolumeException(-errno
.EINVAL
, "invalid UID")
213 gid
= attrs
.get("gid")
221 raise VolumeException(-errno
.EINVAL
, "invalid GID")
223 if uid
is not None and gid
is not None:
224 self
.fs
.chown(path
, uid
, gid
)
226 def _resize(self
, path
, newsize
, noshrink
):
228 newsize
= int(newsize
)
230 raise VolumeException(-errno
.EINVAL
, "Invalid subvolume size")
232 newsize
= newsize
.lower()
233 if not (newsize
== "inf" or newsize
== "infinite"):
234 raise VolumeException(-errno
.EINVAL
, "invalid size option '{0}'".format(newsize
))
239 maxbytes
= int(self
.fs
.getxattr(path
, 'ceph.quota.max_bytes').decode('utf-8'))
240 except cephfs
.NoData
:
242 except cephfs
.Error
as e
:
243 raise VolumeException(-e
.args
[0], e
.args
[1])
245 subvolstat
= self
.fs
.stat(path
)
246 if newsize
> 0 and newsize
< subvolstat
.st_size
:
248 raise VolumeException(-errno
.EINVAL
, "Can't resize the subvolume. The new size '{0}' would be lesser than the current "
249 "used size '{1}'".format(newsize
, subvolstat
.st_size
))
251 if not newsize
== maxbytes
:
253 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes', str(newsize
).encode('utf-8'), 0)
254 except cephfs
.Error
as e
:
255 raise VolumeException(-e
.args
[0], "Cannot set new size for the subvolume. '{0}'".format(e
.args
[1]))
256 return newsize
, subvolstat
.st_size
258 def pin(self
, pin_type
, pin_setting
):
259 return pin(self
.fs
, self
.base_path
, pin_type
, pin_setting
)
261 def init_config(self
, version
, subvolume_type
, subvolume_path
, subvolume_state
):
262 self
.metadata_mgr
.init(version
, subvolume_type
.value
, subvolume_path
, subvolume_state
.value
)
263 self
.metadata_mgr
.flush()
266 log
.debug("discovering subvolume '{0}' [mode: {1}]".format(self
.subvolname
, "legacy" if self
.legacy_mode
else "new"))
268 self
.fs
.stat(self
.base_path
)
269 self
.metadata_mgr
.refresh()
270 log
.debug("loaded subvolume '{0}'".format(self
.subvolname
))
271 except MetadataMgrException
as me
:
272 if me
.errno
== -errno
.ENOENT
and not self
.legacy_mode
:
273 self
.legacy_mode
= True
278 except cephfs
.Error
as e
:
279 if e
.args
[0] == errno
.ENOENT
:
280 raise VolumeException(-errno
.ENOENT
, "subvolume '{0}' does not exist".format(self
.subvolname
))
281 raise VolumeException(-e
.args
[0], "error accessing subvolume '{0}'".format(self
.subvolname
))
283 def _trash_dir(self
, path
):
284 create_trashcan(self
.fs
, self
.vol_spec
)
285 with
open_trashcan(self
.fs
, self
.vol_spec
) as trashcan
:
287 log
.info("subvolume path '{0}' moved to trashcan".format(path
))
289 def _link_dir(self
, path
, bname
):
290 create_trashcan(self
.fs
, self
.vol_spec
)
291 with
open_trashcan(self
.fs
, self
.vol_spec
) as trashcan
:
292 trashcan
.link(path
, bname
)
293 log
.info("subvolume path '{0}' linked in trashcan bname {1}".format(path
, bname
))
295 def trash_base_dir(self
):
297 self
.fs
.unlink(self
.legacy_config_path
)
298 self
._trash
_dir
(self
.base_path
)
300 def create_base_dir(self
, mode
):
302 self
.fs
.mkdirs(self
.base_path
, mode
)
303 except cephfs
.Error
as e
:
304 raise VolumeException(-e
.args
[0], e
.args
[1])
307 subvolpath
= self
.metadata_mgr
.get_global_option(MetadataManager
.GLOBAL_META_KEY_PATH
)
308 etype
= self
.subvol_type
309 st
= self
.fs
.statx(subvolpath
, cephfs
.CEPH_STATX_BTIME | cephfs
.CEPH_STATX_SIZE |
310 cephfs
.CEPH_STATX_UID | cephfs
.CEPH_STATX_GID |
311 cephfs
.CEPH_STATX_MODE | cephfs
.CEPH_STATX_ATIME |
312 cephfs
.CEPH_STATX_MTIME | cephfs
.CEPH_STATX_CTIME
,
313 cephfs
.AT_SYMLINK_NOFOLLOW
)
314 usedbytes
= st
["size"]
316 nsize
= int(self
.fs
.getxattr(subvolpath
, 'ceph.quota.max_bytes').decode('utf-8'))
317 except cephfs
.NoData
:
321 data_pool
= self
.fs
.getxattr(subvolpath
, 'ceph.dir.layout.pool').decode('utf-8')
322 pool_namespace
= self
.fs
.getxattr(subvolpath
, 'ceph.dir.layout.pool_namespace').decode('utf-8')
323 except cephfs
.Error
as e
:
324 raise VolumeException(-e
.args
[0], e
.args
[1])
326 return {'path': subvolpath
, 'type': etype
.value
, 'uid': int(st
["uid"]), 'gid': int(st
["gid"]),
327 'atime': str(st
["atime"]), 'mtime': str(st
["mtime"]), 'ctime': str(st
["ctime"]),
328 'mode': int(st
["mode"]), 'data_pool': data_pool
, 'created_at': str(st
["btime"]),
329 'bytes_quota': "infinite" if nsize
== 0 else nsize
, 'bytes_used': int(usedbytes
),
330 'bytes_pcent': "undefined" if nsize
== 0 else '{0:.2f}'.format((float(usedbytes
) / nsize
) * 100.0),
331 'pool_namespace': pool_namespace
, 'features': self
.features
, 'state': self
.state
.value
}