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 

33from constants import CBT_BLOCK_SIZE 

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 

40 

41 

42class VDI(object): 

43 """Virtual Disk Instance descriptor. 

44 

45 Attributes: 

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

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

48 description: string, longer user generated description string 

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

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

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

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

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

54 CoW instance 

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

56 e.g. shared OCFS disk 

57 attached: boolean, whether VDI is attached 

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

59 """ 

60 

61 def __init__(self, sr, uuid): 

62 self.sr = sr 

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

64 # ever come of this. 

65 if uuid is not None: 

66 self.uuid = uuid 

67 self.location = uuid 

68 self.path = None 

69 else: 

70 # We assume that children class initializors calling without 

71 # uuid will set these attributes themselves somewhere. They 

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

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

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

75 pass 

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

77 # ommitted from the XML output 

78 

79 self.label = '' 

80 self.description = '' 

81 self.vbds = [] 

82 self.size = 0 

83 self.utilisation = 0 

84 self.vdi_type = '' 

85 self.has_child = 0 

86 self.parent = None 

87 self.shareable = False 

88 self.attached = False 

89 self.status = 0 

90 self.read_only = False 

91 self.xenstore_data = {} 

92 self.deleted = False 

93 self.session = sr.session 

94 self.managed = True 

95 self.sm_config_override = {} 

96 self.sm_config_keep = ["key_hash"] 

97 self.ty = "user" 

98 self.cbt_enabled = False 

99 

100 self.load(uuid) 

101 

102 @staticmethod 

103 def from_uuid(session, vdi_uuid): 

104 vdi_ref = session.xenapi.VDI.get_by_uuid(vdi_uuid) 

105 sr_uuid = util.get_sr_uuid_from_vdi_ref(session, vdi_ref) 

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

107 

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

109 return sr.vdi(vdi_uuid) 

110 

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

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

113 

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

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

116 be explicitly attached via the attach() command following 

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

118 requested size if the substrate requires a size in multiples 

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

120 size. 

121 """ 

122 raise xs_errors.XenError('Unimplemented') 

123 

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

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

126 

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

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

129 this call is made. 

130 """ 

131 # no-op unless individual backends implement it 

132 return 

133 

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

135 """Explicitly introduce a particular VDI. 

136 

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

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

139 object and then creates the XenAPI VDI object. 

140 """ 

141 raise xs_errors.XenError('Unimplemented') 

142 

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

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

145 state required to access the VDI. 

146 

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

148 attached or if the VDI is already attached. 

149 

150 Returns: 

151 string, local device path. 

152 """ 

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

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

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

156 

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

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

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

160 

161 This operation is idempotent. 

162 """ 

163 raise xs_errors.XenError('Unimplemented') 

164 

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

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

167 

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

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

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

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

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

173 cloning this operation should raise SRUnsupportedOperation. 

174 

175 Arguments: 

176 Raises: 

177 SRUnsupportedOperation 

178 """ 

179 raise xs_errors.XenError('Unimplemented') 

180 

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

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

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

184 raise xs_errors.XenError('Unimplemented') 

185 

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

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

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

189 vdi_attach_from_config() SMAPI call. 

190 """ 

191 raise xs_errors.XenError('Unimplemented') 

192 

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

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

195 [vdi2]. 

196 

197 Raises: 

198 SRUnsupportedOperation 

199 """ 

200 raise xs_errors.XenError('Unimplemented') 

201 

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

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

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

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

206 """ 

207 raise xs_errors.XenError('Unimplemented') 

208 

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

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

211 raise xs_errors.XenError('Unimplemented') 

212 

213 def _delete_cbt_log(self) -> None: 

214 raise xs_errors.XenError('Unimplemented') 

215 

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

217 raise xs_errors.XenError('Unimplemented') 

218 

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

220 """Check if CBT log file exists 

221 

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

223 """ 

224 raise xs_errors.XenError('Unimplemented') 

225 

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

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

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

229 the current value. 

230 

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

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

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

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

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

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

237 the disk such as the filesystem. Responsibility for resizing 

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

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

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

241 advance. 

242 """ 

243 raise xs_errors.XenError('Unimplemented') 

244 

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

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

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

248 the current value. 

249 

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

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

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

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

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

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

256 the disk such as the filesystem. Responsibility for resizing 

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

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

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

260 advance. 

261 """ 

262 try: 

263 if self._get_blocktracking_status(): 

264 logpath = self._get_cbt_logpath(vdi_uuid) 

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

266 except util.CommandException as ex: 

267 alert_name = "VDI_CBT_RESIZE_FAILED" 

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

269 % vdi_uuid) 

270 self._disable_cbt_on_error(alert_name, alert_str) 

271 

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

273 """Delete this VDI. 

274 

275 This operation IS idempotent and should succeed if the VDI 

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

277 the responsibility of the higher-level management tool to 

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

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

280 disk is still attached. 

281 """ 

282 import blktap2 

283 from lock import Lock 

284 

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

286 logpath = self._get_cbt_logpath(vdi_uuid) 

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

288 logpath) 

289 parent_path = self._get_cbt_logpath(parent_uuid) 

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

291 child_path = self._get_cbt_logpath(child_uuid) 

292 

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

294 

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

296 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

297 parent_path, child_uuid) 

298 

299 if self._cbt_log_exists(child_path): 

300 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

301 child_path, parent_uuid) 

302 lock.acquire() 

303 paused_for_coalesce = False 

304 try: 

305 # Coalesce contents of bitmap with child's bitmap 

306 # Check if child bitmap is currently attached 

307 consistent = self._cbt_op(child_uuid, 

308 cbtutil.get_cbt_consistency, 

309 child_path) 

310 if not consistent: 

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

312 sr_uuid, child_uuid): 

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

314 paused_for_coalesce = True 

315 self._activate_cbt_log(self._get_cbt_logname(vdi_uuid)) 

316 self._cbt_op(child_uuid, cbtutil.coalesce_bitmap, 

317 logpath, child_path) 

318 lock.release() 

319 except util.CommandException: 

320 # If there is an exception in coalescing, 

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

322 # to what they were 

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

324 " restoring to previous state") 

325 try: 

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

327 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

328 parent_path, vdi_uuid) 

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

330 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

331 child_path, vdi_uuid) 

332 finally: 

333 lock.release() 

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

335 return 

336 finally: 

337 # Unpause tapdisk if it wasn't originally paused 

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

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

340 child_uuid) 

341 lock.acquire() 

342 try: 

343 self._delete_cbt_log() 

344 finally: 

345 lock.release() 

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

347 

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

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

350 

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

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

353 be explicitly attached via the vdi_attach() command following 

354 creation. If the driver does not support snapshotting this 

355 operation should raise SRUnsupportedOperation 

356 

357 Arguments: 

358 Raises: 

359 SRUnsupportedOperation 

360 """ 

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

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

363 # in both cases, unless driver_params overrides it 

364 snapType = SNAPSHOT_DOUBLE 

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

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

367 snapType = SNAPSHOT_SINGLE 

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

369 snapType = SNAPSHOT_INTERNAL 

370 

371 secondary = None 

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

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

374 

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

376 # 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. 

377 

378 if self._get_blocktracking_status(): 

379 cbtlog = self._get_cbt_logpath(self.uuid) 

380 else: 

381 cbtlog = None 

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

383 secondary=secondary, cbtlog=cbtlog, is_mirror_destination=is_mirror_destination) 

384 

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

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

387 if self._get_blocktracking_status(): 

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

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

390 if read_write == "false": 

391 # Disk is being attached in RO mode, 

392 # don't attach metadata log file 

393 return None 

394 

395 from lock import Lock 

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

397 lock.acquire() 

398 

399 try: 

400 logpath = self._get_cbt_logpath(vdi_uuid) 

401 logname = self._get_cbt_logname(vdi_uuid) 

402 

403 # Activate CBT log file, if required 

404 self._activate_cbt_log(logname) 

405 finally: 

406 lock.release() 

407 

408 # Check and update consistency 

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

410 logpath) 

411 if not consistent: 

412 alert_name = "VDI_CBT_METADATA_INCONSISTENT" 

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

414 " for disk %s." % vdi_uuid) 

415 self._disable_cbt_on_error(alert_name, alert_str) 

416 return None 

417 

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

419 logpath, False) 

420 return {'cbtlog': logpath} 

421 return None 

422 

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

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

425 if self._get_blocktracking_status(): 

426 from lock import Lock 

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

428 lock.acquire() 

429 

430 try: 

431 logpath = self._get_cbt_logpath(vdi_uuid) 

432 logname = self._get_cbt_logname(vdi_uuid) 

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

434 # Finally deactivate log file 

435 self._deactivate_cbt_log(logname) 

436 finally: 

437 lock.release() 

438 

439 def get_params(self) -> str: 

440 """ 

441 Returns: 

442 XMLRPC response containing a single struct with fields 

443 'location' and 'uuid' 

444 """ 

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

446 'uuid': self.uuid} 

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

448 

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

450 """Post-init hook""" 

451 pass 

452 

453 def _db_introduce(self): 

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

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

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

457 for key in SM_CONFIG_PASS_THROUGH_FIELDS: 

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

459 if val: 

460 sm_config[key] = val 

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

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

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

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

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

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

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

468 return vdi 

469 

470 def _db_forget(self): 

471 self.sr.forget_vdi(self.uuid) 

472 

473 def _override_sm_config(self, sm_config): 

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

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

476 continue 

477 if val: 

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

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

480 sm_config[key] = val 

481 elif key in sm_config: 

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

483 del sm_config[key] 

484 

485 def _db_update_sm_config(self, ref, sm_config): 

486 import cleanup 

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

488 smconfig_protected_keys = [ 

489 cleanup.VDI.DB_VDI_PAUSED, 

490 cleanup.VDI.DB_VDI_BLOCKS, 

491 cleanup.VDI.DB_VDI_RELINKING, 

492 cleanup.VDI.DB_VDI_ACTIVATING] 

493 

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

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

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

497 key in smconfig_protected_keys): 

498 continue 

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

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

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

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

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

504 

505 for key in current_sm_config.keys(): 

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

507 key in smconfig_protected_keys or 

508 key in self.sm_config_keep): 

509 continue 

510 if not sm_config.get(key): 

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

512 (self.uuid, key)) 

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

514 

515 def _db_update(self): 

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

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

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

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

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

521 self._override_sm_config(sm_config) 

522 self._db_update_sm_config(vdi, sm_config) 

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

524 self._get_blocktracking_status()) 

525 

526 def in_sync_with_xenapi_record(self, x): 

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

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

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

530 return False 

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

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

533 return False 

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

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

536 return False 

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

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

539 return False 

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

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

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

543 return False 

544 for k in sm_config.keys(): 

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

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

547 return False 

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

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

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

551 return False 

552 return True 

553 

554 def update_slaves_on_cbt_disable(self, cbtlog): 

555 # Override in implementation as required. 

556 pass 

557 

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

559 """Function for configuring blocktracking""" 

560 import blktap2 

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

562 

563 # Check if raw VDI or snapshot 

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

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

566 raise xs_errors.XenError('VDIType', 

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

568 

569 # Check if already enabled 

570 if self._get_blocktracking_status() == enable: 

571 return 

572 

573 # Save disk state before pause 

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

575 

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

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

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

579 logfile = None 

580 

581 self.size = int(self.session.xenapi.VDI.get_virtual_size(vdi_ref)) 

582 # We need virtual_size to compute the CBT volume size in case of a bigger VDI (e.g. for creating LV) but it's not already available 

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 ↛ 624line 591 didn't jump to line 624, 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 ↛ 616line 613 didn't jump to line 616, 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 if disk_state: 616 ↛ 621line 616 didn't jump to line 621, because the condition on line 616 was never false

617 self.update_slaves_on_cbt_disable(logpath) 

618 except Exception as error: 

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

620 finally: 

621 lock.release() 

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

623 finally: 

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

625 

626 def data_destroy(self, sr_uuid, vdi_uuid): 

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

628 

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

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

631 by upper layers 

632 """ 

633 

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

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

636 raise xs_errors.XenError('VDIType', 

637 opterr='Only allowed for snapshot VDIs') 

638 

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

640 

641 def list_changed_blocks(self): 

642 """ List all changed blocks """ 

643 vdi_from = self.uuid 

644 params = self.sr.srcmd.params 

645 _VDI = self.session.xenapi.VDI 

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

647 sr_uuid = params['sr_uuid'] 

648 

649 if vdi_from == vdi_to: 

650 raise xs_errors.XenError('CBTChangedBlocksError', 

651 "Source and target VDI are same") 

652 

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

654 if (self._get_blocktracking_status(vdi_from) and 

655 self._get_blocktracking_status(vdi_to)): 

656 merged_bitmap = None 

657 curr_vdi = vdi_from 

658 vdi_size = 0 

659 logpath = self._get_cbt_logpath(curr_vdi) 

660 

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

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

663 # * We've reached destination VDI 

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

665 while True: 

666 # Check if we have reached end of CBT chain 

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

668 logpath) 

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

670 # VDIs are not part of the same metadata chain 

671 break 

672 else: 

673 curr_vdi = next_vdi 

674 

675 logpath = self._get_cbt_logpath(curr_vdi) 

676 curr_vdi_size = self._cbt_op(curr_vdi, 

677 cbtutil.get_cbt_size, logpath) 

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

679 % (curr_vdi, curr_vdi_size)) 

680 curr_bitmap = bitarray() 

681 curr_bitmap.frombytes(self._cbt_op(curr_vdi, 

682 cbtutil.get_cbt_bitmap, 

683 logpath)) 

684 curr_bitmap.bytereverse() 

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

686 

687 expected_bitmap_len = curr_vdi_size // CBT_BLOCK_SIZE 

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

689 # changed blocks instead of returning corrupt data 

690 if len(curr_bitmap) < expected_bitmap_len: 

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

692 % (len(curr_bitmap), expected_bitmap_len)) 

693 raise xs_errors.XenError('CBTMetadataInconsistent', 

694 "Inconsistent bitmaps") 

695 

696 if merged_bitmap: 

697 # Rule out error conditions 

698 # 1) New VDI size < original VDI size 

699 # 2) New bitmap size < original bitmap size 

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

701 # is not bigger 

702 if (curr_vdi_size < vdi_size or 

703 len(curr_bitmap) < len(merged_bitmap) or 

704 (curr_vdi_size > vdi_size and 

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

706 # Return error: Failure to calculate changed blocks 

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

708 "inconsistent bitmap sizes") 

709 raise xs_errors.XenError('CBTMetadataInconsistent', 

710 "Inconsistent bitmaps") 

711 

712 # Check if disk has been resized 

713 if curr_vdi_size > vdi_size: 

714 vdi_size = curr_vdi_size 

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

716 # Extend merged_bitmap to match size of curr_bitmap 

717 extended_bitmap = extended_size * bitarray('0') 

718 merged_bitmap += extended_bitmap 

719 

720 # At this point bitmap sizes should be same 

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

722 curr_vdi_size == vdi_size): 

723 # This is unusual. Log it but calculate merged 

724 # bitmap by truncating new bitmap 

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

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

727 curr_bitmap = curr_bitmap[:len(merged_bitmap)] 

728 

729 merged_bitmap = merged_bitmap | curr_bitmap 

730 else: 

731 merged_bitmap = curr_bitmap 

732 vdi_size = curr_vdi_size 

733 

734 # Check if we have reached "vdi_to" 

735 if curr_vdi == vdi_to: 

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

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

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

739 # find common ancestor and find difference 

740 

741 # TODO: VDIs are unrelated 

742 # return fully populated bitmap size of to VDI 

743 

744 raise xs_errors.XenError('CBTChangedBlocksError', 

745 "Source and target VDI are unrelated") 

746 

747 def _cbt_snapshot(self, snapshot_uuid, consistency_state): 

748 """ CBT snapshot""" 

749 snap_logpath = self._get_cbt_logpath(snapshot_uuid) 

750 vdi_logpath = self._get_cbt_logpath(self.uuid) 

751 

752 # Rename vdi vdi.cbtlog to snapshot.cbtlog 

753 # and mark it consistent 

754 self._rename(vdi_logpath, snap_logpath) 

755 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_consistency, 

756 snap_logpath, True) 

757 

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

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

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

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

762 # do this 

763 parent = self._cbt_op(snapshot_uuid, 

764 cbtutil.get_cbt_parent, snap_logpath) 

765 parent_path = self._get_cbt_logpath(parent) 

766 if self._cbt_log_exists(parent_path): 

767 self._cbt_op(parent, cbtutil.set_cbt_child, 

768 parent_path, snapshot_uuid) 

769 try: 

770 # Ensure enough space for metadata file 

771 self._ensure_cbt_space() 

772 # Create new vdi.cbtlog 

773 self._create_cbt_log() 

774 # Set previous vdi node consistency status 

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

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

777 vdi_logpath, consistency_state) 

778 # Set relationship pointers 

779 # Save the child of the VDI just snapshotted 

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

781 snap_logpath) 

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

783 vdi_logpath, snapshot_uuid) 

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

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

786 vdi_logpath, curr_child_uuid) 

787 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_child, 

788 snap_logpath, self.uuid) 

789 except Exception as ex: 

790 alert_name = "VDI_CBT_SNAPSHOT_FAILED" 

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

792 % self.uuid) 

793 self._disable_cbt_on_error(alert_name, alert_str) 

794 

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

796 """ Get blocktracking status """ 

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

798 uuid = self.uuid 

799 if self.vdi_type == VdiType.RAW: 799 ↛ 800line 799 didn't jump to line 800, because the condition on line 799 was never true

800 return False 

801 elif 'VDI_CONFIG_CBT' not in util.sr_get_capability( 

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

803 return False 

804 logpath = self._get_cbt_logpath(uuid) 

805 return self._cbt_log_exists(logpath) 

806 

807 def _set_blocktracking_status(self, vdi_ref, enable): 

808 """ Set blocktracking status""" 

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

810 if "cbt_enabled" in vdi_config: 

811 self.session.xenapi.VDI.remove_from_other_config( 

812 vdi_ref, "cbt_enabled") 

813 

814 self.session.xenapi.VDI.add_to_other_config( 

815 vdi_ref, "cbt_enabled", enable) 

816 

817 def _ensure_cbt_space(self) -> None: 

818 """ Ensure enough CBT space """ 

819 pass 

820 

821 def _get_cbt_logname(self, uuid): 

822 """ Get CBT logname """ 

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

824 return logName 

825 

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

827 """ Get CBT logpath """ 

828 logName = self._get_cbt_logname(uuid) 

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

830 

831 def _create_cbt_log(self) -> str: 

832 """ Create CBT log """ 

833 try: 

834 logpath = self._get_cbt_logpath(self.uuid) 

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

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

837 #cbtutil.create_cbt_log(logpath, size) 

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

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

840 except Exception as e: 

841 try: 

842 self._delete_cbt_log() 

843 except: 

844 pass 

845 finally: 

846 raise e 

847 

848 return logpath 

849 

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

851 """Activate CBT log file 

852 

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

854 No-op otherwise 

855 """ 

856 return False 

857 

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

859 """Deactivate CBT log file 

860 

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

862 No-op otherwise 

863 """ 

864 pass 

865 

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

867 # Lock cbtlog operations 

868 from lock import Lock 

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

870 lock.acquire() 

871 

872 try: 

873 logname = self._get_cbt_logname(uuid) 

874 activated = self._activate_cbt_log(logname) 

875 ret = func( * args) 

876 if activated: 

877 self._deactivate_cbt_log(logname) 

878 return ret 

879 finally: 

880 lock.release() 

881 

882 def _disable_cbt_on_error(self, alert_name, alert_str): 

883 util.SMlog(alert_str) 

884 self._delete_cbt_log() 

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

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

887 alert_prio_warning = "3" 

888 alert_obj = "VDI" 

889 alert_uuid = str(self.uuid) 

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

891 alert_prio_warning, 

892 alert_obj, alert_uuid, 

893 alert_str) 

894 

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

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

897 self.session.xenapi.VDI.remove_from_other_config( 

898 vdi_ref, cleanup.VDI.DB_LEAFCLSC) 

899 if secondary is not None: 

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

901 "blocking leaf coalesce") 

902 self.session.xenapi.VDI.add_to_other_config( 

903 vdi_ref, cleanup.VDI.DB_LEAFCLSC, 

904 cleanup.VDI.LEAFCLSC_DISABLED)