Hide keyboard shortcuts

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 

19 

20import SR 

21import VDI 

22import SRCommand 

23import util 

24import nfs 

25import os 

26import re 

27import xs_errors 

28import cifutils 

29 

30CAPABILITIES = ["VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", 

31 "SR_SCAN", "SR_ATTACH", "SR_DETACH"] 

32 

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] 

39 

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 } 

50 

51TYPE = "iso" 

52SMB_VERSION_1 = '1.0' 

53SMB_VERSION_3 = '3.0' 

54NFSPORT = 2049 

55 

56 

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 

64 

65 regex = re.compile(r"\.iso$|\.img$", re.I) 

66 images = [] 

67 num_images_ignored = 0 

68 

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)) 

90 

91 return images, num_images_ignored 

92 

93 

94def loggable_filename(filename): 

95 # Strip the 'b"' and '"' off the string representation of bytes 

96 return str(os.fsencode(filename))[2:-1] 

97 

98 

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). 

106 

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. 

132 

133 filename_bytes = os.fsencode(filename) 

134 return filename == filename_bytes.decode("utf-8") 

135 

136 

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" 

144 

145 

146class ISOSR(SR.SR): 

147 """Local file storage repository""" 

148 

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 

159 

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] 

179 

180 try: 

181 util._convertDNS(tgt) 

182 except: 

183 raise xs_errors.XenError('DNSError') 

184 

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) 

188 

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 

194 

195 image_names, _ = list_images(self.path) 

196 

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) 

204 

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 

217 

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) 

225 

226 def content_type(self, sr_uuid): 

227 """Returns the content_type XML""" 

228 return super(ISOSR, self).content_type(sr_uuid) 

229 

230 # pylint: disable=no-member 

231 vdi_path_regex = re.compile(r"[a-z0-9.-]+\.(iso|img)", re.I) 

232 

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.""" 

236 

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) 

251 

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 

262 

263 return ISOVDI(self, filename) 

264 

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') 

270 

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) 

278 

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 

287 

288 # Handle optional dconf attributes 

289 self.nfsversion = nfs.validate_nfsversion(self.dconf.get('nfsversion')) 

290 

291 # Fill the required SMB version 

292 self.smbversion = SMB_VERSION_3 

293 

294 # Check if smb version is specified from client 

295 self.is_smbversion_specified = False 

296 

297 # Some info we need: 

298 self.sr_vditype = 'phy' 

299 

300 def delete(self, sr_uuid): 

301 pass 

302 

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 

311 

312 # Check whether we're already mounted 

313 if self._checkmount(): 

314 return 

315 

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' 

324 

325 if protocol == 'nfs_iso': 

326 self._check_nfs_server(location) 

327 

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) 

331 

332 mountcmd = [] 

333 options = '' 

334 

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) 

341 

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') 

377 

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)) 

430 

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') 

435 

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(':') 

452 

453 return serv_path[0], serv_path[1], transport 

454 

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) 

463 

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)) 

471 

472 

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 

480 

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 

492 

493 def getSMBVersion(self): 

494 """Pass smb version option to mount.cifs""" 

495 smbversion = "vers=%s" % self.smbversion 

496 return smbversion 

497 

498 def mountOverSMB(self, mountcmd): 

499 """This function raises util.CommandException""" 

500 new_env, domain = cifutils.getCIFCredentials(self.dconf, self.session, 

501 prefix="cifs") 

502 

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 

513 

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') 

524 

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 

533 

534 return nfsOptions 

535 

536 def appendCIFSMountOptions(self, mountcmd): 

537 """Append options to mount.cifs""" 

538 options = [] 

539 try: 

540 options.append(self.getCacheOptions()) 

541 

542 if not cifutils.containsCredentials(self.dconf, prefix="cifs"): 

543 options.append('guest') 

544 

545 options.append(self.getSMBVersion()) 

546 

547 username, domain = ( 

548 cifutils.splitDomainAndUsername(self.dconf['username']) 

549 ) 

550 

551 if domain: 

552 options.append('domain=' + domain) 

553 except: 

554 util.SMlog("Exception while attempting to append mount options") 

555 raise 

556 

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]) 

561 

562 def getCacheOptions(self): 

563 """Pass cache options to mount.cifs""" 

564 return "cache=none" 

565 

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 

570 

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) 

576 

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) 

582 

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) 

586 

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 

593 

594 self.other_config = self.session.xenapi.SR.get_other_config(self.sr_ref) 

595 

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. 

600 

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] 

606 

607 if latest_build_vdi is None: 

608 latest_build_vdi = vdi.location 

609 latest_build_number = "0" 

610 

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 

616 

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'] 

625 

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() 

630 

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) 

643 

644 

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') 

656 

657 else: 

658 return super(ISOSR, self).scan(sr_uuid) 

659 

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) 

666 

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") 

676 

677 self.detach(sr_uuid) 

678 

679 

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 

692 

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 

722 

723 def detach(self, sr_uuid, vdi_uuid): 

724 pass 

725 

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') 

732 

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() 

741 

742 if util.pathexists(self.path): 

743 raise xs_errors.XenError('VDIExists') 

744 

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) 

755 

756 def delete(self, sr_uuid, vdi_uuid): 

757 util.SMlog("Deleting...") 

758 

759 self.uuid = vdi_uuid 

760 self._db_forget() 

761 

762 if not util.pathexists(self.path): 

763 return 

764 

765 try: 

766 util.SMlog("Unlinking...") 

767 os.unlink(self.path) 

768 util.SMlog("Done...") 

769 except: 

770 raise xs_errors.XenError('VDIDelete') 

771 

772 # delete, update, introduce unimplemented. super class will raise 

773 # exceptions 

774 

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)