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# Copyright (C) Citrix Systems Inc. 

2# 

3# This program is free software; you can redistribute it and/or modify 

4# it under the terms of the GNU Lesser General Public License as published 

5# by the Free Software Foundation; version 2.1 only. 

6# 

7# This program is distributed in the hope that it will be useful, 

8# but WITHOUT ANY WARRANTY; without even the implied warranty of 

9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

10# GNU Lesser General Public License for more details. 

11# 

12# You should have received a copy of the GNU Lesser General Public License 

13# along with this program; if not, write to the Free Software Foundation, Inc., 

14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

15# 

16# VDI: Base class for virtual disk instances 

17# 

18 

19from sm_typing import Dict, Optional 

20 

21import cleanup 

22import SR 

23import xmlrpc.client 

24import xs_errors 

25import util 

26import vhdutil 

27import cbtutil 

28import os 

29import base64 

30from constants import CBTLOG_TAG 

31from bitarray import bitarray 

32import uuid 

33 

34 

35SM_CONFIG_PASS_THROUGH_FIELDS = ["base_mirror", "key_hash"] 

36 

37SNAPSHOT_SINGLE = 1 # true snapshot: 1 leaf, 1 read-only parent 

38SNAPSHOT_DOUBLE = 2 # regular snapshot/clone that creates 2 leaves 

39SNAPSHOT_INTERNAL = 3 # SNAPSHOT_SINGLE but don't update SR's virtual allocation 

40CBT_BLOCK_SIZE = (64 * 1024) 

41 

42 

43def VDIMetadataSize(type, virtualsize): 

44 size = 0 

45 if type == 'vhd': 

46 size_mb = virtualsize // (1024 * 1024) 

47 #Footer + footer copy + header + possible CoW parent locator fields 

48 size = 3 * 1024 

49 

50 # BAT 4 Bytes per block segment 

51 size += (size_mb // 2) * 4 

52 size = util.roundup(512, size) 

53 

54 # BATMAP 1 bit per block segment 

55 size += (size_mb // 2) // 8 

56 size = util.roundup(4096, size) 

57 

58 # Segment bitmaps + Page align offsets 

59 size += (size_mb // 2) * 4096 

60 

61 return size 

62 

63 

64class VDI(object): 

65 """Virtual Disk Instance descriptor. 

66 

67 Attributes: 

68 uuid: string, globally unique VDI identifier conforming to OSF DEC 1.1 

69 label: string, user-generated tag string for identifyng the VDI 

70 description: string, longer user generated description string 

71 size: int, virtual size in bytes of this VDI 

72 utilisation: int, actual size in Bytes of data on disk that is  

73 utilised. For non-sparse disks, utilisation == size 

74 vdi_type: string, disk type, e.g. raw file, partition 

75 parent: VDI object, parent backing VDI if this disk is a  

76 CoW instance 

77 shareable: boolean, does this disk support multiple writer instances? 

78 e.g. shared OCFS disk 

79 attached: boolean, whether VDI is attached 

80 read_only: boolean, whether disk is read-only. 

81 """ 

82 

83 def __init__(self, sr, uuid): 

84 self.sr = sr 

85 # Don't set either the UUID or location to None- no good can 

86 # ever come of this. 

87 if uuid is not None: 

88 self.uuid = uuid 

89 self.location = uuid 

90 self.path = None 

91 else: 

92 # We assume that children class initializors calling without 

93 # uuid will set these attributes themselves somewhere. They 

94 # are VDIs whose physical paths/locations have no direct 

95 # connections with their UUID strings (e.g. ISOSR, udevSR, 

96 # SHMSR). So we avoid overwriting these attributes here. 

97 pass 

98 # deliberately not initialised self.sm_config so that it is 

99 # ommitted from the XML output 

100 

101 self.label = '' 

102 self.description = '' 

103 self.vbds = [] 

104 self.size = 0 

105 self.utilisation = 0 

106 self.vdi_type = '' 

107 self.has_child = 0 

108 self.parent = None 

109 self.shareable = False 

110 self.attached = False 

111 self.status = 0 

112 self.read_only = False 

113 self.xenstore_data = {} 

114 self.deleted = False 

115 self.session = sr.session 

116 self.managed = True 

117 self.sm_config_override = {} 

118 self.sm_config_keep = ["key_hash"] 

119 self.ty = "user" 

120 self.cbt_enabled = False 

121 

122 self.load(uuid) 

123 

124 @staticmethod 

125 def from_uuid(session, vdi_uuid): 

126 

127 _VDI = session.xenapi.VDI 

128 vdi_ref = _VDI.get_by_uuid(vdi_uuid) 

129 sr_ref = _VDI.get_SR(vdi_ref) 

130 

131 _SR = session.xenapi.SR 

132 sr_uuid = _SR.get_uuid(sr_ref) 

133 

134 sr = SR.SR.from_uuid(session, sr_uuid) 

135 

136 sr.srcmd.params['vdi_ref'] = vdi_ref 

137 return sr.vdi(vdi_uuid) 

138 

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

140 """Create a VDI of size <Size> MB on the given SR.  

141 

142 This operation IS NOT idempotent and will fail if the UUID 

143 already exists or if there is insufficient space. The vdi must 

144 be explicitly attached via the attach() command following 

145 creation. The actual disk size created may be larger than the 

146 requested size if the substrate requires a size in multiples 

147 of a certain extent size. The SR must be queried for the exact 

148 size. 

149 """ 

150 raise xs_errors.XenError('Unimplemented') 

151 

152 def update(self, sr_uuid, vdi_uuid) -> None: 

153 """Query and update the configuration of a particular VDI. 

154 

155 Given an SR and VDI UUID, this operation returns summary statistics 

156 on the named VDI. Note the XenAPI VDI object will exist when 

157 this call is made. 

158 """ 

159 # no-op unless individual backends implement it 

160 return 

161 

162 def introduce(self, sr_uuid, vdi_uuid) -> str: 

163 """Explicitly introduce a particular VDI. 

164 

165 Given an SR and VDI UUID and a disk location (passed in via the <conf> 

166 XML), this operation verifies the existence of the underylying disk 

167 object and then creates the XenAPI VDI object. 

168 """ 

169 raise xs_errors.XenError('Unimplemented') 

170 

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

172 """Initiate local access to the VDI. Initialises any device 

173 state required to access the VDI. 

174 

175 This operation IS idempotent and should succeed if the VDI can be 

176 attached or if the VDI is already attached. 

177 

178 Returns: 

179 string, local device path. 

180 """ 

181 struct = {'params': self.path, 

182 'xenstore_data': (self.xenstore_data or {})} 

183 return xmlrpc.client.dumps((struct, ), "", True) 

184 

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

186 """Remove local access to the VDI. Destroys any device  

187 state initialised via the vdi.attach() command. 

188 

189 This operation is idempotent. 

190 """ 

191 raise xs_errors.XenError('Unimplemented') 

192 

193 def clone(self, sr_uuid, vdi_uuid) -> str: 

194 """Create a mutable instance of the referenced VDI. 

195 

196 This operation is not idempotent and will fail if the UUID 

197 already exists or if there is insufficient space. The SRC VDI 

198 must be in a detached state and deactivated. Upon successful 

199 creation of the clone, the clone VDI must be explicitly 

200 attached via vdi.attach(). If the driver does not support 

201 cloning this operation should raise SRUnsupportedOperation. 

202 

203 Arguments: 

204 Raises: 

205 SRUnsupportedOperation 

206 """ 

207 raise xs_errors.XenError('Unimplemented') 

208 

209 def resize_online(self, sr_uuid, vdi_uuid, size): 

210 """Resize the given VDI which may have active VBDs, which have 

211 been paused for the duration of this call.""" 

212 raise xs_errors.XenError('Unimplemented') 

213 

214 def generate_config(self, sr_uuid, vdi_uuid) -> str: 

215 """Generate the XML config required to activate a VDI for use 

216 when XAPI is not running. Activation is handled by the 

217 vdi_attach_from_config() SMAPI call. 

218 """ 

219 raise xs_errors.XenError('Unimplemented') 

220 

221 def compose(self, sr_uuid, vdi1, vdi2) -> None: 

222 """Layer the updates from [vdi2] onto [vdi1], calling the result 

223 [vdi2]. 

224 

225 Raises: 

226 SRUnsupportedOperation 

227 """ 

228 raise xs_errors.XenError('Unimplemented') 

229 

230 def attach_from_config(self, sr_uuid, vdi_uuid) -> str: 

231 """Activate a VDI based on the config passed in on the CLI. For 

232 use when XAPI is not running. The config is generated by the 

233 Activation is handled by the vdi_generate_config() SMAPI call. 

234 """ 

235 raise xs_errors.XenError('Unimplemented') 

236 

237 def _do_snapshot(self, sr_uuid, vdi_uuid, snapType, 

238 cloneOp=False, secondary=None, cbtlog=None) -> str: 

239 raise xs_errors.XenError('Unimplemented') 

240 

241 def _delete_cbt_log(self) -> None: 

242 raise xs_errors.XenError('Unimplemented') 

243 

244 def _rename(self, old, new) -> None: 

245 raise xs_errors.XenError('Unimplemented') 

246 

247 def _cbt_log_exists(self, logpath) -> bool: 

248 """Check if CBT log file exists 

249 

250 Must be implemented by all classes inheriting from base VDI class 

251 """ 

252 raise xs_errors.XenError('Unimplemented') 

253 

254 def resize(self, sr_uuid, vdi_uuid, size) -> str: 

255 """Resize the given VDI to size <size> MB. Size can 

256 be any valid disk size greater than [or smaller than] 

257 the current value. 

258 

259 This operation IS idempotent and should succeed if the VDI can 

260 be resized to the specified value or if the VDI is already the 

261 specified size. The actual disk size created may be larger 

262 than the requested size if the substrate requires a size in 

263 multiples of a certain extent size. The SR must be queried for 

264 the exact size. This operation does not modify the contents on 

265 the disk such as the filesystem. Responsibility for resizing 

266 the FS is left to the VM administrator. [Reducing the size of 

267 the disk is a very dangerous operation and should be conducted 

268 very carefully.] Disk contents should always be backed up in 

269 advance. 

270 """ 

271 raise xs_errors.XenError('Unimplemented') 

272 

273 def resize_cbt(self, sr_uuid, vdi_uuid, size): 

274 """Resize the given VDI to size <size> MB. Size can 

275 be any valid disk size greater than [or smaller than] 

276 the current value. 

277 

278 This operation IS idempotent and should succeed if the VDI can 

279 be resized to the specified value or if the VDI is already the 

280 specified size. The actual disk size created may be larger 

281 than the requested size if the substrate requires a size in 

282 multiples of a certain extent size. The SR must be queried for 

283 the exact size. This operation does not modify the contents on 

284 the disk such as the filesystem. Responsibility for resizing 

285 the FS is left to the VM administrator. [Reducing the size of 

286 the disk is a very dangerous operation and should be conducted 

287 very carefully.] Disk contents should always be backed up in 

288 advance. 

289 """ 

290 try: 

291 if self._get_blocktracking_status(): 

292 logpath = self._get_cbt_logpath(vdi_uuid) 

293 self._cbt_op(vdi_uuid, cbtutil.set_cbt_size, logpath, size) 

294 except util.CommandException as ex: 

295 alert_name = "VDI_CBT_RESIZE_FAILED" 

296 alert_str = ("Resizing of CBT metadata for disk %s failed." 

297 % vdi_uuid) 

298 self._disable_cbt_on_error(alert_name, alert_str) 

299 

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

301 """Delete this VDI. 

302 

303 This operation IS idempotent and should succeed if the VDI 

304 exists and can be deleted or if the VDI does not exist. It is 

305 the responsibility of the higher-level management tool to 

306 ensure that the detach() operation has been explicitly called 

307 prior to deletion, otherwise the delete() will fail if the 

308 disk is still attached. 

309 """ 

310 import blktap2 

311 from lock import Lock 

312 

313 if data_only == False and self._get_blocktracking_status(): 

314 logpath = self._get_cbt_logpath(vdi_uuid) 

315 parent_uuid = self._cbt_op(vdi_uuid, cbtutil.get_cbt_parent, 

316 logpath) 

317 parent_path = self._get_cbt_logpath(parent_uuid) 

318 child_uuid = self._cbt_op(vdi_uuid, cbtutil.get_cbt_child, logpath) 

319 child_path = self._get_cbt_logpath(child_uuid) 

320 

321 lock = Lock("cbtlog", str(vdi_uuid)) 

322 

323 if self._cbt_log_exists(parent_path): 323 ↛ 327line 323 didn't jump to line 327, because the condition on line 323 was never false

324 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

325 parent_path, child_uuid) 

326 

327 if self._cbt_log_exists(child_path): 

328 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

329 child_path, parent_uuid) 

330 lock.acquire() 

331 paused_for_coalesce = False 

332 try: 

333 # Coalesce contents of bitmap with child's bitmap 

334 # Check if child bitmap is currently attached 

335 consistent = self._cbt_op(child_uuid, 

336 cbtutil.get_cbt_consistency, 

337 child_path) 

338 if not consistent: 

339 if not blktap2.VDI.tap_pause(self.session, 339 ↛ 341line 339 didn't jump to line 341, because the condition on line 339 was never true

340 sr_uuid, child_uuid): 

341 raise util.SMException("failed to pause VDI %s") 

342 paused_for_coalesce = True 

343 self._activate_cbt_log(self._get_cbt_logname(vdi_uuid)) 

344 self._cbt_op(child_uuid, cbtutil.coalesce_bitmap, 

345 logpath, child_path) 

346 lock.release() 

347 except util.CommandException: 

348 # If there is an exception in coalescing, 

349 # CBT log file is not deleted and pointers are reset 

350 # to what they were 

351 util.SMlog("Exception in coalescing bitmaps on VDI delete," 

352 " restoring to previous state") 

353 try: 

354 if self._cbt_log_exists(parent_path): 354 ↛ 357line 354 didn't jump to line 357, because the condition on line 354 was never false

355 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

356 parent_path, vdi_uuid) 

357 if self._cbt_log_exists(child_path): 357 ↛ 361line 357 didn't jump to line 361, because the condition on line 357 was never false

358 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

359 child_path, vdi_uuid) 

360 finally: 

361 lock.release() 

362 lock.cleanup("cbtlog", str(vdi_uuid)) 

363 return 

364 finally: 

365 # Unpause tapdisk if it wasn't originally paused 

366 if paused_for_coalesce: 366 ↛ 369line 366 didn't jump to line 369, because the condition on line 366 was never false

367 blktap2.VDI.tap_unpause(self.session, sr_uuid, 367 ↛ exitline 367 didn't return from function 'delete', because the return on line 363 wasn't executed

368 child_uuid) 

369 lock.acquire() 

370 try: 

371 self._delete_cbt_log() 

372 finally: 

373 lock.release() 

374 lock.cleanup("cbtlog", str(vdi_uuid)) 

375 

376 def snapshot(self, sr_uuid, vdi_uuid) -> str: 

377 """Save an immutable copy of the referenced VDI. 

378 

379 This operation IS NOT idempotent and will fail if the UUID 

380 already exists or if there is insufficient space. The vdi must 

381 be explicitly attached via the vdi_attach() command following 

382 creation. If the driver does not support snapshotting this 

383 operation should raise SRUnsupportedOperation 

384 

385 Arguments: 

386 Raises: 

387 SRUnsupportedOperation 

388 """ 

389 # logically, "snapshot" should mean SNAPSHOT_SINGLE and "clone" should 

390 # mean "SNAPSHOT_DOUBLE", but in practice we have to do SNAPSHOT_DOUBLE 

391 # in both cases, unless driver_params overrides it 

392 snapType = SNAPSHOT_DOUBLE 

393 if self.sr.srcmd.params['driver_params'].get("type"): 393 ↛ 399line 393 didn't jump to line 399, because the condition on line 393 was never false

394 if self.sr.srcmd.params['driver_params']["type"] == "single": 394 ↛ 395line 394 didn't jump to line 395, because the condition on line 394 was never true

395 snapType = SNAPSHOT_SINGLE 

396 elif self.sr.srcmd.params['driver_params']["type"] == "internal": 396 ↛ 397line 396 didn't jump to line 397, because the condition on line 396 was never true

397 snapType = SNAPSHOT_INTERNAL 

398 

399 secondary = None 

400 if self.sr.srcmd.params['driver_params'].get("mirror"): 

401 secondary = self.sr.srcmd.params['driver_params']["mirror"] 

402 

403 if self._get_blocktracking_status(): 

404 cbtlog = self._get_cbt_logpath(self.uuid) 

405 else: 

406 cbtlog = None 

407 return self._do_snapshot(sr_uuid, vdi_uuid, snapType, 

408 secondary=secondary, cbtlog=cbtlog) 

409 

410 def activate(self, sr_uuid, vdi_uuid) -> Optional[Dict[str, str]]: 

411 """Activate VDI - called pre tapdisk open""" 

412 if self._get_blocktracking_status(): 

413 if 'args' in self.sr.srcmd.params: 413 ↛ 414line 413 didn't jump to line 414, because the condition on line 413 was never true

414 read_write = self.sr.srcmd.params['args'][0] 

415 if read_write == "false": 

416 # Disk is being attached in RO mode, 

417 # don't attach metadata log file 

418 return None 

419 

420 from lock import Lock 

421 lock = Lock("cbtlog", str(vdi_uuid)) 

422 lock.acquire() 

423 

424 try: 

425 logpath = self._get_cbt_logpath(vdi_uuid) 

426 logname = self._get_cbt_logname(vdi_uuid) 

427 

428 # Activate CBT log file, if required 

429 self._activate_cbt_log(logname) 

430 finally: 

431 lock.release() 

432 

433 # Check and update consistency 

434 consistent = self._cbt_op(vdi_uuid, cbtutil.get_cbt_consistency, 

435 logpath) 

436 if not consistent: 

437 alert_name = "VDI_CBT_METADATA_INCONSISTENT" 

438 alert_str = ("Changed Block Tracking metadata is inconsistent" 

439 " for disk %s." % vdi_uuid) 

440 self._disable_cbt_on_error(alert_name, alert_str) 

441 return None 

442 

443 self._cbt_op(self.uuid, cbtutil.set_cbt_consistency, 

444 logpath, False) 

445 return {'cbtlog': logpath} 

446 return None 

447 

448 def deactivate(self, sr_uuid, vdi_uuid) -> None: 

449 """Deactivate VDI - called post tapdisk close""" 

450 if self._get_blocktracking_status(): 

451 from lock import Lock 

452 lock = Lock("cbtlog", str(vdi_uuid)) 

453 lock.acquire() 

454 

455 try: 

456 logpath = self._get_cbt_logpath(vdi_uuid) 

457 logname = self._get_cbt_logname(vdi_uuid) 

458 self._cbt_op(vdi_uuid, cbtutil.set_cbt_consistency, logpath, True) 

459 # Finally deactivate log file 

460 self._deactivate_cbt_log(logname) 

461 finally: 

462 lock.release() 

463 

464 def get_params(self) -> str: 

465 """ 

466 Returns: 

467 XMLRPC response containing a single struct with fields 

468 'location' and 'uuid' 

469 """ 

470 struct = {'location': self.location, 

471 'uuid': self.uuid} 

472 return xmlrpc.client.dumps((struct, ), "", True) 

473 

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

475 """Post-init hook""" 

476 pass 

477 

478 def _db_introduce(self): 

479 uuid = util.default(self, "uuid", lambda: util.gen_uuid()) 479 ↛ exitline 479 didn't run the lambda on line 479

480 sm_config = util.default(self, "sm_config", lambda: {}) 

481 if "vdi_sm_config" in self.sr.srcmd.params: 481 ↛ 482line 481 didn't jump to line 482, because the condition on line 481 was never true

482 for key in SM_CONFIG_PASS_THROUGH_FIELDS: 

483 val = self.sr.srcmd.params["vdi_sm_config"].get(key) 

484 if val: 

485 sm_config[key] = val 

486 ty = util.default(self, "ty", lambda: "user") 486 ↛ exitline 486 didn't run the lambda on line 486

487 is_a_snapshot = util.default(self, "is_a_snapshot", lambda: False) 

488 metadata_of_pool = util.default(self, "metadata_of_pool", lambda: "OpaqueRef:NULL") 

489 snapshot_time = util.default(self, "snapshot_time", lambda: "19700101T00:00:00Z") 

490 snapshot_of = util.default(self, "snapshot_of", lambda: "OpaqueRef:NULL") 

491 cbt_enabled = util.default(self, "cbt_enabled", lambda: False) 491 ↛ exitline 491 didn't run the lambda on line 491

492 vdi = self.sr.session.xenapi.VDI.db_introduce(uuid, self.label, self.description, self.sr.sr_ref, ty, self.shareable, self.read_only, {}, self.location, {}, sm_config, self.managed, str(self.size), str(self.utilisation), metadata_of_pool, is_a_snapshot, xmlrpc.client.DateTime(snapshot_time), snapshot_of, cbt_enabled) 

493 return vdi 

494 

495 def _db_forget(self): 

496 self.sr.forget_vdi(self.uuid) 

497 

498 def _override_sm_config(self, sm_config): 

499 for key, val in self.sm_config_override.items(): 

500 if val == sm_config.get(key): 

501 continue 

502 if val: 502 ↛ 506line 502 didn't jump to line 506, because the condition on line 502 was never false

503 util.SMlog("_override_sm_config: %s: %s -> %s" % \ 

504 (key, sm_config.get(key), val)) 

505 sm_config[key] = val 

506 elif key in sm_config: 

507 util.SMlog("_override_sm_config: del %s" % key) 

508 del sm_config[key] 

509 

510 def _db_update_sm_config(self, ref, sm_config): 

511 import cleanup 

512 # List of sm-config keys that should not be modifed by db_update 

513 smconfig_protected_keys = [ 

514 cleanup.VDI.DB_VDI_PAUSED, 

515 cleanup.VDI.DB_VHD_BLOCKS, 

516 cleanup.VDI.DB_VDI_RELINKING, 

517 cleanup.VDI.DB_VDI_ACTIVATING] 

518 

519 current_sm_config = self.sr.session.xenapi.VDI.get_sm_config(ref) 

520 for key, val in sm_config.items(): 

521 if (key.startswith("host_") or 

522 key in smconfig_protected_keys): 

523 continue 

524 if sm_config.get(key) != current_sm_config.get(key): 

525 util.SMlog("_db_update_sm_config: %s sm-config:%s %s->%s" % \ 

526 (self.uuid, key, current_sm_config.get(key), val)) 

527 self.sr.session.xenapi.VDI.remove_from_sm_config(ref, key) 

528 self.sr.session.xenapi.VDI.add_to_sm_config(ref, key, val) 

529 

530 for key in current_sm_config.keys(): 

531 if (key.startswith("host_") or 

532 key in smconfig_protected_keys or 

533 key in self.sm_config_keep): 

534 continue 

535 if not sm_config.get(key): 

536 util.SMlog("_db_update_sm_config: %s del sm-config:%s" % \ 

537 (self.uuid, key)) 

538 self.sr.session.xenapi.VDI.remove_from_sm_config(ref, key) 

539 

540 def _db_update(self): 

541 vdi = self.sr.session.xenapi.VDI.get_by_uuid(self.uuid) 

542 self.sr.session.xenapi.VDI.set_virtual_size(vdi, str(self.size)) 

543 self.sr.session.xenapi.VDI.set_physical_utilisation(vdi, str(self.utilisation)) 

544 self.sr.session.xenapi.VDI.set_read_only(vdi, self.read_only) 

545 sm_config = util.default(self, "sm_config", lambda: {}) 

546 self._override_sm_config(sm_config) 

547 self._db_update_sm_config(vdi, sm_config) 

548 self.sr.session.xenapi.VDI.set_cbt_enabled(vdi, 

549 self._get_blocktracking_status()) 

550 

551 def in_sync_with_xenapi_record(self, x): 

552 """Returns true if this VDI is in sync with the supplied XenAPI record""" 

553 if self.location != util.to_plain_string(x['location']): 

554 util.SMlog("location %s <> %s" % (self.location, x['location'])) 

555 return False 

556 if self.read_only != x['read_only']: 

557 util.SMlog("read_only %s <> %s" % (self.read_only, x['read_only'])) 

558 return False 

559 if str(self.size) != x['virtual_size']: 

560 util.SMlog("virtual_size %s <> %s" % (self.size, x['virtual_size'])) 

561 return False 

562 if str(self.utilisation) != x['physical_utilisation']: 

563 util.SMlog("utilisation %s <> %s" % (self.utilisation, x['physical_utilisation'])) 

564 return False 

565 sm_config = util.default(self, "sm_config", lambda: {}) 

566 if set(sm_config.keys()) != set(x['sm_config'].keys()): 

567 util.SMlog("sm_config %s <> %s" % (repr(sm_config), repr(x['sm_config']))) 

568 return False 

569 for k in sm_config.keys(): 

570 if sm_config[k] != x['sm_config'][k]: 

571 util.SMlog("sm_config %s <> %s" % (repr(sm_config), repr(x['sm_config']))) 

572 return False 

573 if self.cbt_enabled != x['cbt_enabled']: 

574 util.SMlog("cbt_enabled %s <> %s" % ( 

575 self.cbt_enabled, x['cbt_enabled'])) 

576 return False 

577 return True 

578 

579 def update_slaves_on_cbt_disable(self, cbtlog): 

580 # Override in implementation as required. 

581 pass 

582 

583 def configure_blocktracking(self, sr_uuid, vdi_uuid, enable): 

584 """Function for configuring blocktracking""" 

585 import blktap2 

586 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

587 

588 # Check if raw VDI or snapshot 

589 if self.vdi_type == vhdutil.VDI_TYPE_RAW or \ 

590 self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref): 

591 raise xs_errors.XenError('VDIType', 

592 opterr='Raw VDI or snapshot not permitted') 

593 

594 # Check if already enabled 

595 if self._get_blocktracking_status() == enable: 

596 return 

597 

598 # Save disk state before pause 

599 disk_state = blktap2.VDI.tap_status(self.session, vdi_uuid) 

600 

601 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid): 

602 error = "Failed to pause VDI %s" % vdi_uuid 

603 raise xs_errors.XenError('CBTActivateFailed', opterr=error) 

604 logfile = None 

605 

606 try: 

607 if enable: 

608 try: 

609 # Check available space 

610 self._ensure_cbt_space() 

611 logfile = self._create_cbt_log() 

612 # Set consistency 

613 if disk_state: 613 ↛ 646line 613 didn't jump to line 646, because the condition on line 613 was never false

614 util.SMlog("Setting consistency of cbtlog file to False for VDI: %s" 

615 % self.uuid) 

616 logpath = self._get_cbt_logpath(self.uuid) 

617 self._cbt_op(self.uuid, cbtutil.set_cbt_consistency, 

618 logpath, False) 

619 except Exception as error: 

620 self._delete_cbt_log() 

621 raise xs_errors.XenError('CBTActivateFailed', 

622 opterr=str(error)) 

623 else: 

624 from lock import Lock 

625 lock = Lock("cbtlog", str(vdi_uuid)) 

626 lock.acquire() 

627 try: 

628 # Find parent of leaf metadata file, if any, 

629 # and nullify its successor 

630 logpath = self._get_cbt_logpath(self.uuid) 

631 parent = self._cbt_op(self.uuid, 

632 cbtutil.get_cbt_parent, logpath) 

633 self._delete_cbt_log() 

634 parent_path = self._get_cbt_logpath(parent) 

635 if self._cbt_log_exists(parent_path): 635 ↛ 638line 635 didn't jump to line 638, because the condition on line 635 was never false

636 self._cbt_op(parent, cbtutil.set_cbt_child, 

637 parent_path, uuid.UUID(int=0)) 

638 if disk_state: 638 ↛ 643line 638 didn't jump to line 643, because the condition on line 638 was never false

639 self.update_slaves_on_cbt_disable(logpath) 

640 except Exception as error: 

641 raise xs_errors.XenError('CBTDeactivateFailed', str(error)) 

642 finally: 

643 lock.release() 

644 lock.cleanup("cbtlog", str(vdi_uuid)) 

645 finally: 

646 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid) 

647 

648 def data_destroy(self, sr_uuid, vdi_uuid): 

649 """Delete the data associated with a CBT enabled snapshot 

650 

651 Can only be called for a snapshot VDI on a VHD chain that has 

652 had CBT enabled on it at some point. The latter is enforced 

653 by upper layers 

654 """ 

655 

656 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

657 if not self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref): 

658 raise xs_errors.XenError('VDIType', 

659 opterr='Only allowed for snapshot VDIs') 

660 

661 self.delete(sr_uuid, vdi_uuid, data_only=True) 

662 

663 def list_changed_blocks(self): 

664 """ List all changed blocks """ 

665 vdi_from = self.uuid 

666 params = self.sr.srcmd.params 

667 _VDI = self.session.xenapi.VDI 

668 vdi_to = _VDI.get_uuid(params['args'][0]) 

669 sr_uuid = params['sr_uuid'] 

670 

671 if vdi_from == vdi_to: 

672 raise xs_errors.XenError('CBTChangedBlocksError', 

673 "Source and target VDI are same") 

674 

675 # Check 1: Check if CBT is enabled on VDIs and they are related 

676 if (self._get_blocktracking_status(vdi_from) and 

677 self._get_blocktracking_status(vdi_to)): 

678 merged_bitmap = None 

679 curr_vdi = vdi_from 

680 vdi_size = 0 

681 logpath = self._get_cbt_logpath(curr_vdi) 

682 

683 # Starting at log file after "vdi_from", traverse the CBT chain 

684 # through child pointers until one of the following is true 

685 # * We've reached destination VDI 

686 # * We've reached end of CBT chain originating at "vdi_from" 

687 while True: 

688 # Check if we have reached end of CBT chain 

689 next_vdi = self._cbt_op(curr_vdi, cbtutil.get_cbt_child, 

690 logpath) 

691 if not self._cbt_log_exists(self._get_cbt_logpath(next_vdi)): 691 ↛ 693line 691 didn't jump to line 693, because the condition on line 691 was never true

692 # VDIs are not part of the same metadata chain 

693 break 

694 else: 

695 curr_vdi = next_vdi 

696 

697 logpath = self._get_cbt_logpath(curr_vdi) 

698 curr_vdi_size = self._cbt_op(curr_vdi, 

699 cbtutil.get_cbt_size, logpath) 

700 util.SMlog("DEBUG: Processing VDI %s of size %d" 

701 % (curr_vdi, curr_vdi_size)) 

702 curr_bitmap = bitarray() 

703 curr_bitmap.frombytes(self._cbt_op(curr_vdi, 

704 cbtutil.get_cbt_bitmap, 

705 logpath)) 

706 curr_bitmap.bytereverse() 

707 util.SMlog("Size of bitmap: %d" % len(curr_bitmap)) 

708 

709 expected_bitmap_len = curr_vdi_size // CBT_BLOCK_SIZE 

710 # This should ideally never happen but fail call to calculate 

711 # changed blocks instead of returning corrupt data 

712 if len(curr_bitmap) < expected_bitmap_len: 

713 util.SMlog("Size of bitmap %d is less than expected size %d" 

714 % (len(curr_bitmap), expected_bitmap_len)) 

715 raise xs_errors.XenError('CBTMetadataInconsistent', 

716 "Inconsistent bitmaps") 

717 

718 if merged_bitmap: 

719 # Rule out error conditions 

720 # 1) New VDI size < original VDI size 

721 # 2) New bitmap size < original bitmap size 

722 # 3) new VDI size > original VDI size but new bitmap 

723 # is not bigger 

724 if (curr_vdi_size < vdi_size or 

725 len(curr_bitmap) < len(merged_bitmap) or 

726 (curr_vdi_size > vdi_size and 

727 len(curr_bitmap) <= len(merged_bitmap))): 

728 # Return error: Failure to calculate changed blocks 

729 util.SMlog("Cannot calculate changed blocks with" 

730 "inconsistent bitmap sizes") 

731 raise xs_errors.XenError('CBTMetadataInconsistent', 

732 "Inconsistent bitmaps") 

733 

734 # Check if disk has been resized 

735 if curr_vdi_size > vdi_size: 

736 vdi_size = curr_vdi_size 

737 extended_size = len(curr_bitmap) - len(merged_bitmap) 

738 # Extend merged_bitmap to match size of curr_bitmap 

739 extended_bitmap = extended_size * bitarray('0') 

740 merged_bitmap += extended_bitmap 

741 

742 # At this point bitmap sizes should be same 

743 if (len(curr_bitmap) > len(merged_bitmap) and 

744 curr_vdi_size == vdi_size): 

745 # This is unusual. Log it but calculate merged 

746 # bitmap by truncating new bitmap 

747 util.SMlog("Bitmap for %s bigger than other bitmaps" 

748 "in chain without change in size" % curr_vdi) 

749 curr_bitmap = curr_bitmap[:len(merged_bitmap)] 

750 

751 merged_bitmap = merged_bitmap | curr_bitmap 

752 else: 

753 merged_bitmap = curr_bitmap 

754 vdi_size = curr_vdi_size 

755 

756 # Check if we have reached "vdi_to" 

757 if curr_vdi == vdi_to: 

758 encoded_string = base64.b64encode(merged_bitmap.tobytes()).decode() 

759 return xmlrpc.client.dumps((encoded_string, ), "", True) 

760 # TODO: Check 2: If both VDIs still exist, 

761 # find common ancestor and find difference 

762 

763 # TODO: VDIs are unrelated 

764 # return fully populated bitmap size of to VDI 

765 

766 raise xs_errors.XenError('CBTChangedBlocksError', 

767 "Source and target VDI are unrelated") 

768 

769 def _cbt_snapshot(self, snapshot_uuid, consistency_state): 

770 """ CBT snapshot""" 

771 snap_logpath = self._get_cbt_logpath(snapshot_uuid) 

772 vdi_logpath = self._get_cbt_logpath(self.uuid) 

773 

774 # Rename vdi vdi.cbtlog to snapshot.cbtlog 

775 # and mark it consistent 

776 self._rename(vdi_logpath, snap_logpath) 

777 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_consistency, 

778 snap_logpath, True) 

779 

780 #TODO: Make parent detection logic better. Ideally, get_cbt_parent 

781 # should return None if the parent is set to a UUID made of all 0s. 

782 # In this case, we don't know the difference between whether it is a 

783 # NULL UUID or the parent file is missing. See cbtutil for why we can't 

784 # do this 

785 parent = self._cbt_op(snapshot_uuid, 

786 cbtutil.get_cbt_parent, snap_logpath) 

787 parent_path = self._get_cbt_logpath(parent) 

788 if self._cbt_log_exists(parent_path): 

789 self._cbt_op(parent, cbtutil.set_cbt_child, 

790 parent_path, snapshot_uuid) 

791 try: 

792 # Ensure enough space for metadata file 

793 self._ensure_cbt_space() 

794 # Create new vdi.cbtlog 

795 self._create_cbt_log() 

796 # Set previous vdi node consistency status 

797 if not consistency_state: 797 ↛ 798line 797 didn't jump to line 798, because the condition on line 797 was never true

798 self._cbt_op(self.uuid, cbtutil.set_cbt_consistency, 

799 vdi_logpath, consistency_state) 

800 # Set relationship pointers 

801 # Save the child of the VDI just snapshotted 

802 curr_child_uuid = self._cbt_op(snapshot_uuid, cbtutil.get_cbt_child, 

803 snap_logpath) 

804 self._cbt_op(self.uuid, cbtutil.set_cbt_parent, 

805 vdi_logpath, snapshot_uuid) 

806 # Set child of new vdi to existing child of snapshotted VDI 

807 self._cbt_op(self.uuid, cbtutil.set_cbt_child, 

808 vdi_logpath, curr_child_uuid) 

809 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_child, 

810 snap_logpath, self.uuid) 

811 except Exception as ex: 

812 alert_name = "VDI_CBT_SNAPSHOT_FAILED" 

813 alert_str = ("Creating CBT metadata log for disk %s failed." 

814 % self.uuid) 

815 self._disable_cbt_on_error(alert_name, alert_str) 

816 

817 def _get_blocktracking_status(self, uuid=None) -> bool: 

818 """ Get blocktracking status """ 

819 if not uuid: 819 ↛ 821line 819 didn't jump to line 821, because the condition on line 819 was never false

820 uuid = self.uuid 

821 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 821 ↛ 822line 821 didn't jump to line 822, because the condition on line 821 was never true

822 return False 

823 elif 'VDI_CONFIG_CBT' not in util.sr_get_capability( 

824 self.sr.uuid, session=self.sr.session): 

825 return False 

826 logpath = self._get_cbt_logpath(uuid) 

827 return self._cbt_log_exists(logpath) 

828 

829 def _set_blocktracking_status(self, vdi_ref, enable): 

830 """ Set blocktracking status""" 

831 vdi_config = self.session.xenapi.VDI.get_other_config(vdi_ref) 

832 if "cbt_enabled" in vdi_config: 

833 self.session.xenapi.VDI.remove_from_other_config( 

834 vdi_ref, "cbt_enabled") 

835 

836 self.session.xenapi.VDI.add_to_other_config( 

837 vdi_ref, "cbt_enabled", enable) 

838 

839 def _ensure_cbt_space(self) -> None: 

840 """ Ensure enough CBT space """ 

841 pass 

842 

843 def _get_cbt_logname(self, uuid): 

844 """ Get CBT logname """ 

845 logName = "%s.%s" % (uuid, CBTLOG_TAG) 

846 return logName 

847 

848 def _get_cbt_logpath(self, uuid) -> str: 

849 """ Get CBT logpath """ 

850 logName = self._get_cbt_logname(uuid) 

851 return os.path.join(self.sr.path, logName) 

852 

853 def _create_cbt_log(self) -> str: 

854 """ Create CBT log """ 

855 try: 

856 logpath = self._get_cbt_logpath(self.uuid) 

857 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

858 size = self.session.xenapi.VDI.get_virtual_size(vdi_ref) 

859 #cbtutil.create_cbt_log(logpath, size) 

860 self._cbt_op(self.uuid, cbtutil.create_cbt_log, logpath, size) 

861 self._cbt_op(self.uuid, cbtutil.set_cbt_consistency, logpath, True) 

862 except Exception as e: 

863 try: 

864 self._delete_cbt_log() 

865 except: 

866 pass 

867 finally: 

868 raise e 

869 

870 return logpath 

871 

872 def _activate_cbt_log(self, logname) -> bool: 

873 """Activate CBT log file 

874 

875 SR specific Implementation required for VDIs on block-based SRs. 

876 No-op otherwise 

877 """ 

878 return False 

879 

880 def _deactivate_cbt_log(self, logname) -> None: 

881 """Deactivate CBT log file 

882 

883 SR specific Implementation required for VDIs on block-based SRs. 

884 No-op otherwise 

885 """ 

886 pass 

887 

888 def _cbt_op(self, uuid, func, *args): 

889 # Lock cbtlog operations 

890 from lock import Lock 

891 lock = Lock("cbtlog", str(uuid)) 

892 lock.acquire() 

893 

894 try: 

895 logname = self._get_cbt_logname(uuid) 

896 activated = self._activate_cbt_log(logname) 

897 ret = func( * args) 

898 if activated: 

899 self._deactivate_cbt_log(logname) 

900 return ret 

901 finally: 

902 lock.release() 

903 

904 def _disable_cbt_on_error(self, alert_name, alert_str): 

905 util.SMlog(alert_str) 

906 self._delete_cbt_log() 

907 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

908 self.sr.session.xenapi.VDI.set_cbt_enabled(vdi_ref, False) 

909 alert_prio_warning = "3" 

910 alert_obj = "VDI" 

911 alert_uuid = str(self.uuid) 

912 self.sr.session.xenapi.message.create(alert_name, 

913 alert_prio_warning, 

914 alert_obj, alert_uuid, 

915 alert_str) 

916 

917 def disable_leaf_on_secondary(self, vdi_uuid, secondary=None): 

918 vdi_ref = self.session.xenapi.VDI.get_by_uuid(vdi_uuid) 

919 self.session.xenapi.VDI.remove_from_other_config( 

920 vdi_ref, cleanup.VDI.DB_LEAFCLSC) 

921 if secondary is not None: 

922 util.SMlog(f"We have secondary for {vdi_uuid}, " 

923 "blocking leaf coalesce") 

924 self.session.xenapi.VDI.add_to_other_config( 

925 vdi_ref, cleanup.VDI.DB_LEAFCLSC, 

926 cleanup.VDI.LEAFCLSC_DISABLED)