Coverage for drivers/MooseFSSR.py : 33%

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) Tappest sp. z o.o., 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
18#
19# MooseFSSR: Based on CEPHFSSR and FileSR, mounts MooseFS share
21import distutils.util
22import errno
23import os
24import syslog as _syslog
25import xmlrpc.client
26from syslog import syslog
28# careful with the import order here
29# FileSR has a circular dependency:
30# FileSR -> blktap2 -> lvutil -> EXTSR -> FileSR
31# importing in this order seems to avoid triggering the issue.
32import SR
33import SRCommand
34import FileSR
35# end of careful
36import cleanup
37import util
38import vhdutil
39import xs_errors
40from lock import Lock
42CAPABILITIES = ["SR_PROBE", "SR_UPDATE",
43 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH",
44 "VDI_UPDATE", "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR",
45 "VDI_GENERATE_CONFIG",
46 "VDI_RESET_ON_BOOT/2", "ATOMIC_PAUSE"]
48CONFIGURATION = [
49 ['masterhost', 'MooseFS Master Server hostname or IP address (required, e.g.: "mfsmaster.local.lan" or "10.10.10.1")'],
50 ['masterport', 'MooseFS Master Server port, default: 9421'],
51 ['rootpath', 'MooseFS path (required, e.g.: "/")'],
52 ['options', 'MooseFS Client additional options (e.g.: "mfspassword=PASSWORD,mfstimeout=300")']
53]
55DRIVER_INFO = {
56 'name': 'MooseFS VHD',
57 'description': 'SR plugin which stores disks as VHD files on a MooseFS storage',
58 'vendor': 'Tappest sp. z o.o.',
59 'copyright': '(C) 2021 Tappest sp. z o.o.',
60 'driver_version': '1.0',
61 'required_api_version': '1.0',
62 'capabilities': CAPABILITIES,
63 'configuration': CONFIGURATION
64}
66DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True}
68# The mountpoint for the directory when performing an sr_probe. All probes
69# are guaranteed to be serialised by xapi, so this single mountpoint is fine.
70PROBE_MOUNTPOINT = os.path.join(SR.MOUNT_BASE, "probe")
73class MooseFSException(Exception):
74 def __init__(self, errstr):
75 self.errstr = errstr
78class MooseFSSR(FileSR.FileSR):
79 """MooseFS file-based storage"""
81 DRIVER_TYPE = 'moosefs'
83 def handles(sr_type):
84 # fudge, because the parent class (FileSR) checks for smb to alter its behavior
85 return sr_type == MooseFSSR.DRIVER_TYPE or sr_type == 'smb'
87 handles = staticmethod(handles)
89 def load(self, sr_uuid):
90 if not self._is_moosefs_available(): 90 ↛ 91line 90 didn't jump to line 91, because the condition on line 90 was never true
91 raise xs_errors.XenError(
92 'SRUnavailable',
93 opterr='MooseFS Client is not installed!'
94 )
96 self.ops_exclusive = FileSR.OPS_EXCLUSIVE
97 self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid)
98 self.sr_vditype = SR.DEFAULT_TAP
99 self.driver_config = DRIVER_CONFIG
100 if 'masterhost' not in self.dconf: 100 ↛ 101line 100 didn't jump to line 101, because the condition on line 100 was never true
101 raise xs_errors.XenError('ConfigServerMissing')
102 self.remoteserver = self.dconf['masterhost']
103 self.rootpath = self.dconf['rootpath']
104 self.remotepath = self.rootpath
105 # if masterport is not specified, use default: 9421
106 if 'masterport' not in self.dconf: 106 ↛ 109line 106 didn't jump to line 109, because the condition on line 106 was never false
107 self.remoteport = "9421"
108 else:
109 self.remoteport = self.dconf['masterport']
110 if self.sr_ref and self.session is not None: 110 ↛ 111line 110 didn't jump to line 111, because the condition on line 110 was never true
111 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref)
112 else:
113 self.sm_config = self.srcmd.params.get('sr_sm_config') or {}
115 if self.srcmd.cmd != 'sr_create': 115 ↛ 122line 115 didn't jump to line 122, because the condition on line 115 was never false
116 self.subdir = distutils.util.strtobool(
117 self.sm_config.get('subdir') or '0'
118 )
119 if self.subdir: 119 ↛ 120line 119 didn't jump to line 120, because the condition on line 119 was never true
120 self.remotepath = os.path.join(self.remotepath, sr_uuid)
122 self.attached = False
123 self.path = os.path.join(SR.MOUNT_BASE, sr_uuid)
124 self.mountpoint = self.path
125 self.linkpath = self.path
126 self._check_o_direct()
128 def checkmount(self):
129 return util.ioretry(lambda: ((util.pathexists(self.mountpoint) and
130 util.ismount(self.mountpoint))))
132 def mount(self, mountpoint=None):
133 """Mount MooseFS share at 'mountpoint'"""
134 if mountpoint is None: 134 ↛ 136line 134 didn't jump to line 136, because the condition on line 134 was never false
135 mountpoint = self.mountpoint
136 elif not util.is_string(mountpoint) or mountpoint == "":
137 raise MooseFSException("Mountpoint is not a string object")
139 try:
140 if not util.ioretry(lambda: util.isdir(mountpoint)): 140 ↛ 145line 140 didn't jump to line 145, because the condition on line 140 was never false
141 util.ioretry(lambda: util.makedirs(mountpoint)) 141 ↛ 145line 141 didn't jump to line 145
142 except util.CommandException as inst:
143 raise MooseFSException("Failed to make directory: code is %d" % inst.code)
145 try:
146 options = []
147 if 'options' in self.dconf:
148 options.append(self.dconf['options'])
149 if options:
150 options = ['-o', ','.join(options)]
151 remote = '{}:{}:{}'.format(
152 self.remoteserver, self.remoteport, self.remotepath
153 )
154 command = ["mount", '-t', 'moosefs', remote, mountpoint] + options
155 util.ioretry(lambda: util.pread(command), errlist=[errno.EPIPE, errno.EIO], maxretry=2, nofail=True)
156 except util.CommandException as inst:
157 syslog(_syslog.LOG_ERR, 'MooseFS mount failed ' + inst.__str__())
158 raise MooseFSException("Mount failed with return code %d" % inst.code)
160 # Sanity check to ensure that the user has at least RO access to the
161 # mounted share. Windows sharing and security settings can be tricky.
162 try:
163 util.listdir(mountpoint)
164 except util.CommandException:
165 try:
166 self.unmount(mountpoint, True)
167 except MooseFSException:
168 util.logException('MooseFSSR.unmount()')
169 raise MooseFSException("Permission denied. Please check user privileges.")
171 def unmount(self, mountpoint, rmmountpoint):
172 try:
173 util.pread(["umount", mountpoint])
174 except util.CommandException as inst:
175 raise MooseFSException("Command umount failed with return code %d" % inst.code)
176 if rmmountpoint:
177 try:
178 os.rmdir(mountpoint)
179 except OSError as inst:
180 raise MooseFSException("Command rmdir failed with error '%s'" % inst.strerror)
182 def attach(self, sr_uuid):
183 if not self.checkmount(): 183 ↛ 184line 183 didn't jump to line 184, because the condition on line 183 was never true
184 try:
185 self.mount()
186 except MooseFSException as exc:
187 raise SR.SROSError(12, exc.errstr)
188 self.attached = True
190 def probe(self):
191 try:
192 self.mount(PROBE_MOUNTPOINT)
193 sr_list = filter(util.match_uuid, util.listdir(PROBE_MOUNTPOINT))
194 self.unmount(PROBE_MOUNTPOINT, True)
195 except (util.CommandException, xs_errors.XenError):
196 raise
197 # Create a dictionary from the SR uuids to feed SRtoXML()
198 sr_dict = {sr_uuid: {} for sr_uuid in sr_list}
199 return util.SRtoXML(sr_dict)
201 def detach(self, sr_uuid):
202 if not self.checkmount(): 202 ↛ 204line 202 didn't jump to line 204, because the condition on line 202 was never false
203 return
204 util.SMlog("Aborting GC/coalesce")
205 cleanup.abort(sr_uuid)
206 # Change directory to avoid unmount conflicts
207 os.chdir(SR.MOUNT_BASE)
208 self.unmount(self.mountpoint, True)
209 self.attached = False
211 def create(self, sr_uuid, size):
212 if self.checkmount():
213 raise SR.SROSError(113, 'MooseFS mount point already attached')
215 assert self.remotepath == self.rootpath
216 try:
217 self.mount()
218 except MooseFSException as exc:
219 # noinspection PyBroadException
220 try:
221 os.rmdir(self.mountpoint)
222 except:
223 # we have no recovery strategy
224 pass
225 raise SR.SROSError(111, "MooseFS mount error [opterr=%s]" % exc.errstr)
227 try:
228 self.subdir = self.sm_config.get('subdir')
229 if self.subdir is None:
230 self.subdir = True
231 else:
232 self.subdir = distutils.util.strtobool(self.subdir)
234 self.sm_config['subdir'] = str(self.subdir)
235 self.session.xenapi.SR.set_sm_config(self.sr_ref, self.sm_config)
237 if not self.subdir:
238 return
240 subdir = os.path.join(self.mountpoint, sr_uuid)
241 if util.ioretry(lambda: util.pathexists(subdir)):
242 if util.ioretry(lambda: util.isdir(subdir)):
243 raise xs_errors.XenError('SRExists')
244 else:
245 try:
246 util.ioretry(lambda: util.makedirs(subdir))
247 except util.CommandException as e:
248 if e.code != errno.EEXIST:
249 raise MooseFSException(
250 'Failed to create SR subdir: {}'.format(e)
251 )
252 finally:
253 self.detach(sr_uuid)
255 def delete(self, sr_uuid):
256 # try to remove/delete non VDI contents first
257 super(MooseFSSR, self).delete(sr_uuid)
258 try:
259 if self.checkmount():
260 self.detach(sr_uuid)
262 if self.subdir:
263 # Mount using rootpath (<root>) instead of <root>/<sr_uuid>.
264 self.remotepath = self.rootpath
265 self.attach(sr_uuid)
266 subdir = os.path.join(self.mountpoint, sr_uuid)
267 if util.ioretry(lambda: util.pathexists(subdir)):
268 util.ioretry(lambda: os.rmdir(subdir))
269 self.detach(sr_uuid)
270 except util.CommandException as inst:
271 self.detach(sr_uuid)
272 if inst.code != errno.ENOENT:
273 raise SR.SROSError(114, "Failed to remove MooseFS mount point")
275 def vdi(self, uuid, loadLocked=False):
276 return MooseFSFileVDI(self, uuid)
278 @staticmethod
279 def _is_moosefs_available():
280 import distutils.spawn
281 return distutils.spawn.find_executable('mfsmount')
283class MooseFSFileVDI(FileSR.FileVDI):
284 def attach(self, sr_uuid, vdi_uuid):
285 if not hasattr(self, 'xenstore_data'):
286 self.xenstore_data = {}
288 self.xenstore_data['storage-type'] = MooseFSSR.DRIVER_TYPE
290 return super(MooseFSFileVDI, self).attach(sr_uuid, vdi_uuid)
292 def generate_config(self, sr_uuid, vdi_uuid):
293 util.SMlog("MooseFSFileVDI.generate_config")
294 if not util.pathexists(self.path):
295 raise xs_errors.XenError('VDIUnavailable')
296 resp = {'device_config': self.sr.dconf,
297 'sr_uuid': sr_uuid,
298 'vdi_uuid': vdi_uuid,
299 'sr_sm_config': self.sr.sm_config,
300 'command': 'vdi_attach_from_config'}
301 # Return the 'config' encoded within a normal XMLRPC response so that
302 # we can use the regular response/error parsing code.
303 config = xmlrpc.client.dumps(tuple([resp]), "vdi_attach_from_config")
304 return xmlrpc.client.dumps((config,), "", True)
306 def attach_from_config(self, sr_uuid, vdi_uuid):
307 try:
308 if not util.pathexists(self.sr.path):
309 self.sr.attach(sr_uuid)
310 except:
311 util.logException("MooseFSFileVDI.attach_from_config")
312 raise xs_errors.XenError('SRUnavailable',
313 opterr='Unable to attach from config')
316if __name__ == '__main__': 316 ↛ 317line 316 didn't jump to line 317, because the condition on line 316 was never true
317 SRCommand.run(MooseFSSR, DRIVER_INFO)
318else:
319 SR.registerSR(MooseFSSR)