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 

21import VDI 

22import xml.dom.minidom 

23import xs_errors 

24import XenAPI # pylint: disable=import-error 

25import xmlrpc.client 

26import util 

27import copy 

28import os 

29import traceback 

30 

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

32DEFAULT_TAP = 'vhd' 

33TAPDISK_UTIL = '/usr/sbin/td-util' 

34MASTER_LVM_CONF = '/etc/lvm/master' 

35 

36# LUN per VDI key for XenCenter 

37LUNPERVDI = "LUNperVDI" 

38 

39 

40 

41 

42 

43def deviceCheck(op): 

44 def wrapper(self, *args): 

45 if 'device' not in self.dconf: 

46 raise xs_errors.XenError('ConfigDeviceMissing') 

47 return op(self, *args) 

48 return wrapper 

49 

50 

51backends = [] 

52 

53 

54def registerSR(SRClass): 

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

56 the module file 

57 """ 

58 backends.append(SRClass) 

59 

60 

61def driver(type): 

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

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

64 if d.handles(type): 

65 return d 

66 raise xs_errors.XenError('SRUnknownType') 

67 

68 

69class SR(object): 

70 """Semi-abstract storage repository object. 

71 

72 Attributes: 

73 uuid: string, UUID 

74 label: string 

75 description: string 

76 vdis: dictionary, VDI objects indexed by UUID 

77 physical_utilisation: int, bytes consumed by VDIs 

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

79 physical_size: int, bytes consumed by this repository 

80 sr_vditype: string, repository type 

81 """ 

82 

83 def handles(type): 

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

85 return False 

86 handles = staticmethod(handles) 

87 

88 def __init__(self, srcmd, sr_uuid): 

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

90 in their own 

91 initializers. 

92 

93 Arguments: 

94 srcmd: SRCommand instance, contains parsed arguments 

95 """ 

96 try: 

97 self.other_config = {} 

98 self.srcmd = srcmd 

99 self.dconf = srcmd.dconf 

100 if 'session_ref' in srcmd.params: 

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

102 self.session = XenAPI.xapi_local() 

103 self.session._session = self.session_ref 

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

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

106 else: 

107 self.session = None 

108 

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

110 self.host_ref = "" 

111 else: 

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

113 

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

115 

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

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

118 os.environ['LVM_SYSTEM_DIR'] = MASTER_LVM_CONF 

119 

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

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

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

123 os.environ['LVM_DEVICE'] = dev_path 

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

125 

126 except TypeError: 

127 raise Exception(traceback.format_exc()) 

128 except Exception as e: 

129 raise e 

130 raise xs_errors.XenError('SRBadXML') 

131 

132 self.uuid = sr_uuid 

133 

134 self.label = '' 

135 self.description = '' 

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

137 self.vdis = {} 

138 self.physical_utilisation = 0 

139 self.virtual_allocation = 0 

140 self.physical_size = 0 

141 self.sr_vditype = '' 

142 self.passthrough = False 

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

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

145 self.default_vdi_visibility = True 

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

147 self._mpathinit() 

148 self.direct = False 

149 self.ops_exclusive = [] 

150 self.driver_config = {} 

151 

152 self.load(sr_uuid) 

153 

154 @staticmethod 

155 def from_uuid(session, sr_uuid): 

156 import imp 

157 

158 _SR = session.xenapi.SR 

159 sr_ref = _SR.get_by_uuid(sr_uuid) 

160 sm_type = _SR.get_type(sr_ref) 

161 # NB. load the SM driver module 

162 

163 _SM = session.xenapi.SM 

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

165 sm_ref, sm = sms.popitem() 

166 assert not sms 

167 

168 driver_path = _SM.get_driver_filename(sm_ref) 

169 driver_real = os.path.realpath(driver_path) 

170 module_name = os.path.basename(driver_path) 

171 

172 module = imp.load_source(module_name, driver_real) 

173 target = driver(sm_type) 

174 # NB. get the host pbd's device_config 

175 

176 host_ref = util.get_localhost_ref(session) 

177 

178 _PBD = session.xenapi.PBD 

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

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

181 pbd_ref, pbd = pbds.popitem() 

182 assert not pbds 

183 

184 device_config = _PBD.get_device_config(pbd_ref) 

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

186 # FIXME 

187 

188 from SRCommand import SRCommand 

189 cmd = SRCommand(module.DRIVER_INFO) 

190 cmd.dconf = device_config 

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

192 'host_ref': host_ref, 

193 'device_config': device_config, 

194 'sr_ref': sr_ref, 

195 'sr_uuid': sr_uuid, 

196 'command': 'nop'} 

197 

198 return target(cmd, sr_uuid) 

199 

200 def block_setscheduler(self, dev): 

201 try: 

202 realdev = os.path.realpath(dev) 

203 disk = util.diskFromPartition(realdev) 

204 

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

206 # potentially overridden by SR.other_config:scheduler 

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

208 sched = other_config.get('scheduler') 

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

210 scheds = self.scheds 

211 else: 

212 scheds = [sched] 

213 

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

215 if disk in util.dom0_disks(): 215 ↛ 216,   215 ↛ 2182 missed branches: 1) line 215 didn't jump to line 216, because the condition on line 215 was never true, 2) line 215 didn't jump to line 218, because the condition on line 215 was never false

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

217 

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

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

220 except Exception as e: 

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

222 

223 def _addLUNperVDIkey(self): 

224 try: 

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

226 except: 

227 pass 

228 

229 def create(self, uuid, size): 

230 """Create this repository. 

231 This operation may delete existing data. 

232 

233 The operation is NOT idempotent. The operation will fail 

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

235 

236 Returns: 

237 None 

238 Raises: 

239 SRUnimplementedMethod 

240 """ 

241 raise xs_errors.XenError('Unimplemented') 

242 

243 def delete(self, uuid): 

244 """Delete this repository and its contents. 

245 

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

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

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

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

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

251 

252 Returns: 

253 None 

254 Raises: 

255 SRUnimplementedMethod 

256 """ 

257 raise xs_errors.XenError('Unimplemented') 

258 

259 def update(self, uuid): 

260 """Refresh the fields in the SR object 

261 

262 Returns: 

263 None 

264 Raises: 

265 SRUnimplementedMethod 

266 """ 

267 # no-op unless individual backends implement it 

268 return 

269 

270 def attach(self, uuid): 

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

272 device state required to access the substrate. 

273 

274 Idempotent. 

275 

276 Returns: 

277 None 

278 Raises: 

279 SRUnimplementedMethod 

280 """ 

281 raise xs_errors.XenError('Unimplemented') 

282 

283 def after_master_attach(self, uuid): 

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

285 Return: 

286 None 

287 """ 

288 try: 

289 self.scan(uuid) 

290 except Exception as e: 

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

292 msg_name = "POST_ATTACH_SCAN_FAILED" 

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

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

295 self.session.xenapi.message.create( 

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

297 

298 def detach(self, uuid): 

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

300 state initiated by the sr_attach() operation. 

301 

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

303 to succeed. 

304 

305 Returns: 

306 None 

307 Raises: 

308 SRUnimplementedMethod 

309 """ 

310 raise xs_errors.XenError('Unimplemented') 

311 

312 def probe(self): 

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

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

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

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

317 guide the user in improving the dconf. 

318 

319 Idempotent. 

320 

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

322 attach or detach operations on this host. 

323 

324 Returns: 

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

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

327 Raises: 

328 SRUnimplementedMethod 

329 """ 

330 raise xs_errors.XenError('Unimplemented') 

331 

332 def scan(self, uuid): 

333 """ 

334 Returns: 

335 """ 

336 # Update SR parameters 

337 self._db_update() 

338 # Synchronise VDI list 

339 scanrecord = ScanRecord(self) 

340 scanrecord.synchronise() 

341 

342 def replay(self, uuid): 

343 """Replay a multi-stage log entry 

344 

345 Returns: 

346 None 

347 Raises: 

348 SRUnimplementedMethod 

349 """ 

350 raise xs_errors.XenError('Unimplemented') 

351 

352 def content_type(self, uuid): 

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

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

355 

356 def load(self, sr_uuid): 

357 """Post-init hook""" 

358 pass 

359 

360 def vdi(self, uuid): 

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

362 if uuid not in self.vdis: 

363 self.vdis[uuid] = VDI.VDI(self, uuid) 

364 raise xs_errors.XenError('Unimplemented') 

365 return self.vdis[uuid] 

366 

367 def forget_vdi(self, uuid): 

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

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

370 

371 def cleanup(self): 

372 # callback after the op is done 

373 pass 

374 

375 def _db_update(self): 

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

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

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

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

380 

381 def _toxml(self): 

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

383 element = dom.createElement("sr") 

384 dom.appendChild(element) 

385 

386 # Add default uuid, physical_utilisation, physical_size and 

387 # virtual_allocation entries 

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

389 'physical_size'): 

390 try: 

391 aval = getattr(self, attr) 

392 except AttributeError: 

393 raise xs_errors.XenError( 

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

395 

396 entry = dom.createElement(attr) 

397 element.appendChild(entry) 

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

399 entry.appendChild(textnode) 

400 

401 # Add the default_vdi_visibility entry 

402 entry = dom.createElement('default_vdi_visibility') 

403 element.appendChild(entry) 

404 if not self.default_vdi_visibility: 

405 textnode = dom.createTextNode('False') 

406 else: 

407 textnode = dom.createTextNode('True') 

408 entry.appendChild(textnode) 

409 

410 # Add optional label and description entries 

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

412 try: 

413 aval = getattr(self, attr) 

414 except AttributeError: 

415 continue 

416 if aval: 

417 entry = dom.createElement(attr) 

418 element.appendChild(entry) 

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

420 entry.appendChild(textnode) 

421 

422 # Create VDI sub-list 

423 if self.vdis: 

424 for uuid in self.vdis: 

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

426 vdinode = dom.createElement("vdi") 

427 element.appendChild(vdinode) 

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

429 

430 return dom 

431 

432 def _fromxml(self, str, tag): 

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

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

435 taglist = {} 

436 for node in objectlist.childNodes: 

437 taglist[node.nodeName] = "" 

438 for n in node.childNodes: 

439 if n.nodeType == n.TEXT_NODE: 

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

441 return taglist 

442 

443 def _splitstring(self, str): 

444 elementlist = [] 

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

446 elementlist.append(str[i]) 

447 return elementlist 

448 

449 def _mpathinit(self): 

450 self.mpath = "false" 

451 try: 

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

453 'multipathhandle' in self.dconf: 

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

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

456 else: 

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

458 self.mpath = hconf['multipathing'] 

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

460 

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

462 self.mpath = "false" 

463 self.mpathhandle = "null" 

464 

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

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

467 except: 

468 self.mpath = "false" 

469 self.mpathhandle = "null" 

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

471 self.mpathmodule = __import__(module_name) 

472 

473 def _mpathHandle(self): 

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

475 self.mpathmodule.activate() 

476 else: 

477 self.mpathmodule.deactivate() 

478 

479 def _pathrefresh(self, obj): 

480 SCSIid = getattr(self, 'SCSIid') 

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

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

483 

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

485 try: 

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

487 sm_config['multipathable'] = 'true' 

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

489 

490 if self.mpath == "true" and len(SCSIid): 

491 cmd = ['/opt/xensource/sm/mpathcount.py', SCSIid] 

492 util.pread2(cmd) 

493 except: 

494 pass 

495 

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

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

498 

499 Input: 

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

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

502 keys missing 

503 

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

505 Raise: xs_errors.XenError('ConfigParamsMissing') 

506 """ 

507 

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

509 

510 if missing_keys and raise_flag: 

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

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

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

514 

515 return missing_keys 

516 

517 

518class ScanRecord: 

519 def __init__(self, sr): 

520 self.sr = sr 

521 self.__xenapi_locations = {} 

522 self.__xenapi_records = util.list_VDI_records_in_sr(sr) 

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

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

525 self.__sm_records = {} 

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

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

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

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

530 # sm_config_overrides[key]=None) 

531 try: 

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

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

534 except: 

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

536 vdi.sm_config = {} 

537 

538 vdi._override_sm_config(vdi.sm_config) 

539 

540 self.__sm_records[vdi.location] = vdi 

541 

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

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

544 

545 # These ones are new on disk 

546 self.new = sm_locations.difference(xenapi_locations) 

547 # These have disappeared from the disk 

548 self.gone = xenapi_locations.difference(sm_locations) 

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

550 existing = sm_locations.intersection(xenapi_locations) 

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

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

553 # in the storage backend. 

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

555 sm_vdi = self.get_sm_vdi(location) 

556 xenapi_vdi = self.get_xenapi_vdi(location) 

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

558 

559 # Only consider those whose configuration looks different 

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

561 

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

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

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

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

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

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

568 

569 def get_sm_vdi(self, location): 

570 return self.__sm_records[location] 

571 

572 def get_xenapi_vdi(self, location): 

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

574 

575 def all_xenapi_locations(self): 

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

577 

578 def synchronise_new(self): 

579 """Add XenAPI records for new disks""" 

580 for location in self.new: 

581 vdi = self.get_sm_vdi(location) 

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

583 vdi._db_introduce() 

584 

585 def synchronise_gone(self): 

586 """Delete XenAPI record for old disks""" 

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

588 vdi = self.get_xenapi_vdi(location) 

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

590 try: 

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

592 except XenAPI.Failure as e: 

593 if util.isInvalidVDI(e): 

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

595 vdi['uuid']) 

596 else: 

597 raise 

598 

599 def synchronise_existing(self): 

600 """Update existing XenAPI records""" 

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

602 vdi = self.get_sm_vdi(location) 

603 

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

605 vdi._db_update() 

606 

607 def synchronise(self): 

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

609 for most plugins.""" 

610 self.synchronise_new() 

611 self.synchronise_gone() 

612 self.synchronise_existing()