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 domain = None 

545 else: 

546 _, domain = cifutils.splitDomainAndUsername(self.dconf['username']) 

547 

548 options.append(self.getSMBVersion()) 

549 

550 if domain: 

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

552 except: 

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

554 raise 

555 

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

560 

561 def getCacheOptions(self): 

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

563 return "cache=none" 

564 

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 

569 

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) 

575 

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) 

581 

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) 

585 

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 

592 

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

594 

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. 

599 

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] 

605 

606 if latest_build_vdi is None: 

607 latest_build_vdi = vdi.location 

608 latest_build_number = "0" 

609 

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 

615 

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

624 

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

629 

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) 

642 

643 

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

655 

656 else: 

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

658 

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) 

665 

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

675 

676 self.detach(sr_uuid) 

677 

678 

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 

691 

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 

721 

722 def detach(self, sr_uuid, vdi_uuid): 

723 pass 

724 

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

731 

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

740 

741 if util.pathexists(self.path): 

742 raise xs_errors.XenError('VDIExists') 

743 

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) 

754 

755 def delete(self, sr_uuid, vdi_uuid): 

756 util.SMlog("Deleting...") 

757 

758 self.uuid = vdi_uuid 

759 self._db_forget() 

760 

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

762 return 

763 

764 try: 

765 util.SMlog("Unlinking...") 

766 os.unlink(self.path) 

767 util.SMlog("Done...") 

768 except: 

769 raise xs_errors.XenError('VDIDelete') 

770 

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

772 # exceptions 

773 

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)