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_preferred_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"
443 VDI_TYPE = {
444 PARAM_RAW: VdiType.RAW,
445 PARAM_VHD: VdiType.VHD,
446 PARAM_QCOW2: VdiType.QCOW2
447 }
449 def _find_path_with_retries(self, vdi_uuid, maxretry=5, period=2.0):
450 raw_path = os.path.join(self.sr.path, "%s.%s" % \
451 (vdi_uuid, self.PARAM_RAW))
452 vhd_path = os.path.join(self.sr.path, "%s.%s" % \
453 (vdi_uuid, self.PARAM_VHD))
454 qcow2_path = os.path.join(self.sr.path, "%s.%s" % \
455 (vdi_uuid, self.PARAM_QCOW2))
456 cbt_path = os.path.join(self.sr.path, "%s.%s" %
457 (vdi_uuid, CBTLOG_TAG))
458 found = False
459 tries = 0
460 while tries < maxretry and not found:
461 tries += 1
462 if util.ioretry(lambda: util.pathexists(vhd_path)):
463 self.vdi_type = VdiType.VHD
464 self.path = vhd_path
465 found = True
466 elif util.ioretry(lambda: util.pathexists(qcow2_path)): 466 ↛ 467line 466 didn't jump to line 467, because the condition on line 466 was never true
467 self.vdi_type = VdiType.QCOW2
468 self.path = qcow2_path
469 found = True
470 elif util.ioretry(lambda: util.pathexists(raw_path)):
471 self.vdi_type = VdiType.RAW
472 self.path = raw_path
473 self.hidden = False
474 found = True
475 elif util.ioretry(lambda: util.pathexists(cbt_path)): 475 ↛ 476line 475 didn't jump to line 476, because the condition on line 475 was never true
476 self.vdi_type = VdiType.CBTLOG
477 self.path = cbt_path
478 self.hidden = False
479 found = True
481 if found:
482 try:
483 self.cowutil = getCowUtil(self.vdi_type)
484 except:
485 pass
486 else:
487 util.SMlog("VDI %s not found, retry %s of %s" % (vdi_uuid, tries, maxretry))
488 time.sleep(period)
490 return found
492 @override
493 def load(self, vdi_uuid) -> None:
494 self.lock = self.sr.lock
496 self.sr.srcmd.params['o_direct'] = self.sr.o_direct
498 if self.sr.srcmd.cmd == "vdi_create":
499 self.key_hash = None
501 vdi_sm_config = self.sr.srcmd.params.get("vdi_sm_config")
502 if vdi_sm_config: 502 ↛ 513line 502 didn't jump to line 513, because the condition on line 502 was never false
503 self.key_hash = vdi_sm_config.get("key_hash")
505 image_format = vdi_sm_config.get("image-format") or vdi_sm_config.get("type")
506 if image_format: 506 ↛ 513line 506 didn't jump to line 513, because the condition on line 506 was never false
507 vdi_type = self.VDI_TYPE.get(image_format)
508 if not vdi_type: 508 ↛ 509line 508 didn't jump to line 509, because the condition on line 508 was never true
509 raise xs_errors.XenError('VDIType',
510 opterr='Invalid VDI type %s' % vdi_type)
511 self.vdi_type = vdi_type
513 if not self.vdi_type: 513 ↛ 514line 513 didn't jump to line 514, because the condition on line 513 was never true
514 size = int(self.sr.srcmd.params['args'][0])
515 # In the case of vdi_create, the first parameter is size.
516 # We need it to validate the vdi_type choice
517 for image_format in self.sr.preferred_image_formats:
518 vdi_type = getVdiTypeFromImageFormat(image_format)
519 cowutil = getCowUtil(vdi_type)
520 try:
521 cowutil.validateAndRoundImageSize(size)
522 break
523 except xs_errors.SROSError:
524 util.SMlog(f"We won't be able to create the VDI with format {vdi_type}.")
525 # If the last one also fail we still give the vdi_type and cowutil,
526 # it will fail in the `create` function instead when re-running `validateAndRoundImageSize`
527 self.vdi_type = vdi_type
528 self.cowutil = cowutil
529 else:
530 self.cowutil = getCowUtil(self.vdi_type)
531 self.path = os.path.join(self.sr.path, "%s%s" %
532 (vdi_uuid, VDI_TYPE_TO_EXTENSION[self.vdi_type]))
533 else:
534 found = self._find_path_with_retries(vdi_uuid)
535 if not found: 535 ↛ 536line 535 didn't jump to line 536, because the condition on line 535 was never true
536 if self.sr.srcmd.cmd == "vdi_delete":
537 # Could be delete for CBT log file
538 self.path = os.path.join(self.sr.path, f"{vdi_uuid}.deleted")
539 return
540 if self.sr.srcmd.cmd == "vdi_attach_from_config":
541 return
542 raise xs_errors.XenError('VDIUnavailable',
543 opterr="VDI %s not found" % vdi_uuid)
545 image_info = VdiType.isCowImage(self.vdi_type) and self.sr.image_info.get(vdi_uuid)
546 if image_info:
547 # Image info already preloaded: use it instead of querying directly
548 self.utilisation = image_info.sizePhys
549 self.size = image_info.sizeVirt
550 self.hidden = image_info.hidden
551 if self.hidden: 551 ↛ 552line 551 didn't jump to line 552, because the condition on line 551 was never true
552 self.managed = False
553 self.parent = image_info.parentUuid
554 if self.parent: 554 ↛ 555line 554 didn't jump to line 555, because the condition on line 554 was never true
555 self.sm_config_override = {'vhd-parent': self.parent}
556 else:
557 self.sm_config_override = {'vhd-parent': None}
558 return
560 try:
561 # Change to the SR directory in case parent
562 # locator field path has changed
563 os.chdir(self.sr.path)
564 except Exception as chdir_exception:
565 util.SMlog("Unable to change to SR directory, SR unavailable, %s" %
566 str(chdir_exception))
567 raise xs_errors.XenError('SRUnavailable', opterr=str(chdir_exception))
569 if util.ioretry( 569 ↛ exitline 569 didn't return from function 'load', because the condition on line 569 was never false
570 lambda: util.pathexists(self.path),
571 errlist=[errno.EIO, errno.ENOENT]):
572 try:
573 st = util.ioretry(lambda: os.stat(self.path),
574 errlist=[errno.EIO, errno.ENOENT])
575 self.utilisation = int(st.st_size)
576 except util.CommandException as inst:
577 if inst.code == errno.EIO:
578 raise xs_errors.XenError('VDILoad', \
579 opterr='Failed load VDI information %s' % self.path)
580 else:
581 util.SMlog("Stat failed for %s, %s" % (
582 self.path, str(inst)))
583 raise xs_errors.XenError('VDIType', \
584 opterr='Invalid VDI type %s' % self.vdi_type)
586 if self.vdi_type == VdiType.RAW: 586 ↛ 587line 586 didn't jump to line 587, because the condition on line 586 was never true
587 self.exists = True
588 self.size = self.utilisation
589 self.sm_config_override = {'type': self.PARAM_RAW}
590 return
592 if self.vdi_type == VdiType.CBTLOG: 592 ↛ 593line 592 didn't jump to line 593, because the condition on line 592 was never true
593 self.exists = True
594 self.size = self.utilisation
595 return
597 try:
598 # The VDI might be activated in R/W mode so the VHD footer
599 # won't be valid, use the back-up one instead.
600 image_info = self.cowutil.getInfo(self.path, FileVDI.extractUuid, useBackupFooter=True)
602 if image_info.parentUuid: 602 ↛ 603line 602 didn't jump to line 603, because the condition on line 602 was never true
603 self.parent = image_info.parentUuid
604 self.sm_config_override = {'vhd-parent': self.parent}
605 else:
606 self.parent = ""
607 self.sm_config_override = {'vhd-parent': None}
608 self.size = image_info.sizeVirt
609 self.hidden = image_info.hidden
610 if self.hidden: 610 ↛ 611line 610 didn't jump to line 611, because the condition on line 610 was never true
611 self.managed = False
612 self.exists = True
613 except util.CommandException as inst:
614 raise xs_errors.XenError('VDILoad', \
615 opterr='Failed load VDI information %s' % self.path)
617 @override
618 def update(self, sr_uuid, vdi_location) -> None:
619 self.load(vdi_location)
620 vdi_ref = self.sr.srcmd.params['vdi_ref']
621 self.sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
622 self._db_update()
624 @override
625 def create(self, sr_uuid, vdi_uuid, size) -> str:
626 if util.ioretry(lambda: util.pathexists(self.path)): 626 ↛ 627line 626 didn't jump to line 627, because the condition on line 626 was never true
627 raise xs_errors.XenError('VDIExists')
629 if VdiType.isCowImage(self.vdi_type):
630 try:
631 size = self.cowutil.validateAndRoundImageSize(int(size))
632 util.ioretry(lambda: self._create(size, self.path))
633 self.size = self.cowutil.getSizeVirt(self.path)
634 except util.CommandException as inst:
635 raise xs_errors.XenError('VDICreate',
636 opterr='error %d' % inst.code)
637 else:
638 f = open(self.path, 'w')
639 f.truncate(int(size))
640 f.close()
641 self.size = size
643 self.sr.added_vdi(self)
645 st = util.ioretry(lambda: os.stat(self.path))
646 self.utilisation = int(st.st_size)
647 if self.vdi_type == VdiType.RAW:
648 # Legacy code.
649 self.sm_config = {"type": self.PARAM_RAW}
650 if not hasattr(self, 'sm_config'):
651 self.sm_config = {}
652 self.sm_config = {"image-format": getImageStringFromVdiType(self.vdi_type)}
654 self._db_introduce()
655 self.sr._update(self.sr.uuid, self.size)
656 return super(FileVDI, self).get_params()
658 @override
659 def delete(self, sr_uuid, vdi_uuid, data_only=False) -> None:
660 if not util.ioretry(lambda: util.pathexists(self.path)):
661 return super(FileVDI, self).delete(sr_uuid, vdi_uuid, data_only)
663 if self.attached:
664 raise xs_errors.XenError('VDIInUse')
666 try:
667 util.force_unlink(self.path)
668 except Exception as e:
669 raise xs_errors.XenError(
670 'VDIDelete',
671 opterr='Failed to unlink file during deleting VDI: %s' % str(e))
673 self.sr.deleted_vdi(vdi_uuid)
674 # If this is a data_destroy call, don't remove from XAPI db
675 if not data_only:
676 self._db_forget()
677 self.sr._update(self.sr.uuid, -self.size)
678 self.sr.lock.cleanupAll(vdi_uuid)
679 self.sr._kickGC()
680 return super(FileVDI, self).delete(sr_uuid, vdi_uuid, data_only)
682 @override
683 def attach(self, sr_uuid, vdi_uuid) -> str:
684 if self.path is None:
685 self._find_path_with_retries(vdi_uuid)
686 if not self._checkpath(self.path):
687 raise xs_errors.XenError('VDIUnavailable', \
688 opterr='VDI %s unavailable %s' % (vdi_uuid, self.path))
689 try:
690 self.attached = True
692 if not hasattr(self, 'xenstore_data'):
693 self.xenstore_data = {}
695 self.xenstore_data.update(scsiutil.update_XS_SCSIdata(vdi_uuid, \
696 scsiutil.gen_synthetic_page_data(vdi_uuid)))
698 if self.sr.handles("file"):
699 # XXX: PR-1255: if these are constants then they should
700 # be returned by the attach API call, not persisted in the
701 # pool database.
702 self.xenstore_data['storage-type'] = 'ext'
703 return super(FileVDI, self).attach(sr_uuid, vdi_uuid)
704 except util.CommandException as inst:
705 raise xs_errors.XenError('VDILoad', opterr='error %d' % inst.code)
707 @override
708 def detach(self, sr_uuid, vdi_uuid) -> None:
709 self.attached = False
711 @override
712 def resize(self, sr_uuid, vdi_uuid, size) -> str:
713 if not self.exists:
714 raise xs_errors.XenError('VDIUnavailable', \
715 opterr='VDI %s unavailable %s' % (vdi_uuid, self.path))
717 if not VdiType.isCowImage(self.vdi_type):
718 raise xs_errors.XenError('Unimplemented')
720 if self.hidden:
721 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
723 if size < self.size:
724 util.SMlog('vdi_resize: shrinking not supported: ' + \
725 '(current size: %d, new size: %d)' % (self.size, size))
726 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
728 if size == self.size:
729 return VDI.VDI.get_params(self)
731 # We already checked it is a cow image.
732 size = self.cowutil.validateAndRoundImageSize(int(size))
734 jFile = JOURNAL_FILE_PREFIX + self.uuid
735 try:
736 self.cowutil.setSizeVirt(self.path, size, jFile)
737 except:
738 # Revert the operation
739 self.cowutil.revert(self.path, jFile)
740 raise xs_errors.XenError('VDISize', opterr='resize operation failed')
742 old_size = self.size
743 self.size = self.cowutil.getSizeVirt(self.path)
744 st = util.ioretry(lambda: os.stat(self.path))
745 self.utilisation = int(st.st_size)
747 self._db_update()
748 self.sr._update(self.sr.uuid, self.size - old_size)
749 super(FileVDI, self).resize_cbt(self.sr.uuid, self.uuid, self.size)
750 return VDI.VDI.get_params(self)
752 @override
753 def clone(self, sr_uuid, vdi_uuid) -> str:
754 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE)
756 @override
757 def compose(self, sr_uuid, vdi1, vdi2) -> None:
758 if not VdiType.isCowImage(self.vdi_type):
759 raise xs_errors.XenError('Unimplemented')
760 parent_fn = vdi1 + VDI_TYPE_TO_EXTENSION[self.vdi_type]
761 parent_path = os.path.join(self.sr.path, parent_fn)
762 assert(util.pathexists(parent_path))
763 self.cowutil.setParent(self.path, parent_path, False)
764 self.cowutil.setHidden(parent_path)
765 self.sr.session.xenapi.VDI.set_managed(self.sr.srcmd.params['args'][0], False)
766 # Tell tapdisk the chain has changed
767 if not blktap2.VDI.tap_refresh(self.session, sr_uuid, vdi2):
768 raise util.SMException("failed to refresh VDI %s" % self.uuid)
769 util.SMlog("VDI.compose: relinked %s->%s" % (vdi2, vdi1))
771 def reset_leaf(self, sr_uuid, vdi_uuid):
772 if not VdiType.isCowImage(self.vdi_type):
773 raise xs_errors.XenError('Unimplemented')
775 # safety check
776 if not self.cowutil.hasParent(self.path):
777 raise util.SMException("ERROR: VDI %s has no parent, " + \
778 "will not reset contents" % self.uuid)
780 self.cowutil.killData(self.path)
782 @override
783 def _do_snapshot(self, sr_uuid, vdi_uuid, snapType,
784 cloneOp=False, secondary=None, cbtlog=None, is_mirror_destination=False) -> str:
785 # If cbt enabled, save file consistency state
786 if cbtlog is not None: 786 ↛ 787line 786 didn't jump to line 787, because the condition on line 786 was never true
787 if blktap2.VDI.tap_status(self.session, vdi_uuid):
788 consistency_state = False
789 else:
790 consistency_state = True
791 util.SMlog("Saving log consistency state of %s for vdi: %s" %
792 (consistency_state, vdi_uuid))
793 else:
794 consistency_state = None
796 if not VdiType.isCowImage(self.vdi_type): 796 ↛ 797line 796 didn't jump to line 797, because the condition on line 796 was never true
797 raise xs_errors.XenError('Unimplemented')
799 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid): 799 ↛ 800line 799 didn't jump to line 800, because the condition on line 799 was never true
800 raise util.SMException("failed to pause VDI %s" % vdi_uuid)
801 try:
802 return self._snapshot(snapType, cbtlog, consistency_state, is_mirror_destination)
803 finally:
804 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary)
805 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
807 @override
808 def _rename(self, src, dst) -> None:
809 util.SMlog("FileVDI._rename %s to %s" % (src, dst))
810 util.ioretry(lambda: os.rename(src, dst))
812 def _link(self, src, dst):
813 util.SMlog("FileVDI._link %s to %s" % (src, dst))
814 os.link(src, dst)
816 def _unlink(self, path):
817 util.SMlog("FileVDI._unlink %s" % (path))
818 os.unlink(path)
820 def _create_new_parent(self, src, newsrc):
821 if self.sr._check_hardlinks():
822 self._link(src, newsrc)
823 else:
824 self._rename(src, newsrc)
826 def __fist_enospace(self):
827 raise util.CommandException(28, "cowutil snapshot", reason="No space")
829 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None, is_mirror_destination=False):
830 util.SMlog("FileVDI._snapshot for %s (type %s)" % (self.uuid, snap_type))
832 args = []
833 args.append("vdi_clone")
834 args.append(self.sr.uuid)
835 args.append(self.uuid)
837 dest = None
838 dst = None
839 extension = VDI_TYPE_TO_EXTENSION[self.vdi_type]
840 if snap_type == VDI.SNAPSHOT_DOUBLE: 840 ↛ 845line 840 didn't jump to line 845, because the condition on line 840 was never false
841 dest = util.gen_uuid()
842 dst = os.path.join(self.sr.path, dest + extension)
843 args.append(dest)
845 if self.hidden: 845 ↛ 846line 845 didn't jump to line 846, because the condition on line 845 was never true
846 raise xs_errors.XenError('VDIClone', opterr='hidden VDI')
848 depth = self.cowutil.getDepth(self.path)
849 if depth == -1: 849 ↛ 850line 849 didn't jump to line 850, because the condition on line 849 was never true
850 raise xs_errors.XenError('VDIUnavailable', \
851 opterr='failed to get image depth')
852 elif depth >= self.cowutil.getMaxChainLength(): 852 ↛ 853line 852 didn't jump to line 853, because the condition on line 852 was never true
853 raise xs_errors.XenError('SnapshotChainTooLong')
855 newuuid = util.gen_uuid()
856 src = self.path
857 newsrcname = newuuid + extension
858 newsrc = os.path.join(self.sr.path, newsrcname)
860 if not self._checkpath(src): 860 ↛ 861line 860 didn't jump to line 861, because the condition on line 860 was never true
861 raise xs_errors.XenError('VDIUnavailable', \
862 opterr='VDI %s unavailable %s' % (self.uuid, src))
864 # wkcfix: multiphase
865 util.start_log_entry(self.sr.path, self.path, args)
867 # We assume the filehandle has been released
868 try:
869 self._create_new_parent(src, newsrc)
871 # Create the snapshot under a temporary name, then rename
872 # it afterwards. This avoids a small window where it exists
873 # but is invalid. We do not need to do this for
874 # snap_type == VDI.SNAPSHOT_DOUBLE because dst never existed
875 # before so nobody will try to query it.
876 tmpsrc = "%s.%s" % (src, "new")
877 # Fault injection site to fail the snapshot with ENOSPACE
878 util.fistpoint.activate_custom_fn(
879 "FileSR_fail_snap1",
880 self.__fist_enospace)
881 util.ioretry(lambda: self._snap(tmpsrc, newsrcname, is_mirror_destination))
882 # SMB3 can return EACCES if we attempt to rename over the
883 # hardlink leaf too quickly after creating it.
884 util.ioretry(lambda: self._rename(tmpsrc, src),
885 errlist=[errno.EIO, errno.EACCES])
886 if snap_type == VDI.SNAPSHOT_DOUBLE: 886 ↛ 894line 886 didn't jump to line 894, because the condition on line 886 was never false
887 # Fault injection site to fail the snapshot with ENOSPACE
888 util.fistpoint.activate_custom_fn(
889 "FileSR_fail_snap2",
890 self.__fist_enospace)
891 util.ioretry(lambda: self._snap(dst, newsrcname))
892 # mark the original file (in this case, its newsrc)
893 # as hidden so that it does not show up in subsequent scans
894 util.ioretry(lambda: self._mark_hidden(newsrc))
896 #Verify parent locator field of both children and delete newsrc if unused
897 introduce_parent = True
898 try:
899 srcparent = self.cowutil.getParent(src, FileVDI.extractUuid)
900 dstparent = None
901 if snap_type == VDI.SNAPSHOT_DOUBLE: 901 ↛ 903line 901 didn't jump to line 903, because the condition on line 901 was never false
902 dstparent = self.cowutil.getParent(dst, FileVDI.extractUuid)
903 if srcparent != newuuid and \ 903 ↛ 907line 903 didn't jump to line 907, because the condition on line 903 was never true
904 (snap_type == VDI.SNAPSHOT_SINGLE or \
905 snap_type == VDI.SNAPSHOT_INTERNAL or \
906 dstparent != newuuid):
907 util.ioretry(lambda: self._unlink(newsrc))
908 introduce_parent = False
909 except Exception as e:
910 raise
912 # Introduce the new VDI records
913 leaf_vdi = None
914 if snap_type == VDI.SNAPSHOT_DOUBLE: 914 ↛ 935line 914 didn't jump to line 935, because the condition on line 914 was never false
915 leaf_vdi = VDI.VDI(self.sr, dest) # user-visible leaf VDI
916 leaf_vdi.read_only = False
917 leaf_vdi.location = dest
918 leaf_vdi.size = self.size
919 leaf_vdi.utilisation = self.utilisation
920 leaf_vdi.sm_config = {}
921 leaf_vdi.sm_config['vhd-parent'] = dstparent
922 # TODO: fix the raw snapshot case
923 leaf_vdi.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
924 # If the parent is encrypted set the key_hash
925 # for the new snapshot disk
926 vdi_ref = self.sr.srcmd.params['vdi_ref']
927 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
928 if "key_hash" in sm_config: 928 ↛ 929line 928 didn't jump to line 929, because the condition on line 928 was never true
929 leaf_vdi.sm_config['key_hash'] = sm_config['key_hash']
930 # If we have CBT enabled on the VDI,
931 # set CBT status for the new snapshot disk
932 if cbtlog: 932 ↛ 933line 932 didn't jump to line 933, because the condition on line 932 was never true
933 leaf_vdi.cbt_enabled = True
935 base_vdi = None
936 if introduce_parent: 936 ↛ 950line 936 didn't jump to line 950, because the condition on line 936 was never false
937 base_vdi = VDI.VDI(self.sr, newuuid) # readonly parent
938 base_vdi.label = "base copy"
939 base_vdi.read_only = True
940 base_vdi.location = newuuid
941 base_vdi.size = self.size
942 base_vdi.utilisation = self.utilisation
943 base_vdi.sm_config = {}
944 # TODO: fix the raw snapshot case
945 base_vdi.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
946 grandparent = self.cowutil.getParent(newsrc, FileVDI.extractUuid)
947 if grandparent: 947 ↛ 950line 947 didn't jump to line 950, because the condition on line 947 was never false
948 base_vdi.sm_config['vhd-parent'] = grandparent
950 try:
951 if snap_type == VDI.SNAPSHOT_DOUBLE: 951 ↛ 956line 951 didn't jump to line 956, because the condition on line 951 was never false
952 leaf_vdi_ref = leaf_vdi._db_introduce()
953 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % \
954 (leaf_vdi_ref, dest))
956 if introduce_parent: 956 ↛ 960line 956 didn't jump to line 960, because the condition on line 956 was never false
957 base_vdi_ref = base_vdi._db_introduce()
958 self.session.xenapi.VDI.set_managed(base_vdi_ref, False)
959 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % (base_vdi_ref, newuuid))
960 vdi_ref = self.sr.srcmd.params['vdi_ref']
961 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
962 sm_config['vhd-parent'] = srcparent
963 # TODO: fix the raw snapshot case
964 sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
965 self.session.xenapi.VDI.set_sm_config(vdi_ref, sm_config)
966 except Exception as e:
967 util.SMlog("vdi_clone: caught error during VDI.db_introduce: %s" % (str(e)))
968 # Note it's too late to actually clean stuff up here: the base disk has
969 # been marked as deleted already.
970 util.end_log_entry(self.sr.path, self.path, ["error"])
971 raise
972 except util.CommandException as inst:
973 # XXX: it might be too late if the base disk has been marked as deleted!
974 self._clonecleanup(src, dst, newsrc)
975 util.end_log_entry(self.sr.path, self.path, ["error"])
976 raise xs_errors.XenError('VDIClone',
977 opterr='VDI clone failed error %d' % inst.code)
979 # Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
980 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 980 ↛ 981line 980 didn't jump to line 981, because the condition on line 980 was never true
981 try:
982 self._cbt_snapshot(dest, cbt_consistency)
983 except:
984 # CBT operation failed.
985 util.end_log_entry(self.sr.path, self.path, ["error"])
986 raise
988 util.end_log_entry(self.sr.path, self.path, ["done"])
989 if snap_type != VDI.SNAPSHOT_INTERNAL: 989 ↛ 992line 989 didn't jump to line 992, because the condition on line 989 was never false
990 self.sr._update(self.sr.uuid, self.size)
991 # Return info on the new user-visible leaf VDI
992 ret_vdi = leaf_vdi
993 if not ret_vdi: 993 ↛ 994line 993 didn't jump to line 994, because the condition on line 993 was never true
994 ret_vdi = base_vdi
995 if not ret_vdi: 995 ↛ 996line 995 didn't jump to line 996, because the condition on line 995 was never true
996 ret_vdi = self
997 return ret_vdi.get_params()
999 @override
1000 def get_params(self) -> str:
1001 if not self._checkpath(self.path):
1002 raise xs_errors.XenError('VDIUnavailable', \
1003 opterr='VDI %s unavailable %s' % (self.uuid, self.path))
1004 return super(FileVDI, self).get_params()
1006 def _snap(self, child, parent, is_mirror_destination=False):
1007 self.cowutil.snapshot(child, parent, self.vdi_type == VdiType.RAW, is_mirror_image=is_mirror_destination)
1009 def _clonecleanup(self, src, dst, newsrc):
1010 try:
1011 if dst: 1011 ↛ 1015line 1011 didn't jump to line 1015, because the condition on line 1011 was never false
1012 util.ioretry(lambda: self._unlink(dst))
1013 except util.CommandException as inst:
1014 pass
1015 try:
1016 if util.ioretry(lambda: util.pathexists(newsrc)): 1016 ↛ exitline 1016 didn't return from function '_clonecleanup', because the condition on line 1016 was never false
1017 stats = os.stat(newsrc)
1018 # Check if we have more than one link to newsrc
1019 if (stats.st_nlink > 1):
1020 util.ioretry(lambda: self._unlink(newsrc))
1021 elif not self._is_hidden(newsrc): 1021 ↛ exitline 1021 didn't return from function '_clonecleanup', because the condition on line 1021 was never false
1022 self._rename(newsrc, src)
1023 except util.CommandException as inst:
1024 pass
1026 def _checkpath(self, path):
1027 try:
1028 if not util.ioretry(lambda: util.pathexists(path)): 1028 ↛ 1029line 1028 didn't jump to line 1029, because the condition on line 1028 was never true
1029 return False
1030 return True
1031 except util.CommandException as inst:
1032 raise xs_errors.XenError('EIO', \
1033 opterr='IO error checking path %s' % path)
1035 def _create(self, size, path):
1036 self.cowutil.create(path, size, False)
1037 if self.key_hash: 1037 ↛ 1038line 1037 didn't jump to line 1038, because the condition on line 1037 was never true
1038 self.cowutil.setKey(path, self.key_hash)
1040 def _mark_hidden(self, path):
1041 self.cowutil.setHidden(path, True)
1042 self.hidden = 1
1044 def _is_hidden(self, path):
1045 return self.cowutil.getHidden(path) == 1
1047 @staticmethod
1048 def extractUuid(path: str) -> str:
1049 fileName = os.path.basename(path)
1050 return os.path.splitext(fileName)[0]
1052 @override
1053 def generate_config(self, sr_uuid, vdi_uuid) -> str:
1054 """
1055 Generate the XML config required to attach and activate
1056 a VDI for use when XAPI is not running. Attach and
1057 activation is handled by vdi_attach_from_config below.
1058 """
1059 util.SMlog("FileVDI.generate_config")
1060 if not util.pathexists(self.path): 1060 ↛ 1061line 1060 didn't jump to line 1061, because the condition on line 1060 was never true
1061 raise xs_errors.XenError('VDIUnavailable')
1062 resp = {}
1063 resp['device_config'] = self.sr.dconf
1064 resp['sr_uuid'] = sr_uuid
1065 resp['vdi_uuid'] = vdi_uuid
1066 resp['command'] = 'vdi_attach_from_config'
1067 # Return the 'config' encoded within a normal XMLRPC response so that
1068 # we can use the regular response/error parsing code.
1069 config = xmlrpc.client.dumps(tuple([resp]), "vdi_attach_from_config")
1070 return xmlrpc.client.dumps((config, ), "", True)
1072 @override
1073 def attach_from_config(self, sr_uuid, vdi_uuid) -> str:
1074 """
1075 Attach and activate a VDI using config generated by
1076 vdi_generate_config above. This is used for cases such as
1077 the HA state-file and the redo-log.
1078 """
1079 util.SMlog("FileVDI.attach_from_config")
1080 try:
1081 if not util.pathexists(self.sr.path):
1082 return self.sr.attach(sr_uuid)
1083 except:
1084 util.logException("FileVDI.attach_from_config")
1085 raise xs_errors.XenError(
1086 'SRUnavailable',
1087 opterr='Unable to attach from config'
1088 )
1089 return ''
1091 @override
1092 def _create_cbt_log(self) -> str:
1093 # Create CBT log file
1094 # Name: <vdi_uuid>.cbtlog
1095 #Handle if file already exists
1096 log_path = self._get_cbt_logpath(self.uuid)
1097 open_file = open(log_path, "w+")
1098 open_file.close()
1099 return super(FileVDI, self)._create_cbt_log()
1101 @override
1102 def _delete_cbt_log(self) -> None:
1103 logPath = self._get_cbt_logpath(self.uuid)
1104 try:
1105 os.remove(logPath)
1106 except OSError as e:
1107 if e.errno != errno.ENOENT:
1108 raise
1110 @override
1111 def _cbt_log_exists(self, logpath) -> bool:
1112 return util.pathexists(logpath)
1115class SharedFileSR(FileSR):
1116 """
1117 FileSR subclass for SRs that use shared network storage
1118 """
1120 def _check_writable(self):
1121 """
1122 Checks that the filesystem being used by the SR can be written to,
1123 raising an exception if it can't.
1124 """
1125 test_name = os.path.join(self.path, str(uuid4()))
1126 try:
1127 open(test_name, 'ab').close()
1128 except OSError as e:
1129 util.SMlog("Cannot write to SR file system: %s" % e)
1130 raise xs_errors.XenError('SharedFileSystemNoWrite')
1131 finally:
1132 util.force_unlink(test_name)
1134 def _raise_hardlink_error(self):
1135 raise OSError(524, "Unknown error 524")
1137 @override
1138 def _check_hardlinks(self) -> bool:
1139 hardlink_conf = self._read_hardlink_conf()
1140 if hardlink_conf is not None: 1140 ↛ 1141line 1140 didn't jump to line 1141, because the condition on line 1140 was never true
1141 return hardlink_conf
1143 test_name = os.path.join(self.path, str(uuid4()))
1144 open(test_name, 'ab').close()
1146 link_name = '%s.new' % test_name
1147 try:
1148 # XSI-1100: Let tests simulate failure of the link operation
1149 util.fistpoint.activate_custom_fn(
1150 "FileSR_fail_hardlink",
1151 self._raise_hardlink_error)
1153 os.link(test_name, link_name)
1154 self._write_hardlink_conf(supported=True)
1155 return True
1156 except OSError:
1157 self._write_hardlink_conf(supported=False)
1159 msg = "File system for SR %s does not support hardlinks, crash " \
1160 "consistency of snapshots cannot be assured" % self.uuid
1161 util.SMlog(msg, priority=util.LOG_WARNING)
1162 # Note: session can be not set during attach/detach_from_config calls.
1163 if self.session: 1163 ↛ 1172line 1163 didn't jump to line 1172, because the condition on line 1163 was never false
1164 try:
1165 self.session.xenapi.message.create(
1166 "sr_does_not_support_hardlinks", 2, "SR", self.uuid,
1167 msg)
1168 except XenAPI.Failure:
1169 # Might already be set and checking has TOCTOU issues
1170 pass
1171 finally:
1172 util.force_unlink(link_name)
1173 util.force_unlink(test_name)
1175 return False
1177 def _get_hardlink_conf_path(self):
1178 return os.path.join(self.path, 'sm-hardlink.conf')
1180 def _read_hardlink_conf(self) -> Optional[bool]:
1181 try:
1182 with open(self._get_hardlink_conf_path(), 'r') as f:
1183 try:
1184 return bool(int(f.read()))
1185 except Exception as e:
1186 # If we can't read, assume the file is empty and test for hardlink support.
1187 return None
1188 except IOError as e:
1189 if e.errno == errno.ENOENT:
1190 # If the config file doesn't exist, assume we want to support hardlinks.
1191 return None
1192 util.SMlog('Failed to read hardlink conf: {}'.format(e))
1193 # Can be caused by a concurrent access, not a major issue.
1194 return None
1196 def _write_hardlink_conf(self, supported):
1197 try:
1198 with open(self._get_hardlink_conf_path(), 'w') as f:
1199 f.write('1' if supported else '0')
1200 except Exception as e:
1201 # Can be caused by a concurrent access, not a major issue.
1202 util.SMlog('Failed to write hardlink conf: {}'.format(e))
1204if __name__ == '__main__': 1204 ↛ 1205line 1204 didn't jump to line 1205, because the condition on line 1204 was never true
1205 SRCommand.run(FileSR, DRIVER_INFO)
1206else:
1207 SR.registerSR(FileSR)