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 cbtutil 

27import os 

28import base64 

29from constants import CBTLOG_TAG 

30from bitarray import bitarray 

31from vditype import VdiType 

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 

43class VDI(object): 

44 """Virtual Disk Instance descriptor. 

45 

46 Attributes: 

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

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

49 description: string, longer user generated description string 

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

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

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

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

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

55 CoW instance 

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

57 e.g. shared OCFS disk 

58 attached: boolean, whether VDI is attached 

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

60 """ 

61 

62 def __init__(self, sr, uuid): 

63 self.sr = sr 

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

65 # ever come of this. 

66 if uuid is not None: 

67 self.uuid = uuid 

68 self.location = uuid 

69 self.path = None 

70 else: 

71 # We assume that children class initializors calling without 

72 # uuid will set these attributes themselves somewhere. They 

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

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

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

76 pass 

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

78 # ommitted from the XML output 

79 

80 self.label = '' 

81 self.description = '' 

82 self.vbds = [] 

83 self.size = 0 

84 self.utilisation = 0 

85 self.vdi_type = '' 

86 self.has_child = 0 

87 self.parent = None 

88 self.shareable = False 

89 self.attached = False 

90 self.status = 0 

91 self.read_only = False 

92 self.xenstore_data = {} 

93 self.deleted = False 

94 self.session = sr.session 

95 self.managed = True 

96 self.sm_config_override = {} 

97 self.sm_config_keep = ["key_hash"] 

98 self.ty = "user" 

99 self.cbt_enabled = False 

100 

101 self.load(uuid) 

102 

103 @staticmethod 

104 def from_uuid(session, vdi_uuid): 

105 

106 _VDI = session.xenapi.VDI 

107 vdi_ref = _VDI.get_by_uuid(vdi_uuid) 

108 sr_ref = _VDI.get_SR(vdi_ref) 

109 

110 _SR = session.xenapi.SR 

111 sr_uuid = _SR.get_uuid(sr_ref) 

112 

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

114 

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

116 return sr.vdi(vdi_uuid) 

117 

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

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

120 

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

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

123 be explicitly attached via the attach() command following 

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

125 requested size if the substrate requires a size in multiples 

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

127 size. 

128 """ 

129 raise xs_errors.XenError('Unimplemented') 

130 

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

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

133 

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

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

136 this call is made. 

137 """ 

138 # no-op unless individual backends implement it 

139 return 

140 

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

142 """Explicitly introduce a particular VDI. 

143 

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

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

146 object and then creates the XenAPI VDI object. 

147 """ 

148 raise xs_errors.XenError('Unimplemented') 

149 

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

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

152 state required to access the VDI. 

153 

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

155 attached or if the VDI is already attached. 

156 

157 Returns: 

158 string, local device path. 

159 """ 

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

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

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

163 

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

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

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

167 

168 This operation is idempotent. 

169 """ 

170 raise xs_errors.XenError('Unimplemented') 

171 

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

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

174 

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

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

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

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

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

180 cloning this operation should raise SRUnsupportedOperation. 

181 

182 Arguments: 

183 Raises: 

184 SRUnsupportedOperation 

185 """ 

186 raise xs_errors.XenError('Unimplemented') 

187 

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

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

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

191 raise xs_errors.XenError('Unimplemented') 

192 

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

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

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

196 vdi_attach_from_config() SMAPI call. 

197 """ 

198 raise xs_errors.XenError('Unimplemented') 

199 

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

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

202 [vdi2]. 

203 

204 Raises: 

205 SRUnsupportedOperation 

206 """ 

207 raise xs_errors.XenError('Unimplemented') 

208 

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

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

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

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

213 """ 

214 raise xs_errors.XenError('Unimplemented') 

215 

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

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

218 raise xs_errors.XenError('Unimplemented') 

219 

220 def _delete_cbt_log(self) -> None: 

221 raise xs_errors.XenError('Unimplemented') 

222 

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

224 raise xs_errors.XenError('Unimplemented') 

225 

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

227 """Check if CBT log file exists 

228 

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

230 """ 

231 raise xs_errors.XenError('Unimplemented') 

232 

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

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

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

236 the current value. 

237 

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

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

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

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

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

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

244 the disk such as the filesystem. Responsibility for resizing 

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

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

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

248 advance. 

249 """ 

250 raise xs_errors.XenError('Unimplemented') 

251 

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

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 try: 

270 if self._get_blocktracking_status(): 

271 logpath = self._get_cbt_logpath(vdi_uuid) 

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

273 except util.CommandException as ex: 

274 alert_name = "VDI_CBT_RESIZE_FAILED" 

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

276 % vdi_uuid) 

277 self._disable_cbt_on_error(alert_name, alert_str) 

278 

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

280 """Delete this VDI. 

281 

282 This operation IS idempotent and should succeed if the VDI 

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

284 the responsibility of the higher-level management tool to 

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

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

287 disk is still attached. 

288 """ 

289 import blktap2 

290 from lock import Lock 

291 

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

293 logpath = self._get_cbt_logpath(vdi_uuid) 

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

295 logpath) 

296 parent_path = self._get_cbt_logpath(parent_uuid) 

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

298 child_path = self._get_cbt_logpath(child_uuid) 

299 

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

301 

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

303 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

304 parent_path, child_uuid) 

305 

306 if self._cbt_log_exists(child_path): 

307 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

308 child_path, parent_uuid) 

309 lock.acquire() 

310 paused_for_coalesce = False 

311 try: 

312 # Coalesce contents of bitmap with child's bitmap 

313 # Check if child bitmap is currently attached 

314 consistent = self._cbt_op(child_uuid, 

315 cbtutil.get_cbt_consistency, 

316 child_path) 

317 if not consistent: 

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

319 sr_uuid, child_uuid): 

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

321 paused_for_coalesce = True 

322 self._activate_cbt_log(self._get_cbt_logname(vdi_uuid)) 

323 self._cbt_op(child_uuid, cbtutil.coalesce_bitmap, 

324 logpath, child_path) 

325 lock.release() 

326 except util.CommandException: 

327 # If there is an exception in coalescing, 

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

329 # to what they were 

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

331 " restoring to previous state") 

332 try: 

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

334 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

335 parent_path, vdi_uuid) 

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

337 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

338 child_path, vdi_uuid) 

339 finally: 

340 lock.release() 

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

342 return 

343 finally: 

344 # Unpause tapdisk if it wasn't originally paused 

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

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

347 child_uuid) 

348 lock.acquire() 

349 try: 

350 self._delete_cbt_log() 

351 finally: 

352 lock.release() 

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

354 

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

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

357 

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

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

360 be explicitly attached via the vdi_attach() command following 

361 creation. If the driver does not support snapshotting this 

362 operation should raise SRUnsupportedOperation 

363 

364 Arguments: 

365 Raises: 

366 SRUnsupportedOperation 

367 """ 

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

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

370 # in both cases, unless driver_params overrides it 

371 snapType = SNAPSHOT_DOUBLE 

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

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

374 snapType = SNAPSHOT_SINGLE 

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

376 snapType = SNAPSHOT_INTERNAL 

377 

378 secondary = None 

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

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

381 

382 is_mirror_destination = bool(self.sr.srcmd.params['driver_params'].get("base_mirror")) and not secondary 

383 # This allow us to know is we are a snapshot for a migration mirror on the destination SR to apply specific configuration on the QCOW2 snapshot. See qcow2util.py::QCowUtil.snapshot() for more details. 

384 

385 if self._get_blocktracking_status(): 

386 cbtlog = self._get_cbt_logpath(self.uuid) 

387 else: 

388 cbtlog = None 

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

390 secondary=secondary, cbtlog=cbtlog, is_mirror_destination=is_mirror_destination) 

391 

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

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

394 if self._get_blocktracking_status(): 

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

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

397 if read_write == "false": 

398 # Disk is being attached in RO mode, 

399 # don't attach metadata log file 

400 return None 

401 

402 from lock import Lock 

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

404 lock.acquire() 

405 

406 try: 

407 logpath = self._get_cbt_logpath(vdi_uuid) 

408 logname = self._get_cbt_logname(vdi_uuid) 

409 

410 # Activate CBT log file, if required 

411 self._activate_cbt_log(logname) 

412 finally: 

413 lock.release() 

414 

415 # Check and update consistency 

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

417 logpath) 

418 if not consistent: 

419 alert_name = "VDI_CBT_METADATA_INCONSISTENT" 

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

421 " for disk %s." % vdi_uuid) 

422 self._disable_cbt_on_error(alert_name, alert_str) 

423 return None 

424 

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

426 logpath, False) 

427 return {'cbtlog': logpath} 

428 return None 

429 

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

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

432 if self._get_blocktracking_status(): 

433 from lock import Lock 

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

435 lock.acquire() 

436 

437 try: 

438 logpath = self._get_cbt_logpath(vdi_uuid) 

439 logname = self._get_cbt_logname(vdi_uuid) 

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

441 # Finally deactivate log file 

442 self._deactivate_cbt_log(logname) 

443 finally: 

444 lock.release() 

445 

446 def get_params(self) -> str: 

447 """ 

448 Returns: 

449 XMLRPC response containing a single struct with fields 

450 'location' and 'uuid' 

451 """ 

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

453 'uuid': self.uuid} 

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

455 

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

457 """Post-init hook""" 

458 pass 

459 

460 def _db_introduce(self): 

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

462 sm_config = util.default(self, "sm_config", lambda: {}) 462 ↛ exitline 462 didn't run the lambda on line 462

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

464 for key in SM_CONFIG_PASS_THROUGH_FIELDS: 

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

466 if val: 

467 sm_config[key] = val 

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

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

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

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

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

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

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

475 return vdi 

476 

477 def _db_forget(self): 

478 self.sr.forget_vdi(self.uuid) 

479 

480 def _override_sm_config(self, sm_config): 

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

482 if val == sm_config.get(key): 482 ↛ 484line 482 didn't jump to line 484, because the condition on line 482 was never false

483 continue 

484 if val: 

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

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

487 sm_config[key] = val 

488 elif key in sm_config: 

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

490 del sm_config[key] 

491 

492 def _db_update_sm_config(self, ref, sm_config): 

493 import cleanup 

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

495 smconfig_protected_keys = [ 

496 cleanup.VDI.DB_VDI_PAUSED, 

497 cleanup.VDI.DB_VDI_BLOCKS, 

498 cleanup.VDI.DB_VDI_RELINKING, 

499 cleanup.VDI.DB_VDI_ACTIVATING] 

500 

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

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

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

504 key in smconfig_protected_keys): 

505 continue 

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

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

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

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

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

511 

512 for key in current_sm_config.keys(): 

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

514 key in smconfig_protected_keys or 

515 key in self.sm_config_keep): 

516 continue 

517 if not sm_config.get(key): 

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

519 (self.uuid, key)) 

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

521 

522 def _db_update(self): 

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

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

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

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

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

528 self._override_sm_config(sm_config) 

529 self._db_update_sm_config(vdi, sm_config) 

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

531 self._get_blocktracking_status()) 

532 

533 def in_sync_with_xenapi_record(self, x): 

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

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

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

537 return False 

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

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

540 return False 

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

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

543 return False 

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

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

546 return False 

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

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

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

550 return False 

551 for k in sm_config.keys(): 

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

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

554 return False 

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

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

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

558 return False 

559 return True 

560 

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

562 """Function for configuring blocktracking""" 

563 import blktap2 

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

565 

566 # Check if raw VDI or snapshot 

567 if not VdiType.isCowImage(self.vdi_type) or \ 

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

569 raise xs_errors.XenError('VDIType', 

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

571 

572 # Check if already enabled 

573 if self._get_blocktracking_status() == enable: 

574 return 

575 

576 # Save disk state before pause 

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

578 

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

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

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

582 logfile = None 

583 

584 try: 

585 if enable: 

586 try: 

587 # Check available space 

588 self._ensure_cbt_space() 

589 logfile = self._create_cbt_log() 

590 # Set consistency 

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

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

593 % self.uuid) 

594 logpath = self._get_cbt_logpath(self.uuid) 

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

596 logpath, False) 

597 except Exception as error: 

598 self._delete_cbt_log() 

599 raise xs_errors.XenError('CBTActivateFailed', 

600 opterr=str(error)) 

601 else: 

602 from lock import Lock 

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

604 lock.acquire() 

605 try: 

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

607 # and nullify its successor 

608 logpath = self._get_cbt_logpath(self.uuid) 

609 parent = self._cbt_op(self.uuid, 

610 cbtutil.get_cbt_parent, logpath) 

611 self._delete_cbt_log() 

612 parent_path = self._get_cbt_logpath(parent) 

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

614 self._cbt_op(parent, cbtutil.set_cbt_child, 

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

616 except Exception as error: 

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

618 finally: 

619 lock.release() 

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

621 finally: 

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

623 

624 def data_destroy(self, sr_uuid, vdi_uuid): 

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

626 

627 Can only be called for a snapshot VDI on a COW chain that has 

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

629 by upper layers 

630 """ 

631 

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

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

634 raise xs_errors.XenError('VDIType', 

635 opterr='Only allowed for snapshot VDIs') 

636 

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

638 

639 def list_changed_blocks(self): 

640 """ List all changed blocks """ 

641 vdi_from = self.uuid 

642 params = self.sr.srcmd.params 

643 _VDI = self.session.xenapi.VDI 

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

645 sr_uuid = params['sr_uuid'] 

646 

647 if vdi_from == vdi_to: 

648 raise xs_errors.XenError('CBTChangedBlocksError', 

649 "Source and target VDI are same") 

650 

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

652 if (self._get_blocktracking_status(vdi_from) and 

653 self._get_blocktracking_status(vdi_to)): 

654 merged_bitmap = None 

655 curr_vdi = vdi_from 

656 vdi_size = 0 

657 logpath = self._get_cbt_logpath(curr_vdi) 

658 

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

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

661 # * We've reached destination VDI 

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

663 while True: 

664 # Check if we have reached end of CBT chain 

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

666 logpath) 

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

668 # VDIs are not part of the same metadata chain 

669 break 

670 else: 

671 curr_vdi = next_vdi 

672 

673 logpath = self._get_cbt_logpath(curr_vdi) 

674 curr_vdi_size = self._cbt_op(curr_vdi, 

675 cbtutil.get_cbt_size, logpath) 

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

677 % (curr_vdi, curr_vdi_size)) 

678 curr_bitmap = bitarray() 

679 curr_bitmap.frombytes(self._cbt_op(curr_vdi, 

680 cbtutil.get_cbt_bitmap, 

681 logpath)) 

682 curr_bitmap.bytereverse() 

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

684 

685 expected_bitmap_len = curr_vdi_size // CBT_BLOCK_SIZE 

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

687 # changed blocks instead of returning corrupt data 

688 if len(curr_bitmap) < expected_bitmap_len: 

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

690 % (len(curr_bitmap), expected_bitmap_len)) 

691 raise xs_errors.XenError('CBTMetadataInconsistent', 

692 "Inconsistent bitmaps") 

693 

694 if merged_bitmap: 

695 # Rule out error conditions 

696 # 1) New VDI size < original VDI size 

697 # 2) New bitmap size < original bitmap size 

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

699 # is not bigger 

700 if (curr_vdi_size < vdi_size or 

701 len(curr_bitmap) < len(merged_bitmap) or 

702 (curr_vdi_size > vdi_size and 

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

704 # Return error: Failure to calculate changed blocks 

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

706 "inconsistent bitmap sizes") 

707 raise xs_errors.XenError('CBTMetadataInconsistent', 

708 "Inconsistent bitmaps") 

709 

710 # Check if disk has been resized 

711 if curr_vdi_size > vdi_size: 

712 vdi_size = curr_vdi_size 

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

714 # Extend merged_bitmap to match size of curr_bitmap 

715 extended_bitmap = extended_size * bitarray('0') 

716 merged_bitmap += extended_bitmap 

717 

718 # At this point bitmap sizes should be same 

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

720 curr_vdi_size == vdi_size): 

721 # This is unusual. Log it but calculate merged 

722 # bitmap by truncating new bitmap 

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

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

725 curr_bitmap = curr_bitmap[:len(merged_bitmap)] 

726 

727 merged_bitmap = merged_bitmap | curr_bitmap 

728 else: 

729 merged_bitmap = curr_bitmap 

730 vdi_size = curr_vdi_size 

731 

732 # Check if we have reached "vdi_to" 

733 if curr_vdi == vdi_to: 

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

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

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

737 # find common ancestor and find difference 

738 

739 # TODO: VDIs are unrelated 

740 # return fully populated bitmap size of to VDI 

741 

742 raise xs_errors.XenError('CBTChangedBlocksError', 

743 "Source and target VDI are unrelated") 

744 

745 def _cbt_snapshot(self, snapshot_uuid, consistency_state): 

746 """ CBT snapshot""" 

747 snap_logpath = self._get_cbt_logpath(snapshot_uuid) 

748 vdi_logpath = self._get_cbt_logpath(self.uuid) 

749 

750 # Rename vdi vdi.cbtlog to snapshot.cbtlog 

751 # and mark it consistent 

752 self._rename(vdi_logpath, snap_logpath) 

753 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_consistency, 

754 snap_logpath, True) 

755 

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

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

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

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

760 # do this 

761 parent = self._cbt_op(snapshot_uuid, 

762 cbtutil.get_cbt_parent, snap_logpath) 

763 parent_path = self._get_cbt_logpath(parent) 

764 if self._cbt_log_exists(parent_path): 

765 self._cbt_op(parent, cbtutil.set_cbt_child, 

766 parent_path, snapshot_uuid) 

767 try: 

768 # Ensure enough space for metadata file 

769 self._ensure_cbt_space() 

770 # Create new vdi.cbtlog 

771 self._create_cbt_log() 

772 # Set previous vdi node consistency status 

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

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

775 vdi_logpath, consistency_state) 

776 # Set relationship pointers 

777 # Save the child of the VDI just snapshotted 

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

779 snap_logpath) 

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

781 vdi_logpath, snapshot_uuid) 

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

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

784 vdi_logpath, curr_child_uuid) 

785 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_child, 

786 snap_logpath, self.uuid) 

787 except Exception as ex: 

788 alert_name = "VDI_CBT_SNAPSHOT_FAILED" 

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

790 % self.uuid) 

791 self._disable_cbt_on_error(alert_name, alert_str) 

792 

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

794 """ Get blocktracking status """ 

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

796 uuid = self.uuid 

797 if not VdiType.isCowImage(self.vdi_type): 797 ↛ 798line 797 didn't jump to line 798, because the condition on line 797 was never true

798 return False 

799 elif 'VDI_CONFIG_CBT' not in util.sr_get_capability( 

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

801 return False 

802 logpath = self._get_cbt_logpath(uuid) 

803 return self._cbt_log_exists(logpath) 

804 

805 def _set_blocktracking_status(self, vdi_ref, enable): 

806 """ Set blocktracking status""" 

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

808 if "cbt_enabled" in vdi_config: 

809 self.session.xenapi.VDI.remove_from_other_config( 

810 vdi_ref, "cbt_enabled") 

811 

812 self.session.xenapi.VDI.add_to_other_config( 

813 vdi_ref, "cbt_enabled", enable) 

814 

815 def _ensure_cbt_space(self) -> None: 

816 """ Ensure enough CBT space """ 

817 pass 

818 

819 def _get_cbt_logname(self, uuid): 

820 """ Get CBT logname """ 

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

822 return logName 

823 

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

825 """ Get CBT logpath """ 

826 logName = self._get_cbt_logname(uuid) 

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

828 

829 def _create_cbt_log(self) -> str: 

830 """ Create CBT log """ 

831 try: 

832 logpath = self._get_cbt_logpath(self.uuid) 

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

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

835 #cbtutil.create_cbt_log(logpath, size) 

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

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

838 except Exception as e: 

839 try: 

840 self._delete_cbt_log() 

841 except: 

842 pass 

843 finally: 

844 raise e 

845 

846 return logpath 

847 

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

849 """Activate CBT log file 

850 

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

852 No-op otherwise 

853 """ 

854 return False 

855 

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

857 """Deactivate CBT log file 

858 

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

860 No-op otherwise 

861 """ 

862 pass 

863 

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

865 # Lock cbtlog operations 

866 from lock import Lock 

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

868 lock.acquire() 

869 

870 try: 

871 logname = self._get_cbt_logname(uuid) 

872 activated = self._activate_cbt_log(logname) 

873 ret = func( * args) 

874 if activated: 

875 self._deactivate_cbt_log(logname) 

876 return ret 

877 finally: 

878 lock.release() 

879 

880 def _disable_cbt_on_error(self, alert_name, alert_str): 

881 util.SMlog(alert_str) 

882 self._delete_cbt_log() 

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

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

885 alert_prio_warning = "3" 

886 alert_obj = "VDI" 

887 alert_uuid = str(self.uuid) 

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

889 alert_prio_warning, 

890 alert_obj, alert_uuid, 

891 alert_str) 

892 

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

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

895 self.session.xenapi.VDI.remove_from_other_config( 

896 vdi_ref, cleanup.VDI.DB_LEAFCLSC) 

897 if secondary is not None: 

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

899 "blocking leaf coalesce") 

900 self.session.xenapi.VDI.add_to_other_config( 

901 vdi_ref, cleanup.VDI.DB_LEAFCLSC, 

902 cleanup.VDI.LEAFCLSC_DISABLED)