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# SR: Base class for storage repositories 

19# 

20 

21from sm_typing import Dict, List, Optional 

22 

23import VDI 

24import xml.dom.minidom 

25import xs_errors 

26import XenAPI # pylint: disable=import-error 

27import xmlrpc.client 

28import util 

29import copy 

30import os 

31import traceback 

32 

33from cowutil import ( 

34 getCowUtilFromImageFormat, 

35 getImageStringFromVdiType, 

36 getVdiTypeFromImageFormat, 

37 ImageFormat, 

38 parseImageFormats, 

39 STR_TO_IMAGE_FORMAT, 

40 IMAGE_FORMAT_TO_STR, 

41) 

42 

43from vditype import VdiType 

44 

45MOUNT_BASE = '/var/run/sr-mount' 

46DEFAULT_TAP = "vhd,qcow2" 

47MASTER_LVM_CONF = '/etc/lvm/master' 

48 

49# LUN per VDI key for XenCenter 

50LUNPERVDI = "LUNperVDI" 

51 

52DEFAULT_PREFERRED_IMAGE_FORMATS = [ImageFormat.VHD, ImageFormat.QCOW2] 

53DEFAULT_SUPPORTED_IMAGE_FORMATS = [ImageFormat.RAW, ImageFormat.VHD, ImageFormat.QCOW2] 

54 

55 

56 

57 

58def deviceCheck(op): 

59 def wrapper(self, *args): 

60 if 'device' not in self.dconf: 

61 raise xs_errors.XenError('ConfigDeviceMissing') 

62 return op(self, *args) 

63 return wrapper 

64 

65 

66backends: List["SR"] = [] 

67 

68 

69def registerSR(SRClass): 

70 """Register SR with handler. All SR subclasses should call this in 

71 the module file 

72 """ 

73 backends.append(SRClass) 

74 

75 

76def driver(type): 

77 """Find the SR for the given dconf string""" 

78 for d in backends: 78 ↛ 81line 78 didn't jump to line 81, because the loop on line 78 didn't complete

79 if d.handles(type): 

80 return d 

81 raise xs_errors.XenError('SRUnknownType') 

82 

83 

84class SR(object): 

85 """Semi-abstract storage repository object. 

86 

87 Attributes: 

88 uuid: string, UUID 

89 label: string 

90 description: string 

91 vdis: dictionary, VDI objects indexed by UUID 

92 physical_utilisation: int, bytes consumed by VDIs 

93 virtual_allocation: int, bytes allocated to this repository (virtual) 

94 physical_size: int, bytes consumed by this repository 

95 sr_vditype: string, repository type 

96 """ 

97 

98 @staticmethod 

99 def handles(type) -> bool: 

100 """Returns True if this SR class understands the given dconf string""" 

101 return False 

102 

103 def __init__(self, srcmd, sr_uuid): 

104 """Base class initializer. All subclasses should call SR.__init__ 

105 in their own 

106 initializers. 

107 

108 Arguments: 

109 srcmd: SRCommand instance, contains parsed arguments 

110 """ 

111 try: 

112 self.other_config = {} 

113 self.srcmd = srcmd 

114 self.dconf = srcmd.dconf 

115 if 'session_ref' in srcmd.params: 

116 self.session_ref = srcmd.params['session_ref'] 

117 self.session = XenAPI.xapi_local() 

118 self.session._session = self.session_ref 

119 if 'subtask_of' in self.srcmd.params: 119 ↛ 120line 119 didn't jump to line 120, because the condition on line 119 was never true

120 self.session.transport.add_extra_header('Subtask-of', self.srcmd.params['subtask_of']) 

121 else: 

122 self.session = None 

123 

124 if 'host_ref' not in self.srcmd.params: 

125 self.host_ref = "" 

126 else: 

127 self.host_ref = self.srcmd.params['host_ref'] 

128 

129 self.sr_ref = self.srcmd.params.get('sr_ref') 

130 

131 if 'device_config' in self.srcmd.params: 

132 if self.dconf.get("SRmaster") == "true": 

133 os.environ['LVM_SYSTEM_DIR'] = MASTER_LVM_CONF 

134 

135 if 'device_config' in self.srcmd.params: 

136 if 'SCSIid' in self.srcmd.params['device_config']: 

137 dev_path = '/dev/disk/by-scsid/' + self.srcmd.params['device_config']['SCSIid'] 

138 os.environ['LVM_DEVICE'] = dev_path 

139 util.SMlog('Setting LVM_DEVICE to %s' % dev_path) 

140 

141 except TypeError: 

142 raise Exception(traceback.format_exc()) 

143 except Exception as e: 

144 raise e 

145 raise xs_errors.XenError('SRBadXML') 

146 

147 self.uuid = sr_uuid 

148 

149 self.label = '' 

150 self.description = '' 

151 self.cmd = srcmd.params['command'] 

152 self.vdis = {} 

153 self.physical_utilisation = 0 

154 self.virtual_allocation = 0 

155 self.physical_size = 0 

156 self.sr_vditype = '' 

157 self.passthrough = False 

158 # XXX: if this is really needed then we must make a deep copy 

159 self.original_srcmd = copy.deepcopy(self.srcmd) 

160 self.default_vdi_visibility = True 

161 self.scheds = ['none', 'noop'] 

162 self._mpathinit() 

163 self.direct = False 

164 self.ops_exclusive = [] 

165 self.driver_config = {} 

166 self._is_shared = None 

167 

168 self.load(sr_uuid) 

169 

170 @staticmethod 

171 def from_uuid(session, sr_uuid): 

172 import importlib.util 

173 

174 _SR = session.xenapi.SR 

175 sr_ref = _SR.get_by_uuid(sr_uuid) 

176 sm_type = _SR.get_type(sr_ref) 

177 # NB. load the SM driver module 

178 

179 _SM = session.xenapi.SM 

180 sms = _SM.get_all_records_where('field "type" = "%s"' % sm_type) 

181 sm_ref, sm = sms.popitem() 

182 assert not sms 

183 

184 driver_path = _SM.get_driver_filename(sm_ref) 

185 driver_real = os.path.realpath(driver_path) 

186 module_name = os.path.basename(driver_path) 

187 

188 spec = importlib.util.spec_from_file_location(module_name, driver_real) 

189 module = importlib.util.module_from_spec(spec) 

190 spec.loader.exec_module(module) 

191 

192 target = driver(sm_type) 

193 # NB. get the host pbd's device_config 

194 

195 host_ref = util.get_localhost_ref(session) 

196 

197 _PBD = session.xenapi.PBD 

198 pbds = _PBD.get_all_records_where('field "SR" = "%s" and' % sr_ref + 

199 'field "host" = "%s"' % host_ref) 

200 pbd_ref, pbd = pbds.popitem() 

201 assert not pbds 

202 

203 device_config = _PBD.get_device_config(pbd_ref) 

204 # NB. make srcmd, to please our supersized SR constructor. 

205 # FIXME 

206 

207 from SRCommand import SRCommand 

208 cmd = SRCommand(module.DRIVER_INFO) 

209 cmd.dconf = device_config 

210 cmd.params = {'session_ref': session._session, 

211 'host_ref': host_ref, 

212 'device_config': device_config, 

213 'sr_ref': sr_ref, 

214 'sr_uuid': sr_uuid, 

215 'command': 'nop'} 

216 

217 return target(cmd, sr_uuid) 

218 

219 def block_setscheduler(self, dev): 

220 try: 

221 realdev = os.path.realpath(dev) 

222 disk = util.diskFromPartition(realdev) 

223 

224 # the normal case: the sr default scheduler (typically none/noop), 

225 # potentially overridden by SR.other_config:scheduler 

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

227 sched = other_config.get('scheduler') 

228 if not sched or sched in self.scheds: 228 ↛ 229line 228 didn't jump to line 229, because the condition on line 228 was never true

229 scheds = self.scheds 

230 else: 

231 scheds = [sched] 

232 

233 # special case: BFQ/CFQ if the underlying disk holds dom0's file systems. 

234 if disk in util.dom0_disks(): 234 ↛ 235,   234 ↛ 2372 missed branches: 1) line 234 didn't jump to line 235, because the condition on line 234 was never true, 2) line 234 didn't jump to line 237, because the condition on line 234 was never false

235 scheds = ['bfq', 'cfq'] 

236 

237 util.SMlog("Block scheduler: %s (%s) wants %s" % (dev, disk, scheds)) 

238 util.set_scheduler(realdev[5:], scheds) 

239 except Exception as e: 

240 util.SMlog("Failed to set block scheduler on %s: %s" % (dev, e)) 

241 

242 def _addLUNperVDIkey(self): 

243 try: 

244 self.session.xenapi.SR.add_to_sm_config(self.sr_ref, LUNPERVDI, "true") 

245 except: 

246 pass 

247 

248 def is_shared(self): 

249 if not self._is_shared: 

250 self._is_shared = self.session.xenapi.SR.get_shared(self.sr_ref) 

251 return self._is_shared 

252 

253 def create(self, uuid, size) -> None: 

254 """Create this repository. 

255 This operation may delete existing data. 

256 

257 The operation is NOT idempotent. The operation will fail 

258 if an SR of the same UUID and driver type already exits. 

259 

260 Returns: 

261 None 

262 Raises: 

263 SRUnimplementedMethod 

264 """ 

265 raise xs_errors.XenError('Unimplemented') 

266 

267 def delete(self, uuid) -> None: 

268 """Delete this repository and its contents. 

269 

270 This operation IS idempotent -- it will succeed if the repository 

271 exists and can be deleted or if the repository does not exist. 

272 The caller must ensure that all VDIs are deactivated and detached 

273 and that the SR itself has been detached before delete(). 

274 The call will FAIL if any VDIs in the SR are in use. 

275 

276 Returns: 

277 None 

278 Raises: 

279 SRUnimplementedMethod 

280 """ 

281 raise xs_errors.XenError('Unimplemented') 

282 

283 def update(self, uuid) -> None: 

284 """Refresh the fields in the SR object 

285 

286 Returns: 

287 None 

288 Raises: 

289 SRUnimplementedMethod 

290 """ 

291 # no-op unless individual backends implement it 

292 return 

293 

294 def attach(self, uuid) -> None: 

295 """Initiate local access to the SR. Initialises any 

296 device state required to access the substrate. 

297 

298 Idempotent. 

299 

300 Returns: 

301 None 

302 Raises: 

303 SRUnimplementedMethod 

304 """ 

305 raise xs_errors.XenError('Unimplemented') 

306 

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

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

309 Return: 

310 None 

311 """ 

312 try: 

313 self.scan(uuid) 

314 except Exception as e: 

315 util.SMlog("Error in SR.after_master_attach %s" % e) 

316 msg_name = "POST_ATTACH_SCAN_FAILED" 

317 msg_body = "Failed to scan SR %s after attaching, " \ 

318 "error %s" % (uuid, e) 

319 self.session.xenapi.message.create( 

320 msg_name, 2, "SR", uuid, msg_body) 

321 

322 def detach(self, uuid) -> None: 

323 """Remove local access to the SR. Destroys any device 

324 state initiated by the sr_attach() operation. 

325 

326 Idempotent. All VDIs must be detached in order for the operation 

327 to succeed. 

328 

329 Returns: 

330 None 

331 Raises: 

332 SRUnimplementedMethod 

333 """ 

334 raise xs_errors.XenError('Unimplemented') 

335 

336 def probe(self) -> str: 

337 """Perform a backend-specific scan, using the current dconf. If the 

338 dconf is complete, then this will return a list of the SRs present of 

339 this type on the device, if any. If the dconf is partial, then a 

340 backend-specific scan will be performed, returning results that will 

341 guide the user in improving the dconf. 

342 

343 Idempotent. 

344 

345 xapi will ensure that this is serialised wrt any other probes, or 

346 attach or detach operations on this host. 

347 

348 Returns: 

349 An XML fragment containing the scan results. These are specific 

350 to the scan being performed, and the current backend. 

351 Raises: 

352 SRUnimplementedMethod 

353 """ 

354 raise xs_errors.XenError('Unimplemented') 

355 

356 def scan(self, uuid) -> None: 

357 """ 

358 Returns: 

359 """ 

360 # Update SR parameters 

361 self._db_update() 

362 # Synchronise VDI list 

363 scanrecord = ScanRecord(self) 

364 scanrecord.synchronise() 

365 

366 def replay(self, uuid) -> None: 

367 """Replay a multi-stage log entry 

368 

369 Returns: 

370 None 

371 Raises: 

372 SRUnimplementedMethod 

373 """ 

374 raise xs_errors.XenError('Unimplemented') 

375 

376 def content_type(self, uuid) -> str: 

377 """Returns the 'content_type' of an SR as a string""" 

378 return xmlrpc.client.dumps((str(self.sr_vditype), ), "", True) 

379 

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

381 """Post-init hook""" 

382 pass 

383 

384 def check_sr(self, sr_uuid) -> None: 

385 """Hook to check SR health""" 

386 pass 

387 

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

389 """Return VDI object owned by this repository""" 

390 raise xs_errors.XenError('Unimplemented') 

391 

392 def forget_vdi(self, uuid) -> None: 

393 vdi = self.session.xenapi.VDI.get_by_uuid(uuid) 

394 self.session.xenapi.VDI.db_forget(vdi) 

395 

396 def cleanup(self) -> None: 

397 # callback after the op is done 

398 pass 

399 

400 def _db_update(self): 

401 sr = self.session.xenapi.SR.get_by_uuid(self.uuid) 

402 self.session.xenapi.SR.set_virtual_allocation(sr, str(self.virtual_allocation)) 

403 self.session.xenapi.SR.set_physical_size(sr, str(self.physical_size)) 

404 self.session.xenapi.SR.set_physical_utilisation(sr, str(self.physical_utilisation)) 

405 

406 def _toxml(self): 

407 dom = xml.dom.minidom.Document() 

408 element = dom.createElement("sr") 

409 dom.appendChild(element) 

410 

411 # Add default uuid, physical_utilisation, physical_size and 

412 # virtual_allocation entries 

413 for attr in ('uuid', 'physical_utilisation', 'virtual_allocation', 

414 'physical_size'): 

415 try: 

416 aval = getattr(self, attr) 

417 except AttributeError: 

418 raise xs_errors.XenError( 

419 'InvalidArg', opterr='Missing required field [%s]' % attr) 

420 

421 entry = dom.createElement(attr) 

422 element.appendChild(entry) 

423 textnode = dom.createTextNode(str(aval)) 

424 entry.appendChild(textnode) 

425 

426 # Add the default_vdi_visibility entry 

427 entry = dom.createElement('default_vdi_visibility') 

428 element.appendChild(entry) 

429 if not self.default_vdi_visibility: 

430 textnode = dom.createTextNode('False') 

431 else: 

432 textnode = dom.createTextNode('True') 

433 entry.appendChild(textnode) 

434 

435 # Add optional label and description entries 

436 for attr in ('label', 'description'): 

437 try: 

438 aval = getattr(self, attr) 

439 except AttributeError: 

440 continue 

441 if aval: 

442 entry = dom.createElement(attr) 

443 element.appendChild(entry) 

444 textnode = dom.createTextNode(str(aval)) 

445 entry.appendChild(textnode) 

446 

447 # Create VDI sub-list 

448 if self.vdis: 

449 for uuid in self.vdis: 

450 if not self.vdis[uuid].deleted: 

451 vdinode = dom.createElement("vdi") 

452 element.appendChild(vdinode) 

453 self.vdis[uuid]._toxml(dom, vdinode) 

454 

455 return dom 

456 

457 def _fromxml(self, str, tag): 

458 dom = xml.dom.minidom.parseString(str) 

459 objectlist = dom.getElementsByTagName(tag)[0] 

460 taglist = {} 

461 for node in objectlist.childNodes: 

462 taglist[node.nodeName] = "" 

463 for n in node.childNodes: 

464 if n.nodeType == n.TEXT_NODE: 

465 taglist[node.nodeName] += n.data 

466 return taglist 

467 

468 def _splitstring(self, str): 

469 elementlist = [] 

470 for i in range(0, len(str)): 

471 elementlist.append(str[i]) 

472 return elementlist 

473 

474 def _mpathinit(self): 

475 self.mpath = "false" 

476 try: 

477 if 'multipathing' in self.dconf and \ 477 ↛ 479line 477 didn't jump to line 479, because the condition on line 477 was never true

478 'multipathhandle' in self.dconf: 

479 self.mpath = self.dconf['multipathing'] 

480 self.mpathhandle = self.dconf['multipathhandle'] 

481 else: 

482 hconf = self.session.xenapi.host.get_other_config(self.host_ref) 

483 self.mpath = hconf['multipathing'] 

484 self.mpathhandle = hconf.get('multipathhandle', 'dmp') 

485 

486 if self.mpath != "true": 486 ↛ 490line 486 didn't jump to line 490, because the condition on line 486 was never false

487 self.mpath = "false" 

488 self.mpathhandle = "null" 

489 

490 if not os.path.exists("/opt/xensource/sm/mpath_%s.py" % self.mpathhandle): 490 ↛ 495line 490 didn't jump to line 495, because the condition on line 490 was never false

491 raise IOError("File does not exist = %s" % self.mpathhandle) 

492 except: 

493 self.mpath = "false" 

494 self.mpathhandle = "null" 

495 module_name = "mpath_%s" % self.mpathhandle 

496 self.mpathmodule = __import__(module_name) 

497 

498 def _mpathHandle(self): 

499 if self.mpath == "true": 499 ↛ 500line 499 didn't jump to line 500, because the condition on line 499 was never true

500 self.mpathmodule.activate() 

501 else: 

502 self.mpathmodule.deactivate() 

503 

504 def _pathrefresh(self, obj): 

505 SCSIid = getattr(self, 'SCSIid') 

506 self.dconf['device'] = self.mpathmodule.path(SCSIid) 

507 super(obj, self).load(self.uuid) 

508 

509 def _setMultipathableFlag(self, SCSIid=''): 

510 try: 

511 sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref) 

512 sm_config['multipathable'] = 'true' 

513 self.session.xenapi.SR.set_sm_config(self.sr_ref, sm_config) 

514 

515 if self.mpath == "true" and len(SCSIid): 515 ↛ 516line 515 didn't jump to line 516, because the condition on line 515 was never true

516 util.kickpipe_mpathcount() 

517 except: 

518 pass 

519 

520 def check_dconf(self, key_list, raise_flag=True): 

521 """ Checks if all keys in 'key_list' exist in 'self.dconf'. 

522 

523 Input: 

524 key_list: a list of keys to check if they exist in self.dconf 

525 raise_flag: if true, raise an exception if there are 1 or more 

526 keys missing 

527 

528 Return: set() containing the missing keys (empty set() if all exist) 

529 Raise: xs_errors.XenError('ConfigParamsMissing') 

530 """ 

531 

532 missing_keys = {key for key in key_list if key not in self.dconf} 

533 

534 if missing_keys and raise_flag: 

535 errstr = 'device-config is missing the following parameters: ' + \ 

536 ', '.join([key for key in missing_keys]) 

537 raise xs_errors.XenError('ConfigParamsMissing', opterr=errstr) 

538 

539 return missing_keys 

540 

541 @staticmethod 

542 def read_config_image_format(config: Dict[str, str]) -> Optional[ImageFormat]: 

543 str_image_format = config.get("image-format") or config.get("type") 

544 if not str_image_format: 544 ↛ 545line 544 didn't jump to line 545, because the condition on line 544 was never true

545 return None 

546 

547 image_format = STR_TO_IMAGE_FORMAT.get(str_image_format) 

548 if image_format: 548 ↛ 551line 548 didn't jump to line 551, because the condition on line 548 was never false

549 return image_format 

550 

551 raise xs_errors.XenError('VDIType', opterr=f'Unknown image format `{str_image_format}`') 

552 

553 def _init_image_formats( 

554 self, 

555 *, 

556 preferred_image_formats = DEFAULT_PREFERRED_IMAGE_FORMATS, 

557 supported_image_formats = DEFAULT_SUPPORTED_IMAGE_FORMATS 

558 ) -> None: 

559 self.preferred_image_formats = parseImageFormats( 

560 self.dconf and self.dconf.get('preferred-image-formats'), 

561 preferred_image_formats, 

562 supported_image_formats 

563 ) 

564 self.supported_image_formats = supported_image_formats 

565 

566 def _resolve_vdi_type_from_image_format(self, image_format: ImageFormat) -> str: 

567 if image_format in self.supported_image_formats: 567 ↛ 569line 567 didn't jump to line 569, because the condition on line 567 was never false

568 return getVdiTypeFromImageFormat(image_format) 

569 raise xs_errors.XenError('VDIType', opterr=f'Unsupported image format `{IMAGE_FORMAT_TO_STR[image_format]}`') 

570 

571 def _get_snap_vdi_type(self, vdi_type: str, size: int) -> str: 

572 if VdiType.isCowImage(vdi_type): 572 ↛ 574line 572 didn't jump to line 574, because the condition on line 572 was never false

573 return vdi_type 

574 if vdi_type == VdiType.RAW: 

575 for image_format in self.preferred_image_formats: 

576 if getCowUtilFromImageFormat(image_format).canSnapshotRaw(size): 

577 return getVdiTypeFromImageFormat(image_format) 

578 raise xs_errors.XenError('VDISnapshot', opterr=f"cannot snap from `{vdi_type}`") 

579 

580class ScanRecord: 

581 def __init__(self, sr): 

582 self.sr = sr 

583 self.__xenapi_locations = {} 

584 self.__xenapi_records = util.list_VDI_records_in_sr(sr) 

585 for vdi in list(self.__xenapi_records.keys()): 585 ↛ 586line 585 didn't jump to line 586, because the loop on line 585 never started

586 self.__xenapi_locations[util.to_plain_string(self.__xenapi_records[vdi]['location'])] = vdi 

587 self.__sm_records = {} 

588 for vdi in list(sr.vdis.values()): 

589 # We initialise the sm_config field with the values from the database 

590 # The sm_config_overrides contains any new fields we want to add to 

591 # sm_config, and also any field to delete (by virtue of having 

592 # sm_config_overrides[key]=None) 

593 try: 

594 if not hasattr(vdi, "sm_config"): 594 ↛ 600line 594 didn't jump to line 600, because the condition on line 594 was never false

595 vdi.sm_config = self.__xenapi_records[self.__xenapi_locations[vdi.location]]['sm_config'].copy() 

596 except: 

597 util.SMlog("missing config for vdi: %s" % vdi.location) 

598 vdi.sm_config = {} 

599 

600 if "image-format" not in vdi.sm_config: 600 ↛ 606line 600 didn't jump to line 606, because the condition on line 600 was never false

601 try: 

602 vdi.sm_config["image-format"] = getImageStringFromVdiType(vdi.vdi_type) 

603 except: 

604 pass # No image format for this VDI type. 

605 

606 vdi._override_sm_config(vdi.sm_config) 

607 

608 self.__sm_records[vdi.location] = vdi 

609 

610 xenapi_locations = set(self.__xenapi_locations.keys()) 

611 sm_locations = set(self.__sm_records.keys()) 

612 

613 # These ones are new on disk 

614 self.new = sm_locations.difference(xenapi_locations) 

615 # These have disappeared from the disk 

616 self.gone = xenapi_locations.difference(sm_locations) 

617 # These are the ones which are still present but might have changed... 

618 existing = sm_locations.intersection(xenapi_locations) 

619 # Synchronise the uuid fields using the location as the primary key 

620 # This ensures we know what the UUIDs are even though they aren't stored 

621 # in the storage backend. 

622 for location in existing: 622 ↛ 623line 622 didn't jump to line 623, because the loop on line 622 never started

623 sm_vdi = self.get_sm_vdi(location) 

624 xenapi_vdi = self.get_xenapi_vdi(location) 

625 sm_vdi.uuid = util.default(sm_vdi, "uuid", lambda: xenapi_vdi['uuid']) 

626 

627 # Only consider those whose configuration looks different 

628 self.existing = [x for x in existing if not(self.get_sm_vdi(x).in_sync_with_xenapi_record(self.get_xenapi_vdi(x)))] 

629 

630 if len(self.new) != 0: 

631 util.SMlog("new VDIs on disk: " + repr(self.new)) 

632 if len(self.gone) != 0: 632 ↛ 633line 632 didn't jump to line 633, because the condition on line 632 was never true

633 util.SMlog("VDIs missing from disk: " + repr(self.gone)) 

634 if len(self.existing) != 0: 634 ↛ 635line 634 didn't jump to line 635, because the condition on line 634 was never true

635 util.SMlog("VDIs changed on disk: " + repr(self.existing)) 

636 

637 def get_sm_vdi(self, location): 

638 return self.__sm_records[location] 

639 

640 def get_xenapi_vdi(self, location): 

641 return self.__xenapi_records[self.__xenapi_locations[location]] 

642 

643 def all_xenapi_locations(self): 

644 return set(self.__xenapi_locations.keys()) 

645 

646 def synchronise_new(self): 

647 """Add XenAPI records for new disks""" 

648 for location in self.new: 

649 vdi = self.get_sm_vdi(location) 

650 util.SMlog("Introducing VDI with location=%s" % (vdi.location)) 

651 vdi._db_introduce() 

652 

653 def synchronise_gone(self): 

654 """Delete XenAPI record for old disks""" 

655 for location in self.gone: 655 ↛ 656line 655 didn't jump to line 656, because the loop on line 655 never started

656 vdi = self.get_xenapi_vdi(location) 

657 util.SMlog("Forgetting VDI with location=%s uuid=%s" % (util.to_plain_string(vdi['location']), vdi['uuid'])) 

658 try: 

659 self.sr.forget_vdi(vdi['uuid']) 

660 except XenAPI.Failure as e: 

661 if util.isInvalidVDI(e): 

662 util.SMlog("VDI %s not found, ignoring exception" % 

663 vdi['uuid']) 

664 else: 

665 raise 

666 

667 def synchronise_existing(self): 

668 """Update existing XenAPI records""" 

669 for location in self.existing: 669 ↛ 670line 669 didn't jump to line 670, because the loop on line 669 never started

670 vdi = self.get_sm_vdi(location) 

671 

672 util.SMlog("Updating VDI with location=%s uuid=%s" % (vdi.location, vdi.uuid)) 

673 vdi._db_update() 

674 

675 def synchronise(self): 

676 """Perform the default SM -> xenapi synchronisation; ought to be good enough 

677 for most plugins.""" 

678 self.synchronise_new() 

679 self.synchronise_gone() 

680 self.synchronise_existing()