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