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