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 

105 _VDI = session.xenapi.VDI 

106 vdi_ref = _VDI.get_by_uuid(vdi_uuid) 

107 sr_ref = _VDI.get_SR(vdi_ref) 

108 

109 _SR = session.xenapi.SR 

110 sr_uuid = _SR.get_uuid(sr_ref) 

111 

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

113 

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

115 return sr.vdi(vdi_uuid) 

116 

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

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

119 

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

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

122 be explicitly attached via the attach() command following 

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

124 requested size if the substrate requires a size in multiples 

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

126 size. 

127 """ 

128 raise xs_errors.XenError('Unimplemented') 

129 

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

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

132 

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

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

135 this call is made. 

136 """ 

137 # no-op unless individual backends implement it 

138 return 

139 

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

141 """Explicitly introduce a particular VDI. 

142 

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

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

145 object and then creates the XenAPI VDI object. 

146 """ 

147 raise xs_errors.XenError('Unimplemented') 

148 

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

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

151 state required to access the VDI. 

152 

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

154 attached or if the VDI is already attached. 

155 

156 Returns: 

157 string, local device path. 

158 """ 

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

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

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

162 

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

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

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

166 

167 This operation is idempotent. 

168 """ 

169 raise xs_errors.XenError('Unimplemented') 

170 

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

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

173 

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

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

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

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

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

179 cloning this operation should raise SRUnsupportedOperation. 

180 

181 Arguments: 

182 Raises: 

183 SRUnsupportedOperation 

184 """ 

185 raise xs_errors.XenError('Unimplemented') 

186 

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

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

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

190 raise xs_errors.XenError('Unimplemented') 

191 

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

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

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

195 vdi_attach_from_config() SMAPI call. 

196 """ 

197 raise xs_errors.XenError('Unimplemented') 

198 

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

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

201 [vdi2]. 

202 

203 Raises: 

204 SRUnsupportedOperation 

205 """ 

206 raise xs_errors.XenError('Unimplemented') 

207 

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

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

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

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

212 """ 

213 raise xs_errors.XenError('Unimplemented') 

214 

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

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

217 raise xs_errors.XenError('Unimplemented') 

218 

219 def _delete_cbt_log(self) -> None: 

220 raise xs_errors.XenError('Unimplemented') 

221 

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

223 raise xs_errors.XenError('Unimplemented') 

224 

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

226 """Check if CBT log file exists 

227 

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

229 """ 

230 raise xs_errors.XenError('Unimplemented') 

231 

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

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

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

235 the current value. 

236 

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

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

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

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

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

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

243 the disk such as the filesystem. Responsibility for resizing 

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

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

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

247 advance. 

248 """ 

249 raise xs_errors.XenError('Unimplemented') 

250 

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

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

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

254 the current value. 

255 

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

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

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

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

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

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

262 the disk such as the filesystem. Responsibility for resizing 

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

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

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

266 advance. 

267 """ 

268 try: 

269 if self._get_blocktracking_status(): 

270 logpath = self._get_cbt_logpath(vdi_uuid) 

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

272 except util.CommandException as ex: 

273 alert_name = "VDI_CBT_RESIZE_FAILED" 

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

275 % vdi_uuid) 

276 self._disable_cbt_on_error(alert_name, alert_str) 

277 

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

279 """Delete this VDI. 

280 

281 This operation IS idempotent and should succeed if the VDI 

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

283 the responsibility of the higher-level management tool to 

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

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

286 disk is still attached. 

287 """ 

288 import blktap2 

289 from lock import Lock 

290 

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

292 logpath = self._get_cbt_logpath(vdi_uuid) 

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

294 logpath) 

295 parent_path = self._get_cbt_logpath(parent_uuid) 

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

297 child_path = self._get_cbt_logpath(child_uuid) 

298 

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

300 

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

302 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

303 parent_path, child_uuid) 

304 

305 if self._cbt_log_exists(child_path): 

306 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

307 child_path, parent_uuid) 

308 lock.acquire() 

309 paused_for_coalesce = False 

310 try: 

311 # Coalesce contents of bitmap with child's bitmap 

312 # Check if child bitmap is currently attached 

313 consistent = self._cbt_op(child_uuid, 

314 cbtutil.get_cbt_consistency, 

315 child_path) 

316 if not consistent: 

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

318 sr_uuid, child_uuid): 

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

320 paused_for_coalesce = True 

321 self._activate_cbt_log(self._get_cbt_logname(vdi_uuid)) 

322 self._cbt_op(child_uuid, cbtutil.coalesce_bitmap, 

323 logpath, child_path) 

324 lock.release() 

325 except util.CommandException: 

326 # If there is an exception in coalescing, 

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

328 # to what they were 

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

330 " restoring to previous state") 

331 try: 

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

333 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

334 parent_path, vdi_uuid) 

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

336 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

337 child_path, vdi_uuid) 

338 finally: 

339 lock.release() 

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

341 return 

342 finally: 

343 # Unpause tapdisk if it wasn't originally paused 

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

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

346 child_uuid) 

347 lock.acquire() 

348 try: 

349 self._delete_cbt_log() 

350 finally: 

351 lock.release() 

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

353 

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

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

356 

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

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

359 be explicitly attached via the vdi_attach() command following 

360 creation. If the driver does not support snapshotting this 

361 operation should raise SRUnsupportedOperation 

362 

363 Arguments: 

364 Raises: 

365 SRUnsupportedOperation 

366 """ 

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

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

369 # in both cases, unless driver_params overrides it 

370 snapType = SNAPSHOT_DOUBLE 

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

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

373 snapType = SNAPSHOT_SINGLE 

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

375 snapType = SNAPSHOT_INTERNAL 

376 

377 secondary = None 

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

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

380 

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

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

383 

384 if self._get_blocktracking_status(): 

385 cbtlog = self._get_cbt_logpath(self.uuid) 

386 else: 

387 cbtlog = None 

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

389 secondary=secondary, cbtlog=cbtlog, is_mirror_destination=is_mirror_destination) 

390 

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

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

393 if self._get_blocktracking_status(): 

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

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

396 if read_write == "false": 

397 # Disk is being attached in RO mode, 

398 # don't attach metadata log file 

399 return None 

400 

401 from lock import Lock 

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

403 lock.acquire() 

404 

405 try: 

406 logpath = self._get_cbt_logpath(vdi_uuid) 

407 logname = self._get_cbt_logname(vdi_uuid) 

408 

409 # Activate CBT log file, if required 

410 self._activate_cbt_log(logname) 

411 finally: 

412 lock.release() 

413 

414 # Check and update consistency 

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

416 logpath) 

417 if not consistent: 

418 alert_name = "VDI_CBT_METADATA_INCONSISTENT" 

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

420 " for disk %s." % vdi_uuid) 

421 self._disable_cbt_on_error(alert_name, alert_str) 

422 return None 

423 

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

425 logpath, False) 

426 return {'cbtlog': logpath} 

427 return None 

428 

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

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

431 if self._get_blocktracking_status(): 

432 from lock import Lock 

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

434 lock.acquire() 

435 

436 try: 

437 logpath = self._get_cbt_logpath(vdi_uuid) 

438 logname = self._get_cbt_logname(vdi_uuid) 

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

440 # Finally deactivate log file 

441 self._deactivate_cbt_log(logname) 

442 finally: 

443 lock.release() 

444 

445 def get_params(self) -> str: 

446 """ 

447 Returns: 

448 XMLRPC response containing a single struct with fields 

449 'location' and 'uuid' 

450 """ 

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

452 'uuid': self.uuid} 

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

454 

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

456 """Post-init hook""" 

457 pass 

458 

459 def _db_introduce(self): 

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

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

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

463 for key in SM_CONFIG_PASS_THROUGH_FIELDS: 

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

465 if val: 

466 sm_config[key] = val 

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

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

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

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

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

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

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

474 return vdi 

475 

476 def _db_forget(self): 

477 self.sr.forget_vdi(self.uuid) 

478 

479 def _override_sm_config(self, sm_config): 

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

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

482 continue 

483 if val: 

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

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

486 sm_config[key] = val 

487 elif key in sm_config: 

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

489 del sm_config[key] 

490 

491 def _db_update_sm_config(self, ref, sm_config): 

492 import cleanup 

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

494 smconfig_protected_keys = [ 

495 cleanup.VDI.DB_VDI_PAUSED, 

496 cleanup.VDI.DB_VDI_BLOCKS, 

497 cleanup.VDI.DB_VDI_RELINKING, 

498 cleanup.VDI.DB_VDI_ACTIVATING] 

499 

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

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

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

503 key in smconfig_protected_keys): 

504 continue 

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

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

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

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

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

510 

511 for key in current_sm_config.keys(): 

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

513 key in smconfig_protected_keys or 

514 key in self.sm_config_keep): 

515 continue 

516 if not sm_config.get(key): 

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

518 (self.uuid, key)) 

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

520 

521 def _db_update(self): 

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

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

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

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

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

527 self._override_sm_config(sm_config) 

528 self._db_update_sm_config(vdi, sm_config) 

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

530 self._get_blocktracking_status()) 

531 

532 def in_sync_with_xenapi_record(self, x): 

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

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

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

536 return False 

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

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

539 return False 

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

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

542 return False 

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

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

545 return False 

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

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

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

549 return False 

550 for k in sm_config.keys(): 

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

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

553 return False 

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

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

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

557 return False 

558 return True 

559 

560 def update_slaves_on_cbt_disable(self, cbtlog): 

561 # Override in implementation as required. 

562 pass 

563 

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

565 """Function for configuring blocktracking""" 

566 import blktap2 

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

568 

569 # Check if raw VDI or snapshot 

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

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

572 raise xs_errors.XenError('VDIType', 

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

574 

575 # Check if already enabled 

576 if self._get_blocktracking_status() == enable: 

577 return 

578 

579 # Save disk state before pause 

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

581 

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

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

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

585 logfile = None 

586 

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

588 # 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 

589 

590 try: 

591 if enable: 

592 try: 

593 # Check available space 

594 self._ensure_cbt_space() 

595 logfile = self._create_cbt_log() 

596 # Set consistency 

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

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

599 % self.uuid) 

600 logpath = self._get_cbt_logpath(self.uuid) 

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

602 logpath, False) 

603 except Exception as error: 

604 self._delete_cbt_log() 

605 raise xs_errors.XenError('CBTActivateFailed', 

606 opterr=str(error)) 

607 else: 

608 from lock import Lock 

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

610 lock.acquire() 

611 try: 

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

613 # and nullify its successor 

614 logpath = self._get_cbt_logpath(self.uuid) 

615 parent = self._cbt_op(self.uuid, 

616 cbtutil.get_cbt_parent, logpath) 

617 self._delete_cbt_log() 

618 parent_path = self._get_cbt_logpath(parent) 

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

620 self._cbt_op(parent, cbtutil.set_cbt_child, 

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

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

623 self.update_slaves_on_cbt_disable(logpath) 

624 except Exception as error: 

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

626 finally: 

627 lock.release() 

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

629 finally: 

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

631 

632 def data_destroy(self, sr_uuid, vdi_uuid): 

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

634 

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

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

637 by upper layers 

638 """ 

639 

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

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

642 raise xs_errors.XenError('VDIType', 

643 opterr='Only allowed for snapshot VDIs') 

644 

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

646 

647 def list_changed_blocks(self): 

648 """ List all changed blocks """ 

649 vdi_from = self.uuid 

650 params = self.sr.srcmd.params 

651 _VDI = self.session.xenapi.VDI 

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

653 sr_uuid = params['sr_uuid'] 

654 

655 if vdi_from == vdi_to: 

656 raise xs_errors.XenError('CBTChangedBlocksError', 

657 "Source and target VDI are same") 

658 

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

660 if (self._get_blocktracking_status(vdi_from) and 

661 self._get_blocktracking_status(vdi_to)): 

662 merged_bitmap = None 

663 curr_vdi = vdi_from 

664 vdi_size = 0 

665 logpath = self._get_cbt_logpath(curr_vdi) 

666 

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

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

669 # * We've reached destination VDI 

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

671 while True: 

672 # Check if we have reached end of CBT chain 

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

674 logpath) 

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

676 # VDIs are not part of the same metadata chain 

677 break 

678 else: 

679 curr_vdi = next_vdi 

680 

681 logpath = self._get_cbt_logpath(curr_vdi) 

682 curr_vdi_size = self._cbt_op(curr_vdi, 

683 cbtutil.get_cbt_size, logpath) 

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

685 % (curr_vdi, curr_vdi_size)) 

686 curr_bitmap = bitarray() 

687 curr_bitmap.frombytes(self._cbt_op(curr_vdi, 

688 cbtutil.get_cbt_bitmap, 

689 logpath)) 

690 curr_bitmap.bytereverse() 

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

692 

693 expected_bitmap_len = curr_vdi_size // CBT_BLOCK_SIZE 

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

695 # changed blocks instead of returning corrupt data 

696 if len(curr_bitmap) < expected_bitmap_len: 

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

698 % (len(curr_bitmap), expected_bitmap_len)) 

699 raise xs_errors.XenError('CBTMetadataInconsistent', 

700 "Inconsistent bitmaps") 

701 

702 if merged_bitmap: 

703 # Rule out error conditions 

704 # 1) New VDI size < original VDI size 

705 # 2) New bitmap size < original bitmap size 

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

707 # is not bigger 

708 if (curr_vdi_size < vdi_size or 

709 len(curr_bitmap) < len(merged_bitmap) or 

710 (curr_vdi_size > vdi_size and 

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

712 # Return error: Failure to calculate changed blocks 

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

714 "inconsistent bitmap sizes") 

715 raise xs_errors.XenError('CBTMetadataInconsistent', 

716 "Inconsistent bitmaps") 

717 

718 # Check if disk has been resized 

719 if curr_vdi_size > vdi_size: 

720 vdi_size = curr_vdi_size 

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

722 # Extend merged_bitmap to match size of curr_bitmap 

723 extended_bitmap = extended_size * bitarray('0') 

724 merged_bitmap += extended_bitmap 

725 

726 # At this point bitmap sizes should be same 

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

728 curr_vdi_size == vdi_size): 

729 # This is unusual. Log it but calculate merged 

730 # bitmap by truncating new bitmap 

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

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

733 curr_bitmap = curr_bitmap[:len(merged_bitmap)] 

734 

735 merged_bitmap = merged_bitmap | curr_bitmap 

736 else: 

737 merged_bitmap = curr_bitmap 

738 vdi_size = curr_vdi_size 

739 

740 # Check if we have reached "vdi_to" 

741 if curr_vdi == vdi_to: 

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

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

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

745 # find common ancestor and find difference 

746 

747 # TODO: VDIs are unrelated 

748 # return fully populated bitmap size of to VDI 

749 

750 raise xs_errors.XenError('CBTChangedBlocksError', 

751 "Source and target VDI are unrelated") 

752 

753 def _cbt_snapshot(self, snapshot_uuid, consistency_state): 

754 """ CBT snapshot""" 

755 snap_logpath = self._get_cbt_logpath(snapshot_uuid) 

756 vdi_logpath = self._get_cbt_logpath(self.uuid) 

757 

758 # Rename vdi vdi.cbtlog to snapshot.cbtlog 

759 # and mark it consistent 

760 self._rename(vdi_logpath, snap_logpath) 

761 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_consistency, 

762 snap_logpath, True) 

763 

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

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

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

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

768 # do this 

769 parent = self._cbt_op(snapshot_uuid, 

770 cbtutil.get_cbt_parent, snap_logpath) 

771 parent_path = self._get_cbt_logpath(parent) 

772 if self._cbt_log_exists(parent_path): 

773 self._cbt_op(parent, cbtutil.set_cbt_child, 

774 parent_path, snapshot_uuid) 

775 try: 

776 # Ensure enough space for metadata file 

777 self._ensure_cbt_space() 

778 # Create new vdi.cbtlog 

779 self._create_cbt_log() 

780 # Set previous vdi node consistency status 

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

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

783 vdi_logpath, consistency_state) 

784 # Set relationship pointers 

785 # Save the child of the VDI just snapshotted 

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

787 snap_logpath) 

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

789 vdi_logpath, snapshot_uuid) 

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

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

792 vdi_logpath, curr_child_uuid) 

793 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_child, 

794 snap_logpath, self.uuid) 

795 except Exception as ex: 

796 alert_name = "VDI_CBT_SNAPSHOT_FAILED" 

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

798 % self.uuid) 

799 self._disable_cbt_on_error(alert_name, alert_str) 

800 

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

802 """ Get blocktracking status """ 

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

804 uuid = self.uuid 

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

806 return False 

807 elif 'VDI_CONFIG_CBT' not in util.sr_get_capability( 

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

809 return False 

810 logpath = self._get_cbt_logpath(uuid) 

811 return self._cbt_log_exists(logpath) 

812 

813 def _set_blocktracking_status(self, vdi_ref, enable): 

814 """ Set blocktracking status""" 

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

816 if "cbt_enabled" in vdi_config: 

817 self.session.xenapi.VDI.remove_from_other_config( 

818 vdi_ref, "cbt_enabled") 

819 

820 self.session.xenapi.VDI.add_to_other_config( 

821 vdi_ref, "cbt_enabled", enable) 

822 

823 def _ensure_cbt_space(self) -> None: 

824 """ Ensure enough CBT space """ 

825 pass 

826 

827 def _get_cbt_logname(self, uuid): 

828 """ Get CBT logname """ 

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

830 return logName 

831 

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

833 """ Get CBT logpath """ 

834 logName = self._get_cbt_logname(uuid) 

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

836 

837 def _create_cbt_log(self) -> str: 

838 """ Create CBT log """ 

839 try: 

840 logpath = self._get_cbt_logpath(self.uuid) 

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

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

843 #cbtutil.create_cbt_log(logpath, size) 

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

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

846 except Exception as e: 

847 try: 

848 self._delete_cbt_log() 

849 except: 

850 pass 

851 finally: 

852 raise e 

853 

854 return logpath 

855 

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

857 """Activate CBT log file 

858 

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

860 No-op otherwise 

861 """ 

862 return False 

863 

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

865 """Deactivate CBT log file 

866 

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

868 No-op otherwise 

869 """ 

870 pass 

871 

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

873 # Lock cbtlog operations 

874 from lock import Lock 

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

876 lock.acquire() 

877 

878 try: 

879 logname = self._get_cbt_logname(uuid) 

880 activated = self._activate_cbt_log(logname) 

881 ret = func( * args) 

882 if activated: 

883 self._deactivate_cbt_log(logname) 

884 return ret 

885 finally: 

886 lock.release() 

887 

888 def _disable_cbt_on_error(self, alert_name, alert_str): 

889 util.SMlog(alert_str) 

890 self._delete_cbt_log() 

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

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

893 alert_prio_warning = "3" 

894 alert_obj = "VDI" 

895 alert_uuid = str(self.uuid) 

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

897 alert_prio_warning, 

898 alert_obj, alert_uuid, 

899 alert_str) 

900 

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

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

903 self.session.xenapi.VDI.remove_from_other_config( 

904 vdi_ref, cleanup.VDI.DB_LEAFCLSC) 

905 if secondary is not None: 

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

907 "blocking leaf coalesce") 

908 self.session.xenapi.VDI.add_to_other_config( 

909 vdi_ref, cleanup.VDI.DB_LEAFCLSC, 

910 cleanup.VDI.LEAFCLSC_DISABLED)