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 (e.g. \'-o ro\')'], 

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

38 nfs.NFS_VERSION, 

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

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

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

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

43] 

44 

45DRIVER_INFO = { 

46 'name': 'ISO', 

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

48 'vendor': 'Citrix Systems Inc', 

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

50 'driver_version': '1.0', 

51 'required_api_version': '1.0', 

52 'capabilities': CAPABILITIES, 

53 'configuration': CONFIGURATION 

54 } 

55 

56TYPE = "iso" 

57SMB_VERSION_1 = '1.0' 

58SMB_VERSION_3 = '3.0' 

59NFSPORT = 2049 

60 

61 

62def list_images(path): 

63 """ 

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

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

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

67 """ 

68 # pylint: disable=no-member 

69 

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

71 images = [] 

72 num_images_ignored = 0 

73 

74 for filename in os.listdir(path): 

75 if not regex.search(filename): 

76 # Not an image file 

77 pass 

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

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

80 % loggable_filename(filename)) 

81 else: 

82 try: 

83 if is_consistent_utf8_filename(filename): 

84 images.append(filename) 

85 else: 

86 num_images_ignored += 1 

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

88 " encoding issues" 

89 % loggable_filename(filename)) 

90 except UnicodeDecodeError as e: 

91 num_images_ignored += 1 

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

93 " not UTF-8 compatible" 

94 % loggable_filename(filename)) 

95 

96 return images, num_images_ignored 

97 

98 

99def loggable_filename(filename): 

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

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

102 

103 

104def is_consistent_utf8_filename(filename): 

105 """ 

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

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

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

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

110 open, os.stat). 

111 

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

113 encoded. 

114 """ 

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

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

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

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

119 # exception. 

120 # 

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

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

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

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

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

126 # 

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

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

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

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

131 # we say the name is inconsistent. 

132 # 

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

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

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

136 # without further code changes being necessary. 

137 

138 filename_bytes = os.fsencode(filename) 

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

140 

141 

142def tools_iso_name(filename): 

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

144 # We recognise both and set the name_label accordingly. 

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

146 return "xs-tools.iso" 

147 else: 

148 return "guest-tools.iso" 

149 

150 

151class ISOSR(SR.SR): 

152 """Local file storage repository""" 

153 

154 # Some helper functions: 

155 def _checkmount(self): 

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

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

158 return False 

159 try: 

160 ismount = util.ismount(self.mountpoint) 

161 except util.CommandException as inst: 

162 return False 

163 return ismount 

164 

165 def _checkTargetStr(self, location): 

166 if 'type' not in self.dconf: 

167 return 

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

169 tgt = '' 

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

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

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

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

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

175 if i: 

176 tgt = i 

177 break 

178 if not tgt: 

179 raise xs_errors.XenError('ISOLocationStringError') 

180 else: 

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

182 raise xs_errors.XenError('ISOLocationStringError') 

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

184 

185 try: 

186 util._convertDNS(tgt) 

187 except: 

188 raise xs_errors.XenError('DNSError') 

189 

190 # pylint: disable=no-member 

191 uuid_file_regex = re.compile( 

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

193 

194 def _loadvdis(self): 

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

196 or by creating a new one.""" 

197 if self.vdis: 

198 return 

199 

200 image_names, _ = list_images(self.path) 

201 

202 for name in image_names: 

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

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

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

206 m = self.uuid_file_regex.match(name) 

207 if m: 

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

209 

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

211 __xenapi_records = util.list_VDI_records_in_sr(self) 

212 __xenapi_locations = {} 

213 for vdi in __xenapi_records.keys(): 

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

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

216 if vdi.location in __xenapi_locations: 

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

218 sm_config = v['sm_config'] 

219 if 'created' in sm_config: 

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

221 vdi.read_only = False 

222 

223# Now for the main functions: 

224 def handles(type): 

225 """Do we handle this type?""" 

226 if type == TYPE: 

227 return True 

228 return False 

229 handles = staticmethod(handles) 

230 

231 def content_type(self, sr_uuid): 

232 """Returns the content_type XML""" 

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

234 

235 # pylint: disable=no-member 

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

237 

238 def vdi(self, uuid): 

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

240 here what its filename should be.""" 

241 

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

243 if filename is None: 

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

245 if smconfig is None: 

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

247 import XenAPI # pylint: disable=import-error 

248 _VDI = self.session.xenapi.VDI 

249 try: 

250 vdi_ref = _VDI.get_by_uuid(uuid) 

251 except XenAPI.Failure as e: 

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

253 raise 

254 else: 

255 filename = _VDI.get_location(vdi_ref) 

256 

257 if filename is None: 

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

259 # if the path param doesn't exist. 

260 if smconfig and 'path' in smconfig: 

261 filename = smconfig['path'] 

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

263 raise xs_errors.XenError('VDICreate', \ 

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

265 else: 

266 filename = '%s.img' % uuid 

267 

268 return ISOVDI(self, filename) 

269 

270 def load(self, sr_uuid): 

271 """Initialises the SR""" 

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

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

274 raise xs_errors.XenError('ConfigLocationMissing') 

275 

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

277 if "legacy_mode" in self.dconf: 

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

279 else: 

280 # Verify the target address 

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

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

283 

284 # Add on the iso_path value if there is one 

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

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

287 if iso_path.startswith("/"): 

288 iso_path = iso_path[1:] 

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

290 else: 

291 self.path = self.mountpoint 

292 

293 # Handle optional dconf attributes 

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

295 

296 # Fill the required SMB version 

297 self.smbversion = SMB_VERSION_3 

298 

299 # Check if smb version is specified from client 

300 self.is_smbversion_specified = False 

301 

302 # Some info we need: 

303 self.sr_vditype = 'phy' 

304 

305 def delete(self, sr_uuid): 

306 pass 

307 

308 def attach(self, sr_uuid): 

309 """Std. attach""" 

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

311 if 'legacy_mode' in self.dconf: 

312 # Verify path exists 

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

314 raise xs_errors.XenError('ISOLocalPath') 

315 return 

316 

317 # Check whether we're already mounted 

318 if self._checkmount(): 

319 return 

320 

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

322 # TODO: Have XC standardise iso type string 

323 if 'type' in self.dconf: 

324 protocol = self.dconf['type'] 

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

326 protocol = 'nfs_iso' 

327 else: 

328 protocol = 'cifs' 

329 

330 if protocol == 'nfs_iso': 

331 self._check_nfs_server(location) 

332 

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

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

335 util.makedirs(self.mountpoint) 

336 

337 mountcmd = [] 

338 options = '' 

339 

340 if 'options' in self.dconf: 

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

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

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

344 else: 

345 options = self.getNFSOptions(options) 

346 

347 # SMB options are passed differently for create via 

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

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

350 # If not use self.smbversion. 

351 if protocol == 'cifs': 

352 if 'type' in self.dconf: 

353 # Create via XC or sr-create 

354 # Check for username and password 

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

356 if 'vers' in self.dconf: 

357 self.is_smbversion_specified = True 

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

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

360 self.appendCIFSMountOptions(mountcmd) 

361 else: 

362 # Creation via xe-mount-iso-sr 

363 try: 

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

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

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

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

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

369 else: 

370 self.smbversion = self.getSMBVersionFromOptions( 

371 options[1]) 

372 self.is_smbversion_specified = True 

373 else: 

374 raise ValueError 

375 mountcmd.extend(options) 

376 except ValueError: 

377 raise xs_errors.XenError('ISOInvalidXeMountOptions') 

378 # Check the validity of 'smbversion'. 

379 # Raise an exception for any invalid version. 

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

381 raise xs_errors.XenError('ISOInvalidSMBversion') 

382 

383 # Attempt mounting 

384 smb3_fail_reason = None 

385 try: 

386 if protocol == 'nfs_iso': 

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

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

389 # to the process waiting. 

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

391 # Extract timeout and retrans values, if any 

392 io_timeout = nfs.get_nfs_timeout(self.other_config) 

393 io_retrans = nfs.get_nfs_retrans(self.other_config) 

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

395 transport, useroptions=options, nfsversion=self.nfsversion, 

396 timeout=io_timeout, retrans=io_retrans) 

397 else: 

398 if self.smbversion in SMB_VERSION_3: 

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

400 try: 

401 self.mountOverSMB(mountcmd) 

402 except util.CommandException as inst: 

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

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

405 smb3_fail_reason = inst.reason 

406 # mountcmd is constructed such that the last two 

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

408 del mountcmd[-2:] 

409 self.smbversion = SMB_VERSION_1 

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

411 self.appendCIFSMountOptions(mountcmd) 

412 else: 

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

414 # regex can be used here since we have 

415 # already validated version entry 

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

417 options[1]) 

418 mountcmd.extend(options) 

419 self.mountOverSMB(mountcmd) 

420 else: 

421 raise xs_errors.XenError( 

422 'ISOMountFailure', opterr=inst.reason) 

423 else: 

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

425 self.mountOverSMB(mountcmd) 

426 except util.CommandException as inst: 

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

428 raise xs_errors.XenError( 

429 'ISOMountFailure', opterr=smb3_fail_reason) 

430 else: 

431 raise xs_errors.XenError( 

432 'ISOMountFailure', opterr=inst.reason) 

433 except nfs.NfsException as e: 

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

435 

436 # Check the iso_path is accessible 

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

438 self.detach(sr_uuid) 

439 raise xs_errors.XenError('ISOSharenameFailure') 

440 

441 def _parse_nfs_location(self, location): 

442 """ 

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

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

445 use. 

446 """ 

447 serv_path = [] 

448 transport = 'tcp' 

449 if location.startswith('['): 

450 # IPv6 target: remove brackets around the IPv6 

451 transport = 'tcp6' 

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

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

454 serv_path = [ip6, path] 

455 else: 

456 serv_path = location.split(':') 

457 

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

459 

460 def _check_nfs_server(self, location): 

461 """ 

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

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

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

465 the case. 

466 """ 

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

468 

469 try: 

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

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

472 raise xs_errors.XenError('NFSVersion', 

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

474 except nfs.NfsException as e: 

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

476 

477 

478 def after_master_attach(self, uuid): 

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

480 Return: 

481 None 

482 """ 

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

484 pass 

485 

486 def getSMBVersionFromOptions(self, options): 

487 """Extract SMB version from options """ 

488 smb_ver = None 

489 options_list = options.split(',') 

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

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

492 version = option.split('=') 

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

494 smb_ver = version[1] 

495 break 

496 return smb_ver 

497 

498 def getSMBVersion(self): 

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

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

501 return smbversion 

502 

503 def mountOverSMB(self, mountcmd): 

504 """This function raises util.CommandException""" 

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

506 prefix="cifs") 

507 

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

509 try: 

510 if not self.is_smbversion_specified: 

511 # Store the successful smb version in PBD config 

512 self.updateSMBVersInPBDConfig() 

513 except Exception as exc: 

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

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

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

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

518 

519 def updateSMBVersInPBDConfig(self): 

520 """Store smb version in PBD config""" 

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

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

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

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

525 dconf['vers'] = self.smbversion 

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

527 else: 

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

529 

530 def getNFSOptions(self, options): 

531 """Append options to mount.nfs""" 

532 #Only return any options specified with -o 

533 nfsOptions = '' 

534 for index, opt in enumerate(options): 

535 if opt == "-o": 

536 nfsOptions = options[index + 1] 

537 break 

538 

539 return nfsOptions 

540 

541 def appendCIFSMountOptions(self, mountcmd): 

542 """Append options to mount.cifs""" 

543 options = [] 

544 try: 

545 options.append(self.getCacheOptions()) 

546 

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

548 options.append('guest') 

549 domain = None 

550 else: 

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

552 

553 options.append(self.getSMBVersion()) 

554 

555 if domain: 

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

557 except: 

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

559 raise 

560 

561 # Extend mountcmd appropriately 

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

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

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

565 

566 def getCacheOptions(self): 

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

568 return "cache=none" 

569 

570 def detach(self, sr_uuid): 

571 """Std. detach""" 

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

573 return 

574 

575 try: 

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

577 except util.CommandException as inst: 

578 raise xs_errors.XenError('NFSUnMount', \ 

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

580 

581 def scan(self, sr_uuid): 

582 """Scan: see _loadvdis""" 

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

584 raise xs_errors.XenError('SRUnavailable', \ 

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

586 

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

588 raise xs_errors.XenError('SRUnavailable', \ 

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

590 

591 #try: 

592 if not self.vdis: 

593 self._loadvdis() 

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

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

596 self.virtual_allocation = self.physical_size 

597 

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

599 

600 if 'xenserver_tools_sr' in self.other_config and \ 

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

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

603 # only one as the official one. 

604 

605 # Pass 1: find the latest version 

606 latest_build_vdi = None 

607 latest_build_number = "0" 

608 for vdi_name in self.vdis: 

609 vdi = self.vdis[vdi_name] 

610 

611 if latest_build_vdi is None: 

612 latest_build_vdi = vdi.location 

613 latest_build_number = "0" 

614 

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

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

617 if bld >= latest_build_number: 

618 latest_build_vdi = vdi.location 

619 latest_build_number = bld 

620 

621 # Pass 2: mark all VDIs accordingly 

622 for vdi_name in self.vdis: 

623 vdi = self.vdis[vdi_name] 

624 if vdi.location == latest_build_vdi: 

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

626 else: 

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

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

629 

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

631 scanrecord = SR.ScanRecord(self) 

632 scanrecord.synchronise_new() 

633 scanrecord.synchronise_existing() 

634 

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

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

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

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

639 for vdi_ref in all_vdis.keys(): 

640 vdi = all_vdis[vdi_ref] 

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

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

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

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

645 else: 

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

647 

648 

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

650 for location in scanrecord.gone: 

651 vdi = scanrecord.get_xenapi_vdi(location) 

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

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

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

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

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

657 # Mark it as missing for informational purposes only 

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

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

660 

661 else: 

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

663 

664 def create(self, sr_uuid, size): 

665 self.attach(sr_uuid) 

666 if 'type' in self.dconf: 

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

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

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

670 

671 _, num_images_ignored = list_images(self.path) 

672 if num_images_ignored > 0: 

673 xapi = self.session.xenapi 

674 xapi.message.create("DISK_IMAGES_IGNORED", 

675 "4", 

676 "SR", 

677 self.uuid, 

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

679 " file name encoding issues") 

680 

681 self.detach(sr_uuid) 

682 

683 

684class ISOVDI(VDI.VDI): 

685 def load(self, vdi_uuid): 

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

687 # will fail. 

688 self.vdi_type = "iso" 

689 try: 

690 stat = os.stat(self.path) 

691 self.utilisation = int(stat.st_size) 

692 self.size = int(stat.st_size) 

693 self.label = self.filename 

694 except: 

695 pass 

696 

697 def __init__(self, mysr, filename): 

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

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

700 self.location = filename 

701 self.filename = filename 

702 self.read_only = True 

703 self.label = filename 

704 self.sm_config = {} 

705 if "legacy_mode" in mysr.dconf: 

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

707 self.label = tools_iso_name(filename) 

708 # Mark this as a Tools CD 

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

710 # Extract a version string, if present 

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

712 # "4.1.0" 

713 if len(vsn) == 1: 

714 build_number = "0" # string 

715 product_version = vsn[0] 

716 # "4.1.0-1234" 

717 elif len(vsn) > 1: 

718 build_number = vsn[1] 

719 product_version = vsn[0] 

720 else: 

721 build_number = 0 

722 product_version = "unknown" 

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

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

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

726 

727 def detach(self, sr_uuid, vdi_uuid): 

728 pass 

729 

730 def attach(self, sr_uuid, vdi_uuid): 

731 try: 

732 os.stat(self.path) 

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

734 except: 

735 raise xs_errors.XenError('VDIMissing') 

736 

737 def create(self, sr_uuid, vdi_uuid, size): 

738 self.uuid = vdi_uuid 

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

740 self.size = size 

741 self.utilisation = 0 

742 self.read_only = False 

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

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

745 

746 if util.pathexists(self.path): 

747 raise xs_errors.XenError('VDIExists') 

748 

749 try: 

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

751 handle.truncate(size) 

752 handle.close() 

753 self._db_introduce() 

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

755 except Exception as exn: 

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

757 raise xs_errors.XenError('VDICreate', \ 

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

759 

760 def delete(self, sr_uuid, vdi_uuid): 

761 util.SMlog("Deleting...") 

762 

763 self.uuid = vdi_uuid 

764 self._db_forget() 

765 

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

767 return 

768 

769 try: 

770 util.SMlog("Unlinking...") 

771 os.unlink(self.path) 

772 util.SMlog("Done...") 

773 except: 

774 raise xs_errors.XenError('VDIDelete') 

775 

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

777 # exceptions 

778 

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

780 SRCommand.run(ISOSR, DRIVER_INFO) 

781else: 

782 SR.registerSR(ISOSR)