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 

20from sm_typing import override 

21 

22import SR 

23import VDI 

24import SRCommand 

25import util 

26import nfs 

27import os 

28import re 

29import xs_errors 

30import cifutils 

31 

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

33 "SR_SCAN", "SR_ATTACH", "SR_DETACH"] 

34 

35CONFIGURATION = [ 

36 ['location', 'path to mount (required) (e.g. server:/path)'], 

37 ['options', 

38 'extra options to pass to mount (e.g. \'-o ro\')'], 

39 ['type', 'cifs (SMB) or nfs_iso'], 

40 nfs.NFS_VERSION, 

41 ['vers', 'SMB version, default version 3'], 

42 ['username', r'Username to authenticate to SMB share with, can be domain\username'], 

43 ['cifspassword_secret', 'Secret ID containing the password to authenticate to SMB'], 

44 ['cifspassword', 'Password to authenticate to SMB, (deprecated see cifspassword_secret)'] 

45] 

46 

47DRIVER_INFO = { 

48 'name': 'ISO', 

49 'description': 'Handles CD images stored as files in iso format', 

50 'vendor': 'Citrix Systems Inc', 

51 'copyright': '(C) 2008 Citrix Systems Inc', 

52 'driver_version': '1.0', 

53 'required_api_version': '1.0', 

54 'capabilities': CAPABILITIES, 

55 'configuration': CONFIGURATION 

56 } 

57 

58TYPE = "iso" 

59SMB_VERSION_1 = '1.0' 

60SMB_VERSION_3 = '3.0' 

61NFSPORT = 2049 

62 

63 

64def list_images(path): 

65 """ 

66 Finds the iso and img files in a given directory that have valid unicode 

67 names. Returns a list of these, together with a count of number of image 

68 files that had to be ignored due to encoding issues in their names. 

69 """ 

70 # pylint: disable=no-member 

71 

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

73 images = [] 

74 num_images_ignored = 0 

75 

76 for filename in os.listdir(path): 

77 if not regex.search(filename): 

78 # Not an image file 

79 pass 

80 elif os.path.isdir(os.path.join(path, filename)): 

81 util.SMlog("list_images: '%s' is a directory. Ignore" 

82 % loggable_filename(filename)) 

83 else: 

84 try: 

85 if is_consistent_utf8_filename(filename): 

86 images.append(filename) 

87 else: 

88 num_images_ignored += 1 

89 util.SMlog("WARNING: ignoring image file '%s' due to" 

90 " encoding issues" 

91 % loggable_filename(filename)) 

92 except UnicodeDecodeError as e: 

93 num_images_ignored += 1 

94 util.SMlog("WARNING: ignoring image file '%s' as its name is" 

95 " not UTF-8 compatible" 

96 % loggable_filename(filename)) 

97 

98 return images, num_images_ignored 

99 

100 

101def loggable_filename(filename): 

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

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

104 

105 

106def is_consistent_utf8_filename(filename): 

107 """ 

108 Determines whether a filename, which is assumed to come from a filesystem 

109 where names are UTF-8 encoded, is consistent in the sense that its name in 

110 the form we'd like to use it (that is, as valid unicode) is the same as 

111 the form it needs to take when passed to Python library functions (e.g. 

112 open, os.stat). 

113 

114 Raises UnicodeDecodeError if the name on the filesystem isn't UTF-8 

115 encoded. 

116 """ 

117 # We generally expect that names of files in the mounted file system will 

118 # be utf-8 encoded. That need not always be true - for example, mount.cifs 

119 # provides an "iocharset" option to control this. But we make no attempt 

120 # to cope with, say, latin-1, and so such a file name will cause an 

121 # exception. 

122 # 

123 # Even if a file's name is utf-8 encoded, we might still have to reject it 

124 # for being "inconsistent". That's because Python's filesystem encoding 

125 # (see `sys.getfilesystemencoding`) might be ascii rather than utf-8, in 

126 # which case non-ascii characters in file names will show up as "surrogate 

127 # escapes" - which makes them technically not valid as unicode. 

128 # 

129 # Although it would be easy enough to recover the originally intended name 

130 # for such a file, it would be awkward elsewhere in the code base either 

131 # to have invalid unicode in file paths, or to have file paths that needed 

132 # massaging before they could be used for actual file operations. Hence 

133 # we say the name is inconsistent. 

134 # 

135 # From Python 3.7 onwards it looks like it should be much more likely that 

136 # the filesystem encoding will be utf-8, which will hopefully mean that we 

137 # would then get previously rejected image files showing up, and working 

138 # without further code changes being necessary. 

139 

140 filename_bytes = os.fsencode(filename) 

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

142 

143 

144def tools_iso_name(filename): 

145 # The tools ISO used have a "xs-" prefix in its name. 

146 # We recognise both and set the name_label accordingly. 

147 if filename[:3] == "xs-": 

148 return "xs-tools.iso" 

149 else: 

150 return "guest-tools.iso" 

151 

152 

153class ISOSR(SR.SR): 

154 """Local file storage repository""" 

155 

156 # Some helper functions: 

157 def _checkmount(self) -> bool: 

158 """Checks that the mountpoint exists and is mounted""" 

159 if not util.pathexists(self.mountpoint): 159 ↛ 161line 159 didn't jump to line 161, because the condition on line 159 was never false

160 return False 

161 try: 

162 ismount = util.ismount(self.mountpoint) 

163 except util.CommandException as inst: 

164 return False 

165 return ismount 

166 

167 def _checkTargetStr(self, location): 

168 if 'type' not in self.dconf: 

169 return 

170 if self.dconf['type'] == 'cifs': 170 ↛ 171line 170 didn't jump to line 171, because the condition on line 170 was never true

171 tgt = '' 

172 if re.search('^//', location): 

173 tgt = location.split('/')[2] 

174 elif re.search(r'^\\', location): 

175 l = location.split('\\') 

176 for i in location.split('\\'): 

177 if i: 

178 tgt = i 

179 break 

180 if not tgt: 

181 raise xs_errors.XenError('ISOLocationStringError') 

182 else: 

183 if location.find(':') == -1: 183 ↛ 184line 183 didn't jump to line 184, because the condition on line 183 was never true

184 raise xs_errors.XenError('ISOLocationStringError') 

185 tgt = location.split(':')[0] 

186 

187 try: 

188 util._convertDNS(tgt) 

189 except: 

190 raise xs_errors.XenError('DNSError') 

191 

192 # pylint: disable=no-member 

193 uuid_file_regex = re.compile( 

194 r"([0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12})\.(iso|img)", re.I) 

195 

196 def _loadvdis(self): 

197 """Scan the directory and get uuids either from the VDI filename, \ 

198 or by creating a new one.""" 

199 if self.vdis: 

200 return 

201 

202 image_names, _ = list_images(self.path) 

203 

204 for name in image_names: 

205 self.vdis[name] = ISOVDI(self, name) 

206 # Set the VDI UUID if the filename is of the correct form. 

207 # Otherwise, one will be generated later in VDI._db_introduce. 

208 m = self.uuid_file_regex.match(name) 

209 if m: 

210 self.vdis[name].uuid = m.group(1) 

211 

212 # Synchronise the read-only status with existing VDI records 

213 __xenapi_records = util.list_VDI_records_in_sr(self) 

214 __xenapi_locations = {} 

215 for vdi in __xenapi_records.keys(): 

216 __xenapi_locations[__xenapi_records[vdi]['location']] = vdi 

217 for vdi in self.vdis.values(): 

218 if vdi.location in __xenapi_locations: 

219 v = __xenapi_records[__xenapi_locations[vdi.location]] 

220 sm_config = v['sm_config'] 

221 if 'created' in sm_config: 

222 vdi.sm_config['created'] = sm_config['created'] 

223 vdi.read_only = False 

224 

225# Now for the main functions: 

226 @override 

227 @staticmethod 

228 def handles(type) -> bool: 

229 """Do we handle this type?""" 

230 if type == TYPE: 

231 return True 

232 return False 

233 

234 @override 

235 def content_type(self, sr_uuid) -> str: 

236 """Returns the content_type XML""" 

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

238 

239 # pylint: disable=no-member 

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

241 

242 @override 

243 def vdi(self, uuid) -> VDI.VDI: 

244 """Create a VDI class. If the VDI does not exist, we determine 

245 here what its filename should be.""" 

246 

247 filename = util.to_plain_string(self.srcmd.params.get('vdi_location')) 

248 if filename is None: 

249 smconfig = self.srcmd.params.get('vdi_sm_config') 

250 if smconfig is None: 

251 # uh, oh, a VDI.from_uuid() 

252 import XenAPI # pylint: disable=import-error 

253 _VDI = self.session.xenapi.VDI 

254 try: 

255 vdi_ref = _VDI.get_by_uuid(uuid) 

256 except XenAPI.Failure as e: 

257 if e.details[0] != 'UUID_INVALID': 

258 raise 

259 else: 

260 filename = _VDI.get_location(vdi_ref) 

261 

262 if filename is None: 

263 # Get the filename from sm-config['path'], or use the UUID 

264 # if the path param doesn't exist. 

265 if smconfig and 'path' in smconfig: 

266 filename = smconfig['path'] 

267 if not self.vdi_path_regex.match(filename): 

268 raise xs_errors.XenError('VDICreate', \ 

269 opterr='Invalid path "%s"' % filename) 

270 else: 

271 filename = '%s.img' % uuid 

272 

273 return ISOVDI(self, filename) 

274 

275 @override 

276 def load(self, sr_uuid) -> None: 

277 """Initialises the SR""" 

278 # First of all, check we've got the correct keys in dconf 

279 if 'location' not in self.dconf: 279 ↛ 280line 279 didn't jump to line 280, because the condition on line 279 was never true

280 raise xs_errors.XenError('ConfigLocationMissing') 

281 

282 # Construct the path we're going to mount under: 

283 if "legacy_mode" in self.dconf: 

284 self.mountpoint = util.to_plain_string(self.dconf['location']) 

285 else: 

286 # Verify the target address 

287 self._checkTargetStr(self.dconf['location']) 

288 self.mountpoint = os.path.join(SR.MOUNT_BASE, sr_uuid) 

289 

290 # Add on the iso_path value if there is one 

291 if "iso_path" in self.dconf: 291 ↛ 292line 291 didn't jump to line 292, because the condition on line 291 was never true

292 iso_path = util.to_plain_string(self.dconf['iso_path']) 

293 if iso_path.startswith("/"): 

294 iso_path = iso_path[1:] 

295 self.path = os.path.join(self.mountpoint, iso_path) 

296 else: 

297 self.path = self.mountpoint 

298 

299 # Handle optional dconf attributes 

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

301 

302 # Fill the required SMB version 

303 self.smbversion = SMB_VERSION_3 

304 

305 # Check if smb version is specified from client 

306 self.is_smbversion_specified = False 

307 

308 # Some info we need: 

309 self.sr_vditype = 'phy' 

310 

311 @override 

312 def delete(self, sr_uuid) -> None: 

313 pass 

314 

315 @override 

316 def attach(self, sr_uuid) -> None: 

317 """Std. attach""" 

318 # Very-Legacy mode means the ISOs are in the local fs - so no need to attach. 

319 if 'legacy_mode' in self.dconf: 

320 # Verify path exists 

321 if not os.path.exists(self.mountpoint): 

322 raise xs_errors.XenError('ISOLocalPath') 

323 return 

324 

325 # Check whether we're already mounted 

326 if self._checkmount(): 

327 return 

328 

329 location = util.to_plain_string(self.dconf['location']) 

330 # TODO: Have XC standardise iso type string 

331 if 'type' in self.dconf: 

332 protocol = self.dconf['type'] 

333 elif ":/" in location: 333 ↛ 334line 333 didn't jump to line 334, because the condition on line 333 was never true

334 protocol = 'nfs_iso' 

335 else: 

336 protocol = 'cifs' 

337 

338 if protocol == 'nfs_iso': 

339 self._check_nfs_server(location) 

340 

341 # Create the mountpoint if it's not already there 

342 if not util.isdir(self.mountpoint): 342 ↛ 345line 342 didn't jump to line 345, because the condition on line 342 was never false

343 util.makedirs(self.mountpoint) 

344 

345 mountcmd = [] 

346 options = [] 

347 nfs_options = '' 

348 

349 if 'options' in self.dconf: 

350 options = self.dconf['options'].split(' ') 

351 if protocol == 'cifs': 351 ↛ 354line 351 didn't jump to line 354, because the condition on line 351 was never false

352 options = [x for x in options if x != ""] 

353 else: 

354 nfs_options = self.getNFSOptions(options) 

355 

356 # SMB options are passed differently for create via 

357 # XC/xe sr-create and create via xe-mount-iso-sr 

358 # In both cases check if SMB version is passed are not. 

359 # If not use self.smbversion. 

360 if protocol == 'cifs': 

361 if 'type' in self.dconf: 

362 # Create via XC or sr-create 

363 # Check for username and password 

364 mountcmd = ["mount.cifs", location, self.mountpoint] 

365 if 'vers' in self.dconf: 

366 self.is_smbversion_specified = True 

367 self.smbversion = self.dconf['vers'] 

368 util.SMlog("self.dconf['vers'] = %s" % self.dconf['vers']) 

369 self.appendCIFSMountOptions(mountcmd) 

370 else: 

371 # Creation via xe-mount-iso-sr 

372 try: 

373 mountcmd = ["mount", location, self.mountpoint] 

374 if options and options[0] == '-o': 374 ↛ 383line 374 didn't jump to line 383, because the condition on line 374 was never false

375 pos = options[1].find('vers=') 

376 if pos == -1: 376 ↛ 377line 376 didn't jump to line 377, because the condition on line 376 was never true

377 options[1] += ',' + self.getSMBVersion() 

378 else: 

379 self.smbversion = self.getSMBVersionFromOptions( 

380 options[1]) 

381 self.is_smbversion_specified = True 

382 else: 

383 raise ValueError 

384 mountcmd.extend(options) 

385 except ValueError: 

386 raise xs_errors.XenError('ISOInvalidXeMountOptions') 

387 # Check the validity of 'smbversion'. 

388 # Raise an exception for any invalid version. 

389 if self.smbversion not in [SMB_VERSION_1, SMB_VERSION_3]: 

390 raise xs_errors.XenError('ISOInvalidSMBversion') 

391 

392 # Attempt mounting 

393 smb3_fail_reason = None 

394 try: 

395 if protocol == 'nfs_iso': 

396 # For NFS, do a soft mount with tcp as protocol. Since ISO SR is 

397 # going to be r-only, a failure in nfs link can be reported back 

398 # to the process waiting. 

399 server, path, transport = self._parse_nfs_location(location) 

400 # Extract timeout and retrans values, if any 

401 io_timeout = nfs.get_nfs_timeout(self.other_config) 

402 io_retrans = nfs.get_nfs_retrans(self.other_config) 

403 nfs.soft_mount(self.mountpoint, server, path, 

404 transport, useroptions=nfs_options, nfsversion=self.nfsversion, 

405 timeout=io_timeout, retrans=io_retrans) 

406 else: 

407 if self.smbversion in SMB_VERSION_3: 

408 util.SMlog('ISOSR mount over smb 3.0') 

409 try: 

410 self.mountOverSMB(mountcmd) 

411 except util.CommandException as inst: 

412 if not self.is_smbversion_specified: 412 ↛ 430line 412 didn't jump to line 430, because the condition on line 412 was never false

413 util.SMlog('Retrying ISOSR mount over smb 1.0') 

414 smb3_fail_reason = inst.reason 

415 # mountcmd is constructed such that the last two 

416 # items will contain -o argument and its value. 

417 del mountcmd[-2:] 

418 self.smbversion = SMB_VERSION_1 

419 if not options: 419 ↛ 422line 419 didn't jump to line 422, because the condition on line 419 was never false

420 self.appendCIFSMountOptions(mountcmd) 

421 else: 

422 if options[0] == '-o': 

423 # regex can be used here since we have 

424 # already validated version entry 

425 options[1] = re.sub('vers=3.0', 'vers=1.0', 

426 options[1]) 

427 mountcmd.extend(options) 

428 self.mountOverSMB(mountcmd) 

429 else: 

430 raise xs_errors.XenError( 

431 'ISOMountFailure', opterr=inst.reason) 

432 else: 

433 util.SMlog('ISOSR mount over smb 1.0') 

434 self.mountOverSMB(mountcmd) 

435 except util.CommandException as inst: 

436 if not self.is_smbversion_specified: 436 ↛ 440line 436 didn't jump to line 440, because the condition on line 436 was never false

437 raise xs_errors.XenError( 

438 'ISOMountFailure', opterr=smb3_fail_reason) 

439 else: 

440 raise xs_errors.XenError( 

441 'ISOMountFailure', opterr=inst.reason) 

442 except nfs.NfsException as e: 

443 raise xs_errors.XenError('ISOMountFailure', opterr=str(e.errstr)) 

444 

445 # Check the iso_path is accessible 

446 if not self._checkmount(): 446 ↛ 447line 446 didn't jump to line 447, because the condition on line 446 was never true

447 self.detach(sr_uuid) 

448 raise xs_errors.XenError('ISOSharenameFailure') 

449 

450 def _parse_nfs_location(self, location): 

451 """ 

452 Given the location of an NFS share, parse it to give 

453 a tuple of the remove server, remote path, and transport protocol to 

454 use. 

455 """ 

456 serv_path = [] 

457 transport = 'tcp' 

458 if location.startswith('['): 

459 # IPv6 target: remove brackets around the IPv6 

460 transport = 'tcp6' 

461 ip6 = location[1:location.index(']')] 

462 path = location[location.index(']') + 2:] 

463 serv_path = [ip6, path] 

464 else: 

465 serv_path = location.split(':') 

466 

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

468 

469 def _check_nfs_server(self, location): 

470 """ 

471 Given that we want to mount a given NFS share, checks that there is an 

472 NFS server running in the remote server, and that it supports the 

473 desired NFS version. Raises an appropriate exception if this is not 

474 the case. 

475 """ 

476 server, _, transport = self._parse_nfs_location(location) 

477 

478 try: 

479 util._testHost(server, NFSPORT, 'NFSTarget') 

480 if not nfs.check_server_tcp(server, transport, self.nfsversion): 

481 raise xs_errors.XenError('NFSVersion', 

482 opterr="Unsupported NFS version: %s" % self.nfsversion) 

483 except nfs.NfsException as e: 

484 raise xs_errors.XenError('NFSTarget', opterr=str(e.errstr)) 

485 

486 @override 

487 def after_master_attach(self, uuid) -> None: 

488 """Perform actions required after attaching on the pool master 

489 Return: 

490 None 

491 """ 

492 # Nothing required here for ISOs and tools ISOs will fail if scanned 

493 pass 

494 

495 def getSMBVersionFromOptions(self, options): 

496 """Extract SMB version from options """ 

497 smb_ver = None 

498 options_list = options.split(',') 

499 for option in options_list: 499 ↛ 505line 499 didn't jump to line 505, because the loop on line 499 didn't complete

500 if option.startswith('vers='): 500 ↛ 499line 500 didn't jump to line 499, because the condition on line 500 was never false

501 version = option.split('=') 

502 if len(version) == 2: 502 ↛ 504line 502 didn't jump to line 504, because the condition on line 502 was never false

503 smb_ver = version[1] 

504 break 

505 return smb_ver 

506 

507 def getSMBVersion(self): 

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

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

510 return smbversion 

511 

512 def mountOverSMB(self, mountcmd): 

513 """This function raises util.CommandException""" 

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

515 prefix="cifs") 

516 

517 util.pread(mountcmd, True, new_env=new_env) 

518 try: 

519 if not self.is_smbversion_specified: 

520 # Store the successful smb version in PBD config 

521 self.updateSMBVersInPBDConfig() 

522 except Exception as exc: 

523 util.SMlog("Exception: %s" % str(exc)) 

524 if self._checkmount(): 524 ↛ 526line 524 didn't jump to line 526, because the condition on line 524 was never false

525 util.pread(["umount", self.mountpoint]) 

526 raise xs_errors.XenError('SMBMount') from exc 

527 

528 def updateSMBVersInPBDConfig(self): 

529 """Store smb version in PBD config""" 

530 pbd = util.find_my_pbd(self.session, self.host_ref, self.sr_ref) 

531 if pbd is not None: 531 ↛ 532line 531 didn't jump to line 532, because the condition on line 531 was never true

532 util.SMlog('Updating SMB version in PBD device config') 

533 dconf = self.session.xenapi.PBD.get_device_config(pbd) 

534 dconf['vers'] = self.smbversion 

535 self.session.xenapi.PBD.set_device_config(pbd, dconf) 

536 else: 

537 raise Exception('Could not find PBD for corresponding SR') 

538 

539 def getNFSOptions(self, options): 

540 """Append options to mount.nfs""" 

541 #Only return any options specified with -o 

542 nfsOptions = '' 

543 for index, opt in enumerate(options): 

544 if opt == "-o": 

545 nfsOptions = options[index + 1] 

546 break 

547 

548 return nfsOptions 

549 

550 def appendCIFSMountOptions(self, mountcmd): 

551 """Append options to mount.cifs""" 

552 options = [] 

553 try: 

554 options.append(self.getCacheOptions()) 

555 

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

557 options.append('guest') 

558 domain = None 

559 else: 

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

561 

562 options.append(self.getSMBVersion()) 

563 

564 if domain: 

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

566 except: 

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

568 raise 

569 

570 # Extend mountcmd appropriately 

571 if options: 571 ↛ exitline 571 didn't return from function 'appendCIFSMountOptions', because the condition on line 571 was never false

572 options = ",".join(str(x) for x in options if x) 

573 mountcmd.extend(["-o", options]) 

574 

575 def getCacheOptions(self): 

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

577 return "cache=none" 

578 

579 @override 

580 def detach(self, sr_uuid) -> None: 

581 """Std. detach""" 

582 if 'legacy_mode' in self.dconf or not self._checkmount(): 582 ↛ 585line 582 didn't jump to line 585, because the condition on line 582 was never false

583 return 

584 

585 try: 

586 util.pread(["umount", self.mountpoint]) 

587 except util.CommandException as inst: 

588 raise xs_errors.XenError('NFSUnMount', \ 

589 opterr='error is %d' % inst.code) 

590 

591 @override 

592 def scan(self, sr_uuid) -> None: 

593 """Scan: see _loadvdis""" 

594 if not util.isdir(self.path): 

595 raise xs_errors.XenError('SRUnavailable', \ 

596 opterr='no such directory %s' % self.path) 

597 

598 if ('legacy_mode' not in self.dconf) and (not self._checkmount()): 

599 raise xs_errors.XenError('SRUnavailable', \ 

600 opterr='directory not mounted: %s' % self.path) 

601 

602 #try: 

603 if not self.vdis: 

604 self._loadvdis() 

605 self.physical_size = util.get_fs_size(self.path) 

606 self.physical_utilisation = util.get_fs_utilisation(self.path) 

607 self.virtual_allocation = self.physical_size 

608 

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

610 

611 if 'xenserver_tools_sr' in self.other_config and \ 

612 self.other_config['xenserver_tools_sr'] == "true": 

613 # Out of all the xs-tools ISOs which exist in this dom0, we mark 

614 # only one as the official one. 

615 

616 # Pass 1: find the latest version 

617 latest_build_vdi = None 

618 latest_build_number = "0" 

619 for vdi_name in self.vdis: 

620 vdi = self.vdis[vdi_name] 

621 

622 if latest_build_vdi is None: 

623 latest_build_vdi = vdi.location 

624 latest_build_number = "0" 

625 

626 if 'xs-tools-build' in vdi.sm_config: 

627 bld = vdi.sm_config['xs-tools-build'] 

628 if bld >= latest_build_number: 

629 latest_build_vdi = vdi.location 

630 latest_build_number = bld 

631 

632 # Pass 2: mark all VDIs accordingly 

633 for vdi_name in self.vdis: 

634 vdi = self.vdis[vdi_name] 

635 if vdi.location == latest_build_vdi: 

636 vdi.sm_config['xs-tools'] = "true" 

637 else: 

638 if "xs-tools" in vdi.sm_config: 

639 del vdi.sm_config['xs-tools'] 

640 

641 # Synchronise the VDIs: this will update the sm_config maps of current records 

642 scanrecord = SR.ScanRecord(self) 

643 scanrecord.synchronise_new() 

644 scanrecord.synchronise_existing() 

645 

646 # Everything that looks like an xs-tools ISO but which isn't the 

647 # primary one will also be renamed "Old version of ..." 

648 sr = self.session.xenapi.SR.get_by_uuid(sr_uuid) 

649 all_vdis = self.session.xenapi.VDI.get_all_records_where("field \"SR\" = \"%s\"" % sr) 

650 for vdi_ref in all_vdis.keys(): 

651 vdi = all_vdis[vdi_ref] 

652 if 'xs-tools-version' in vdi['sm_config']: 

653 name = tools_iso_name(vdi['location']) 

654 if 'xs-tools' in vdi['sm_config']: 

655 self.session.xenapi.VDI.set_name_label(vdi_ref, name) 

656 else: 

657 self.session.xenapi.VDI.set_name_label(vdi_ref, "Old version of " + name) 

658 

659 

660 # never forget old VDI records to cope with rolling upgrade 

661 for location in scanrecord.gone: 

662 vdi = scanrecord.get_xenapi_vdi(location) 

663 util.SMlog("Marking previous version of tools ISO: location=%s uuid=%s" % (vdi['location'], vdi['uuid'])) 

664 vdi = self.session.xenapi.VDI.get_by_uuid(vdi['uuid']) 

665 name_label = self.session.xenapi.VDI.get_name_label(vdi) 

666 if not(name_label.startswith("Old version of ")): 

667 self.session.xenapi.VDI.set_name_label(vdi, "Old version of " + name_label) 

668 # Mark it as missing for informational purposes only 

669 self.session.xenapi.VDI.set_missing(vdi, True) 

670 self.session.xenapi.VDI.remove_from_sm_config(vdi, 'xs-tools') 

671 

672 else: 

673 super(ISOSR, self).scan(sr_uuid) 

674 

675 @override 

676 def create(self, sr_uuid, size) -> None: 

677 self.attach(sr_uuid) 

678 if 'type' in self.dconf: 

679 smconfig = self.session.xenapi.SR.get_sm_config(self.sr_ref) 

680 smconfig['iso_type'] = self.dconf['type'] 

681 self.session.xenapi.SR.set_sm_config(self.sr_ref, smconfig) 

682 

683 _, num_images_ignored = list_images(self.path) 

684 if num_images_ignored > 0: 

685 xapi = self.session.xenapi 

686 xapi.message.create("DISK_IMAGES_IGNORED", 

687 "4", 

688 "SR", 

689 self.uuid, 

690 "Ignored disk image file(s) due to" 

691 " file name encoding issues") 

692 

693 self.detach(sr_uuid) 

694 

695 

696class ISOVDI(VDI.VDI): 

697 @override 

698 def load(self, vdi_uuid) -> None: 

699 # Nb, in the vdi_create call, the filename is unset, so the following 

700 # will fail. 

701 self.vdi_type = "iso" 

702 try: 

703 stat = os.stat(self.path) 

704 self.utilisation = int(stat.st_size) 

705 self.size = int(stat.st_size) 

706 self.label = self.filename 

707 except: 

708 pass 

709 

710 def __init__(self, mysr, filename): 

711 self.path = os.path.join(mysr.path, filename) 

712 VDI.VDI.__init__(self, mysr, None) 

713 self.location = filename 

714 self.filename = filename 

715 self.read_only = True 

716 self.label = filename 

717 self.sm_config = {} 

718 if "legacy_mode" in mysr.dconf: 

719 if filename.startswith("xs-tools") or filename.startswith("guest-tools"): 

720 self.label = tools_iso_name(filename) 

721 # Mark this as a Tools CD 

722 # self.sm_config['xs-tools'] = 'true' 

723 # Extract a version string, if present 

724 vsn = filename[filename.find("tools") + len("tools"):][:-len(".iso")].strip("-").split("-", 1) 

725 # "4.1.0" 

726 if len(vsn) == 1: 

727 build_number = "0" # string 

728 product_version = vsn[0] 

729 # "4.1.0-1234" 

730 elif len(vsn) > 1: 

731 build_number = vsn[1] 

732 product_version = vsn[0] 

733 else: 

734 build_number = 0 

735 product_version = "unknown" 

736 util.SMlog("version=%s build=%s" % (product_version, build_number)) 

737 self.sm_config['xs-tools-version'] = product_version 

738 self.sm_config['xs-tools-build'] = build_number 

739 

740 @override 

741 def detach(self, sr_uuid, vdi_uuid) -> None: 

742 pass 

743 

744 @override 

745 def attach(self, sr_uuid, vdi_uuid) -> str: 

746 try: 

747 os.stat(self.path) 

748 return super(ISOVDI, self).attach(sr_uuid, vdi_uuid) 

749 except: 

750 raise xs_errors.XenError('VDIMissing') 

751 

752 @override 

753 def create(self, sr_uuid, vdi_uuid, size) -> str: 

754 self.uuid = vdi_uuid 

755 self.path = os.path.join(self.sr.path, self.filename) 

756 self.size = size 

757 self.utilisation = 0 

758 self.read_only = False 

759 self.sm_config = self.sr.srcmd.params['vdi_sm_config'] 

760 self.sm_config['created'] = util._getDateString() 

761 

762 if util.pathexists(self.path): 

763 raise xs_errors.XenError('VDIExists') 

764 

765 try: 

766 handle = open(self.path, "w") 

767 handle.truncate(size) 

768 handle.close() 

769 self._db_introduce() 

770 return super(ISOVDI, self).get_params() 

771 except Exception as exn: 

772 util.SMlog("Exception when creating VDI: %s" % exn) 

773 raise xs_errors.XenError('VDICreate', \ 

774 opterr='could not create file: "%s"' % self.path) 

775 

776 @override 

777 def delete(self, sr_uuid, vdi_uuid, data_only=False) -> None: 

778 util.SMlog("Deleting...") 

779 

780 self.uuid = vdi_uuid 

781 self._db_forget() 

782 

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

784 return 

785 

786 try: 

787 util.SMlog("Unlinking...") 

788 os.unlink(self.path) 

789 util.SMlog("Done...") 

790 except: 

791 raise xs_errors.XenError('VDIDelete') 

792 

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

794 # exceptions 

795 

796if __name__ == '__main__': 796 ↛ 797line 796 didn't jump to line 797, because the condition on line 796 was never true

797 SRCommand.run(ISOSR, DRIVER_INFO) 

798else: 

799 SR.registerSR(ISOSR)