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) 

41 

42from vditype import VdiType 

43 

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

45DEFAULT_TAP = "vhd,qcow2" 

46MASTER_LVM_CONF = '/etc/lvm/master' 

47 

48# LUN per VDI key for XenCenter 

49LUNPERVDI = "LUNperVDI" 

50 

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

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

53 

54 

55 

56 

57def deviceCheck(op): 

58 def wrapper(self, *args): 

59 if 'device' not in self.dconf: 

60 raise xs_errors.XenError('ConfigDeviceMissing') 

61 return op(self, *args) 

62 return wrapper 

63 

64 

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

66 

67 

68def registerSR(SRClass): 

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

70 the module file 

71 """ 

72 backends.append(SRClass) 

73 

74 

75def driver(type): 

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

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

78 if d.handles(type): 

79 return d 

80 raise xs_errors.XenError('SRUnknownType') 

81 

82 

83class SR(object): 

84 """Semi-abstract storage repository object. 

85 

86 Attributes: 

87 uuid: string, UUID 

88 label: string 

89 description: string 

90 vdis: dictionary, VDI objects indexed by UUID 

91 physical_utilisation: int, bytes consumed by VDIs 

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

93 physical_size: int, bytes consumed by this repository 

94 sr_vditype: string, repository type 

95 """ 

96 

97 @staticmethod 

98 def handles(type) -> bool: 

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

100 return False 

101 

102 def __init__(self, srcmd, sr_uuid): 

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

104 in their own 

105 initializers. 

106 

107 Arguments: 

108 srcmd: SRCommand instance, contains parsed arguments 

109 """ 

110 try: 

111 self.other_config = {} 

112 self.srcmd = srcmd 

113 self.dconf = srcmd.dconf 

114 if 'session_ref' in srcmd.params: 

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

116 self.session = XenAPI.xapi_local() 

117 self.session._session = self.session_ref 

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

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

120 else: 

121 self.session = None 

122 

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

124 self.host_ref = "" 

125 else: 

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

127 

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

129 

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

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

132 os.environ['LVM_SYSTEM_DIR'] = MASTER_LVM_CONF 

133 

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

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

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

137 os.environ['LVM_DEVICE'] = dev_path 

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

139 

140 except TypeError: 

141 raise Exception(traceback.format_exc()) 

142 except Exception as e: 

143 raise e 

144 raise xs_errors.XenError('SRBadXML') 

145 

146 self.uuid = sr_uuid 

147 

148 self.label = '' 

149 self.description = '' 

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

151 self.vdis = {} 

152 self.physical_utilisation = 0 

153 self.virtual_allocation = 0 

154 self.physical_size = 0 

155 self.sr_vditype = '' 

156 self.passthrough = False 

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

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

159 self.default_vdi_visibility = True 

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

161 self._mpathinit() 

162 self.direct = False 

163 self.ops_exclusive = [] 

164 self.driver_config = {} 

165 self._is_shared = None 

166 

167 self.load(sr_uuid) 

168 

169 @staticmethod 

170 def from_uuid(session, sr_uuid): 

171 import importlib.util 

172 

173 _SR = session.xenapi.SR 

174 sr_ref = _SR.get_by_uuid(sr_uuid) 

175 sm_type = _SR.get_type(sr_ref) 

176 # NB. load the SM driver module 

177 

178 _SM = session.xenapi.SM 

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

180 sm_ref, sm = sms.popitem() 

181 assert not sms 

182 

183 driver_path = _SM.get_driver_filename(sm_ref) 

184 driver_real = os.path.realpath(driver_path) 

185 module_name = os.path.basename(driver_path) 

186 

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

188 module = importlib.util.module_from_spec(spec) 

189 spec.loader.exec_module(module) 

190 

191 target = driver(sm_type) 

192 # NB. get the host pbd's device_config 

193 

194 host_ref = util.get_localhost_ref(session) 

195 

196 _PBD = session.xenapi.PBD 

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

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

199 pbd_ref, pbd = pbds.popitem() 

200 assert not pbds 

201 

202 device_config = _PBD.get_device_config(pbd_ref) 

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

204 # FIXME 

205 

206 from SRCommand import SRCommand 

207 cmd = SRCommand(module.DRIVER_INFO) 

208 cmd.dconf = device_config 

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

210 'host_ref': host_ref, 

211 'device_config': device_config, 

212 'sr_ref': sr_ref, 

213 'sr_uuid': sr_uuid, 

214 'command': 'nop'} 

215 

216 return target(cmd, sr_uuid) 

217 

218 def block_setscheduler(self, dev): 

219 try: 

220 realdev = os.path.realpath(dev) 

221 disk = util.diskFromPartition(realdev) 

222 

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

224 # potentially overridden by SR.other_config:scheduler 

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

226 sched = other_config.get('scheduler') 

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

228 scheds = self.scheds 

229 else: 

230 scheds = [sched] 

231 

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

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

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

235 

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

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

238 except Exception as e: 

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

240 

241 def _addLUNperVDIkey(self): 

242 try: 

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

244 except: 

245 pass 

246 

247 def is_shared(self): 

248 if not self._is_shared: 

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

250 return self._is_shared 

251 

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

253 """Create this repository. 

254 This operation may delete existing data. 

255 

256 The operation is NOT idempotent. The operation will fail 

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

258 

259 Returns: 

260 None 

261 Raises: 

262 SRUnimplementedMethod 

263 """ 

264 raise xs_errors.XenError('Unimplemented') 

265 

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

267 """Delete this repository and its contents. 

268 

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

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

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

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

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

274 

275 Returns: 

276 None 

277 Raises: 

278 SRUnimplementedMethod 

279 """ 

280 raise xs_errors.XenError('Unimplemented') 

281 

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

283 """Refresh the fields in the SR object 

284 

285 Returns: 

286 None 

287 Raises: 

288 SRUnimplementedMethod 

289 """ 

290 # no-op unless individual backends implement it 

291 return 

292 

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

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

295 device state required to access the substrate. 

296 

297 Idempotent. 

298 

299 Returns: 

300 None 

301 Raises: 

302 SRUnimplementedMethod 

303 """ 

304 raise xs_errors.XenError('Unimplemented') 

305 

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

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

308 Return: 

309 None 

310 """ 

311 try: 

312 self.scan(uuid) 

313 except Exception as e: 

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

315 msg_name = "POST_ATTACH_SCAN_FAILED" 

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

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

318 self.session.xenapi.message.create( 

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

320 

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

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

323 state initiated by the sr_attach() operation. 

324 

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

326 to succeed. 

327 

328 Returns: 

329 None 

330 Raises: 

331 SRUnimplementedMethod 

332 """ 

333 raise xs_errors.XenError('Unimplemented') 

334 

335 def probe(self) -> str: 

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

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

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

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

340 guide the user in improving the dconf. 

341 

342 Idempotent. 

343 

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

345 attach or detach operations on this host. 

346 

347 Returns: 

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

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

350 Raises: 

351 SRUnimplementedMethod 

352 """ 

353 raise xs_errors.XenError('Unimplemented') 

354 

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

356 """ 

357 Returns: 

358 """ 

359 # Update SR parameters 

360 self._db_update() 

361 # Synchronise VDI list 

362 scanrecord = ScanRecord(self) 

363 scanrecord.synchronise() 

364 

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

366 """Replay a multi-stage log entry 

367 

368 Returns: 

369 None 

370 Raises: 

371 SRUnimplementedMethod 

372 """ 

373 raise xs_errors.XenError('Unimplemented') 

374 

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

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

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

378 

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

380 """Post-init hook""" 

381 pass 

382 

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

384 """Hook to check SR health""" 

385 pass 

386 

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

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

389 raise xs_errors.XenError('Unimplemented') 

390 

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

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

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

394 

395 def cleanup(self) -> None: 

396 # callback after the op is done 

397 pass 

398 

399 def _db_update(self): 

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

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

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

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

404 

405 def _toxml(self): 

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

407 element = dom.createElement("sr") 

408 dom.appendChild(element) 

409 

410 # Add default uuid, physical_utilisation, physical_size and 

411 # virtual_allocation entries 

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

413 'physical_size'): 

414 try: 

415 aval = getattr(self, attr) 

416 except AttributeError: 

417 raise xs_errors.XenError( 

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

419 

420 entry = dom.createElement(attr) 

421 element.appendChild(entry) 

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

423 entry.appendChild(textnode) 

424 

425 # Add the default_vdi_visibility entry 

426 entry = dom.createElement('default_vdi_visibility') 

427 element.appendChild(entry) 

428 if not self.default_vdi_visibility: 

429 textnode = dom.createTextNode('False') 

430 else: 

431 textnode = dom.createTextNode('True') 

432 entry.appendChild(textnode) 

433 

434 # Add optional label and description entries 

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

436 try: 

437 aval = getattr(self, attr) 

438 except AttributeError: 

439 continue 

440 if aval: 

441 entry = dom.createElement(attr) 

442 element.appendChild(entry) 

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

444 entry.appendChild(textnode) 

445 

446 # Create VDI sub-list 

447 if self.vdis: 

448 for uuid in self.vdis: 

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

450 vdinode = dom.createElement("vdi") 

451 element.appendChild(vdinode) 

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

453 

454 return dom 

455 

456 def _fromxml(self, str, tag): 

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

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

459 taglist = {} 

460 for node in objectlist.childNodes: 

461 taglist[node.nodeName] = "" 

462 for n in node.childNodes: 

463 if n.nodeType == n.TEXT_NODE: 

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

465 return taglist 

466 

467 def _splitstring(self, str): 

468 elementlist = [] 

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

470 elementlist.append(str[i]) 

471 return elementlist 

472 

473 def _mpathinit(self): 

474 self.mpath = "false" 

475 try: 

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

477 'multipathhandle' in self.dconf: 

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

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

480 else: 

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

482 self.mpath = hconf['multipathing'] 

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

484 

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

486 self.mpath = "false" 

487 self.mpathhandle = "null" 

488 

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

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

491 except: 

492 self.mpath = "false" 

493 self.mpathhandle = "null" 

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

495 self.mpathmodule = __import__(module_name) 

496 

497 def _mpathHandle(self): 

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

499 self.mpathmodule.activate() 

500 else: 

501 self.mpathmodule.deactivate() 

502 

503 def _pathrefresh(self, obj): 

504 SCSIid = getattr(self, 'SCSIid') 

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

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

507 

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

509 try: 

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

511 sm_config['multipathable'] = 'true' 

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

513 

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

515 util.kickpipe_mpathcount() 

516 except: 

517 pass 

518 

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

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

521 

522 Input: 

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

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

525 keys missing 

526 

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

528 Raise: xs_errors.XenError('ConfigParamsMissing') 

529 """ 

530 

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

532 

533 if missing_keys and raise_flag: 

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

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

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

537 

538 return missing_keys 

539 

540 @staticmethod 

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

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

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

544 return None 

545 

546 image_format = STR_TO_IMAGE_FORMAT.get(str_image_format) 

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

548 return image_format 

549 

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

551 

552 def _init_image_formats( 

553 self, 

554 *, 

555 preferred_image_formats = DEFAULT_PREFERRED_IMAGE_FORMATS, 

556 supported_image_formats = DEFAULT_SUPPORTED_IMAGE_FORMATS 

557 ) -> None: 

558 self.preferred_image_formats = parseImageFormats( 

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

560 preferred_image_formats, 

561 supported_image_formats 

562 ) 

563 self.supported_image_formats = supported_image_formats 

564 

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

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

567 return getVdiTypeFromImageFormat(image_format) 

568 raise xs_errors.XenError('VDIType', opterr=f'Unsupported image format `{image_format}`') 

569 

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

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

572 return vdi_type 

573 if vdi_type == VdiType.RAW: 

574 for image_format in self.preferred_image_formats: 

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

576 return getVdiTypeFromImageFormat(image_format) 

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

578 

579class ScanRecord: 

580 def __init__(self, sr): 

581 self.sr = sr 

582 self.__xenapi_locations = {} 

583 self.__xenapi_records = util.list_VDI_records_in_sr(sr) 

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

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

586 self.__sm_records = {} 

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

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

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

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

591 # sm_config_overrides[key]=None) 

592 try: 

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

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

595 except: 

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

597 vdi.sm_config = {} 

598 

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

600 try: 

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

602 except: 

603 pass # No image format for this VDI type. 

604 

605 vdi._override_sm_config(vdi.sm_config) 

606 

607 self.__sm_records[vdi.location] = vdi 

608 

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

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

611 

612 # These ones are new on disk 

613 self.new = sm_locations.difference(xenapi_locations) 

614 # These have disappeared from the disk 

615 self.gone = xenapi_locations.difference(sm_locations) 

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

617 existing = sm_locations.intersection(xenapi_locations) 

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

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

620 # in the storage backend. 

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

622 sm_vdi = self.get_sm_vdi(location) 

623 xenapi_vdi = self.get_xenapi_vdi(location) 

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

625 

626 # Only consider those whose configuration looks different 

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

628 

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

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

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

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

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

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

635 

636 def get_sm_vdi(self, location): 

637 return self.__sm_records[location] 

638 

639 def get_xenapi_vdi(self, location): 

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

641 

642 def all_xenapi_locations(self): 

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

644 

645 def synchronise_new(self): 

646 """Add XenAPI records for new disks""" 

647 for location in self.new: 

648 vdi = self.get_sm_vdi(location) 

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

650 vdi._db_introduce() 

651 

652 def synchronise_gone(self): 

653 """Delete XenAPI record for old disks""" 

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

655 vdi = self.get_xenapi_vdi(location) 

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

657 try: 

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

659 except XenAPI.Failure as e: 

660 if util.isInvalidVDI(e): 

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

662 vdi['uuid']) 

663 else: 

664 raise 

665 

666 def synchronise_existing(self): 

667 """Update existing XenAPI records""" 

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

669 vdi = self.get_sm_vdi(location) 

670 

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

672 vdi._db_update() 

673 

674 def synchronise(self): 

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

676 for most plugins.""" 

677 self.synchronise_new() 

678 self.synchronise_gone() 

679 self.synchronise_existing()