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 ↛ 919line 900 didn't jump to line 919, 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 # If the parent is encrypted set the key_hash
909 # for the new snapshot disk
910 vdi_ref = self.sr.srcmd.params['vdi_ref']
911 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
912 if "key_hash" in sm_config: 912 ↛ 913line 912 didn't jump to line 913, because the condition on line 912 was never true
913 leaf_vdi.sm_config['key_hash'] = sm_config['key_hash']
914 # If we have CBT enabled on the VDI,
915 # set CBT status for the new snapshot disk
916 if cbtlog: 916 ↛ 917line 916 didn't jump to line 917, because the condition on line 916 was never true
917 leaf_vdi.cbt_enabled = True
919 base_vdi = None
920 if introduce_parent: 920 ↛ 932line 920 didn't jump to line 932, because the condition on line 920 was never false
921 base_vdi = VDI.VDI(self.sr, newuuid) # readonly parent
922 base_vdi.label = "base copy"
923 base_vdi.read_only = True
924 base_vdi.location = newuuid
925 base_vdi.size = self.size
926 base_vdi.utilisation = self.utilisation
927 base_vdi.sm_config = {}
928 grandparent = self.cowutil.getParent(newsrc, FileVDI.extractUuid)
929 if grandparent: 929 ↛ 932line 929 didn't jump to line 932, because the condition on line 929 was never false
930 base_vdi.sm_config['vhd-parent'] = grandparent
932 try:
933 if snap_type == VDI.SNAPSHOT_DOUBLE: 933 ↛ 938line 933 didn't jump to line 938, because the condition on line 933 was never false
934 leaf_vdi_ref = leaf_vdi._db_introduce()
935 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % \
936 (leaf_vdi_ref, dest))
938 if introduce_parent: 938 ↛ 942line 938 didn't jump to line 942, because the condition on line 938 was never false
939 base_vdi_ref = base_vdi._db_introduce()
940 self.session.xenapi.VDI.set_managed(base_vdi_ref, False)
941 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % (base_vdi_ref, newuuid))
942 vdi_ref = self.sr.srcmd.params['vdi_ref']
943 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
944 sm_config['vhd-parent'] = srcparent
945 self.session.xenapi.VDI.set_sm_config(vdi_ref, sm_config)
946 except Exception as e:
947 util.SMlog("vdi_clone: caught error during VDI.db_introduce: %s" % (str(e)))
948 # Note it's too late to actually clean stuff up here: the base disk has
949 # been marked as deleted already.
950 util.end_log_entry(self.sr.path, self.path, ["error"])
951 raise
952 except util.CommandException as inst:
953 # XXX: it might be too late if the base disk has been marked as deleted!
954 self._clonecleanup(src, dst, newsrc)
955 util.end_log_entry(self.sr.path, self.path, ["error"])
956 raise xs_errors.XenError('VDIClone',
957 opterr='VDI clone failed error %d' % inst.code)
959 # Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
960 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 960 ↛ 961line 960 didn't jump to line 961, because the condition on line 960 was never true
961 try:
962 self._cbt_snapshot(dest, cbt_consistency)
963 except:
964 # CBT operation failed.
965 util.end_log_entry(self.sr.path, self.path, ["error"])
966 raise
968 util.end_log_entry(self.sr.path, self.path, ["done"])
969 if snap_type != VDI.SNAPSHOT_INTERNAL: 969 ↛ 972line 969 didn't jump to line 972, because the condition on line 969 was never false
970 self.sr._update(self.sr.uuid, self.size)
971 # Return info on the new user-visible leaf VDI
972 ret_vdi = leaf_vdi
973 if not ret_vdi: 973 ↛ 974line 973 didn't jump to line 974, because the condition on line 973 was never true
974 ret_vdi = base_vdi
975 if not ret_vdi: 975 ↛ 976line 975 didn't jump to line 976, because the condition on line 975 was never true
976 ret_vdi = self
977 return ret_vdi.get_params()
979 @override
980 def get_params(self) -> str:
981 if not self._checkpath(self.path):
982 raise xs_errors.XenError('VDIUnavailable', \
983 opterr='VDI %s unavailable %s' % (self.uuid, self.path))
984 return super(FileVDI, self).get_params()
986 def _snap(self, child, parent, is_mirror_destination=False):
987 self.cowutil.snapshot(child, parent, self.vdi_type == VdiType.RAW, is_mirror_image=is_mirror_destination)
989 def _clonecleanup(self, src, dst, newsrc):
990 try:
991 if dst: 991 ↛ 995line 991 didn't jump to line 995, because the condition on line 991 was never false
992 util.ioretry(lambda: self._unlink(dst))
993 except util.CommandException as inst:
994 pass
995 try:
996 if util.ioretry(lambda: util.pathexists(newsrc)): 996 ↛ exitline 996 didn't return from function '_clonecleanup', because the condition on line 996 was never false
997 stats = os.stat(newsrc)
998 # Check if we have more than one link to newsrc
999 if (stats.st_nlink > 1):
1000 util.ioretry(lambda: self._unlink(newsrc))
1001 elif not self._is_hidden(newsrc): 1001 ↛ exitline 1001 didn't return from function '_clonecleanup', because the condition on line 1001 was never false
1002 self._rename(newsrc, src)
1003 except util.CommandException as inst:
1004 pass
1006 def _checkpath(self, path):
1007 try:
1008 if not util.ioretry(lambda: util.pathexists(path)): 1008 ↛ 1009line 1008 didn't jump to line 1009, because the condition on line 1008 was never true
1009 return False
1010 return True
1011 except util.CommandException as inst:
1012 raise xs_errors.XenError('EIO', \
1013 opterr='IO error checking path %s' % path)
1015 def _create(self, size, path):
1016 self.cowutil.create(path, size, False)
1017 if self.key_hash: 1017 ↛ 1018line 1017 didn't jump to line 1018, because the condition on line 1017 was never true
1018 self.cowutil.setKey(path, self.key_hash)
1020 def _mark_hidden(self, path):
1021 self.cowutil.setHidden(path, True)
1022 self.hidden = 1
1024 def _is_hidden(self, path):
1025 return self.cowutil.getHidden(path) == 1
1027 @staticmethod
1028 def extractUuid(path: str) -> str:
1029 fileName = os.path.basename(path)
1030 return os.path.splitext(fileName)[0]
1032 @override
1033 def generate_config(self, sr_uuid, vdi_uuid) -> str:
1034 """
1035 Generate the XML config required to attach and activate
1036 a VDI for use when XAPI is not running. Attach and
1037 activation is handled by vdi_attach_from_config below.
1038 """
1039 util.SMlog("FileVDI.generate_config")
1040 if not util.pathexists(self.path): 1040 ↛ 1041line 1040 didn't jump to line 1041, because the condition on line 1040 was never true
1041 raise xs_errors.XenError('VDIUnavailable')
1042 resp = {}
1043 resp['device_config'] = self.sr.dconf
1044 resp['sr_uuid'] = sr_uuid
1045 resp['vdi_uuid'] = vdi_uuid
1046 resp['command'] = 'vdi_attach_from_config'
1047 # Return the 'config' encoded within a normal XMLRPC response so that
1048 # we can use the regular response/error parsing code.
1049 config = xmlrpc.client.dumps(tuple([resp]), "vdi_attach_from_config")
1050 return xmlrpc.client.dumps((config, ), "", True)
1052 @override
1053 def attach_from_config(self, sr_uuid, vdi_uuid) -> str:
1054 """
1055 Attach and activate a VDI using config generated by
1056 vdi_generate_config above. This is used for cases such as
1057 the HA state-file and the redo-log.
1058 """
1059 util.SMlog("FileVDI.attach_from_config")
1060 try:
1061 if not util.pathexists(self.sr.path):
1062 return self.sr.attach(sr_uuid)
1063 except:
1064 util.logException("FileVDI.attach_from_config")
1065 raise xs_errors.XenError(
1066 'SRUnavailable',
1067 opterr='Unable to attach from config'
1068 )
1069 return ''
1071 @override
1072 def _create_cbt_log(self) -> str:
1073 # Create CBT log file
1074 # Name: <vdi_uuid>.cbtlog
1075 #Handle if file already exists
1076 log_path = self._get_cbt_logpath(self.uuid)
1077 open_file = open(log_path, "w+")
1078 open_file.close()
1079 return super(FileVDI, self)._create_cbt_log()
1081 @override
1082 def _delete_cbt_log(self) -> None:
1083 logPath = self._get_cbt_logpath(self.uuid)
1084 try:
1085 os.remove(logPath)
1086 except OSError as e:
1087 if e.errno != errno.ENOENT:
1088 raise
1090 @override
1091 def _cbt_log_exists(self, logpath) -> bool:
1092 return util.pathexists(logpath)
1095class SharedFileSR(FileSR):
1096 """
1097 FileSR subclass for SRs that use shared network storage
1098 """
1100 def _check_writable(self):
1101 """
1102 Checks that the filesystem being used by the SR can be written to,
1103 raising an exception if it can't.
1104 """
1105 test_name = os.path.join(self.path, str(uuid4()))
1106 try:
1107 open(test_name, 'ab').close()
1108 except OSError as e:
1109 util.SMlog("Cannot write to SR file system: %s" % e)
1110 raise xs_errors.XenError('SharedFileSystemNoWrite')
1111 finally:
1112 util.force_unlink(test_name)
1114 def _raise_hardlink_error(self):
1115 raise OSError(524, "Unknown error 524")
1117 @override
1118 def _check_hardlinks(self) -> bool:
1119 hardlink_conf = self._read_hardlink_conf()
1120 if hardlink_conf is not None: 1120 ↛ 1121line 1120 didn't jump to line 1121, because the condition on line 1120 was never true
1121 return hardlink_conf
1123 test_name = os.path.join(self.path, str(uuid4()))
1124 open(test_name, 'ab').close()
1126 link_name = '%s.new' % test_name
1127 try:
1128 # XSI-1100: Let tests simulate failure of the link operation
1129 util.fistpoint.activate_custom_fn(
1130 "FileSR_fail_hardlink",
1131 self._raise_hardlink_error)
1133 os.link(test_name, link_name)
1134 self._write_hardlink_conf(supported=True)
1135 return True
1136 except OSError:
1137 self._write_hardlink_conf(supported=False)
1139 msg = "File system for SR %s does not support hardlinks, crash " \
1140 "consistency of snapshots cannot be assured" % self.uuid
1141 util.SMlog(msg, priority=util.LOG_WARNING)
1142 # Note: session can be not set during attach/detach_from_config calls.
1143 if self.session: 1143 ↛ 1152line 1143 didn't jump to line 1152, because the condition on line 1143 was never false
1144 try:
1145 self.session.xenapi.message.create(
1146 "sr_does_not_support_hardlinks", 2, "SR", self.uuid,
1147 msg)
1148 except XenAPI.Failure:
1149 # Might already be set and checking has TOCTOU issues
1150 pass
1151 finally:
1152 util.force_unlink(link_name)
1153 util.force_unlink(test_name)
1155 return False
1157 def _get_hardlink_conf_path(self):
1158 return os.path.join(self.path, 'sm-hardlink.conf')
1160 def _read_hardlink_conf(self) -> Optional[bool]:
1161 try:
1162 with open(self._get_hardlink_conf_path(), 'r') as f:
1163 try:
1164 return bool(int(f.read()))
1165 except Exception as e:
1166 # If we can't read, assume the file is empty and test for hardlink support.
1167 return None
1168 except IOError as e:
1169 if e.errno == errno.ENOENT:
1170 # If the config file doesn't exist, assume we want to support hardlinks.
1171 return None
1172 util.SMlog('Failed to read hardlink conf: {}'.format(e))
1173 # Can be caused by a concurrent access, not a major issue.
1174 return None
1176 def _write_hardlink_conf(self, supported):
1177 try:
1178 with open(self._get_hardlink_conf_path(), 'w') as f:
1179 f.write('1' if supported else '0')
1180 except Exception as e:
1181 # Can be caused by a concurrent access, not a major issue.
1182 util.SMlog('Failed to write hardlink conf: {}'.format(e))
1184if __name__ == '__main__': 1184 ↛ 1185line 1184 didn't jump to line 1185, because the condition on line 1184 was never true
1185 SRCommand.run(FileSR, DRIVER_INFO)
1186else:
1187 SR.registerSR(FileSR)