Coverage for drivers/GlusterFSSR.py : 22%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python3
2#
3# Original work copyright (C) Citrix systems
4# Modified work copyright (C) Vates SAS and XCP-ng community
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU Lesser General Public License as published
8# by the Free Software Foundation; version 2.1 only.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public License
16# along with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19from sm_typing import override
21import errno
22import os
23import syslog as _syslog
24import xmlrpc.client
25from syslog import syslog
27# careful with the import order here
28# FileSR has a circular dependency: FileSR- > blktap2 -> lvutil -> EXTSR -> FileSR
29# importing in this order seems to avoid triggering the issue.
30import SR
31import SRCommand
32import FileSR
33# end of careful
34import VDI
35import cleanup
36import util
37import vhdutil
38import xs_errors
39from lock import Lock
41CAPABILITIES = ["SR_PROBE", "SR_UPDATE",
42 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH",
43 "VDI_UPDATE", "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR",
44 "VDI_GENERATE_CONFIG",
45 "VDI_RESET_ON_BOOT/2", "ATOMIC_PAUSE"]
47CONFIGURATION = [['server', 'Full path to share on gluster server (required, ex: "192.168.0.12:/gv0")'],
48 ['backupservers', 'list of servers separated by ":"'],
49 ['fetchattempts', 'number of attempts to fetch files before switching to the backup server']
50 ]
52DRIVER_INFO = {
53 'name': 'GlusterFS VHD',
54 'description': 'SR plugin which stores disks as VHD files on a GlusterFS storage',
55 'vendor': 'Vates SAS',
56 'copyright': '(C) 2020 Vates SAS',
57 'driver_version': '1.0',
58 'required_api_version': '1.0',
59 'capabilities': CAPABILITIES,
60 'configuration': CONFIGURATION
61}
63DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True}
65# The mountpoint for the directory when performing an sr_probe. All probes
66# are guaranteed to be serialised by xapi, so this single mountpoint is fine.
67PROBE_MOUNTPOINT = os.path.join(SR.MOUNT_BASE, "probe")
70class GlusterFSException(Exception):
71 def __init__(self, errstr):
72 self.errstr = errstr
75# mountpoint = /var/run/sr-mount/GlusterFS/<glusterfs_server_name>/uuid
76# linkpath = mountpoint/uuid - path to SR directory on share
77# path = /var/run/sr-mount/uuid - symlink to SR directory on share
78class GlusterFSSR(FileSR.FileSR):
79 """Gluster file-based storage repository"""
81 DRIVER_TYPE = 'glusterfs'
83 @override
84 @staticmethod
85 def handles(sr_type) -> bool:
86 # fudge, because the parent class (FileSR) checks for smb to alter its behavior
87 return sr_type == GlusterFSSR.DRIVER_TYPE or sr_type == 'smb'
89 @override
90 def load(self, sr_uuid) -> None:
91 if not self._is_glusterfs_available():
92 raise xs_errors.XenError(
93 'SRUnavailable',
94 opterr='glusterfs is not installed'
95 )
97 self.ops_exclusive = FileSR.OPS_EXCLUSIVE
98 self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid)
99 self.sr_vditype = SR.DEFAULT_TAP
100 self.driver_config = DRIVER_CONFIG
101 if 'server' not in self.dconf:
102 raise xs_errors.XenError('ConfigServerMissing')
103 # Can be None => on-slave plugin hack (is_open function).
104 self.remoteserver = self.dconf['server'] or ''
105 if self.sr_ref and self.session is not None:
106 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref)
107 else:
108 self.sm_config = self.srcmd.params.get('sr_sm_config') or {}
109 self.mountpoint = os.path.join(SR.MOUNT_BASE, 'GlusterFS', self.remoteserver.split(':')[0], sr_uuid)
110 self.linkpath = os.path.join(self.mountpoint, sr_uuid or "")
111 self.path = os.path.join(SR.MOUNT_BASE, sr_uuid)
112 self._check_o_direct()
114 def checkmount(self):
115 return util.ioretry(lambda: ((util.pathexists(self.mountpoint) and
116 util.ismount(self.mountpoint)) and
117 util.pathexists(self.linkpath)))
119 def mount(self, mountpoint=None):
120 """Mount the remote gluster export at 'mountpoint'"""
121 if mountpoint is None:
122 mountpoint = self.mountpoint
123 elif not util.is_string(mountpoint) or mountpoint == "":
124 raise GlusterFSException("mountpoint not a string object")
126 try:
127 if not util.ioretry(lambda: util.isdir(mountpoint)):
128 util.ioretry(lambda: util.makedirs(mountpoint))
129 except util.CommandException as inst:
130 raise GlusterFSException("Failed to make directory: code is %d" % inst.code)
131 try:
132 options = []
133 if 'backupservers' in self.dconf:
134 options.append('backup-volfile-servers=' + self.dconf['backupservers'])
135 if 'fetchattempts' in self.dconf:
136 options.append('fetch-attempts=' + self.dconf['fetchattempts'])
137 if options:
138 options = ['-o', ','.join(options)]
139 command = ["mount", '-t', 'glusterfs', self.remoteserver, mountpoint] + options
140 util.ioretry(lambda: util.pread(command), errlist=[errno.EPIPE, errno.EIO], maxretry=2, nofail=True)
141 except util.CommandException as inst:
142 syslog(_syslog.LOG_ERR, 'GlusterFS mount failed ' + inst.__str__())
143 raise GlusterFSException("mount failed with return code %d" % inst.code)
145 # Sanity check to ensure that the user has at least RO access to the
146 # mounted share. Windows sharing and security settings can be tricky.
147 try:
148 util.listdir(mountpoint)
149 except util.CommandException:
150 try:
151 self.unmount(mountpoint, True)
152 except GlusterFSException:
153 util.logException('GlusterFSSR.unmount()')
154 raise GlusterFSException("Permission denied. Please check user privileges.")
156 def unmount(self, mountpoint, rmmountpoint):
157 try:
158 util.pread(["umount", mountpoint])
159 except util.CommandException as inst:
160 raise GlusterFSException("umount failed with return code %d" % inst.code)
161 if rmmountpoint:
162 try:
163 os.rmdir(mountpoint)
164 except OSError as inst:
165 raise GlusterFSException("rmdir failed with error '%s'" % inst.strerror)
167 @override
168 def attach(self, sr_uuid) -> None:
169 if not self.checkmount():
170 try:
171 self.mount()
172 os.symlink(self.linkpath, self.path)
173 except GlusterFSException as exc:
174 raise xs_errors.SROSError(12, exc.errstr)
175 self.attached = True
177 @override
178 def probe(self) -> str:
179 try:
180 self.mount(PROBE_MOUNTPOINT)
181 sr_list = filter(util.match_uuid, util.listdir(PROBE_MOUNTPOINT))
182 self.unmount(PROBE_MOUNTPOINT, True)
183 except (util.CommandException, xs_errors.XenError):
184 raise
185 # Create a dictionary from the SR uuids to feed SRtoXML()
186 return util.SRtoXML({sr_uuid: {} for sr_uuid in sr_list})
188 @override
189 def detach(self, sr_uuid) -> None:
190 if not self.checkmount():
191 return
192 util.SMlog("Aborting GC/coalesce")
193 cleanup.abort(self.uuid)
194 # Change directory to avoid unmount conflicts
195 os.chdir(SR.MOUNT_BASE)
196 self.unmount(self.mountpoint, True)
197 os.unlink(self.path)
198 self.attached = False
200 @override
201 def create(self, sr_uuid, size) -> None:
202 if self.checkmount():
203 raise xs_errors.SROSError(113, 'GlusterFS mount point already attached')
205 try:
206 self.mount()
207 except GlusterFSException as exc:
208 # noinspection PyBroadException
209 try:
210 os.rmdir(self.mountpoint)
211 except:
212 # we have no recovery strategy
213 pass
214 raise xs_errors.SROSError(111, "GlusterFS mount error [opterr=%s]" % exc.errstr)
216 if util.ioretry(lambda: util.pathexists(self.linkpath)):
217 if len(util.ioretry(lambda: util.listdir(self.linkpath))) != 0:
218 self.detach(sr_uuid)
219 raise xs_errors.XenError('SRExists')
220 else:
221 try:
222 util.ioretry(lambda: util.makedirs(self.linkpath))
223 os.symlink(self.linkpath, self.path)
224 except util.CommandException as inst:
225 if inst.code != errno.EEXIST:
226 try:
227 self.unmount(self.mountpoint, True)
228 except GlusterFSException:
229 util.logException('GlusterFSSR.unmount()')
230 raise xs_errors.SROSError(116,
231 "Failed to create GlusterFS SR. remote directory creation error: {}".format(
232 os.strerror(inst.code)))
233 self.detach(sr_uuid)
235 @override
236 def delete(self, sr_uuid) -> None:
237 # try to remove/delete non VDI contents first
238 super(GlusterFSSR, self).delete(sr_uuid)
239 try:
240 if self.checkmount():
241 self.detach(sr_uuid)
242 self.mount()
243 if util.ioretry(lambda: util.pathexists(self.linkpath)):
244 util.ioretry(lambda: os.rmdir(self.linkpath))
245 self.unmount(self.mountpoint, True)
246 except util.CommandException as inst:
247 self.detach(sr_uuid)
248 if inst.code != errno.ENOENT:
249 raise xs_errors.SROSError(114, "Failed to remove GlusterFS mount point")
251 @override
252 def vdi(self, uuid, loadLocked=False) -> VDI.VDI:
253 return GlusterFSFileVDI(self, uuid)
255 @staticmethod
256 def _is_glusterfs_available():
257 return util.find_executable('glusterfs')
260class GlusterFSFileVDI(FileSR.FileVDI):
261 @override
262 def attach(self, sr_uuid, vdi_uuid) -> str:
263 if not hasattr(self, 'xenstore_data'):
264 self.xenstore_data = {}
266 self.xenstore_data['storage-type'] = GlusterFSSR.DRIVER_TYPE
268 return super(GlusterFSFileVDI, self).attach(sr_uuid, vdi_uuid)
270 @override
271 def generate_config(self, sr_uuid, vdi_uuid) -> str:
272 util.SMlog("SMBFileVDI.generate_config")
273 if not util.pathexists(self.path):
274 raise xs_errors.XenError('VDIUnavailable')
275 resp = {'device_config': self.sr.dconf,
276 'sr_uuid': sr_uuid,
277 'vdi_uuid': vdi_uuid,
278 'sr_sm_config': self.sr.sm_config,
279 'command': 'vdi_attach_from_config'}
280 # Return the 'config' encoded within a normal XMLRPC response so that
281 # we can use the regular response/error parsing code.
282 config = xmlrpc.client.dumps(tuple([resp]), "vdi_attach_from_config")
283 return xmlrpc.client.dumps((config,), "", True)
285 @override
286 def attach_from_config(self, sr_uuid, vdi_uuid) -> str:
287 try:
288 if not util.pathexists(self.sr.path):
289 return self.sr.attach(sr_uuid)
290 except:
291 util.logException("SMBFileVDI.attach_from_config")
292 raise xs_errors.XenError('SRUnavailable',
293 opterr='Unable to attach from config')
294 return ''
296if __name__ == '__main__': 296 ↛ 297line 296 didn't jump to line 297, because the condition on line 296 was never true
297 SRCommand.run(GlusterFSSR, DRIVER_INFO)
298else:
299 SR.registerSR(GlusterFSSR)