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')
545 options.append(self.getSMBVersion())
547 username, domain = (
548 cifutils.splitDomainAndUsername(self.dconf['username'])
549 )
551 if domain:
552 options.append('domain=' + domain)
553 except:
554 util.SMlog("Exception while attempting to append mount options")
555 raise
557 # Extend mountcmd appropriately
558 if options: 558 ↛ exitline 558 didn't return from function 'appendCIFSMountOptions', because the condition on line 558 was never false
559 options = ",".join(str(x) for x in options if x)
560 mountcmd.extend(["-o", options])
562 def getCacheOptions(self):
563 """Pass cache options to mount.cifs"""
564 return "cache=none"
566 def detach(self, sr_uuid):
567 """Std. detach"""
568 if 'legacy_mode' in self.dconf or not self._checkmount(): 568 ↛ 571line 568 didn't jump to line 571, because the condition on line 568 was never false
569 return
571 try:
572 util.pread(["umount", self.mountpoint])
573 except util.CommandException as inst:
574 raise xs_errors.XenError('NFSUnMount', \
575 opterr='error is %d' % inst.code)
577 def scan(self, sr_uuid):
578 """Scan: see _loadvdis"""
579 if not util.isdir(self.path):
580 raise xs_errors.XenError('SRUnavailable', \
581 opterr='no such directory %s' % self.path)
583 if ('legacy_mode' not in self.dconf) and (not self._checkmount()):
584 raise xs_errors.XenError('SRUnavailable', \
585 opterr='directory not mounted: %s' % self.path)
587 #try:
588 if not self.vdis:
589 self._loadvdis()
590 self.physical_size = util.get_fs_size(self.path)
591 self.physical_utilisation = util.get_fs_utilisation(self.path)
592 self.virtual_allocation = self.physical_size
594 self.other_config = self.session.xenapi.SR.get_other_config(self.sr_ref)
596 if 'xenserver_tools_sr' in self.other_config and \
597 self.other_config['xenserver_tools_sr'] == "true":
598 # Out of all the xs-tools ISOs which exist in this dom0, we mark
599 # only one as the official one.
601 # Pass 1: find the latest version
602 latest_build_vdi = None
603 latest_build_number = "0"
604 for vdi_name in self.vdis:
605 vdi = self.vdis[vdi_name]
607 if latest_build_vdi is None:
608 latest_build_vdi = vdi.location
609 latest_build_number = "0"
611 if 'xs-tools-build' in vdi.sm_config:
612 bld = vdi.sm_config['xs-tools-build']
613 if bld >= latest_build_number:
614 latest_build_vdi = vdi.location
615 latest_build_number = bld
617 # Pass 2: mark all VDIs accordingly
618 for vdi_name in self.vdis:
619 vdi = self.vdis[vdi_name]
620 if vdi.location == latest_build_vdi:
621 vdi.sm_config['xs-tools'] = "true"
622 else:
623 if "xs-tools" in vdi.sm_config:
624 del vdi.sm_config['xs-tools']
626 # Synchronise the VDIs: this will update the sm_config maps of current records
627 scanrecord = SR.ScanRecord(self)
628 scanrecord.synchronise_new()
629 scanrecord.synchronise_existing()
631 # Everything that looks like an xs-tools ISO but which isn't the
632 # primary one will also be renamed "Old version of ..."
633 sr = self.session.xenapi.SR.get_by_uuid(sr_uuid)
634 all_vdis = self.session.xenapi.VDI.get_all_records_where("field \"SR\" = \"%s\"" % sr)
635 for vdi_ref in all_vdis.keys():
636 vdi = all_vdis[vdi_ref]
637 if 'xs-tools-version' in vdi['sm_config']:
638 name = tools_iso_name(vdi['location'])
639 if 'xs-tools' in vdi['sm_config']:
640 self.session.xenapi.VDI.set_name_label(vdi_ref, name)
641 else:
642 self.session.xenapi.VDI.set_name_label(vdi_ref, "Old version of " + name)
645 # never forget old VDI records to cope with rolling upgrade
646 for location in scanrecord.gone:
647 vdi = scanrecord.get_xenapi_vdi(location)
648 util.SMlog("Marking previous version of tools ISO: location=%s uuid=%s" % (vdi['location'], vdi['uuid']))
649 vdi = self.session.xenapi.VDI.get_by_uuid(vdi['uuid'])
650 name_label = self.session.xenapi.VDI.get_name_label(vdi)
651 if not(name_label.startswith("Old version of ")):
652 self.session.xenapi.VDI.set_name_label(vdi, "Old version of " + name_label)
653 # Mark it as missing for informational purposes only
654 self.session.xenapi.VDI.set_missing(vdi, True)
655 self.session.xenapi.VDI.remove_from_sm_config(vdi, 'xs-tools')
657 else:
658 return super(ISOSR, self).scan(sr_uuid)
660 def create(self, sr_uuid, size):
661 self.attach(sr_uuid)
662 if 'type' in self.dconf:
663 smconfig = self.session.xenapi.SR.get_sm_config(self.sr_ref)
664 smconfig['iso_type'] = self.dconf['type']
665 self.session.xenapi.SR.set_sm_config(self.sr_ref, smconfig)
667 _, num_images_ignored = list_images(self.path)
668 if num_images_ignored > 0:
669 xapi = self.session.xenapi
670 xapi.message.create("DISK_IMAGES_IGNORED",
671 "4",
672 "SR",
673 self.uuid,
674 "Ignored disk image file(s) due to"
675 " file name encoding issues")
677 self.detach(sr_uuid)
680class ISOVDI(VDI.VDI):
681 def load(self, vdi_uuid):
682 # Nb, in the vdi_create call, the filename is unset, so the following
683 # will fail.
684 self.vdi_type = "iso"
685 try:
686 stat = os.stat(self.path)
687 self.utilisation = int(stat.st_size)
688 self.size = int(stat.st_size)
689 self.label = self.filename
690 except:
691 pass
693 def __init__(self, mysr, filename):
694 self.path = os.path.join(mysr.path, filename)
695 VDI.VDI.__init__(self, mysr, None)
696 self.location = filename
697 self.filename = filename
698 self.read_only = True
699 self.label = filename
700 self.sm_config = {}
701 if "legacy_mode" in mysr.dconf:
702 if filename.startswith("xs-tools") or filename.startswith("guest-tools"):
703 self.label = tools_iso_name(filename)
704 # Mark this as a Tools CD
705 # self.sm_config['xs-tools'] = 'true'
706 # Extract a version string, if present
707 vsn = filename[filename.find("tools") + len("tools"):][:-len(".iso")].strip("-").split("-", 1)
708 # "4.1.0"
709 if len(vsn) == 1:
710 build_number = "0" # string
711 product_version = vsn[0]
712 # "4.1.0-1234"
713 elif len(vsn) > 1:
714 build_number = vsn[1]
715 product_version = vsn[0]
716 else:
717 build_number = 0
718 product_version = "unknown"
719 util.SMlog("version=%s build=%s" % (product_version, build_number))
720 self.sm_config['xs-tools-version'] = product_version
721 self.sm_config['xs-tools-build'] = build_number
723 def detach(self, sr_uuid, vdi_uuid):
724 pass
726 def attach(self, sr_uuid, vdi_uuid):
727 try:
728 os.stat(self.path)
729 return super(ISOVDI, self).attach(sr_uuid, vdi_uuid)
730 except:
731 raise xs_errors.XenError('VDIMissing')
733 def create(self, sr_uuid, vdi_uuid, size):
734 self.uuid = vdi_uuid
735 self.path = os.path.join(self.sr.path, self.filename)
736 self.size = size
737 self.utilisation = 0
738 self.read_only = False
739 self.sm_config = self.sr.srcmd.params['vdi_sm_config']
740 self.sm_config['created'] = util._getDateString()
742 if util.pathexists(self.path):
743 raise xs_errors.XenError('VDIExists')
745 try:
746 handle = open(self.path, "w")
747 handle.truncate(size)
748 handle.close()
749 self._db_introduce()
750 return super(ISOVDI, self).get_params()
751 except Exception as exn:
752 util.SMlog("Exception when creating VDI: %s" % exn)
753 raise xs_errors.XenError('VDICreate', \
754 opterr='could not create file: "%s"' % self.path)
756 def delete(self, sr_uuid, vdi_uuid):
757 util.SMlog("Deleting...")
759 self.uuid = vdi_uuid
760 self._db_forget()
762 if not util.pathexists(self.path):
763 return
765 try:
766 util.SMlog("Unlinking...")
767 os.unlink(self.path)
768 util.SMlog("Done...")
769 except:
770 raise xs_errors.XenError('VDIDelete')
772 # delete, update, introduce unimplemented. super class will raise
773 # exceptions
775if __name__ == '__main__': 775 ↛ 776line 775 didn't jump to line 776, because the condition on line 775 was never true
776 SRCommand.run(ISOSR, DRIVER_INFO)
777else:
778 SR.registerSR(ISOSR)