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