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 

31from vditype import VdiType 

32 

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

34 "SR_SCAN", "SR_ATTACH", "SR_DETACH"] 

35 

36CONFIGURATION = [ 

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

38 ['options', 

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

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

41 nfs.NFS_VERSION, 

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

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

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

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

46] 

47 

48DRIVER_INFO = { 

49 'name': 'ISO', 

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

51 'vendor': 'Citrix Systems Inc', 

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

53 'driver_version': '1.0', 

54 'required_api_version': '1.0', 

55 'capabilities': CAPABILITIES, 

56 'configuration': CONFIGURATION 

57 } 

58 

59TYPE = "iso" 

60SMB_VERSION_1 = '1.0' 

61SMB_VERSION_3 = '3.0' 

62NFSPORT = 2049 

63 

64 

65def list_images(path): 

66 """ 

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

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

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

70 """ 

71 # pylint: disable=no-member 

72 

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

74 images = [] 

75 num_images_ignored = 0 

76 

77 for filename in os.listdir(path): 

78 if not regex.search(filename): 

79 # Not an image file 

80 pass 

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

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

83 % loggable_filename(filename)) 

84 else: 

85 try: 

86 if is_consistent_utf8_filename(filename): 

87 images.append(filename) 

88 else: 

89 num_images_ignored += 1 

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

91 " encoding issues" 

92 % loggable_filename(filename)) 

93 except UnicodeDecodeError as e: 

94 num_images_ignored += 1 

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

96 " not UTF-8 compatible" 

97 % loggable_filename(filename)) 

98 

99 return images, num_images_ignored 

100 

101 

102def loggable_filename(filename): 

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

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

105 

106 

107def is_consistent_utf8_filename(filename): 

108 """ 

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

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

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

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

113 open, os.stat). 

114 

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

116 encoded. 

117 """ 

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

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

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

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

122 # exception. 

123 # 

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

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

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

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

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

129 # 

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

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

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

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

134 # we say the name is inconsistent. 

135 # 

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

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

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

139 # without further code changes being necessary. 

140 

141 filename_bytes = os.fsencode(filename) 

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

143 

144 

145def tools_iso_name(filename): 

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

147 # We recognise both and set the name_label accordingly. 

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

149 return "xs-tools.iso" 

150 else: 

151 return "guest-tools.iso" 

152 

153 

154class ISOSR(SR.SR): 

155 """Local file storage repository""" 

156 

157 # Some helper functions: 

158 def _checkmount(self) -> bool: 

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

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

161 return False 

162 try: 

163 ismount = util.ismount(self.mountpoint) 

164 except util.CommandException as inst: 

165 return False 

166 return ismount 

167 

168 def _checkTargetStr(self, location): 

169 if 'type' not in self.dconf: 

170 return 

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

172 tgt = '' 

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

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

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

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

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

178 if i: 

179 tgt = i 

180 break 

181 if not tgt: 

182 raise xs_errors.XenError('ISOLocationStringError') 

183 else: 

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

185 raise xs_errors.XenError('ISOLocationStringError') 

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

187 

188 try: 

189 util._convertDNS(tgt) 

190 except: 

191 raise xs_errors.XenError('DNSError') 

192 

193 # pylint: disable=no-member 

194 uuid_file_regex = re.compile( 

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

196 

197 def _loadvdis(self): 

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

199 or by creating a new one.""" 

200 if self.vdis: 

201 return 

202 

203 image_names, _ = list_images(self.path) 

204 

205 for name in image_names: 

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

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

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

209 m = self.uuid_file_regex.match(name) 

210 if m: 

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

212 

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

214 __xenapi_records = util.list_VDI_records_in_sr(self) 

215 __xenapi_locations = {} 

216 for vdi in __xenapi_records.keys(): 

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

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

219 if vdi.location in __xenapi_locations: 

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

221 sm_config = v['sm_config'] 

222 if 'created' in sm_config: 

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

224 vdi.read_only = False 

225 

226# Now for the main functions: 

227 @override 

228 @staticmethod 

229 def handles(type) -> bool: 

230 """Do we handle this type?""" 

231 if type == TYPE: 

232 return True 

233 return False 

234 

235 @override 

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

237 """Returns the content_type XML""" 

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

239 

240 # pylint: disable=no-member 

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

242 

243 @override 

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

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

246 here what its filename should be.""" 

247 

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

249 if filename is None: 

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

251 if smconfig is None: 

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

253 import XenAPI # pylint: disable=import-error 

254 _VDI = self.session.xenapi.VDI 

255 try: 

256 vdi_ref = _VDI.get_by_uuid(uuid) 

257 except XenAPI.Failure as e: 

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

259 raise 

260 else: 

261 filename = _VDI.get_location(vdi_ref) 

262 

263 if filename is None: 

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

265 # if the path param doesn't exist. 

266 if smconfig and 'path' in smconfig: 

267 filename = smconfig['path'] 

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

269 raise xs_errors.XenError('VDICreate', \ 

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

271 else: 

272 filename = '%s.img' % uuid 

273 

274 return ISOVDI(self, filename) 

275 

276 @override 

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

278 """Initialises the SR""" 

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

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

281 raise xs_errors.XenError('ConfigLocationMissing') 

282 

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

284 if "legacy_mode" in self.dconf: 

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

286 else: 

287 # Verify the target address 

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

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

290 

291 # Add on the iso_path value if there is one 

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

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

294 if iso_path.startswith("/"): 

295 iso_path = iso_path[1:] 

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

297 else: 

298 self.path = self.mountpoint 

299 

300 # Handle optional dconf attributes 

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

302 

303 # Fill the required SMB version 

304 self.smbversion = SMB_VERSION_3 

305 

306 # Check if smb version is specified from client 

307 self.is_smbversion_specified = False 

308 

309 # Some info we need: 

310 self.sr_vditype = 'phy' 

311 

312 @override 

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

314 pass 

315 

316 @override 

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

318 """Std. attach""" 

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

320 if 'legacy_mode' in self.dconf: 

321 # Verify path exists 

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

323 raise xs_errors.XenError('ISOLocalPath') 

324 return 

325 

326 # Check whether we're already mounted 

327 if self._checkmount(): 

328 return 

329 

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

331 # TODO: Have XC standardise iso type string 

332 if 'type' in self.dconf: 

333 protocol = self.dconf['type'] 

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

335 protocol = 'nfs_iso' 

336 else: 

337 protocol = 'cifs' 

338 

339 if protocol == 'nfs_iso': 

340 self._check_nfs_server(location) 

341 

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

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

344 util.makedirs(self.mountpoint) 

345 

346 mountcmd = [] 

347 options = [] 

348 nfs_options = '' 

349 

350 if 'options' in self.dconf: 

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

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

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

354 else: 

355 nfs_options = self.getNFSOptions(options) 

356 

357 # SMB options are passed differently for create via 

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

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

360 # If not use self.smbversion. 

361 if protocol == 'cifs': 

362 if 'type' in self.dconf: 

363 # Create via XC or sr-create 

364 # Check for username and password 

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

366 if 'vers' in self.dconf: 

367 self.is_smbversion_specified = True 

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

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

370 self.appendCIFSMountOptions(mountcmd) 

371 else: 

372 # Creation via xe-mount-iso-sr 

373 try: 

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

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

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

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

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

379 else: 

380 self.smbversion = self.getSMBVersionFromOptions( 

381 options[1]) 

382 self.is_smbversion_specified = True 

383 else: 

384 raise ValueError 

385 mountcmd.extend(options) 

386 except ValueError: 

387 raise xs_errors.XenError('ISOInvalidXeMountOptions') 

388 # Check the validity of 'smbversion'. 

389 # Raise an exception for any invalid version. 

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

391 raise xs_errors.XenError('ISOInvalidSMBversion') 

392 

393 # Attempt mounting 

394 smb3_fail_reason = None 

395 try: 

396 if protocol == 'nfs_iso': 

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

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

399 # to the process waiting. 

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

401 # Extract timeout and retrans values, if any 

402 io_timeout = nfs.get_nfs_timeout(self.other_config) 

403 io_retrans = nfs.get_nfs_retrans(self.other_config) 

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

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

406 timeout=io_timeout, retrans=io_retrans) 

407 else: 

408 if self.smbversion in SMB_VERSION_3: 

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

410 try: 

411 self.mountOverSMB(mountcmd) 

412 except util.CommandException as inst: 

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

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

415 smb3_fail_reason = inst.reason 

416 # mountcmd is constructed such that the last two 

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

418 del mountcmd[-2:] 

419 self.smbversion = SMB_VERSION_1 

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

421 self.appendCIFSMountOptions(mountcmd) 

422 else: 

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

424 # regex can be used here since we have 

425 # already validated version entry 

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

427 options[1]) 

428 mountcmd.extend(options) 

429 self.mountOverSMB(mountcmd) 

430 else: 

431 raise xs_errors.XenError( 

432 'ISOMountFailure', opterr=inst.reason) 

433 else: 

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

435 self.mountOverSMB(mountcmd) 

436 except util.CommandException as inst: 

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

438 raise xs_errors.XenError( 

439 'ISOMountFailure', opterr=smb3_fail_reason) 

440 else: 

441 raise xs_errors.XenError( 

442 'ISOMountFailure', opterr=inst.reason) 

443 except nfs.NfsException as e: 

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

445 

446 # Check the iso_path is accessible 

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

448 self.detach(sr_uuid) 

449 raise xs_errors.XenError('ISOSharenameFailure') 

450 

451 def _parse_nfs_location(self, location): 

452 """ 

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

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

455 use. 

456 """ 

457 serv_path = [] 

458 transport = 'tcp' 

459 if location.startswith('['): 

460 # IPv6 target: remove brackets around the IPv6 

461 transport = 'tcp6' 

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

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

464 serv_path = [ip6, path] 

465 else: 

466 serv_path = location.split(':') 

467 

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

469 

470 def _check_nfs_server(self, location): 

471 """ 

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

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

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

475 the case. 

476 """ 

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

478 

479 try: 

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

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

482 raise xs_errors.XenError('NFSVersion', 

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

484 except nfs.NfsException as e: 

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

486 

487 @override 

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

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

490 Return: 

491 None 

492 """ 

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

494 pass 

495 

496 def getSMBVersionFromOptions(self, options): 

497 """Extract SMB version from options """ 

498 smb_ver = None 

499 options_list = options.split(',') 

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

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

502 version = option.split('=') 

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

504 smb_ver = version[1] 

505 break 

506 return smb_ver 

507 

508 def getSMBVersion(self): 

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

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

511 return smbversion 

512 

513 def mountOverSMB(self, mountcmd): 

514 """This function raises util.CommandException""" 

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

516 prefix="cifs") 

517 

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

519 try: 

520 if not self.is_smbversion_specified: 

521 # Store the successful smb version in PBD config 

522 self.updateSMBVersInPBDConfig() 

523 except Exception as exc: 

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

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

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

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

528 

529 def updateSMBVersInPBDConfig(self): 

530 """Store smb version in PBD config""" 

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

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

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

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

535 dconf['vers'] = self.smbversion 

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

537 else: 

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

539 

540 def getNFSOptions(self, options): 

541 """Append options to mount.nfs""" 

542 #Only return any options specified with -o 

543 nfsOptions = '' 

544 for index, opt in enumerate(options): 

545 if opt == "-o": 

546 nfsOptions = options[index + 1] 

547 break 

548 

549 return nfsOptions 

550 

551 def appendCIFSMountOptions(self, mountcmd): 

552 """Append options to mount.cifs""" 

553 options = [] 

554 try: 

555 options.append(self.getCacheOptions()) 

556 

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

558 options.append('guest') 

559 domain = None 

560 else: 

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

562 

563 options.append(self.getSMBVersion()) 

564 

565 if domain: 

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

567 except: 

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

569 raise 

570 

571 # Extend mountcmd appropriately 

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

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

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

575 

576 def getCacheOptions(self): 

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

578 return "cache=none" 

579 

580 @override 

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

582 """Std. detach""" 

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

584 return 

585 

586 try: 

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

588 except util.CommandException as inst: 

589 raise xs_errors.XenError('NFSUnMount', \ 

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

591 

592 @override 

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

594 """Scan: see _loadvdis""" 

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

596 raise xs_errors.XenError('SRUnavailable', \ 

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

598 

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

600 raise xs_errors.XenError('SRUnavailable', \ 

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

602 

603 #try: 

604 if not self.vdis: 

605 self._loadvdis() 

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

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

608 self.virtual_allocation = self.physical_size 

609 

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

611 

612 if 'xenserver_tools_sr' in self.other_config and \ 

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

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

615 # only one as the official one. 

616 

617 # Pass 1: find the latest version 

618 latest_build_vdi = None 

619 latest_build_number = "0" 

620 for vdi_name in self.vdis: 

621 vdi = self.vdis[vdi_name] 

622 

623 if latest_build_vdi is None: 

624 latest_build_vdi = vdi.location 

625 latest_build_number = "0" 

626 

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

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

629 if bld >= latest_build_number: 

630 latest_build_vdi = vdi.location 

631 latest_build_number = bld 

632 

633 # Pass 2: mark all VDIs accordingly 

634 for vdi_name in self.vdis: 

635 vdi = self.vdis[vdi_name] 

636 if vdi.location == latest_build_vdi: 

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

638 else: 

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

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

641 

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

643 scanrecord = SR.ScanRecord(self) 

644 scanrecord.synchronise_new() 

645 scanrecord.synchronise_existing() 

646 

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

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

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

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

651 for vdi_ref in all_vdis.keys(): 

652 vdi = all_vdis[vdi_ref] 

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

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

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

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

657 else: 

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

659 

660 

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

662 for location in scanrecord.gone: 

663 vdi = scanrecord.get_xenapi_vdi(location) 

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

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

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

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

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

669 # Mark it as missing for informational purposes only 

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

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

672 

673 else: 

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

675 

676 @override 

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

678 self.attach(sr_uuid) 

679 if 'type' in self.dconf: 

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

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

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

683 

684 _, num_images_ignored = list_images(self.path) 

685 if num_images_ignored > 0: 

686 xapi = self.session.xenapi 

687 xapi.message.create("DISK_IMAGES_IGNORED", 

688 "4", 

689 "SR", 

690 self.uuid, 

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

692 " file name encoding issues") 

693 

694 self.detach(sr_uuid) 

695 

696 

697class ISOVDI(VDI.VDI): 

698 @override 

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

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

701 # will fail. 

702 self.vdi_type = VdiType.ISO 

703 try: 

704 stat = os.stat(self.path) 

705 self.utilisation = int(stat.st_size) 

706 self.size = int(stat.st_size) 

707 self.label = self.filename 

708 except: 

709 pass 

710 

711 def __init__(self, mysr, filename): 

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

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

714 self.location = filename 

715 self.filename = filename 

716 self.read_only = True 

717 self.label = filename 

718 self.sm_config = {} 

719 if "legacy_mode" in mysr.dconf: 

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

721 self.label = tools_iso_name(filename) 

722 # Mark this as a Tools CD 

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

724 # Extract a version string, if present 

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

726 # "4.1.0" 

727 if len(vsn) == 1: 

728 build_number = "0" # string 

729 product_version = vsn[0] 

730 # "4.1.0-1234" 

731 elif len(vsn) > 1: 

732 build_number = vsn[1] 

733 product_version = vsn[0] 

734 else: 

735 build_number = 0 

736 product_version = "unknown" 

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

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

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

740 

741 @override 

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

743 pass 

744 

745 @override 

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

747 try: 

748 os.stat(self.path) 

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

750 except: 

751 raise xs_errors.XenError('VDIMissing') 

752 

753 @override 

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

755 self.uuid = vdi_uuid 

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

757 self.size = size 

758 self.utilisation = 0 

759 self.read_only = False 

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

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

762 

763 if util.pathexists(self.path): 

764 raise xs_errors.XenError('VDIExists') 

765 

766 try: 

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

768 handle.truncate(size) 

769 handle.close() 

770 self._db_introduce() 

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

772 except Exception as exn: 

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

774 raise xs_errors.XenError('VDICreate', \ 

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

776 

777 @override 

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

779 util.SMlog("Deleting...") 

780 

781 self.uuid = vdi_uuid 

782 self._db_forget() 

783 

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

785 return 

786 

787 try: 

788 util.SMlog("Unlinking...") 

789 os.unlink(self.path) 

790 util.SMlog("Done...") 

791 except: 

792 raise xs_errors.XenError('VDIDelete') 

793 

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

795 # exceptions 

796 

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

798 SRCommand.run(ISOSR, DRIVER_INFO) 

799else: 

800 SR.registerSR(ISOSR)