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