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 SR 

22import xmlrpc.client 

23import xs_errors 

24import util 

25import vhdutil 

26import cbtutil 

27import os 

28import base64 

29from constants import CBTLOG_TAG 

30from bitarray import bitarray 

31import uuid 

32 

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

34 

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

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

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

38CBT_BLOCK_SIZE = (64 * 1024) 

39 

40 

41def VDIMetadataSize(type, virtualsize): 

42 size = 0 

43 if type == 'vhd': 

44 size_mb = virtualsize // (1024 * 1024) 

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

46 size = 3 * 1024 

47 

48 # BAT 4 Bytes per block segment 

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

50 size = util.roundup(512, size) 

51 

52 # BATMAP 1 bit per block segment 

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

54 size = util.roundup(4096, size) 

55 

56 # Segment bitmaps + Page align offsets 

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

58 

59 return size 

60 

61 

62class VDI(object): 

63 """Virtual Disk Instance descriptor. 

64 

65 Attributes: 

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

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

68 description: string, longer user generated description string 

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

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

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

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

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

74 CoW instance 

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

76 e.g. shared OCFS disk 

77 attached: boolean, whether VDI is attached 

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

79 """ 

80 

81 def __init__(self, sr, uuid): 

82 self.sr = sr 

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

84 # ever come of this. 

85 if uuid is not None: 

86 self.uuid = uuid 

87 self.location = uuid 

88 self.path = None 

89 else: 

90 # We assume that children class initializors calling without 

91 # uuid will set these attributes themselves somewhere. They 

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

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

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

95 pass 

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

97 # ommitted from the XML output 

98 

99 self.label = '' 

100 self.description = '' 

101 self.vbds = [] 

102 self.size = 0 

103 self.utilisation = 0 

104 self.vdi_type = '' 

105 self.has_child = 0 

106 self.parent = None 

107 self.shareable = False 

108 self.attached = False 

109 self.status = 0 

110 self.read_only = False 

111 self.xenstore_data = {} 

112 self.deleted = False 

113 self.session = sr.session 

114 self.managed = True 

115 self.sm_config_override = {} 

116 self.sm_config_keep = ["key_hash"] 

117 self.ty = "user" 

118 self.cbt_enabled = False 

119 

120 self.load(uuid) 

121 

122 @staticmethod 

123 def from_uuid(session, vdi_uuid): 

124 

125 _VDI = session.xenapi.VDI 

126 vdi_ref = _VDI.get_by_uuid(vdi_uuid) 

127 sr_ref = _VDI.get_SR(vdi_ref) 

128 

129 _SR = session.xenapi.SR 

130 sr_uuid = _SR.get_uuid(sr_ref) 

131 

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

133 

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

135 return sr.vdi(vdi_uuid) 

136 

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

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

139 

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

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

142 be explicitly attached via the attach() command following 

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

144 requested size if the substrate requires a size in multiples 

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

146 size. 

147 """ 

148 raise xs_errors.XenError('Unimplemented') 

149 

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

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

152 

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

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

155 this call is made. 

156 """ 

157 # no-op unless individual backends implement it 

158 return 

159 

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

161 """Explicitly introduce a particular VDI. 

162 

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

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

165 object and then creates the XenAPI VDI object. 

166 """ 

167 raise xs_errors.XenError('Unimplemented') 

168 

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

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

171 state required to access the VDI. 

172 

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

174 attached or if the VDI is already attached. 

175 

176 Returns: 

177 string, local device path. 

178 """ 

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

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

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

182 

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

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

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

186 

187 This operation is idempotent. 

188 """ 

189 raise xs_errors.XenError('Unimplemented') 

190 

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

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

193 

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

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

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

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

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

199 cloning this operation should raise SRUnsupportedOperation. 

200 

201 Arguments: 

202 Raises: 

203 SRUnsupportedOperation 

204 """ 

205 raise xs_errors.XenError('Unimplemented') 

206 

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

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

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

210 raise xs_errors.XenError('Unimplemented') 

211 

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

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

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

215 vdi_attach_from_config() SMAPI call. 

216 """ 

217 raise xs_errors.XenError('Unimplemented') 

218 

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

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

221 [vdi2]. 

222 

223 Raises: 

224 SRUnsupportedOperation 

225 """ 

226 raise xs_errors.XenError('Unimplemented') 

227 

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

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

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

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

232 """ 

233 raise xs_errors.XenError('Unimplemented') 

234 

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

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

237 raise xs_errors.XenError('Unimplemented') 

238 

239 def _delete_cbt_log(self) -> None: 

240 raise xs_errors.XenError('Unimplemented') 

241 

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

243 raise xs_errors.XenError('Unimplemented') 

244 

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

246 """Check if CBT log file exists 

247 

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

249 """ 

250 raise xs_errors.XenError('Unimplemented') 

251 

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

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

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

255 the current value. 

256 

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

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

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

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

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

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

263 the disk such as the filesystem. Responsibility for resizing 

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

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

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

267 advance. 

268 """ 

269 raise xs_errors.XenError('Unimplemented') 

270 

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

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

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

274 the current value. 

275 

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

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

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

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

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

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

282 the disk such as the filesystem. Responsibility for resizing 

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

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

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

286 advance. 

287 """ 

288 try: 

289 if self._get_blocktracking_status(): 

290 logpath = self._get_cbt_logpath(vdi_uuid) 

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

292 except util.CommandException as ex: 

293 alert_name = "VDI_CBT_RESIZE_FAILED" 

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

295 % vdi_uuid) 

296 self._disable_cbt_on_error(alert_name, alert_str) 

297 

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

299 """Delete this VDI. 

300 

301 This operation IS idempotent and should succeed if the VDI 

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

303 the responsibility of the higher-level management tool to 

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

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

306 disk is still attached. 

307 """ 

308 import blktap2 

309 from lock import Lock 

310 

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

312 logpath = self._get_cbt_logpath(vdi_uuid) 

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

314 logpath) 

315 parent_path = self._get_cbt_logpath(parent_uuid) 

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

317 child_path = self._get_cbt_logpath(child_uuid) 

318 

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

320 

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

322 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

323 parent_path, child_uuid) 

324 

325 if self._cbt_log_exists(child_path): 

326 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

327 child_path, parent_uuid) 

328 lock.acquire() 

329 paused_for_coalesce = False 

330 try: 

331 # Coalesce contents of bitmap with child's bitmap 

332 # Check if child bitmap is currently attached 

333 consistent = self._cbt_op(child_uuid, 

334 cbtutil.get_cbt_consistency, 

335 child_path) 

336 if not consistent: 

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

338 sr_uuid, child_uuid): 

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

340 paused_for_coalesce = True 

341 self._activate_cbt_log(self._get_cbt_logname(vdi_uuid)) 

342 self._cbt_op(child_uuid, cbtutil.coalesce_bitmap, 

343 logpath, child_path) 

344 lock.release() 

345 except util.CommandException: 

346 # If there is an exception in coalescing, 

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

348 # to what they were 

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

350 " restoring to previous state") 

351 try: 

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

353 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

354 parent_path, vdi_uuid) 

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

356 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

357 child_path, vdi_uuid) 

358 finally: 

359 lock.release() 

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

361 return 

362 finally: 

363 # Unpause tapdisk if it wasn't originally paused 

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

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

366 child_uuid) 

367 lock.acquire() 

368 try: 

369 self._delete_cbt_log() 

370 finally: 

371 lock.release() 

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

373 

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

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

376 

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

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

379 be explicitly attached via the vdi_attach() command following 

380 creation. If the driver does not support snapshotting this 

381 operation should raise SRUnsupportedOperation 

382 

383 Arguments: 

384 Raises: 

385 SRUnsupportedOperation 

386 """ 

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

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

389 # in both cases, unless driver_params overrides it 

390 snapType = SNAPSHOT_DOUBLE 

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

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

393 snapType = SNAPSHOT_SINGLE 

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

395 snapType = SNAPSHOT_INTERNAL 

396 

397 secondary = None 

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

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

400 

401 if self._get_blocktracking_status(): 

402 cbtlog = self._get_cbt_logpath(self.uuid) 

403 else: 

404 cbtlog = None 

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

406 secondary=secondary, cbtlog=cbtlog) 

407 

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

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

410 if self._get_blocktracking_status(): 

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

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

413 if read_write == "false": 

414 # Disk is being attached in RO mode, 

415 # don't attach metadata log file 

416 return None 

417 

418 from lock import Lock 

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

420 lock.acquire() 

421 

422 try: 

423 logpath = self._get_cbt_logpath(vdi_uuid) 

424 logname = self._get_cbt_logname(vdi_uuid) 

425 

426 # Activate CBT log file, if required 

427 self._activate_cbt_log(logname) 

428 finally: 

429 lock.release() 

430 

431 # Check and update consistency 

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

433 logpath) 

434 if not consistent: 

435 alert_name = "VDI_CBT_METADATA_INCONSISTENT" 

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

437 " for disk %s." % vdi_uuid) 

438 self._disable_cbt_on_error(alert_name, alert_str) 

439 return None 

440 

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

442 logpath, False) 

443 return {'cbtlog': logpath} 

444 return None 

445 

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

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

448 if self._get_blocktracking_status(): 

449 from lock import Lock 

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

451 lock.acquire() 

452 

453 try: 

454 logpath = self._get_cbt_logpath(vdi_uuid) 

455 logname = self._get_cbt_logname(vdi_uuid) 

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

457 # Finally deactivate log file 

458 self._deactivate_cbt_log(logname) 

459 finally: 

460 lock.release() 

461 

462 def get_params(self) -> str: 

463 """ 

464 Returns: 

465 XMLRPC response containing a single struct with fields 

466 'location' and 'uuid' 

467 """ 

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

469 'uuid': self.uuid} 

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

471 

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

473 """Post-init hook""" 

474 pass 

475 

476 def _db_introduce(self): 

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

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

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

480 for key in SM_CONFIG_PASS_THROUGH_FIELDS: 

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

482 if val: 

483 sm_config[key] = val 

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

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

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

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

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

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

490 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) 

491 return vdi 

492 

493 def _db_forget(self): 

494 self.sr.forget_vdi(self.uuid) 

495 

496 def _override_sm_config(self, sm_config): 

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

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

499 continue 

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

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

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

503 sm_config[key] = val 

504 elif key in sm_config: 

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

506 del sm_config[key] 

507 

508 def _db_update_sm_config(self, ref, sm_config): 

509 import cleanup 

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

511 smconfig_protected_keys = [ 

512 cleanup.VDI.DB_VDI_PAUSED, 

513 cleanup.VDI.DB_VHD_BLOCKS, 

514 cleanup.VDI.DB_VDI_RELINKING, 

515 cleanup.VDI.DB_VDI_ACTIVATING] 

516 

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

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

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

520 key in smconfig_protected_keys): 

521 continue 

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

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

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

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

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

527 

528 for key in current_sm_config.keys(): 

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

530 key in smconfig_protected_keys or 

531 key in self.sm_config_keep): 

532 continue 

533 if not sm_config.get(key): 

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

535 (self.uuid, key)) 

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

537 

538 def _db_update(self): 

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

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

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

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

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

544 self._override_sm_config(sm_config) 

545 self._db_update_sm_config(vdi, sm_config) 

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

547 self._get_blocktracking_status()) 

548 

549 def in_sync_with_xenapi_record(self, x): 

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

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

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

553 return False 

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

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

556 return False 

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

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

559 return False 

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

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

562 return False 

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

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

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

566 return False 

567 for k in sm_config.keys(): 

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

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

570 return False 

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

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

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

574 return False 

575 return True 

576 

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

578 """Function for configuring blocktracking""" 

579 import blktap2 

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

581 

582 # Check if raw VDI or snapshot 

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

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

585 raise xs_errors.XenError('VDIType', 

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

587 

588 # Check if already enabled 

589 if self._get_blocktracking_status() == enable: 

590 return 

591 

592 # Save disk state before pause 

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

594 

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

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

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

598 logfile = None 

599 

600 try: 

601 if enable: 

602 try: 

603 # Check available space 

604 self._ensure_cbt_space() 

605 logfile = self._create_cbt_log() 

606 # Set consistency 

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

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

609 % self.uuid) 

610 logpath = self._get_cbt_logpath(self.uuid) 

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

612 logpath, False) 

613 except Exception as error: 

614 self._delete_cbt_log() 

615 raise xs_errors.XenError('CBTActivateFailed', 

616 opterr=str(error)) 

617 else: 

618 from lock import Lock 

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

620 lock.acquire() 

621 try: 

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

623 # and nullify its successor 

624 logpath = self._get_cbt_logpath(self.uuid) 

625 parent = self._cbt_op(self.uuid, 

626 cbtutil.get_cbt_parent, logpath) 

627 self._delete_cbt_log() 

628 parent_path = self._get_cbt_logpath(parent) 

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

630 self._cbt_op(parent, cbtutil.set_cbt_child, 

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

632 except Exception as error: 

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

634 finally: 

635 lock.release() 

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

637 finally: 

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

639 

640 def data_destroy(self, sr_uuid, vdi_uuid): 

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

642 

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

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

645 by upper layers 

646 """ 

647 

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

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

650 raise xs_errors.XenError('VDIType', 

651 opterr='Only allowed for snapshot VDIs') 

652 

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

654 

655 def list_changed_blocks(self): 

656 """ List all changed blocks """ 

657 vdi_from = self.uuid 

658 params = self.sr.srcmd.params 

659 _VDI = self.session.xenapi.VDI 

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

661 sr_uuid = params['sr_uuid'] 

662 

663 if vdi_from == vdi_to: 

664 raise xs_errors.XenError('CBTChangedBlocksError', 

665 "Source and target VDI are same") 

666 

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

668 if (self._get_blocktracking_status(vdi_from) and 

669 self._get_blocktracking_status(vdi_to)): 

670 merged_bitmap = None 

671 curr_vdi = vdi_from 

672 vdi_size = 0 

673 logpath = self._get_cbt_logpath(curr_vdi) 

674 

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

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

677 # * We've reached destination VDI 

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

679 while True: 

680 # Check if we have reached end of CBT chain 

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

682 logpath) 

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

684 # VDIs are not part of the same metadata chain 

685 break 

686 else: 

687 curr_vdi = next_vdi 

688 

689 logpath = self._get_cbt_logpath(curr_vdi) 

690 curr_vdi_size = self._cbt_op(curr_vdi, 

691 cbtutil.get_cbt_size, logpath) 

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

693 % (curr_vdi, curr_vdi_size)) 

694 curr_bitmap = bitarray() 

695 curr_bitmap.frombytes(self._cbt_op(curr_vdi, 

696 cbtutil.get_cbt_bitmap, 

697 logpath)) 

698 curr_bitmap.bytereverse() 

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

700 

701 expected_bitmap_len = curr_vdi_size // CBT_BLOCK_SIZE 

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

703 # changed blocks instead of returning corrupt data 

704 if len(curr_bitmap) < expected_bitmap_len: 

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

706 % (len(curr_bitmap), expected_bitmap_len)) 

707 raise xs_errors.XenError('CBTMetadataInconsistent', 

708 "Inconsistent bitmaps") 

709 

710 if merged_bitmap: 

711 # Rule out error conditions 

712 # 1) New VDI size < original VDI size 

713 # 2) New bitmap size < original bitmap size 

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

715 # is not bigger 

716 if (curr_vdi_size < vdi_size or 

717 len(curr_bitmap) < len(merged_bitmap) or 

718 (curr_vdi_size > vdi_size and 

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

720 # Return error: Failure to calculate changed blocks 

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

722 "inconsistent bitmap sizes") 

723 raise xs_errors.XenError('CBTMetadataInconsistent', 

724 "Inconsistent bitmaps") 

725 

726 # Check if disk has been resized 

727 if curr_vdi_size > vdi_size: 

728 vdi_size = curr_vdi_size 

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

730 # Extend merged_bitmap to match size of curr_bitmap 

731 extended_bitmap = extended_size * bitarray('0') 

732 merged_bitmap += extended_bitmap 

733 

734 # At this point bitmap sizes should be same 

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

736 curr_vdi_size == vdi_size): 

737 # This is unusual. Log it but calculate merged 

738 # bitmap by truncating new bitmap 

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

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

741 curr_bitmap = curr_bitmap[:len(merged_bitmap)] 

742 

743 merged_bitmap = merged_bitmap | curr_bitmap 

744 else: 

745 merged_bitmap = curr_bitmap 

746 vdi_size = curr_vdi_size 

747 

748 # Check if we have reached "vdi_to" 

749 if curr_vdi == vdi_to: 

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

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

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

753 # find common ancestor and find difference 

754 

755 # TODO: VDIs are unrelated 

756 # return fully populated bitmap size of to VDI 

757 

758 raise xs_errors.XenError('CBTChangedBlocksError', 

759 "Source and target VDI are unrelated") 

760 

761 def _cbt_snapshot(self, snapshot_uuid, consistency_state): 

762 """ CBT snapshot""" 

763 snap_logpath = self._get_cbt_logpath(snapshot_uuid) 

764 vdi_logpath = self._get_cbt_logpath(self.uuid) 

765 

766 # Rename vdi vdi.cbtlog to snapshot.cbtlog 

767 # and mark it consistent 

768 self._rename(vdi_logpath, snap_logpath) 

769 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_consistency, 

770 snap_logpath, True) 

771 

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

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

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

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

776 # do this 

777 parent = self._cbt_op(snapshot_uuid, 

778 cbtutil.get_cbt_parent, snap_logpath) 

779 parent_path = self._get_cbt_logpath(parent) 

780 if self._cbt_log_exists(parent_path): 

781 self._cbt_op(parent, cbtutil.set_cbt_child, 

782 parent_path, snapshot_uuid) 

783 try: 

784 # Ensure enough space for metadata file 

785 self._ensure_cbt_space() 

786 # Create new vdi.cbtlog 

787 self._create_cbt_log() 

788 # Set previous vdi node consistency status 

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

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

791 vdi_logpath, consistency_state) 

792 # Set relationship pointers 

793 # Save the child of the VDI just snapshotted 

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

795 snap_logpath) 

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

797 vdi_logpath, snapshot_uuid) 

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

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

800 vdi_logpath, curr_child_uuid) 

801 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_child, 

802 snap_logpath, self.uuid) 

803 except Exception as ex: 

804 alert_name = "VDI_CBT_SNAPSHOT_FAILED" 

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

806 % self.uuid) 

807 self._disable_cbt_on_error(alert_name, alert_str) 

808 

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

810 """ Get blocktracking status """ 

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

812 uuid = self.uuid 

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

814 return False 

815 elif 'VDI_CONFIG_CBT' not in util.sr_get_capability(self.sr.uuid): 

816 return False 

817 logpath = self._get_cbt_logpath(uuid) 

818 return self._cbt_log_exists(logpath) 

819 

820 def _set_blocktracking_status(self, vdi_ref, enable): 

821 """ Set blocktracking status""" 

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

823 if "cbt_enabled" in vdi_config: 

824 self.session.xenapi.VDI.remove_from_other_config( 

825 vdi_ref, "cbt_enabled") 

826 

827 self.session.xenapi.VDI.add_to_other_config( 

828 vdi_ref, "cbt_enabled", enable) 

829 

830 def _ensure_cbt_space(self) -> None: 

831 """ Ensure enough CBT space """ 

832 pass 

833 

834 def _get_cbt_logname(self, uuid): 

835 """ Get CBT logname """ 

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

837 return logName 

838 

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

840 """ Get CBT logpath """ 

841 logName = self._get_cbt_logname(uuid) 

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

843 

844 def _create_cbt_log(self) -> str: 

845 """ Create CBT log """ 

846 try: 

847 logpath = self._get_cbt_logpath(self.uuid) 

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

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

850 #cbtutil.create_cbt_log(logpath, size) 

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

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

853 except Exception as e: 

854 try: 

855 self._delete_cbt_log() 

856 except: 

857 pass 

858 finally: 

859 raise e 

860 

861 return logpath 

862 

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

864 """Activate CBT log file 

865 

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

867 No-op otherwise 

868 """ 

869 return False 

870 

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

872 """Deactivate CBT log file 

873 

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

875 No-op otherwise 

876 """ 

877 pass 

878 

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

880 # Lock cbtlog operations 

881 from lock import Lock 

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

883 lock.acquire() 

884 

885 try: 

886 logname = self._get_cbt_logname(uuid) 

887 activated = self._activate_cbt_log(logname) 

888 ret = func( * args) 

889 if activated: 

890 self._deactivate_cbt_log(logname) 

891 return ret 

892 finally: 

893 lock.release() 

894 

895 def _disable_cbt_on_error(self, alert_name, alert_str): 

896 util.SMlog(alert_str) 

897 self._delete_cbt_log() 

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

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

900 alert_prio_warning = "3" 

901 alert_obj = "VDI" 

902 alert_uuid = str(self.uuid) 

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

904 alert_prio_warning, 

905 alert_obj, alert_uuid, 

906 alert_str)