Coverage for drivers/ISOSR.py : 45%

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# ISOSR: remote iso storage repository
20import SR
21import VDI
22import SRCommand
23import util
24import nfs
25import os
26import re
27import xs_errors
28import cifutils
30CAPABILITIES = ["VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH",
31 "SR_SCAN", "SR_ATTACH", "SR_DETACH"]
33CONFIGURATION = \
34 [['location', 'path to mount (required) (e.g. server:/path)'],
35 ['options',
36 'extra options to pass to mount (deprecated) (e.g. \'-o ro\')'],
37 ['type', 'cifs or nfs'],
38 nfs.NFS_VERSION]
40DRIVER_INFO = {
41 'name': 'ISO',
42 'description': 'Handles CD images stored as files in iso format',
43 'vendor': 'Citrix Systems Inc',
44 'copyright': '(C) 2008 Citrix Systems Inc',
45 'driver_version': '1.0',
46 'required_api_version': '1.0',
47 'capabilities': CAPABILITIES,
48 'configuration': CONFIGURATION
49 }
51TYPE = "iso"
52SMB_VERSION_1 = '1.0'
53SMB_VERSION_3 = '3.0'
54NFSPORT = 2049
57def list_images(path):
58 """
59 Finds the iso and img files in a given directory that have valid unicode
60 names. Returns a list of these, together with a count of number of image
61 files that had to be ignored due to encoding issues in their names.
62 """
63 # pylint: disable=no-member
65 regex = re.compile(r"\.iso$|\.img$", re.I)
66 images = []
67 num_images_ignored = 0
69 for filename in os.listdir(path):
70 if not regex.search(filename):
71 # Not an image file
72 pass
73 elif os.path.isdir(os.path.join(path, filename)):
74 util.SMlog("list_images: '%s' is a directory. Ignore"
75 % loggable_filename(filename))
76 else:
77 try:
78 if is_consistent_utf8_filename(filename):
79 images.append(filename)
80 else:
81 num_images_ignored += 1
82 util.SMlog("WARNING: ignoring image file '%s' due to"
83 " encoding issues"
84 % loggable_filename(filename))
85 except UnicodeDecodeError as e:
86 num_images_ignored += 1
87 util.SMlog("WARNING: ignoring image file '%s' as its name is"
88 " not UTF-8 compatible"
89 % loggable_filename(filename))
91 return images, num_images_ignored
94def loggable_filename(filename):
95 # Strip the 'b"' and '"' off the string representation of bytes
96 return str(os.fsencode(filename))[2:-1]
99def is_consistent_utf8_filename(filename):
100 """
101 Determines whether a filename, which is assumed to come from a filesystem
102 where names are UTF-8 encoded, is consistent in the sense that its name in
103 the form we'd like to use it (that is, as valid unicode) is the same as
104 the form it needs to take when passed to Python library functions (e.g.
105 open, os.stat).
107 Raises UnicodeDecodeError if the name on the filesystem isn't UTF-8
108 encoded.
109 """
110 # We generally expect that names of files in the mounted file system will
111 # be utf-8 encoded. That need not always be true - for example, mount.cifs
112 # provides an "iocharset" option to control this. But we make no attempt
113 # to cope with, say, latin-1, and so such a file name will cause an
114 # exception.
115 #
116 # Even if a file's name is utf-8 encoded, we might still have to reject it
117 # for being "inconsistent". That's because Python's filesystem encoding
118 # (see `sys.getfilesystemencoding`) might be ascii rather than utf-8, in
119 # which case non-ascii characters in file names will show up as "surrogate
120 # escapes" - which makes them technically not valid as unicode.
121 #
122 # Although it would be easy enough to recover the originally intended name
123 # for such a file, it would be awkward elsewhere in the code base either
124 # to have invalid unicode in file paths, or to have file paths that needed
125 # massaging before they could be used for actual file operations. Hence
126 # we say the name is inconsistent.
127 #
128 # From Python 3.7 onwards it looks like it should be much more likely that
129 # the filesystem encoding will be utf-8, which will hopefully mean that we
130 # would then get previously rejected image files showing up, and working
131 # without further code changes being necessary.
133 filename_bytes = os.fsencode(filename)
134 return filename == filename_bytes.decode("utf-8")
137def tools_iso_name(filename):
138 # The tools ISO used have a "xs-" prefix in its name.
139 # We recognise both and set the name_label accordingly.
140 if filename[:3] == "xs-":
141 return "xs-tools.iso"
142 else:
143 return "guest-tools.iso"
146class ISOSR(SR.SR):
147 """Local file storage repository"""
149 # Some helper functions:
150 def _checkmount(self):
151 """Checks that the mountpoint exists and is mounted"""
152 if not util.pathexists(self.mountpoint): 152 ↛ 154line 152 didn't jump to line 154, because the condition on line 152 was never false
153 return False
154 try:
155 ismount = util.ismount(self.mountpoint)
156 except util.CommandException as inst:
157 return False
158 return ismount
160 def _checkTargetStr(self, location):
161 if 'type' not in self.dconf:
162 return
163 if self.dconf['type'] == 'cifs': 163 ↛ 164line 163 didn't jump to line 164, because the condition on line 163 was never true
164 tgt = ''
165 if re.search('^//', location):
166 tgt = location.split('/')[2]
167 elif re.search(r'^\\', location):
168 l = location.split('\\')
169 for i in location.split('\\'):
170 if i:
171 tgt = i
172 break
173 if not tgt:
174 raise xs_errors.XenError('ISOLocationStringError')
175 else:
176 if location.find(':') == -1: 176 ↛ 177line 176 didn't jump to line 177, because the condition on line 176 was never true
177 raise xs_errors.XenError('ISOLocationStringError')
178 tgt = location.split(':')[0]
180 try:
181 util._convertDNS(tgt)
182 except:
183 raise xs_errors.XenError('DNSError')
185 # pylint: disable=no-member
186 uuid_file_regex = re.compile(
187 r"([0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12})\.(iso|img)", re.I)
189 def _loadvdis(self):
190 """Scan the directory and get uuids either from the VDI filename, \
191 or by creating a new one."""
192 if self.vdis:
193 return
195 image_names, _ = list_images(self.path)
197 for name in image_names:
198 self.vdis[name] = ISOVDI(self, name)
199 # Set the VDI UUID if the filename is of the correct form.
200 # Otherwise, one will be generated later in VDI._db_introduce.
201 m = self.uuid_file_regex.match(name)
202 if m:
203 self.vdis[name].uuid = m.group(1)
205 # Synchronise the read-only status with existing VDI records
206 __xenapi_records = util.list_VDI_records_in_sr(self)
207 __xenapi_locations = {}
208 for vdi in __xenapi_records.keys():
209 __xenapi_locations[__xenapi_records[vdi]['location']] = vdi
210 for vdi in self.vdis.values():
211 if vdi.location in __xenapi_locations:
212 v = __xenapi_records[__xenapi_locations[vdi.location]]
213 sm_config = v['sm_config']
214 if 'created' in sm_config:
215 vdi.sm_config['created'] = sm_config['created']
216 vdi.read_only = False
218# Now for the main functions:
219 def handles(type):
220 """Do we handle this type?"""
221 if type == TYPE:
222 return True
223 return False
224 handles = staticmethod(handles)
226 def content_type(self, sr_uuid):
227 """Returns the content_type XML"""
228 return super(ISOSR, self).content_type(sr_uuid)
230 # pylint: disable=no-member
231 vdi_path_regex = re.compile(r"[a-z0-9.-]+\.(iso|img)", re.I)
233 def vdi(self, uuid):
234 """Create a VDI class. If the VDI does not exist, we determine
235 here what its filename should be."""
237 filename = util.to_plain_string(self.srcmd.params.get('vdi_location'))
238 if filename is None:
239 smconfig = self.srcmd.params.get('vdi_sm_config')
240 if smconfig is None:
241 # uh, oh, a VDI.from_uuid()
242 import XenAPI # pylint: disable=import-error
243 _VDI = self.session.xenapi.VDI
244 try:
245 vdi_ref = _VDI.get_by_uuid(uuid)
246 except XenAPI.Failure as e:
247 if e.details[0] != 'UUID_INVALID':
248 raise
249 else:
250 filename = _VDI.get_location(vdi_ref)
252 if filename is None:
253 # Get the filename from sm-config['path'], or use the UUID
254 # if the path param doesn't exist.
255 if smconfig and 'path' in smconfig:
256 filename = smconfig['path']
257 if not self.vdi_path_regex.match(filename):
258 raise xs_errors.XenError('VDICreate', \
259 opterr='Invalid path "%s"' % filename)
260 else:
261 filename = '%s.img' % uuid
263 return ISOVDI(self, filename)
265 def load(self, sr_uuid):
266 """Initialises the SR"""
267 # First of all, check we've got the correct keys in dconf
268 if 'location' not in self.dconf: 268 ↛ 269line 268 didn't jump to line 269, because the condition on line 268 was never true
269 raise xs_errors.XenError('ConfigLocationMissing')
271 # Construct the path we're going to mount under:
272 if "legacy_mode" in self.dconf:
273 self.mountpoint = util.to_plain_string(self.dconf['location'])
274 else:
275 # Verify the target address
276 self._checkTargetStr(self.dconf['location'])
277 self.mountpoint = os.path.join(SR.MOUNT_BASE, sr_uuid)
279 # Add on the iso_path value if there is one
280 if "iso_path" in self.dconf: 280 ↛ 281line 280 didn't jump to line 281, because the condition on line 280 was never true
281 iso_path = util.to_plain_string(self.dconf['iso_path'])
282 if iso_path.startswith("/"):
283 iso_path = iso_path[1:]
284 self.path = os.path.join(self.mountpoint, iso_path)
285 else:
286 self.path = self.mountpoint
288 # Handle optional dconf attributes
289 self.nfsversion = nfs.validate_nfsversion(self.dconf.get('nfsversion'))
291 # Fill the required SMB version
292 self.smbversion = SMB_VERSION_3
294 # Check if smb version is specified from client
295 self.is_smbversion_specified = False
297 # Some info we need:
298 self.sr_vditype = 'phy'
300 def delete(self, sr_uuid):
301 pass
303 def attach(self, sr_uuid):
304 """Std. attach"""
305 # Very-Legacy mode means the ISOs are in the local fs - so no need to attach.
306 if 'legacy_mode' in self.dconf:
307 # Verify path exists
308 if not os.path.exists(self.mountpoint):
309 raise xs_errors.XenError('ISOLocalPath')
310 return
312 # Check whether we're already mounted
313 if self._checkmount():
314 return
316 location = util.to_plain_string(self.dconf['location'])
317 # TODO: Have XC standardise iso type string
318 if 'type' in self.dconf:
319 protocol = self.dconf['type']
320 elif ":/" in location: 320 ↛ 321line 320 didn't jump to line 321, because the condition on line 320 was never true
321 protocol = 'nfs_iso'
322 else:
323 protocol = 'cifs'
325 if protocol == 'nfs_iso':
326 self._check_nfs_server(location)
328 # Create the mountpoint if it's not already there
329 if not util.isdir(self.mountpoint): 329 ↛ 332line 329 didn't jump to line 332, because the condition on line 329 was never false
330 util.makedirs(self.mountpoint)
332 mountcmd = []
333 options = ''
335 if 'options' in self.dconf:
336 options = self.dconf['options'].split(' ')
337 if protocol == 'cifs': 337 ↛ 340line 337 didn't jump to line 340, because the condition on line 337 was never false
338 options = [x for x in options if x != ""]
339 else:
340 options = self.getNFSOptions(options)
342 # SMB options are passed differently for create via
343 # XC/xe sr-create and create via xe-mount-iso-sr
344 # In both cases check if SMB version is passed are not.
345 # If not use self.smbversion.
346 if protocol == 'cifs':
347 if 'type' in self.dconf:
348 # Create via XC or sr-create
349 # Check for username and password
350 mountcmd = ["mount.cifs", location, self.mountpoint]
351 if 'vers' in self.dconf:
352 self.is_smbversion_specified = True
353 self.smbversion = self.dconf['vers']
354 util.SMlog("self.dconf['vers'] = %s" % self.dconf['vers'])
355 self.appendCIFSMountOptions(mountcmd)
356 else:
357 # Creation via xe-mount-iso-sr
358 try:
359 mountcmd = ["mount", location, self.mountpoint]
360 if options and options[0] == '-o': 360 ↛ 369line 360 didn't jump to line 369, because the condition on line 360 was never false
361 pos = options[1].find('vers=')
362 if pos == -1: 362 ↛ 363line 362 didn't jump to line 363, because the condition on line 362 was never true
363 options[1] += ',' + self.getSMBVersion()
364 else:
365 self.smbversion = self.getSMBVersionFromOptions(
366 options[1])
367 self.is_smbversion_specified = True
368 else:
369 raise ValueError
370 mountcmd.extend(options)
371 except ValueError:
372 raise xs_errors.XenError('ISOInvalidXeMountOptions')
373 # Check the validity of 'smbversion'.
374 # Raise an exception for any invalid version.
375 if self.smbversion not in [SMB_VERSION_1, SMB_VERSION_3]:
376 raise xs_errors.XenError('ISOInvalidSMBversion')
378 # Attempt mounting
379 smb3_fail_reason = None
380 try:
381 if protocol == 'nfs_iso':
382 # For NFS, do a soft mount with tcp as protocol. Since ISO SR is
383 # going to be r-only, a failure in nfs link can be reported back
384 # to the process waiting.
385 server, path, transport = self._parse_nfs_location(location)
386 # Extract timeout and retrans values, if any
387 io_timeout = nfs.get_nfs_timeout(self.other_config)
388 io_retrans = nfs.get_nfs_retrans(self.other_config)
389 nfs.soft_mount(self.mountpoint, server, path,
390 transport, useroptions=options, nfsversion=self.nfsversion,
391 timeout=io_timeout, retrans=io_retrans)
392 else:
393 if self.smbversion in SMB_VERSION_3:
394 util.SMlog('ISOSR mount over smb 3.0')
395 try:
396 self.mountOverSMB(mountcmd)
397 except util.CommandException as inst:
398 if not self.is_smbversion_specified: 398 ↛ 416line 398 didn't jump to line 416, because the condition on line 398 was never false
399 util.SMlog('Retrying ISOSR mount over smb 1.0')
400 smb3_fail_reason = inst.reason
401 # mountcmd is constructed such that the last two
402 # items will contain -o argument and its value.
403 del mountcmd[-2:]
404 self.smbversion = SMB_VERSION_1
405 if not options: 405 ↛ 408line 405 didn't jump to line 408, because the condition on line 405 was never false
406 self.appendCIFSMountOptions(mountcmd)
407 else:
408 if options[0] == '-o':
409 # regex can be used here since we have
410 # already validated version entry
411 options[1] = re.sub('vers=3.0', 'vers=1.0',
412 options[1])
413 mountcmd.extend(options)
414 self.mountOverSMB(mountcmd)
415 else:
416 raise xs_errors.XenError(
417 'ISOMountFailure', opterr=inst.reason)
418 else:
419 util.SMlog('ISOSR mount over smb 1.0')
420 self.mountOverSMB(mountcmd)
421 except util.CommandException as inst:
422 if not self.is_smbversion_specified: 422 ↛ 426line 422 didn't jump to line 426, because the condition on line 422 was never false
423 raise xs_errors.XenError(
424 'ISOMountFailure', opterr=smb3_fail_reason)
425 else:
426 raise xs_errors.XenError(
427 'ISOMountFailure', opterr=inst.reason)
428 except nfs.NfsException as e:
429 raise xs_errors.XenError('ISOMountFailure', opterr=str(e.errstr))
431 # Check the iso_path is accessible
432 if not self._checkmount(): 432 ↛ 433line 432 didn't jump to line 433, because the condition on line 432 was never true
433 self.detach(sr_uuid)
434 raise xs_errors.XenError('ISOSharenameFailure')
436 def _parse_nfs_location(self, location):
437 """
438 Given the location of an NFS share, parse it to give
439 a tuple of the remove server, remote path, and transport protocol to
440 use.
441 """
442 serv_path = []
443 transport = 'tcp'
444 if location.startswith('['):
445 # IPv6 target: remove brackets around the IPv6
446 transport = 'tcp6'
447 ip6 = location[1:location.index(']')]
448 path = location[location.index(']') + 2:]
449 serv_path = [ip6, path]
450 else:
451 serv_path = location.split(':')
453 return serv_path[0], serv_path[1], transport
455 def _check_nfs_server(self, location):
456 """
457 Given that we want to mount a given NFS share, checks that there is an
458 NFS server running in the remote server, and that it supports the
459 desired NFS version. Raises an appropriate exception if this is not
460 the case.
461 """
462 server, _, transport = self._parse_nfs_location(location)
464 try:
465 util._testHost(server, NFSPORT, 'NFSTarget')
466 if not nfs.check_server_tcp(server, transport, self.nfsversion):
467 raise xs_errors.XenError('NFSVersion',
468 opterr="Unsupported NFS version: %s" % self.nfsversion)
469 except nfs.NfsException as e:
470 raise xs_errors.XenError('NFSTarget', opterr=str(e.errstr))
473 def after_master_attach(self, uuid):
474 """Perform actions required after attaching on the pool master
475 Return:
476 None
477 """
478 # Nothing required here for ISOs and tools ISOs will fail if scanned
479 pass
481 def getSMBVersionFromOptions(self, options):
482 """Extract SMB version from options """
483 smb_ver = None
484 options_list = options.split(',')
485 for option in options_list: 485 ↛ 491line 485 didn't jump to line 491, because the loop on line 485 didn't complete
486 if option.startswith('vers='): 486 ↛ 485line 486 didn't jump to line 485, because the condition on line 486 was never false
487 version = option.split('=')
488 if len(version) == 2: 488 ↛ 490line 488 didn't jump to line 490, because the condition on line 488 was never false
489 smb_ver = version[1]
490 break
491 return smb_ver
493 def getSMBVersion(self):
494 """Pass smb version option to mount.cifs"""
495 smbversion = "vers=%s" % self.smbversion
496 return smbversion
498 def mountOverSMB(self, mountcmd):
499 """This function raises util.CommandException"""
500 new_env, domain = cifutils.getCIFCredentials(self.dconf, self.session,
501 prefix="cifs")
503 util.pread(mountcmd, True, new_env=new_env)
504 try:
505 if not self.is_smbversion_specified:
506 # Store the successful smb version in PBD config
507 self.updateSMBVersInPBDConfig()
508 except Exception as exc:
509 util.SMlog("Exception: %s" % str(exc))
510 if self._checkmount(): 510 ↛ 512line 510 didn't jump to line 512, because the condition on line 510 was never false
511 util.pread(["umount", self.mountpoint])
512 raise xs_errors.XenError('SMBMount') from exc
514 def updateSMBVersInPBDConfig(self):
515 """Store smb version in PBD config"""
516 pbd = util.find_my_pbd(self.session, self.host_ref, self.sr_ref)
517 if pbd is not None: 517 ↛ 518line 517 didn't jump to line 518, because the condition on line 517 was never true
518 util.SMlog('Updating SMB version in PBD device config')
519 dconf = self.session.xenapi.PBD.get_device_config(pbd)
520 dconf['vers'] = self.smbversion
521 self.session.xenapi.PBD.set_device_config(pbd, dconf)
522 else:
523 raise Exception('Could not find PBD for corresponding SR')
525 def getNFSOptions(self, options):
526 """Append options to mount.nfs"""
527 #Only return any options specified with -o
528 nfsOptions = ''
529 for index, opt in enumerate(options):
530 if opt == "-o":
531 nfsOptions = options[index + 1]
532 break
534 return nfsOptions
536 def appendCIFSMountOptions(self, mountcmd):
537 """Append options to mount.cifs"""
538 options = []
539 try:
540 options.append(self.getCacheOptions())
542 if not cifutils.containsCredentials(self.dconf, prefix="cifs"):
543 options.append('guest')
544 domain = None
545 else:
546 _, domain = cifutils.splitDomainAndUsername(self.dconf['username'])
548 options.append(self.getSMBVersion())
550 if domain:
551 options.append('domain=' + domain)
552 except:
553 util.SMlog("Exception while attempting to append mount options")
554 raise
556 # Extend mountcmd appropriately
557 if options: 557 ↛ exitline 557 didn't return from function 'appendCIFSMountOptions', because the condition on line 557 was never false
558 options = ",".join(str(x) for x in options if x)
559 mountcmd.extend(["-o", options])
561 def getCacheOptions(self):
562 """Pass cache options to mount.cifs"""
563 return "cache=none"
565 def detach(self, sr_uuid):
566 """Std. detach"""
567 if 'legacy_mode' in self.dconf or not self._checkmount(): 567 ↛ 570line 567 didn't jump to line 570, because the condition on line 567 was never false
568 return
570 try:
571 util.pread(["umount", self.mountpoint])
572 except util.CommandException as inst:
573 raise xs_errors.XenError('NFSUnMount', \
574 opterr='error is %d' % inst.code)
576 def scan(self, sr_uuid):
577 """Scan: see _loadvdis"""
578 if not util.isdir(self.path):
579 raise xs_errors.XenError('SRUnavailable', \
580 opterr='no such directory %s' % self.path)
582 if ('legacy_mode' not in self.dconf) and (not self._checkmount()):
583 raise xs_errors.XenError('SRUnavailable', \
584 opterr='directory not mounted: %s' % self.path)
586 #try:
587 if not self.vdis:
588 self._loadvdis()
589 self.physical_size = util.get_fs_size(self.path)
590 self.physical_utilisation = util.get_fs_utilisation(self.path)
591 self.virtual_allocation = self.physical_size
593 self.other_config = self.session.xenapi.SR.get_other_config(self.sr_ref)
595 if 'xenserver_tools_sr' in self.other_config and \
596 self.other_config['xenserver_tools_sr'] == "true":
597 # Out of all the xs-tools ISOs which exist in this dom0, we mark
598 # only one as the official one.
600 # Pass 1: find the latest version
601 latest_build_vdi = None
602 latest_build_number = "0"
603 for vdi_name in self.vdis:
604 vdi = self.vdis[vdi_name]
606 if latest_build_vdi is None:
607 latest_build_vdi = vdi.location
608 latest_build_number = "0"
610 if 'xs-tools-build' in vdi.sm_config:
611 bld = vdi.sm_config['xs-tools-build']
612 if bld >= latest_build_number:
613 latest_build_vdi = vdi.location
614 latest_build_number = bld
616 # Pass 2: mark all VDIs accordingly
617 for vdi_name in self.vdis:
618 vdi = self.vdis[vdi_name]
619 if vdi.location == latest_build_vdi:
620 vdi.sm_config['xs-tools'] = "true"
621 else:
622 if "xs-tools" in vdi.sm_config:
623 del vdi.sm_config['xs-tools']
625 # Synchronise the VDIs: this will update the sm_config maps of current records
626 scanrecord = SR.ScanRecord(self)
627 scanrecord.synchronise_new()
628 scanrecord.synchronise_existing()
630 # Everything that looks like an xs-tools ISO but which isn't the
631 # primary one will also be renamed "Old version of ..."
632 sr = self.session.xenapi.SR.get_by_uuid(sr_uuid)
633 all_vdis = self.session.xenapi.VDI.get_all_records_where("field \"SR\" = \"%s\"" % sr)
634 for vdi_ref in all_vdis.keys():
635 vdi = all_vdis[vdi_ref]
636 if 'xs-tools-version' in vdi['sm_config']:
637 name = tools_iso_name(vdi['location'])
638 if 'xs-tools' in vdi['sm_config']:
639 self.session.xenapi.VDI.set_name_label(vdi_ref, name)
640 else:
641 self.session.xenapi.VDI.set_name_label(vdi_ref, "Old version of " + name)
644 # never forget old VDI records to cope with rolling upgrade
645 for location in scanrecord.gone:
646 vdi = scanrecord.get_xenapi_vdi(location)
647 util.SMlog("Marking previous version of tools ISO: location=%s uuid=%s" % (vdi['location'], vdi['uuid']))
648 vdi = self.session.xenapi.VDI.get_by_uuid(vdi['uuid'])
649 name_label = self.session.xenapi.VDI.get_name_label(vdi)
650 if not(name_label.startswith("Old version of ")):
651 self.session.xenapi.VDI.set_name_label(vdi, "Old version of " + name_label)
652 # Mark it as missing for informational purposes only
653 self.session.xenapi.VDI.set_missing(vdi, True)
654 self.session.xenapi.VDI.remove_from_sm_config(vdi, 'xs-tools')
656 else:
657 return super(ISOSR, self).scan(sr_uuid)
659 def create(self, sr_uuid, size):
660 self.attach(sr_uuid)
661 if 'type' in self.dconf:
662 smconfig = self.session.xenapi.SR.get_sm_config(self.sr_ref)
663 smconfig['iso_type'] = self.dconf['type']
664 self.session.xenapi.SR.set_sm_config(self.sr_ref, smconfig)
666 _, num_images_ignored = list_images(self.path)
667 if num_images_ignored > 0:
668 xapi = self.session.xenapi
669 xapi.message.create("DISK_IMAGES_IGNORED",
670 "4",
671 "SR",
672 self.uuid,
673 "Ignored disk image file(s) due to"
674 " file name encoding issues")
676 self.detach(sr_uuid)
679class ISOVDI(VDI.VDI):
680 def load(self, vdi_uuid):
681 # Nb, in the vdi_create call, the filename is unset, so the following
682 # will fail.
683 self.vdi_type = "iso"
684 try:
685 stat = os.stat(self.path)
686 self.utilisation = int(stat.st_size)
687 self.size = int(stat.st_size)
688 self.label = self.filename
689 except:
690 pass
692 def __init__(self, mysr, filename):
693 self.path = os.path.join(mysr.path, filename)
694 VDI.VDI.__init__(self, mysr, None)
695 self.location = filename
696 self.filename = filename
697 self.read_only = True
698 self.label = filename
699 self.sm_config = {}
700 if "legacy_mode" in mysr.dconf:
701 if filename.startswith("xs-tools") or filename.startswith("guest-tools"):
702 self.label = tools_iso_name(filename)
703 # Mark this as a Tools CD
704 # self.sm_config['xs-tools'] = 'true'
705 # Extract a version string, if present
706 vsn = filename[filename.find("tools") + len("tools"):][:-len(".iso")].strip("-").split("-", 1)
707 # "4.1.0"
708 if len(vsn) == 1:
709 build_number = "0" # string
710 product_version = vsn[0]
711 # "4.1.0-1234"
712 elif len(vsn) > 1:
713 build_number = vsn[1]
714 product_version = vsn[0]
715 else:
716 build_number = 0
717 product_version = "unknown"
718 util.SMlog("version=%s build=%s" % (product_version, build_number))
719 self.sm_config['xs-tools-version'] = product_version
720 self.sm_config['xs-tools-build'] = build_number
722 def detach(self, sr_uuid, vdi_uuid):
723 pass
725 def attach(self, sr_uuid, vdi_uuid):
726 try:
727 os.stat(self.path)
728 return super(ISOVDI, self).attach(sr_uuid, vdi_uuid)
729 except:
730 raise xs_errors.XenError('VDIMissing')
732 def create(self, sr_uuid, vdi_uuid, size):
733 self.uuid = vdi_uuid
734 self.path = os.path.join(self.sr.path, self.filename)
735 self.size = size
736 self.utilisation = 0
737 self.read_only = False
738 self.sm_config = self.sr.srcmd.params['vdi_sm_config']
739 self.sm_config['created'] = util._getDateString()
741 if util.pathexists(self.path):
742 raise xs_errors.XenError('VDIExists')
744 try:
745 handle = open(self.path, "w")
746 handle.truncate(size)
747 handle.close()
748 self._db_introduce()
749 return super(ISOVDI, self).get_params()
750 except Exception as exn:
751 util.SMlog("Exception when creating VDI: %s" % exn)
752 raise xs_errors.XenError('VDICreate', \
753 opterr='could not create file: "%s"' % self.path)
755 def delete(self, sr_uuid, vdi_uuid):
756 util.SMlog("Deleting...")
758 self.uuid = vdi_uuid
759 self._db_forget()
761 if not util.pathexists(self.path):
762 return
764 try:
765 util.SMlog("Unlinking...")
766 os.unlink(self.path)
767 util.SMlog("Done...")
768 except:
769 raise xs_errors.XenError('VDIDelete')
771 # delete, update, introduce unimplemented. super class will raise
772 # exceptions
774if __name__ == '__main__': 774 ↛ 775line 774 didn't jump to line 775, because the condition on line 774 was never true
775 SRCommand.run(ISOSR, DRIVER_INFO)
776else:
777 SR.registerSR(ISOSR)