Coverage for libs/sm/drivers/FileSR.py: 52%
767 statements
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
1# Copyright (C) Citrix Systems Inc.
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published
5# by the Free Software Foundation; version 2.1 only.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software Foundation, Inc.,
14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15#
16# FileSR: local-file storage repository
17# matches with drivers/FileSR
19import os
20import errno
21import time
22import glob
23from uuid import uuid4
24import xmlrpc.client
26import XenAPI # pylint: disable=import-error
28from sm import SR
29from sm import VDI
30from sm import vhdutil
31from sm import cleanup
32from sm import blktap2
33from sm.core import util
34from sm.core import scsiutil
35from sm.core import xs_errors
36from sm.core.lock import Lock
37from sm.constants import CBTLOG_TAG
39geneology = {}
40CAPABILITIES = ["SR_PROBE", "SR_UPDATE", \
41 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", \
42 "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR",
43 "VDI_GENERATE_CONFIG", "ATOMIC_PAUSE", "VDI_CONFIG_CBT",
44 "VDI_ACTIVATE", "VDI_DEACTIVATE", "THIN_PROVISIONING"]
46CONFIGURATION = [['location', 'local directory path (required)']]
48DRIVER_INFO = {
49 'name': 'Local Path VHD',
50 'description': 'SR plugin which represents disks as VHD files stored on a local path',
51 'vendor': 'Citrix Systems Inc',
52 'copyright': '(C) 2008 Citrix Systems Inc',
53 'driver_version': '1.0',
54 'required_api_version': '1.0',
55 'capabilities': CAPABILITIES,
56 'configuration': CONFIGURATION
57 }
59JOURNAL_FILE_PREFIX = ".journal-"
61OPS_EXCLUSIVE = [
62 "sr_create", "sr_delete", "sr_probe", "sr_attach", "sr_detach",
63 "sr_scan", "vdi_init", "vdi_create", "vdi_delete", "vdi_attach",
64 "vdi_detach", "vdi_resize_online", "vdi_snapshot", "vdi_clone"]
66DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True}
69class FileSR(SR.SR):
70 """Local file storage repository"""
72 SR_TYPE = "file"
74 def handles(srtype):
75 return srtype == 'file'
76 handles = staticmethod(handles)
78 def _check_o_direct(self):
79 if self.sr_ref and self.session is not None:
80 other_config = self.session.xenapi.SR.get_other_config(self.sr_ref)
81 o_direct = other_config.get("o_direct")
82 self.o_direct = o_direct is not None and o_direct == "true"
83 else:
84 self.o_direct = True
86 def __init__(self, srcmd, sr_uuid):
87 # We call SR.SR.__init__ explicitly because
88 # "super" sometimes failed due to circular imports
89 SR.SR.__init__(self, srcmd, sr_uuid)
90 self._check_o_direct()
92 def load(self, sr_uuid):
93 self.ops_exclusive = OPS_EXCLUSIVE
94 self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid)
95 self.sr_vditype = vhdutil.VDI_TYPE_VHD
96 if 'location' not in self.dconf or not self.dconf['location']:
97 raise xs_errors.XenError('ConfigLocationMissing')
98 self.remotepath = self.dconf['location']
99 self.path = os.path.join(SR.MOUNT_BASE, sr_uuid)
100 self.linkpath = self.path
101 self.mountpoint = self.path
102 self.attached = False
103 self.driver_config = DRIVER_CONFIG
105 def create(self, sr_uuid, size):
106 """ Create the SR. The path must not already exist, or if it does,
107 it must be empty. (This accounts for the case where the user has
108 mounted a device onto a directory manually and want to use this as the
109 root of a file-based SR.) """
110 try:
111 if util.ioretry(lambda: util.pathexists(self.remotepath)):
112 if len(util.ioretry(lambda: util.listdir(self.remotepath))) != 0:
113 raise xs_errors.XenError('SRExists')
114 else:
115 try:
116 util.ioretry(lambda: os.mkdir(self.remotepath))
117 except util.CommandException as inst:
118 if inst.code == errno.EEXIST:
119 raise xs_errors.XenError('SRExists')
120 else:
121 raise xs_errors.XenError('FileSRCreate', \
122 opterr='directory creation failure %d' \
123 % inst.code)
124 except:
125 raise xs_errors.XenError('FileSRCreate')
127 def delete(self, sr_uuid):
128 self.attach(sr_uuid)
129 cleanup.gc_force(self.session, self.uuid)
131 # check to make sure no VDIs are present; then remove old
132 # files that are non VDI's
133 try:
134 if util.ioretry(lambda: util.pathexists(self.path)):
135 #Load the VDI list
136 self._loadvdis()
137 for uuid in self.vdis:
138 if not self.vdis[uuid].deleted:
139 raise xs_errors.XenError('SRNotEmpty', \
140 opterr='VDIs still exist in SR')
142 # remove everything else, there are no vdi's
143 for name in util.ioretry(lambda: util.listdir(self.path)):
144 fullpath = os.path.join(self.path, name)
145 try:
146 util.ioretry(lambda: os.unlink(fullpath))
147 except util.CommandException as inst:
148 if inst.code != errno.ENOENT and \
149 inst.code != errno.EISDIR:
150 raise xs_errors.XenError('FileSRDelete', \
151 opterr='failed to remove %s error %d' \
152 % (fullpath, inst.code))
153 self.detach(sr_uuid)
154 except util.CommandException as inst:
155 self.detach(sr_uuid)
156 raise xs_errors.XenError('FileSRDelete', \
157 opterr='error %d' % inst.code)
159 def attach(self, sr_uuid, bind=True):
160 if not self._checkmount():
161 try:
162 util.ioretry(lambda: util.makedirs(self.path, mode=0o700))
163 except util.CommandException as inst:
164 if inst.code != errno.EEXIST:
165 raise xs_errors.XenError("FileSRCreate", \
166 opterr='fail to create mount point. Errno is %s' % inst.code)
167 try:
168 cmd = ["mount", self.remotepath, self.path]
169 if bind:
170 cmd.append("--bind")
171 util.pread(cmd)
172 os.chmod(self.path, mode=0o0700)
173 except util.CommandException as inst:
174 raise xs_errors.XenError('FileSRCreate', \
175 opterr='fail to mount FileSR. Errno is %s' % inst.code)
176 self.attached = True
178 def detach(self, sr_uuid):
179 if self._checkmount():
180 try:
181 util.SMlog("Aborting GC/coalesce")
182 cleanup.abort(self.uuid)
183 os.chdir(SR.MOUNT_BASE)
184 util.pread(["umount", self.path])
185 os.rmdir(self.path)
186 except Exception as e:
187 raise xs_errors.XenError('SRInUse', opterr=str(e))
188 self.attached = False
190 def scan(self, sr_uuid):
191 if not self._checkmount(): 191 ↛ 192line 191 didn't jump to line 192, because the condition on line 191 was never true
192 raise xs_errors.XenError('SRUnavailable', \
193 opterr='no such directory %s' % self.path)
195 if not self.vdis: 195 ↛ 198line 195 didn't jump to line 198, because the condition on line 195 was never false
196 self._loadvdis()
198 if not self.passthrough: 198 ↛ 202line 198 didn't jump to line 202, because the condition on line 198 was never false
199 self.physical_size = self._getsize()
200 self.physical_utilisation = self._getutilisation()
202 for uuid in list(self.vdis.keys()):
203 if self.vdis[uuid].deleted: 203 ↛ 204line 203 didn't jump to line 204, because the condition on line 203 was never true
204 del self.vdis[uuid]
206 # CA-15607: make sure we are robust to the directory being unmounted beneath
207 # us (eg by a confused user). Without this we might forget all our VDI references
208 # which would be a shame.
209 # For SMB SRs, this path is mountpoint
210 mount_path = self.path
211 if self.handles("smb"): 211 ↛ 212line 211 didn't jump to line 212, because the condition on line 211 was never true
212 mount_path = self.mountpoint
214 if not self.handles("file") and not os.path.ismount(mount_path): 214 ↛ 215line 214 didn't jump to line 215, because the condition on line 214 was never true
215 util.SMlog("Error: FileSR.scan called but directory %s isn't a mountpoint" % mount_path)
216 raise xs_errors.XenError('SRUnavailable', \
217 opterr='not mounted %s' % mount_path)
219 self._kickGC()
221 # default behaviour from here on
222 super(FileSR, self).scan(sr_uuid)
224 def update(self, sr_uuid):
225 if not self._checkmount():
226 raise xs_errors.XenError('SRUnavailable', \
227 opterr='no such directory %s' % self.path)
228 self._update(sr_uuid, 0)
230 def _update(self, sr_uuid, virt_alloc_delta):
231 valloc = int(self.session.xenapi.SR.get_virtual_allocation(self.sr_ref))
232 self.virtual_allocation = valloc + virt_alloc_delta
233 self.physical_size = self._getsize()
234 self.physical_utilisation = self._getutilisation()
235 self._db_update()
237 def content_type(self, sr_uuid):
238 return super(FileSR, self).content_type(sr_uuid)
240 def vdi(self, uuid):
241 return FileVDI(self, uuid)
243 def added_vdi(self, vdi):
244 self.vdis[vdi.uuid] = vdi
246 def deleted_vdi(self, uuid):
247 if uuid in self.vdis:
248 del self.vdis[uuid]
250 def replay(self, uuid):
251 try:
252 file = open(self.path + "/filelog.txt", "r")
253 data = file.readlines()
254 file.close()
255 self._process_replay(data)
256 except:
257 raise xs_errors.XenError('SRLog')
259 def _loadvdis(self):
260 if self.vdis: 260 ↛ 261line 260 didn't jump to line 261, because the condition on line 260 was never true
261 return
263 pattern = os.path.join(self.path, "*%s" % vhdutil.FILE_EXTN_VHD)
264 try:
265 self.vhds = vhdutil.getAllVHDs(pattern, FileVDI.extractUuid)
266 except util.CommandException as inst:
267 raise xs_errors.XenError('SRScan', opterr="error VHD-scanning " \
268 "path %s (%s)" % (self.path, inst))
269 try:
270 list_vhds = [FileVDI.extractUuid(v) for v in util.ioretry(lambda: glob.glob(pattern))] 270 ↛ exitline 270 didn't run the lambda on line 270
271 if len(self.vhds) != len(list_vhds): 271 ↛ 276line 271 didn't jump to line 276, because the condition on line 271 was never false
272 util.SMlog("VHD scan returns %d VHDs: %s" % (len(self.vhds), sorted(self.vhds)))
273 util.SMlog("VHD list returns %d VHDs: %s" % (len(list_vhds), sorted(list_vhds)))
274 except:
275 pass
276 for uuid in self.vhds.keys():
277 if self.vhds[uuid].error: 277 ↛ 278line 277 didn't jump to line 278, because the condition on line 277 was never true
278 raise xs_errors.XenError('SRScan', opterr='uuid=%s' % uuid)
279 self.vdis[uuid] = self.vdi(uuid)
280 # Get the key hash of any encrypted VDIs:
281 vhd_path = os.path.join(self.path, self.vhds[uuid].path)
282 key_hash = vhdutil.getKeyHash(vhd_path)
283 self.vdis[uuid].sm_config_override['key_hash'] = key_hash
285 # raw VDIs and CBT log files
286 files = util.ioretry(lambda: util.listdir(self.path)) 286 ↛ exitline 286 didn't run the lambda on line 286
287 for fn in files: 287 ↛ 288line 287 didn't jump to line 288, because the loop on line 287 never started
288 if fn.endswith(vhdutil.FILE_EXTN_RAW):
289 uuid = fn[:-(len(vhdutil.FILE_EXTN_RAW))]
290 self.vdis[uuid] = self.vdi(uuid)
291 elif fn.endswith(CBTLOG_TAG):
292 cbt_uuid = fn.split(".")[0]
293 # If an associated disk exists, update CBT status
294 # else create new VDI of type cbt_metadata
295 if cbt_uuid in self.vdis:
296 self.vdis[cbt_uuid].cbt_enabled = True
297 else:
298 new_vdi = self.vdi(cbt_uuid)
299 new_vdi.ty = "cbt_metadata"
300 new_vdi.cbt_enabled = True
301 self.vdis[cbt_uuid] = new_vdi
303 # Mark parent VDIs as Read-only and generate virtual allocation
304 self.virtual_allocation = 0
305 for uuid, vdi in self.vdis.items():
306 if vdi.parent: 306 ↛ 307line 306 didn't jump to line 307, because the condition on line 306 was never true
307 if vdi.parent in self.vdis:
308 self.vdis[vdi.parent].read_only = True
309 if vdi.parent in geneology:
310 geneology[vdi.parent].append(uuid)
311 else:
312 geneology[vdi.parent] = [uuid]
313 if not vdi.hidden: 313 ↛ 305line 313 didn't jump to line 305, because the condition on line 313 was never false
314 self.virtual_allocation += (vdi.size)
316 # now remove all hidden leaf nodes from self.vdis so that they are not
317 # introduced into the Agent DB when SR is synchronized. With the
318 # asynchronous GC, a deleted VDI might stay around until the next
319 # SR.scan, so if we don't ignore hidden leaves we would pick up
320 # freshly-deleted VDIs as newly-added VDIs
321 for uuid in list(self.vdis.keys()):
322 if uuid not in geneology and self.vdis[uuid].hidden: 322 ↛ 323line 322 didn't jump to line 323, because the condition on line 322 was never true
323 util.SMlog("Scan found hidden leaf (%s), ignoring" % uuid)
324 del self.vdis[uuid]
326 def _getsize(self):
327 path = self.path
328 if self.handles("smb"): 328 ↛ 329line 328 didn't jump to line 329, because the condition on line 328 was never true
329 path = self.linkpath
330 return util.get_fs_size(path)
332 def _getutilisation(self):
333 return util.get_fs_utilisation(self.path)
335 def _replay(self, logentry):
336 # all replay commands have the same 5,6,7th arguments
337 # vdi_command, sr-uuid, vdi-uuid
338 back_cmd = logentry[5].replace("vdi_", "")
339 target = self.vdi(logentry[7])
340 cmd = getattr(target, back_cmd)
341 args = []
342 for item in logentry[6:]:
343 item = item.replace("\n", "")
344 args.append(item)
345 ret = cmd( * args)
346 if ret:
347 print(ret)
349 def _compare_args(self, a, b):
350 try:
351 if a[2] != "log:":
352 return 1
353 if b[2] != "end:" and b[2] != "error:":
354 return 1
355 if a[3] != b[3]:
356 return 1
357 if a[4] != b[4]:
358 return 1
359 return 0
360 except:
361 return 1
363 def _process_replay(self, data):
364 logentries = []
365 for logentry in data:
366 logentry = logentry.split(" ")
367 logentries.append(logentry)
368 # we are looking for a log entry that has a log but no end or error
369 # wkcfix -- recreate (adjusted) logfile
370 index = 0
371 while index < len(logentries) - 1:
372 if self._compare_args(logentries[index], logentries[index + 1]):
373 self._replay(logentries[index])
374 else:
375 # skip the paired one
376 index += 1
377 # next
378 index += 1
380 def _kickGC(self):
381 util.SMlog("Kicking GC")
382 cleanup.start_gc_service(self.uuid)
384 def _isbind(self):
385 # os.path.ismount can't deal with bind mount
386 st1 = os.stat(self.path)
387 st2 = os.stat(self.remotepath)
388 return st1.st_dev == st2.st_dev and st1.st_ino == st2.st_ino
390 def _checkmount(self):
391 mount_path = self.path
392 if self.handles("smb"): 392 ↛ 393line 392 didn't jump to line 393, because the condition on line 392 was never true
393 mount_path = self.mountpoint
395 return util.ioretry(lambda: util.pathexists(mount_path) and \
396 (util.ismount(mount_path) or \
397 util.pathexists(self.remotepath) and self._isbind()))
399 # Override in SharedFileSR.
400 def _check_hardlinks(self):
401 return True
403class FileVDI(VDI.VDI):
404 PARAM_VHD = "vhd"
405 PARAM_RAW = "raw"
406 VDI_TYPE = {
407 PARAM_VHD: vhdutil.VDI_TYPE_VHD,
408 PARAM_RAW: vhdutil.VDI_TYPE_RAW
409 }
411 def _find_path_with_retries(self, vdi_uuid, maxretry=5, period=2.0):
412 vhd_path = os.path.join(self.sr.path, "%s.%s" % \
413 (vdi_uuid, self.PARAM_VHD))
414 raw_path = os.path.join(self.sr.path, "%s.%s" % \
415 (vdi_uuid, self.PARAM_RAW))
416 cbt_path = os.path.join(self.sr.path, "%s.%s" %
417 (vdi_uuid, CBTLOG_TAG))
418 found = False
419 tries = 0
420 while tries < maxretry and not found:
421 tries += 1
422 if util.ioretry(lambda: util.pathexists(vhd_path)):
423 self.vdi_type = vhdutil.VDI_TYPE_VHD
424 self.path = vhd_path
425 found = True
426 elif util.ioretry(lambda: util.pathexists(raw_path)):
427 self.vdi_type = vhdutil.VDI_TYPE_RAW
428 self.path = raw_path
429 self.hidden = False
430 found = True
431 elif util.ioretry(lambda: util.pathexists(cbt_path)): 431 ↛ 432line 431 didn't jump to line 432, because the condition on line 431 was never true
432 self.vdi_type = CBTLOG_TAG
433 self.path = cbt_path
434 self.hidden = False
435 found = True
437 if not found:
438 util.SMlog("VHD %s not found, retry %s of %s" % (vhd_path, tries, maxretry))
439 time.sleep(period)
441 return found
443 def load(self, vdi_uuid):
444 self.lock = self.sr.lock
446 self.sr.srcmd.params['o_direct'] = self.sr.o_direct
448 if self.sr.srcmd.cmd == "vdi_create":
449 self.vdi_type = vhdutil.VDI_TYPE_VHD
450 self.key_hash = None
451 if "vdi_sm_config" in self.sr.srcmd.params: 451 ↛ 452line 451 didn't jump to line 452, because the condition on line 451 was never true
452 if "key_hash" in self.sr.srcmd.params["vdi_sm_config"]:
453 self.key_hash = self.sr.srcmd.params["vdi_sm_config"]["key_hash"]
455 if "type" in self.sr.srcmd.params["vdi_sm_config"]:
456 vdi_type = self.sr.srcmd.params["vdi_sm_config"]["type"]
457 if not self.VDI_TYPE.get(vdi_type):
458 raise xs_errors.XenError('VDIType',
459 opterr='Invalid VDI type %s' % vdi_type)
460 self.vdi_type = self.VDI_TYPE[vdi_type]
461 self.path = os.path.join(self.sr.path, "%s%s" %
462 (vdi_uuid, vhdutil.FILE_EXTN[self.vdi_type]))
463 else:
464 found = self._find_path_with_retries(vdi_uuid)
465 if not found: 465 ↛ 466line 465 didn't jump to line 466, because the condition on line 465 was never true
466 if self.sr.srcmd.cmd == "vdi_delete":
467 # Could be delete for CBT log file
468 self.path = os.path.join(self.sr.path, "%s.%s" %
469 (vdi_uuid, self.PARAM_VHD))
470 return
471 if self.sr.srcmd.cmd == "vdi_attach_from_config":
472 return
473 raise xs_errors.XenError('VDIUnavailable',
474 opterr="VDI %s not found" % vdi_uuid)
477 if self.vdi_type == vhdutil.VDI_TYPE_VHD and \
478 self.sr.__dict__.get("vhds") and self.sr.vhds.get(vdi_uuid):
479 # VHD info already preloaded: use it instead of querying directly
480 vhdInfo = self.sr.vhds[vdi_uuid]
481 self.utilisation = vhdInfo.sizePhys
482 self.size = vhdInfo.sizeVirt
483 self.hidden = vhdInfo.hidden
484 if self.hidden: 484 ↛ 485line 484 didn't jump to line 485, because the condition on line 484 was never true
485 self.managed = False
486 self.parent = vhdInfo.parentUuid
487 if self.parent: 487 ↛ 488line 487 didn't jump to line 488, because the condition on line 487 was never true
488 self.sm_config_override = {'vhd-parent': self.parent}
489 else:
490 self.sm_config_override = {'vhd-parent': None}
491 return
493 try:
494 # Change to the SR directory in case parent
495 # locator field path has changed
496 os.chdir(self.sr.path)
497 except Exception as chdir_exception:
498 util.SMlog("Unable to change to SR directory, SR unavailable, %s" %
499 str(chdir_exception))
500 raise xs_errors.XenError('SRUnavailable', opterr=str(chdir_exception))
502 if util.ioretry( 502 ↛ exitline 502 didn't return from function 'load', because the condition on line 502 was never false
503 lambda: util.pathexists(self.path),
504 errlist=[errno.EIO, errno.ENOENT]):
505 try:
506 st = util.ioretry(lambda: os.stat(self.path),
507 errlist=[errno.EIO, errno.ENOENT])
508 self.utilisation = int(st.st_size)
509 except util.CommandException as inst:
510 if inst.code == errno.EIO:
511 raise xs_errors.XenError('VDILoad', \
512 opterr='Failed load VDI information %s' % self.path)
513 else:
514 util.SMlog("Stat failed for %s, %s" % (
515 self.path, str(inst)))
516 raise xs_errors.XenError('VDIType', \
517 opterr='Invalid VDI type %s' % self.vdi_type)
519 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 519 ↛ 520line 519 didn't jump to line 520, because the condition on line 519 was never true
520 self.exists = True
521 self.size = self.utilisation
522 self.sm_config_override = {'type': self.PARAM_RAW}
523 return
525 if self.vdi_type == CBTLOG_TAG: 525 ↛ 526line 525 didn't jump to line 526, because the condition on line 525 was never true
526 self.exists = True
527 self.size = self.utilisation
528 return
530 try:
531 # The VDI might be activated in R/W mode so the VHD footer
532 # won't be valid, use the back-up one instead.
533 diskinfo = util.ioretry(
534 lambda: self._query_info(self.path, True),
535 errlist=[errno.EIO, errno.ENOENT])
537 if 'parent' in diskinfo: 537 ↛ 538line 537 didn't jump to line 538, because the condition on line 537 was never true
538 self.parent = diskinfo['parent']
539 self.sm_config_override = {'vhd-parent': self.parent}
540 else:
541 self.sm_config_override = {'vhd-parent': None}
542 self.parent = ''
543 self.size = int(diskinfo['size']) * 1024 * 1024
544 self.hidden = int(diskinfo['hidden'])
545 if self.hidden: 545 ↛ 546line 545 didn't jump to line 546, because the condition on line 545 was never true
546 self.managed = False
547 self.exists = True
548 except util.CommandException as inst:
549 raise xs_errors.XenError('VDILoad', \
550 opterr='Failed load VDI information %s' % self.path)
552 def update(self, sr_uuid, vdi_location):
553 self.load(vdi_location)
554 vdi_ref = self.sr.srcmd.params['vdi_ref']
555 self.sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
556 self._db_update()
558 def create(self, sr_uuid, vdi_uuid, size):
559 if util.ioretry(lambda: util.pathexists(self.path)): 559 ↛ 560line 559 didn't jump to line 560, because the condition on line 559 was never true
560 raise xs_errors.XenError('VDIExists')
562 if self.vdi_type == vhdutil.VDI_TYPE_VHD:
563 try:
564 size = vhdutil.validate_and_round_vhd_size(int(size))
565 mb = 1024 * 1024
566 size_mb = size // mb
567 util.ioretry(lambda: self._create(str(size_mb), self.path))
568 self.size = util.ioretry(lambda: self._query_v(self.path))
569 except util.CommandException as inst:
570 raise xs_errors.XenError('VDICreate',
571 opterr='error %d' % inst.code)
572 else:
573 f = open(self.path, 'w')
574 f.truncate(int(size))
575 f.close()
576 self.size = size
578 self.sr.added_vdi(self)
580 st = util.ioretry(lambda: os.stat(self.path))
581 self.utilisation = int(st.st_size)
582 if self.vdi_type == vhdutil.VDI_TYPE_RAW:
583 self.sm_config = {"type": self.PARAM_RAW}
585 self._db_introduce()
586 self.sr._update(self.sr.uuid, self.size)
587 return super(FileVDI, self).get_params()
589 def delete(self, sr_uuid, vdi_uuid, data_only=False):
590 if not util.ioretry(lambda: util.pathexists(self.path)):
591 return super(FileVDI, self).delete(sr_uuid, vdi_uuid, data_only)
593 if self.attached:
594 raise xs_errors.XenError('VDIInUse')
596 try:
597 util.force_unlink(self.path)
598 except Exception as e:
599 raise xs_errors.XenError(
600 'VDIDelete',
601 opterr='Failed to unlink file during deleting VDI: %s' % str(e))
603 self.sr.deleted_vdi(vdi_uuid)
604 # If this is a data_destroy call, don't remove from XAPI db
605 if not data_only:
606 self._db_forget()
607 self.sr._update(self.sr.uuid, -self.size)
608 self.sr.lock.cleanupAll(vdi_uuid)
609 self.sr._kickGC()
610 return super(FileVDI, self).delete(sr_uuid, vdi_uuid, data_only)
612 def attach(self, sr_uuid, vdi_uuid):
613 if self.path is None:
614 self._find_path_with_retries(vdi_uuid)
615 if not self._checkpath(self.path):
616 raise xs_errors.XenError('VDIUnavailable', \
617 opterr='VDI %s unavailable %s' % (vdi_uuid, self.path))
618 try:
619 self.attached = True
621 if not hasattr(self, 'xenstore_data'):
622 self.xenstore_data = {}
624 self.xenstore_data.update(scsiutil.update_XS_SCSIdata(vdi_uuid, \
625 scsiutil.gen_synthetic_page_data(vdi_uuid)))
627 if self.sr.handles("file"):
628 # XXX: PR-1255: if these are constants then they should
629 # be returned by the attach API call, not persisted in the
630 # pool database.
631 self.xenstore_data['storage-type'] = 'ext'
632 return super(FileVDI, self).attach(sr_uuid, vdi_uuid)
633 except util.CommandException as inst:
634 raise xs_errors.XenError('VDILoad', opterr='error %d' % inst.code)
636 def detach(self, sr_uuid, vdi_uuid):
637 self.attached = False
639 def resize(self, sr_uuid, vdi_uuid, size):
640 if not self.exists:
641 raise xs_errors.XenError('VDIUnavailable', \
642 opterr='VDI %s unavailable %s' % (vdi_uuid, self.path))
644 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
645 raise xs_errors.XenError('Unimplemented')
647 if self.hidden:
648 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
650 if size < self.size:
651 util.SMlog('vdi_resize: shrinking not supported: ' + \
652 '(current size: %d, new size: %d)' % (self.size, size))
653 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
655 if size == self.size:
656 return VDI.VDI.get_params(self)
658 # We already checked it is a VDI_TYPE_VHD
659 size = vhdutil.validate_and_round_vhd_size(int(size))
661 jFile = JOURNAL_FILE_PREFIX + self.uuid
662 try:
663 vhdutil.setSizeVirt(self.path, size, jFile)
664 except:
665 # Revert the operation
666 vhdutil.revert(self.path, jFile)
667 raise xs_errors.XenError('VDISize', opterr='resize operation failed')
669 old_size = self.size
670 self.size = vhdutil.getSizeVirt(self.path)
671 st = util.ioretry(lambda: os.stat(self.path))
672 self.utilisation = int(st.st_size)
674 self._db_update()
675 self.sr._update(self.sr.uuid, self.size - old_size)
676 super(FileVDI, self).resize_cbt(self.sr.uuid, self.uuid, self.size)
677 return VDI.VDI.get_params(self)
679 def clone(self, sr_uuid, vdi_uuid):
680 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE)
682 def compose(self, sr_uuid, vdi1, vdi2):
683 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
684 raise xs_errors.XenError('Unimplemented')
685 parent_fn = vdi1 + vhdutil.FILE_EXTN[vhdutil.VDI_TYPE_VHD]
686 parent_path = os.path.join(self.sr.path, parent_fn)
687 assert(util.pathexists(parent_path))
688 vhdutil.setParent(self.path, parent_path, False)
689 vhdutil.setHidden(parent_path)
690 self.sr.session.xenapi.VDI.set_managed(self.sr.srcmd.params['args'][0], False)
691 util.pread2([vhdutil.VHD_UTIL, "modify", "-p", parent_path,
692 "-n", self.path])
693 # Tell tapdisk the chain has changed
694 if not blktap2.VDI.tap_refresh(self.session, sr_uuid, vdi2):
695 raise util.SMException("failed to refresh VDI %s" % self.uuid)
696 util.SMlog("VDI.compose: relinked %s->%s" % (vdi2, vdi1))
698 def reset_leaf(self, sr_uuid, vdi_uuid):
699 if self.vdi_type != vhdutil.VDI_TYPE_VHD:
700 raise xs_errors.XenError('Unimplemented')
702 # safety check
703 if not vhdutil.hasParent(self.path):
704 raise util.SMException("ERROR: VDI %s has no parent, " + \
705 "will not reset contents" % self.uuid)
707 vhdutil.killData(self.path)
709 def _do_snapshot(self, sr_uuid, vdi_uuid, snap_type,
710 _=False, secondary=None, cbtlog=None):
711 # If cbt enabled, save file consistency state
712 if cbtlog is not None: 712 ↛ 713line 712 didn't jump to line 713, because the condition on line 712 was never true
713 if blktap2.VDI.tap_status(self.session, vdi_uuid):
714 consistency_state = False
715 else:
716 consistency_state = True
717 util.SMlog("Saving log consistency state of %s for vdi: %s" %
718 (consistency_state, vdi_uuid))
719 else:
720 consistency_state = None
722 if self.vdi_type != vhdutil.VDI_TYPE_VHD: 722 ↛ 723line 722 didn't jump to line 723, because the condition on line 722 was never true
723 raise xs_errors.XenError('Unimplemented')
725 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid): 725 ↛ 726line 725 didn't jump to line 726, because the condition on line 725 was never true
726 raise util.SMException("failed to pause VDI %s" % vdi_uuid)
727 try:
728 return self._snapshot(snap_type, cbtlog, consistency_state)
729 finally:
730 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary)
731 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
733 def _rename(self, src, dst):
734 util.SMlog("FileVDI._rename %s to %s" % (src, dst))
735 util.ioretry(lambda: os.rename(src, dst))
737 def _link(self, src, dst):
738 util.SMlog("FileVDI._link %s to %s" % (src, dst))
739 os.link(src, dst)
741 def _unlink(self, path):
742 util.SMlog("FileVDI._unlink %s" % (path))
743 os.unlink(path)
745 def _create_new_parent(self, src, newsrc):
746 if self.sr._check_hardlinks():
747 self._link(src, newsrc)
748 else:
749 self._rename(src, newsrc)
751 def __fist_enospace(self):
752 raise util.CommandException(28, "vhd-util snapshot", reason="No space")
754 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None):
755 util.SMlog("FileVDI._snapshot for %s (type %s)" % (self.uuid, snap_type))
757 args = []
758 args.append("vdi_clone")
759 args.append(self.sr.uuid)
760 args.append(self.uuid)
762 dest = None
763 dst = None
764 if snap_type == VDI.SNAPSHOT_DOUBLE: 764 ↛ 769line 764 didn't jump to line 769, because the condition on line 764 was never false
765 dest = util.gen_uuid()
766 dst = os.path.join(self.sr.path, "%s.%s" % (dest, self.vdi_type))
767 args.append(dest)
769 if self.hidden: 769 ↛ 770line 769 didn't jump to line 770, because the condition on line 769 was never true
770 raise xs_errors.XenError('VDIClone', opterr='hidden VDI')
772 depth = vhdutil.getDepth(self.path)
773 if depth == -1: 773 ↛ 774line 773 didn't jump to line 774, because the condition on line 773 was never true
774 raise xs_errors.XenError('VDIUnavailable', \
775 opterr='failed to get VHD depth')
776 elif depth >= vhdutil.MAX_CHAIN_SIZE: 776 ↛ 777line 776 didn't jump to line 777, because the condition on line 776 was never true
777 raise xs_errors.XenError('SnapshotChainTooLong')
779 newuuid = util.gen_uuid()
780 src = self.path
781 newsrc = os.path.join(self.sr.path, "%s.%s" % (newuuid, self.vdi_type))
782 newsrcname = "%s.%s" % (newuuid, self.vdi_type)
784 if not self._checkpath(src): 784 ↛ 785line 784 didn't jump to line 785, because the condition on line 784 was never true
785 raise xs_errors.XenError('VDIUnavailable', \
786 opterr='VDI %s unavailable %s' % (self.uuid, src))
788 # wkcfix: multiphase
789 util.start_log_entry(self.sr.path, self.path, args)
791 # We assume the filehandle has been released
792 try:
793 self._create_new_parent(src, newsrc)
795 # Create the snapshot under a temporary name, then rename
796 # it afterwards. This avoids a small window where it exists
797 # but is invalid. We do not need to do this for
798 # snap_type == VDI.SNAPSHOT_DOUBLE because dst never existed
799 # before so nobody will try to query it.
800 tmpsrc = "%s.%s" % (src, "new")
801 # Fault injection site to fail the snapshot with ENOSPACE
802 util.fistpoint.activate_custom_fn(
803 "FileSR_fail_snap1",
804 self.__fist_enospace)
805 util.ioretry(lambda: self._snap(tmpsrc, newsrcname))
806 # SMB3 can return EACCES if we attempt to rename over the
807 # hardlink leaf too quickly after creating it.
808 util.ioretry(lambda: self._rename(tmpsrc, src),
809 errlist=[errno.EIO, errno.EACCES])
810 if snap_type == VDI.SNAPSHOT_DOUBLE: 810 ↛ 818line 810 didn't jump to line 818, because the condition on line 810 was never false
811 # Fault injection site to fail the snapshot with ENOSPACE
812 util.fistpoint.activate_custom_fn(
813 "FileSR_fail_snap2",
814 self.__fist_enospace)
815 util.ioretry(lambda: self._snap(dst, newsrcname))
816 # mark the original file (in this case, its newsrc)
817 # as hidden so that it does not show up in subsequent scans
818 util.ioretry(lambda: self._mark_hidden(newsrc))
820 #Verify parent locator field of both children and delete newsrc if unused
821 introduce_parent = True
822 try:
823 srcparent = util.ioretry(lambda: self._query_p_uuid(src))
824 dstparent = None
825 if snap_type == VDI.SNAPSHOT_DOUBLE: 825 ↛ 827line 825 didn't jump to line 827, because the condition on line 825 was never false
826 dstparent = util.ioretry(lambda: self._query_p_uuid(dst))
827 if srcparent != newuuid and \ 827 ↛ 831line 827 didn't jump to line 831, because the condition on line 827 was never true
828 (snap_type == VDI.SNAPSHOT_SINGLE or \
829 snap_type == VDI.SNAPSHOT_INTERNAL or \
830 dstparent != newuuid):
831 util.ioretry(lambda: self._unlink(newsrc))
832 introduce_parent = False
833 except:
834 pass
836 # Introduce the new VDI records
837 leaf_vdi = None
838 if snap_type == VDI.SNAPSHOT_DOUBLE: 838 ↛ 857line 838 didn't jump to line 857, because the condition on line 838 was never false
839 leaf_vdi = VDI.VDI(self.sr, dest) # user-visible leaf VDI
840 leaf_vdi.read_only = False
841 leaf_vdi.location = dest
842 leaf_vdi.size = self.size
843 leaf_vdi.utilisation = self.utilisation
844 leaf_vdi.sm_config = {}
845 leaf_vdi.sm_config['vhd-parent'] = dstparent
846 # If the parent is encrypted set the key_hash
847 # for the new snapshot disk
848 vdi_ref = self.sr.srcmd.params['vdi_ref']
849 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
850 if "key_hash" in sm_config: 850 ↛ 851line 850 didn't jump to line 851, because the condition on line 850 was never true
851 leaf_vdi.sm_config['key_hash'] = sm_config['key_hash']
852 # If we have CBT enabled on the VDI,
853 # set CBT status for the new snapshot disk
854 if cbtlog: 854 ↛ 855line 854 didn't jump to line 855, because the condition on line 854 was never true
855 leaf_vdi.cbt_enabled = True
857 base_vdi = None
858 if introduce_parent: 858 ↛ 870line 858 didn't jump to line 870, because the condition on line 858 was never false
859 base_vdi = VDI.VDI(self.sr, newuuid) # readonly parent
860 base_vdi.label = "base copy"
861 base_vdi.read_only = True
862 base_vdi.location = newuuid
863 base_vdi.size = self.size
864 base_vdi.utilisation = self.utilisation
865 base_vdi.sm_config = {}
866 grandparent = util.ioretry(lambda: self._query_p_uuid(newsrc))
867 if grandparent.find("no parent") == -1: 867 ↛ 870line 867 didn't jump to line 870, because the condition on line 867 was never false
868 base_vdi.sm_config['vhd-parent'] = grandparent
870 try:
871 if snap_type == VDI.SNAPSHOT_DOUBLE: 871 ↛ 876line 871 didn't jump to line 876, because the condition on line 871 was never false
872 leaf_vdi_ref = leaf_vdi._db_introduce()
873 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % \
874 (leaf_vdi_ref, dest))
876 if introduce_parent: 876 ↛ 880line 876 didn't jump to line 880, because the condition on line 876 was never false
877 base_vdi_ref = base_vdi._db_introduce()
878 self.session.xenapi.VDI.set_managed(base_vdi_ref, False)
879 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % (base_vdi_ref, newuuid))
880 vdi_ref = self.sr.srcmd.params['vdi_ref']
881 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
882 sm_config['vhd-parent'] = srcparent
883 self.session.xenapi.VDI.set_sm_config(vdi_ref, sm_config)
884 except Exception as e:
885 util.SMlog("vdi_clone: caught error during VDI.db_introduce: %s" % (str(e)))
886 # Note it's too late to actually clean stuff up here: the base disk has
887 # been marked as deleted already.
888 util.end_log_entry(self.sr.path, self.path, ["error"])
889 raise
890 except util.CommandException as inst:
891 # XXX: it might be too late if the base disk has been marked as deleted!
892 self._clonecleanup(src, dst, newsrc)
893 util.end_log_entry(self.sr.path, self.path, ["error"])
894 raise xs_errors.XenError('VDIClone',
895 opterr='VDI clone failed error %d' % inst.code)
897 # Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
898 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 898 ↛ 899line 898 didn't jump to line 899, because the condition on line 898 was never true
899 try:
900 self._cbt_snapshot(dest, cbt_consistency)
901 except:
902 # CBT operation failed.
903 util.end_log_entry(self.sr.path, self.path, ["error"])
904 raise
906 util.end_log_entry(self.sr.path, self.path, ["done"])
907 if snap_type != VDI.SNAPSHOT_INTERNAL: 907 ↛ 910line 907 didn't jump to line 910, because the condition on line 907 was never false
908 self.sr._update(self.sr.uuid, self.size)
909 # Return info on the new user-visible leaf VDI
910 ret_vdi = leaf_vdi
911 if not ret_vdi: 911 ↛ 912line 911 didn't jump to line 912, because the condition on line 911 was never true
912 ret_vdi = base_vdi
913 if not ret_vdi: 913 ↛ 914line 913 didn't jump to line 914, because the condition on line 913 was never true
914 ret_vdi = self
915 return ret_vdi.get_params()
917 def get_params(self):
918 if not self._checkpath(self.path):
919 raise xs_errors.XenError('VDIUnavailable', \
920 opterr='VDI %s unavailable %s' % (self.uuid, self.path))
921 return super(FileVDI, self).get_params()
923 def _snap(self, child, parent):
924 cmd = [SR.TAPDISK_UTIL, "snapshot", vhdutil.VDI_TYPE_VHD, child, parent]
925 text = util.pread(cmd)
927 def _clonecleanup(self, src, dst, newsrc):
928 try:
929 if dst: 929 ↛ 933line 929 didn't jump to line 933, because the condition on line 929 was never false
930 util.ioretry(lambda: self._unlink(dst))
931 except util.CommandException as inst:
932 pass
933 try:
934 if util.ioretry(lambda: util.pathexists(newsrc)): 934 ↛ exitline 934 didn't return from function '_clonecleanup', because the condition on line 934 was never false
935 stats = os.stat(newsrc)
936 # Check if we have more than one link to newsrc
937 if (stats.st_nlink > 1):
938 util.ioretry(lambda: self._unlink(newsrc))
939 elif not self._is_hidden(newsrc): 939 ↛ exitline 939 didn't return from function '_clonecleanup', because the condition on line 939 was never false
940 self._rename(newsrc, src)
941 except util.CommandException as inst:
942 pass
944 def _checkpath(self, path):
945 try:
946 if not util.ioretry(lambda: util.pathexists(path)): 946 ↛ 947line 946 didn't jump to line 947, because the condition on line 946 was never true
947 return False
948 return True
949 except util.CommandException as inst:
950 raise xs_errors.XenError('EIO', \
951 opterr='IO error checking path %s' % path)
953 def _query_v(self, path):
954 cmd = [SR.TAPDISK_UTIL, "query", vhdutil.VDI_TYPE_VHD, "-v", path]
955 return int(util.pread(cmd)) * 1024 * 1024
957 def _query_p_uuid(self, path):
958 cmd = [SR.TAPDISK_UTIL, "query", vhdutil.VDI_TYPE_VHD, "-p", path]
959 parent = util.pread(cmd)
960 parent = parent[:-1]
961 ls = parent.split('/')
962 return ls[len(ls) - 1].replace(vhdutil.FILE_EXTN_VHD, '')
964 def _query_info(self, path, use_bkp_footer=False):
965 diskinfo = {}
966 qopts = '-vpf'
967 if use_bkp_footer: 967 ↛ 969line 967 didn't jump to line 969, because the condition on line 967 was never false
968 qopts += 'b'
969 cmd = [SR.TAPDISK_UTIL, "query", vhdutil.VDI_TYPE_VHD, qopts, path]
970 txt = util.pread(cmd).split('\n')
971 diskinfo['size'] = txt[0]
972 lst = [txt[1].split('/')[-1].replace(vhdutil.FILE_EXTN_VHD, "")]
973 for val in filter(util.exactmatch_uuid, lst): 973 ↛ 974line 973 didn't jump to line 974, because the loop on line 973 never started
974 diskinfo['parent'] = val
975 diskinfo['hidden'] = txt[2].split()[1]
976 return diskinfo
978 def _create(self, size, path):
979 cmd = [SR.TAPDISK_UTIL, "create", vhdutil.VDI_TYPE_VHD, size, path]
980 text = util.pread(cmd)
981 if self.key_hash: 981 ↛ 982line 981 didn't jump to line 982, because the condition on line 981 was never true
982 vhdutil.setKey(path, self.key_hash)
984 def _mark_hidden(self, path):
985 vhdutil.setHidden(path, True)
986 self.hidden = 1
988 def _is_hidden(self, path):
989 return vhdutil.getHidden(path) == 1
991 def extractUuid(path):
992 fileName = os.path.basename(path)
993 uuid = fileName.replace(vhdutil.FILE_EXTN_VHD, "")
994 return uuid
995 extractUuid = staticmethod(extractUuid)
997 def generate_config(self, sr_uuid, vdi_uuid):
998 """
999 Generate the XML config required to attach and activate
1000 a VDI for use when XAPI is not running. Attach and
1001 activation is handled by vdi_attach_from_config below.
1002 """
1003 util.SMlog("FileVDI.generate_config")
1004 if not util.pathexists(self.path): 1004 ↛ 1005line 1004 didn't jump to line 1005, because the condition on line 1004 was never true
1005 raise xs_errors.XenError('VDIUnavailable')
1006 resp = {}
1007 resp['device_config'] = self.sr.dconf
1008 resp['sr_uuid'] = sr_uuid
1009 resp['vdi_uuid'] = vdi_uuid
1010 resp['command'] = 'vdi_attach_from_config'
1011 # Return the 'config' encoded within a normal XMLRPC response so that
1012 # we can use the regular response/error parsing code.
1013 config = xmlrpc.client.dumps(tuple([resp]), "vdi_attach_from_config")
1014 return xmlrpc.client.dumps((config, ), "", True)
1016 def attach_from_config(self, sr_uuid, vdi_uuid):
1017 """
1018 Attach and activate a VDI using config generated by
1019 vdi_generate_config above. This is used for cases such as
1020 the HA state-file and the redo-log.
1021 """
1022 util.SMlog("FileVDI.attach_from_config")
1023 try:
1024 if not util.pathexists(self.sr.path):
1025 self.sr.attach(sr_uuid)
1026 except:
1027 util.logException("FileVDI.attach_from_config")
1028 raise xs_errors.XenError(
1029 'SRUnavailable',
1030 opterr='Unable to attach from config'
1031 )
1033 def _create_cbt_log(self):
1034 # Create CBT log file
1035 # Name: <vdi_uuid>.cbtlog
1036 #Handle if file already exists
1037 log_path = self._get_cbt_logpath(self.uuid)
1038 open_file = open(log_path, "w+")
1039 open_file.close()
1040 return super(FileVDI, self)._create_cbt_log()
1042 def _delete_cbt_log(self):
1043 logPath = self._get_cbt_logpath(self.uuid)
1044 try:
1045 os.remove(logPath)
1046 except OSError as e:
1047 if e.errno != errno.ENOENT:
1048 raise
1050 def _cbt_log_exists(self, logpath):
1051 return util.pathexists(logpath)
1054class SharedFileSR(FileSR):
1055 """
1056 FileSR subclass for SRs that use shared network storage
1057 """
1059 def _check_writable(self):
1060 """
1061 Checks that the filesystem being used by the SR can be written to,
1062 raising an exception if it can't.
1063 """
1064 test_name = os.path.join(self.path, str(uuid4()))
1065 try:
1066 open(test_name, 'ab').close()
1067 except OSError as e:
1068 util.SMlog("Cannot write to SR file system: %s" % e)
1069 raise xs_errors.XenError('SharedFileSystemNoWrite')
1070 finally:
1071 util.force_unlink(test_name)
1073 def _raise_hardlink_error(self):
1074 raise OSError(524, "Unknown error 524")
1076 def _check_hardlinks(self):
1077 hardlink_conf = self._read_hardlink_conf()
1078 if hardlink_conf is not None: 1078 ↛ 1079line 1078 didn't jump to line 1079, because the condition on line 1078 was never true
1079 return hardlink_conf
1081 test_name = os.path.join(self.path, str(uuid4()))
1082 open(test_name, 'ab').close()
1084 link_name = '%s.new' % test_name
1085 try:
1086 # XSI-1100: Let tests simulate failure of the link operation
1087 util.fistpoint.activate_custom_fn(
1088 "FileSR_fail_hardlink",
1089 self._raise_hardlink_error)
1091 os.link(test_name, link_name)
1092 self._write_hardlink_conf(supported=True)
1093 return True
1094 except OSError:
1095 self._write_hardlink_conf(supported=False)
1097 msg = "File system for SR %s does not support hardlinks, crash " \
1098 "consistency of snapshots cannot be assured" % self.uuid
1099 util.SMlog(msg, priority=util.LOG_WARNING)
1100 # Note: session can be not set during attach/detach_from_config calls.
1101 if self.session: 1101 ↛ 1110line 1101 didn't jump to line 1110, because the condition on line 1101 was never false
1102 try:
1103 self.session.xenapi.message.create(
1104 "sr_does_not_support_hardlinks", 2, "SR", self.uuid,
1105 msg)
1106 except XenAPI.Failure:
1107 # Might already be set and checking has TOCTOU issues
1108 pass
1109 finally:
1110 util.force_unlink(link_name)
1111 util.force_unlink(test_name)
1113 return False
1115 def _get_hardlink_conf_path(self):
1116 return os.path.join(self.path, 'sm-hardlink.conf')
1118 def _read_hardlink_conf(self):
1119 try:
1120 with open(self._get_hardlink_conf_path(), 'r') as f:
1121 try:
1122 return bool(int(f.read()))
1123 except Exception as e:
1124 # If we can't read, assume the file is empty and test for hardlink support.
1125 return None
1126 except IOError as e:
1127 if e.errno == errno.ENOENT:
1128 # If the config file doesn't exist, assume we want to support hardlinks.
1129 return None
1130 util.SMlog('Failed to read hardlink conf: {}'.format(e))
1131 # Can be caused by a concurrent access, not a major issue.
1132 return None
1134 def _write_hardlink_conf(self, supported):
1135 try:
1136 with open(self._get_hardlink_conf_path(), 'w') as f:
1137 f.write('1' if supported else '0')
1138 except Exception as e:
1139 # Can be caused by a concurrent access, not a major issue.
1140 util.SMlog('Failed to write hardlink conf: {}'.format(e))
1142# SR registration at import
1143SR.registerSR(FileSR)