Coverage for drivers/GlusterFSSR.py : 17%

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
19import errno
20import os
21import syslog as _syslog
22import xmlrpc.client
23from syslog import syslog
25# careful with the import order here
26# FileSR has a circular dependency: FileSR- > blktap2 -> lvutil -> EXTSR -> FileSR
27# importing in this order seems to avoid triggering the issue.
28import SR
29import SRCommand
30import FileSR
31# end of careful
32import cleanup
33import util
34import vhdutil
35import xs_errors
36from lock import Lock
38CAPABILITIES = ["SR_PROBE", "SR_UPDATE",
39 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH",
40 "VDI_UPDATE", "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR",
41 "VDI_GENERATE_CONFIG",
42 "VDI_RESET_ON_BOOT/2", "ATOMIC_PAUSE"]
44CONFIGURATION = [['server', 'Full path to share on gluster server (required, ex: "192.168.0.12:/gv0")'],
45 ['backupservers', 'list of servers separated by ":"'],
46 ['fetchattempts', 'number of attempts to fetch files before switching to the backup server']
47 ]
49DRIVER_INFO = {
50 'name': 'GlusterFS VHD',
51 'description': 'SR plugin which stores disks as VHD files on a GlusterFS storage',
52 'vendor': 'Vates SAS',
53 'copyright': '(C) 2020 Vates SAS',
54 'driver_version': '1.0',
55 'required_api_version': '1.0',
56 'capabilities': CAPABILITIES,
57 'configuration': CONFIGURATION
58}
60DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True}
62# The mountpoint for the directory when performing an sr_probe. All probes
63# are guaranteed to be serialised by xapi, so this single mountpoint is fine.
64PROBE_MOUNTPOINT = os.path.join(SR.MOUNT_BASE, "probe")
67class GlusterFSException(Exception):
68 def __init__(self, errstr):
69 self.errstr = errstr
72# mountpoint = /var/run/sr-mount/GlusterFS/<glusterfs_server_name>/uuid
73# linkpath = mountpoint/uuid - path to SR directory on share
74# path = /var/run/sr-mount/uuid - symlink to SR directory on share
75class GlusterFSSR(FileSR.FileSR):
76 """Gluster file-based storage repository"""
78 DRIVER_TYPE = 'glusterfs'
80 def handles(sr_type):
81 # fudge, because the parent class (FileSR) checks for smb to alter its behavior
82 return sr_type == GlusterFSSR.DRIVER_TYPE or sr_type == 'smb'
84 handles = staticmethod(handles)
86 def load(self, sr_uuid):
87 if not self._is_glusterfs_available():
88 raise xs_errors.XenError(
89 'SRUnavailable',
90 opterr='glusterfs is not installed'
91 )
93 self.ops_exclusive = FileSR.OPS_EXCLUSIVE
94 self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid)
95 self.sr_vditype = SR.DEFAULT_TAP
96 self.driver_config = DRIVER_CONFIG
97 if 'server' not in self.dconf:
98 raise xs_errors.XenError('ConfigServerMissing')
99 # Can be None => on-slave plugin hack (is_open function).
100 self.remoteserver = self.dconf['server'] or ''
101 if self.sr_ref and self.session is not None:
102 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref)
103 else:
104 self.sm_config = self.srcmd.params.get('sr_sm_config') or {}
105 self.mountpoint = os.path.join(SR.MOUNT_BASE, 'GlusterFS', self.remoteserver.split(':')[0], sr_uuid)
106 self.linkpath = os.path.join(self.mountpoint, sr_uuid or "")
107 self.path = os.path.join(SR.MOUNT_BASE, sr_uuid)
108 self._check_o_direct()
110 def checkmount(self):
111 return util.ioretry(lambda: ((util.pathexists(self.mountpoint) and
112 util.ismount(self.mountpoint)) and
113 util.pathexists(self.linkpath)))
115 def mount(self, mountpoint=None):
116 """Mount the remote gluster export at 'mountpoint'"""
117 if mountpoint is None:
118 mountpoint = self.mountpoint
119 elif not util.is_string(mountpoint) or mountpoint == "":
120 raise GlusterFSException("mountpoint not a string object")
122 try:
123 if not util.ioretry(lambda: util.isdir(mountpoint)):
124 util.ioretry(lambda: util.makedirs(mountpoint))
125 except util.CommandException as inst:
126 raise GlusterFSException("Failed to make directory: code is %d" % inst.code)
127 try:
128 options = []
129 if 'backupservers' in self.dconf:
130 options.append('backup-volfile-servers=' + self.dconf['backupservers'])
131 if 'fetchattempts' in self.dconf:
132 options.append('fetch-attempts=' + self.dconf['fetchattempts'])
133 if options:
134 options = ['-o', ','.join(options)]
135 command = ["mount", '-t', 'glusterfs', self.remoteserver, mountpoint] + options
136 util.ioretry(lambda: util.pread(command), errlist=[errno.EPIPE, errno.EIO], maxretry=2, nofail=True)
137 except util.CommandException as inst:
138 syslog(_syslog.LOG_ERR, 'GlusterFS mount failed ' + inst.__str__())
139 raise GlusterFSException("mount failed with return code %d" % inst.code)
141 # Sanity check to ensure that the user has at least RO access to the
142 # mounted share. Windows sharing and security settings can be tricky.
143 try:
144 util.listdir(mountpoint)
145 except util.CommandException:
146 try:
147 self.unmount(mountpoint, True)
148 except GlusterFSException:
149 util.logException('GlusterFSSR.unmount()')
150 raise GlusterFSException("Permission denied. Please check user privileges.")
152 def unmount(self, mountpoint, rmmountpoint):
153 try:
154 util.pread(["umount", mountpoint])
155 except util.CommandException as inst:
156 raise GlusterFSException("umount failed with return code %d" % inst.code)
157 if rmmountpoint:
158 try:
159 os.rmdir(mountpoint)
160 except OSError as inst:
161 raise GlusterFSException("rmdir failed with error '%s'" % inst.strerror)
163 def attach(self, sr_uuid):
164 if not self.checkmount():
165 try:
166 self.mount()
167 os.symlink(self.linkpath, self.path)
168 except GlusterFSException as exc:
169 raise SR.SROSError(12, exc.errstr)
170 self.attached = True
172 def probe(self):
173 try:
174 self.mount(PROBE_MOUNTPOINT)
175 sr_list = filter(util.match_uuid, util.listdir(PROBE_MOUNTPOINT))
176 self.unmount(PROBE_MOUNTPOINT, True)
177 except (util.CommandException, xs_errors.XenError):
178 raise
179 # Create a dictionary from the SR uuids to feed SRtoXML()
180 sr_dict = {sr_uuid: {} for sr_uuid in sr_list}
181 return util.SRtoXML(sr_dict)
183 def detach(self, sr_uuid):
184 if not self.checkmount():
185 return
186 util.SMlog("Aborting GC/coalesce")
187 cleanup.abort(self.uuid)
188 # Change directory to avoid unmount conflicts
189 os.chdir(SR.MOUNT_BASE)
190 self.unmount(self.mountpoint, True)
191 os.unlink(self.path)
192 self.attached = False
194 def create(self, sr_uuid, size):
195 if self.checkmount():
196 raise SR.SROSError(113, 'GlusterFS mount point already attached')
198 try:
199 self.mount()
200 except GlusterFSException as exc:
201 # noinspection PyBroadException
202 try:
203 os.rmdir(self.mountpoint)
204 except:
205 # we have no recovery strategy
206 pass
207 raise SR.SROSError(111, "GlusterFS mount error [opterr=%s]" % exc.errstr)
209 if util.ioretry(lambda: util.pathexists(self.linkpath)):
210 if len(util.ioretry(lambda: util.listdir(self.linkpath))) != 0:
211 self.detach(sr_uuid)
212 raise xs_errors.XenError('SRExists')
213 else:
214 try:
215 util.ioretry(lambda: util.makedirs(self.linkpath))
216 os.symlink(self.linkpath, self.path)
217 except util.CommandException as inst:
218 if inst.code != errno.EEXIST:
219 try:
220 self.unmount(self.mountpoint, True)
221 except GlusterFSException:
222 util.logException('GlusterFSSR.unmount()')
223 raise SR.SROSError(116,
224 "Failed to create GlusterFS SR. remote directory creation error: {}".format(
225 os.strerror(inst.code)))
226 self.detach(sr_uuid)
228 def delete(self, sr_uuid):
229 # try to remove/delete non VDI contents first
230 super(GlusterFSSR, self).delete(sr_uuid)
231 try:
232 if self.checkmount():
233 self.detach(sr_uuid)
234 self.mount()
235 if util.ioretry(lambda: util.pathexists(self.linkpath)):
236 util.ioretry(lambda: os.rmdir(self.linkpath))
237 self.unmount(self.mountpoint, True)
238 except util.CommandException as inst:
239 self.detach(sr_uuid)
240 if inst.code != errno.ENOENT:
241 raise SR.SROSError(114, "Failed to remove GlusterFS mount point")
243 def vdi(self, uuid, loadLocked=False):
244 return GlusterFSFileVDI(self, uuid)
246 @staticmethod
247 def _is_glusterfs_available():
248 import distutils.spawn
249 return distutils.spawn.find_executable('glusterfs')
252class GlusterFSFileVDI(FileSR.FileVDI):
253 def attach(self, sr_uuid, vdi_uuid):
254 if not hasattr(self, 'xenstore_data'):
255 self.xenstore_data = {}
257 self.xenstore_data['storage-type'] = GlusterFSSR.DRIVER_TYPE
259 return super(GlusterFSFileVDI, self).attach(sr_uuid, vdi_uuid)
261 def generate_config(self, sr_uuid, vdi_uuid):
262 util.SMlog("SMBFileVDI.generate_config")
263 if not util.pathexists(self.path):
264 raise xs_errors.XenError('VDIUnavailable')
265 resp = {'device_config': self.sr.dconf,
266 'sr_uuid': sr_uuid,
267 'vdi_uuid': vdi_uuid,
268 'sr_sm_config': self.sr.sm_config,
269 'command': 'vdi_attach_from_config'}
270 # Return the 'config' encoded within a normal XMLRPC response so that
271 # we can use the regular response/error parsing code.
272 config = xmlrpc.client.dumps(tuple([resp]), "vdi_attach_from_config")
273 return xmlrpc.client.dumps((config,), "", True)
275 def attach_from_config(self, sr_uuid, vdi_uuid):
276 try:
277 if not util.pathexists(self.sr.path):
278 self.sr.attach(sr_uuid)
279 except:
280 util.logException("SMBFileVDI.attach_from_config")
281 raise xs_errors.XenError('SRUnavailable',
282 opterr='Unable to attach from config')
285if __name__ == '__main__': 285 ↛ 286line 285 didn't jump to line 286, because the condition on line 285 was never true
286 SRCommand.run(GlusterFSSR, DRIVER_INFO)
287else:
288 SR.registerSR(GlusterFSSR)