5 from hashlib
import md5
9 from .metadata_manager
import MetadataManager
10 from ..trash
import create_trashcan
, open_trashcan
11 from ...fs_util
import get_ancestor_xattr
12 from ...exception
import MetadataMgrException
, VolumeException
14 log
= logging
.getLogger(__name__
)
16 class SubvolumeBase(object):
17 LEGACY_CONF_DIR
= "_legacy"
19 SUBVOLUME_TYPE_NORMAL
= "subvolume"
20 SUBVOLUME_TYPE_CLONE
= "clone"
22 def __init__(self
, fs
, vol_spec
, group
, subvolname
, legacy
=False):
27 self
.vol_spec
= vol_spec
29 self
.subvolname
= subvolname
30 self
.legacy_mode
= legacy
59 return os
.path
.join(self
.group
.path
, self
.subvolname
.encode('utf-8'))
62 def config_path(self
):
63 return os
.path
.join(self
.base_path
, b
".meta")
67 return os
.path
.join(self
.vol_spec
.base_dir
.encode('utf-8'), SubvolumeBase
.LEGACY_CONF_DIR
.encode('utf-8'))
70 def legacy_config_path(self
):
72 m
.update(self
.base_path
)
73 meta_config
= "{0}.meta".format(m
.digest().hex())
74 return os
.path
.join(self
.legacy_dir
, meta_config
.encode('utf-8'))
78 return "{0}{1}".format(self
.vol_spec
.fs_namespace
, self
.subvolname
)
82 return self
.group
.group_name
85 def subvol_name(self
):
86 return self
.subvolname
89 def legacy_mode(self
):
93 def legacy_mode(self
, mode
):
96 def load_config(self
):
98 self
.metadata_mgr
= MetadataManager(self
.fs
, self
.legacy_config_path
, 0o640)
100 self
.metadata_mgr
= MetadataManager(self
.fs
, self
.config_path
, 0o640)
102 def _set_attrs(self
, path
, size
, isolate_namespace
, pool
, uid
, gid
):
106 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes', str(size
).encode('utf-8'), 0)
107 except cephfs
.InvalidValue
as e
:
108 raise VolumeException(-errno
.EINVAL
, "invalid size specified: '{0}'".format(size
))
109 except cephfs
.Error
as e
:
110 raise VolumeException(-e
.args
[0], e
.args
[1])
115 self
.fs
.setxattr(path
, 'ceph.dir.layout.pool', pool
.encode('utf-8'), 0)
116 except cephfs
.InvalidValue
:
117 raise VolumeException(-errno
.EINVAL
,
118 "invalid pool layout '{0}' -- need a valid data pool".format(pool
))
119 except cephfs
.Error
as e
:
120 raise VolumeException(-e
.args
[0], e
.args
[1])
123 xattr_key
= xattr_val
= None
124 if isolate_namespace
:
125 # enforce security isolation, use separate namespace for this subvolume
126 xattr_key
= 'ceph.dir.layout.pool_namespace'
127 xattr_val
= self
.namespace
129 # If subvolume's namespace layout is not set, then the subvolume's pool
130 # layout remains unset and will undesirably change with ancestor's
131 # pool layout changes.
132 xattr_key
= 'ceph.dir.layout.pool'
133 xattr_val
= get_ancestor_xattr(self
.fs
, path
, "ceph.dir.layout.pool")
134 if xattr_key
and xattr_val
:
136 self
.fs
.setxattr(path
, xattr_key
, xattr_val
.encode('utf-8'), 0)
137 except cephfs
.Error
as e
:
138 raise VolumeException(-e
.args
[0], e
.args
[1])
149 raise VolumeException(-errno
.EINVAL
, "invalid UID")
158 raise VolumeException(-errno
.EINVAL
, "invalid GID")
159 if uid
is not None and gid
is not None:
160 self
.fs
.chown(path
, uid
, gid
)
162 def _resize(self
, path
, newsize
, noshrink
):
164 newsize
= int(newsize
)
166 raise VolumeException(-errno
.EINVAL
, "Invalid subvolume size")
168 newsize
= newsize
.lower()
169 if not (newsize
== "inf" or newsize
== "infinite"):
170 raise VolumeException(-errno
.EINVAL
, "invalid size option '{0}'".format(newsize
))
175 maxbytes
= int(self
.fs
.getxattr(path
, 'ceph.quota.max_bytes').decode('utf-8'))
176 except cephfs
.NoData
:
178 except cephfs
.Error
as e
:
179 raise VolumeException(-e
.args
[0], e
.args
[1])
181 subvolstat
= self
.fs
.stat(path
)
182 if newsize
> 0 and newsize
< subvolstat
.st_size
:
184 raise VolumeException(-errno
.EINVAL
, "Can't resize the subvolume. The new size '{0}' would be lesser than the current "
185 "used size '{1}'".format(newsize
, subvolstat
.st_size
))
187 if not newsize
== maxbytes
:
189 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes', str(newsize
).encode('utf-8'), 0)
190 except cephfs
.Error
as e
:
191 raise VolumeException(-e
.args
[0], "Cannot set new size for the subvolume. '{0}'".format(e
.args
[1]))
192 return newsize
, subvolstat
.st_size
194 def init_config(self
, version
, subvolume_type
, subvolume_path
, subvolume_state
):
195 self
.metadata_mgr
.init(version
, subvolume_type
, subvolume_path
, subvolume_state
)
196 self
.metadata_mgr
.flush()
199 log
.debug("discovering subvolume '{0}' [mode: {1}]".format(self
.subvolname
, "legacy" if self
.legacy_mode
else "new"))
201 self
.fs
.stat(self
.base_path
)
202 self
.metadata_mgr
.refresh()
203 log
.debug("loaded subvolume '{0}'".format(self
.subvolname
))
204 except MetadataMgrException
as me
:
205 if me
.errno
== -errno
.ENOENT
and not self
.legacy_mode
:
206 self
.legacy_mode
= True
211 except cephfs
.Error
as e
:
212 if e
.args
[0] == errno
.ENOENT
:
213 raise VolumeException(-errno
.ENOENT
, "subvolume '{0}' does not exist".format(self
.subvolname
))
214 raise VolumeException(-e
.args
[0], "error accessing subvolume '{0}'".format(self
.subvolname
))
216 def trash_base_dir(self
):
218 self
.fs
.unlink(self
.legacy_config_path
)
219 subvol_path
= self
.base_path
220 create_trashcan(self
.fs
, self
.vol_spec
)
221 with
open_trashcan(self
.fs
, self
.vol_spec
) as trashcan
:
222 trashcan
.dump(subvol_path
)
223 log
.info("subvolume with path '{0}' moved to trashcan".format(subvol_path
))
225 def create_base_dir(self
, mode
):
227 self
.fs
.mkdirs(self
.base_path
, mode
)
228 except cephfs
.Error
as e
:
229 raise VolumeException(-e
.args
[0], e
.args
[1])
232 subvolpath
= self
.metadata_mgr
.get_global_option('path')
233 etype
= self
.metadata_mgr
.get_global_option(MetadataManager
.GLOBAL_META_KEY_TYPE
)
234 st
= self
.fs
.statx(subvolpath
, cephfs
.CEPH_STATX_BTIME | cephfs
.CEPH_STATX_SIZE |
235 cephfs
.CEPH_STATX_UID | cephfs
.CEPH_STATX_GID |
236 cephfs
.CEPH_STATX_MODE | cephfs
.CEPH_STATX_ATIME |
237 cephfs
.CEPH_STATX_MTIME | cephfs
.CEPH_STATX_CTIME
,
238 cephfs
.AT_SYMLINK_NOFOLLOW
)
239 usedbytes
= st
["size"]
241 nsize
= int(self
.fs
.getxattr(subvolpath
, 'ceph.quota.max_bytes').decode('utf-8'))
242 except cephfs
.NoData
:
246 data_pool
= self
.fs
.getxattr(subvolpath
, 'ceph.dir.layout.pool').decode('utf-8')
247 except cephfs
.Error
as e
:
248 raise VolumeException(-e
.args
[0], e
.args
[1])
250 return {'path': subvolpath
, 'type': etype
, 'uid': int(st
["uid"]), 'gid': int(st
["gid"]),
251 'atime': str(st
["atime"]), 'mtime': str(st
["mtime"]), 'ctime': str(st
["ctime"]),
252 'mode': int(st
["mode"]), 'data_pool': data_pool
, 'created_at': str(st
["btime"]),
253 'bytes_quota': "infinite" if nsize
== 0 else nsize
, 'bytes_used': int(usedbytes
),
254 'bytes_pcent': "undefined" if nsize
== 0 else '{0:.2f}'.format((float(usedbytes
) / nsize
) * 100.0)}