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 configure_blocktracking(self, sr_uuid, vdi_uuid, enable): 

580 """Function for configuring blocktracking""" 

581 import blktap2 

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

583 

584 # Check if raw VDI or snapshot 

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

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

587 raise xs_errors.XenError('VDIType', 

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

589 

590 # Check if already enabled 

591 if self._get_blocktracking_status() == enable: 

592 return 

593 

594 # Save disk state before pause 

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

596 

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

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

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

600 logfile = None 

601 

602 try: 

603 if enable: 

604 try: 

605 # Check available space 

606 self._ensure_cbt_space() 

607 logfile = self._create_cbt_log() 

608 # Set consistency 

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

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

611 % self.uuid) 

612 logpath = self._get_cbt_logpath(self.uuid) 

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

614 logpath, False) 

615 except Exception as error: 

616 self._delete_cbt_log() 

617 raise xs_errors.XenError('CBTActivateFailed', 

618 opterr=str(error)) 

619 else: 

620 from lock import Lock 

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

622 lock.acquire() 

623 try: 

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

625 # and nullify its successor 

626 logpath = self._get_cbt_logpath(self.uuid) 

627 parent = self._cbt_op(self.uuid, 

628 cbtutil.get_cbt_parent, logpath) 

629 self._delete_cbt_log() 

630 parent_path = self._get_cbt_logpath(parent) 

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

632 self._cbt_op(parent, cbtutil.set_cbt_child, 

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

634 except Exception as error: 

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

636 finally: 

637 lock.release() 

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

639 finally: 

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

641 

642 def data_destroy(self, sr_uuid, vdi_uuid): 

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

644 

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

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

647 by upper layers 

648 """ 

649 

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

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

652 raise xs_errors.XenError('VDIType', 

653 opterr='Only allowed for snapshot VDIs') 

654 

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

656 

657 def list_changed_blocks(self): 

658 """ List all changed blocks """ 

659 vdi_from = self.uuid 

660 params = self.sr.srcmd.params 

661 _VDI = self.session.xenapi.VDI 

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

663 sr_uuid = params['sr_uuid'] 

664 

665 if vdi_from == vdi_to: 

666 raise xs_errors.XenError('CBTChangedBlocksError', 

667 "Source and target VDI are same") 

668 

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

670 if (self._get_blocktracking_status(vdi_from) and 

671 self._get_blocktracking_status(vdi_to)): 

672 merged_bitmap = None 

673 curr_vdi = vdi_from 

674 vdi_size = 0 

675 logpath = self._get_cbt_logpath(curr_vdi) 

676 

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

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

679 # * We've reached destination VDI 

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

681 while True: 

682 # Check if we have reached end of CBT chain 

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

684 logpath) 

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

686 # VDIs are not part of the same metadata chain 

687 break 

688 else: 

689 curr_vdi = next_vdi 

690 

691 logpath = self._get_cbt_logpath(curr_vdi) 

692 curr_vdi_size = self._cbt_op(curr_vdi, 

693 cbtutil.get_cbt_size, logpath) 

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

695 % (curr_vdi, curr_vdi_size)) 

696 curr_bitmap = bitarray() 

697 curr_bitmap.frombytes(self._cbt_op(curr_vdi, 

698 cbtutil.get_cbt_bitmap, 

699 logpath)) 

700 curr_bitmap.bytereverse() 

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

702 

703 expected_bitmap_len = curr_vdi_size // CBT_BLOCK_SIZE 

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

705 # changed blocks instead of returning corrupt data 

706 if len(curr_bitmap) < expected_bitmap_len: 

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

708 % (len(curr_bitmap), expected_bitmap_len)) 

709 raise xs_errors.XenError('CBTMetadataInconsistent', 

710 "Inconsistent bitmaps") 

711 

712 if merged_bitmap: 

713 # Rule out error conditions 

714 # 1) New VDI size < original VDI size 

715 # 2) New bitmap size < original bitmap size 

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

717 # is not bigger 

718 if (curr_vdi_size < vdi_size or 

719 len(curr_bitmap) < len(merged_bitmap) or 

720 (curr_vdi_size > vdi_size and 

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

722 # Return error: Failure to calculate changed blocks 

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

724 "inconsistent bitmap sizes") 

725 raise xs_errors.XenError('CBTMetadataInconsistent', 

726 "Inconsistent bitmaps") 

727 

728 # Check if disk has been resized 

729 if curr_vdi_size > vdi_size: 

730 vdi_size = curr_vdi_size 

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

732 # Extend merged_bitmap to match size of curr_bitmap 

733 extended_bitmap = extended_size * bitarray('0') 

734 merged_bitmap += extended_bitmap 

735 

736 # At this point bitmap sizes should be same 

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

738 curr_vdi_size == vdi_size): 

739 # This is unusual. Log it but calculate merged 

740 # bitmap by truncating new bitmap 

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

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

743 curr_bitmap = curr_bitmap[:len(merged_bitmap)] 

744 

745 merged_bitmap = merged_bitmap | curr_bitmap 

746 else: 

747 merged_bitmap = curr_bitmap 

748 vdi_size = curr_vdi_size 

749 

750 # Check if we have reached "vdi_to" 

751 if curr_vdi == vdi_to: 

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

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

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

755 # find common ancestor and find difference 

756 

757 # TODO: VDIs are unrelated 

758 # return fully populated bitmap size of to VDI 

759 

760 raise xs_errors.XenError('CBTChangedBlocksError', 

761 "Source and target VDI are unrelated") 

762 

763 def _cbt_snapshot(self, snapshot_uuid, consistency_state): 

764 """ CBT snapshot""" 

765 snap_logpath = self._get_cbt_logpath(snapshot_uuid) 

766 vdi_logpath = self._get_cbt_logpath(self.uuid) 

767 

768 # Rename vdi vdi.cbtlog to snapshot.cbtlog 

769 # and mark it consistent 

770 self._rename(vdi_logpath, snap_logpath) 

771 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_consistency, 

772 snap_logpath, True) 

773 

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

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

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

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

778 # do this 

779 parent = self._cbt_op(snapshot_uuid, 

780 cbtutil.get_cbt_parent, snap_logpath) 

781 parent_path = self._get_cbt_logpath(parent) 

782 if self._cbt_log_exists(parent_path): 

783 self._cbt_op(parent, cbtutil.set_cbt_child, 

784 parent_path, snapshot_uuid) 

785 try: 

786 # Ensure enough space for metadata file 

787 self._ensure_cbt_space() 

788 # Create new vdi.cbtlog 

789 self._create_cbt_log() 

790 # Set previous vdi node consistency status 

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

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

793 vdi_logpath, consistency_state) 

794 # Set relationship pointers 

795 # Save the child of the VDI just snapshotted 

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

797 snap_logpath) 

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

799 vdi_logpath, snapshot_uuid) 

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

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

802 vdi_logpath, curr_child_uuid) 

803 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_child, 

804 snap_logpath, self.uuid) 

805 except Exception as ex: 

806 alert_name = "VDI_CBT_SNAPSHOT_FAILED" 

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

808 % self.uuid) 

809 self._disable_cbt_on_error(alert_name, alert_str) 

810 

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

812 """ Get blocktracking status """ 

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

814 uuid = self.uuid 

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

816 return False 

817 elif 'VDI_CONFIG_CBT' not in util.sr_get_capability( 

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

819 return False 

820 logpath = self._get_cbt_logpath(uuid) 

821 return self._cbt_log_exists(logpath) 

822 

823 def _set_blocktracking_status(self, vdi_ref, enable): 

824 """ Set blocktracking status""" 

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

826 if "cbt_enabled" in vdi_config: 

827 self.session.xenapi.VDI.remove_from_other_config( 

828 vdi_ref, "cbt_enabled") 

829 

830 self.session.xenapi.VDI.add_to_other_config( 

831 vdi_ref, "cbt_enabled", enable) 

832 

833 def _ensure_cbt_space(self) -> None: 

834 """ Ensure enough CBT space """ 

835 pass 

836 

837 def _get_cbt_logname(self, uuid): 

838 """ Get CBT logname """ 

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

840 return logName 

841 

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

843 """ Get CBT logpath """ 

844 logName = self._get_cbt_logname(uuid) 

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

846 

847 def _create_cbt_log(self) -> str: 

848 """ Create CBT log """ 

849 try: 

850 logpath = self._get_cbt_logpath(self.uuid) 

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

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

853 #cbtutil.create_cbt_log(logpath, size) 

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

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

856 except Exception as e: 

857 try: 

858 self._delete_cbt_log() 

859 except: 

860 pass 

861 finally: 

862 raise e 

863 

864 return logpath 

865 

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

867 """Activate CBT log file 

868 

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

870 No-op otherwise 

871 """ 

872 return False 

873 

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

875 """Deactivate CBT log file 

876 

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

878 No-op otherwise 

879 """ 

880 pass 

881 

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

883 # Lock cbtlog operations 

884 from lock import Lock 

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

886 lock.acquire() 

887 

888 try: 

889 logname = self._get_cbt_logname(uuid) 

890 activated = self._activate_cbt_log(logname) 

891 ret = func( * args) 

892 if activated: 

893 self._deactivate_cbt_log(logname) 

894 return ret 

895 finally: 

896 lock.release() 

897 

898 def _disable_cbt_on_error(self, alert_name, alert_str): 

899 util.SMlog(alert_str) 

900 self._delete_cbt_log() 

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

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

903 alert_prio_warning = "3" 

904 alert_obj = "VDI" 

905 alert_uuid = str(self.uuid) 

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

907 alert_prio_warning, 

908 alert_obj, alert_uuid, 

909 alert_str) 

910 

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

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

913 self.session.xenapi.VDI.remove_from_other_config( 

914 vdi_ref, cleanup.VDI.DB_LEAFCLSC) 

915 if secondary is not None: 

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

917 "blocking leaf coalesce") 

918 self.session.xenapi.VDI.add_to_other_config( 

919 vdi_ref, cleanup.VDI.DB_LEAFCLSC, 

920 cleanup.VDI.LEAFCLSC_DISABLED)