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) -> 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 if self._get_blocktracking_status(): 

383 cbtlog = self._get_cbt_logpath(self.uuid) 

384 else: 

385 cbtlog = None 

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

387 secondary=secondary, cbtlog=cbtlog) 

388 

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

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

391 if self._get_blocktracking_status(): 

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

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

394 if read_write == "false": 

395 # Disk is being attached in RO mode, 

396 # don't attach metadata log file 

397 return None 

398 

399 from lock import Lock 

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

401 lock.acquire() 

402 

403 try: 

404 logpath = self._get_cbt_logpath(vdi_uuid) 

405 logname = self._get_cbt_logname(vdi_uuid) 

406 

407 # Activate CBT log file, if required 

408 self._activate_cbt_log(logname) 

409 finally: 

410 lock.release() 

411 

412 # Check and update consistency 

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

414 logpath) 

415 if not consistent: 

416 alert_name = "VDI_CBT_METADATA_INCONSISTENT" 

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

418 " for disk %s." % vdi_uuid) 

419 self._disable_cbt_on_error(alert_name, alert_str) 

420 return None 

421 

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

423 logpath, False) 

424 return {'cbtlog': logpath} 

425 return None 

426 

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

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

429 if self._get_blocktracking_status(): 

430 from lock import Lock 

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

432 lock.acquire() 

433 

434 try: 

435 logpath = self._get_cbt_logpath(vdi_uuid) 

436 logname = self._get_cbt_logname(vdi_uuid) 

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

438 # Finally deactivate log file 

439 self._deactivate_cbt_log(logname) 

440 finally: 

441 lock.release() 

442 

443 def get_params(self) -> str: 

444 """ 

445 Returns: 

446 XMLRPC response containing a single struct with fields 

447 'location' and 'uuid' 

448 """ 

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

450 'uuid': self.uuid} 

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

452 

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

454 """Post-init hook""" 

455 pass 

456 

457 def _db_introduce(self): 

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

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

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

461 for key in SM_CONFIG_PASS_THROUGH_FIELDS: 

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

463 if val: 

464 sm_config[key] = val 

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

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

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

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

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

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

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

472 return vdi 

473 

474 def _db_forget(self): 

475 self.sr.forget_vdi(self.uuid) 

476 

477 def _override_sm_config(self, sm_config): 

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

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

480 continue 

481 if val: 

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

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

484 sm_config[key] = val 

485 elif key in sm_config: 

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

487 del sm_config[key] 

488 

489 def _db_update_sm_config(self, ref, sm_config): 

490 import cleanup 

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

492 smconfig_protected_keys = [ 

493 cleanup.VDI.DB_VDI_PAUSED, 

494 cleanup.VDI.DB_VDI_BLOCKS, 

495 cleanup.VDI.DB_VDI_RELINKING, 

496 cleanup.VDI.DB_VDI_ACTIVATING] 

497 

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

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

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

501 key in smconfig_protected_keys): 

502 continue 

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

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

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

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

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

508 

509 for key in current_sm_config.keys(): 

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

511 key in smconfig_protected_keys or 

512 key in self.sm_config_keep): 

513 continue 

514 if not sm_config.get(key): 

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

516 (self.uuid, key)) 

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

518 

519 def _db_update(self): 

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

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

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

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

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

525 self._override_sm_config(sm_config) 

526 self._db_update_sm_config(vdi, sm_config) 

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

528 self._get_blocktracking_status()) 

529 

530 def in_sync_with_xenapi_record(self, x): 

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

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

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

534 return False 

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

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

537 return False 

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

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

540 return False 

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

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

543 return False 

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

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

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

547 return False 

548 for k in sm_config.keys(): 

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

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

551 return False 

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

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

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

555 return False 

556 return True 

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

582 if enable: 

583 try: 

584 # Check available space 

585 self._ensure_cbt_space() 

586 logfile = self._create_cbt_log() 

587 # Set consistency 

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

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

590 % self.uuid) 

591 logpath = self._get_cbt_logpath(self.uuid) 

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

593 logpath, False) 

594 except Exception as error: 

595 self._delete_cbt_log() 

596 raise xs_errors.XenError('CBTActivateFailed', 

597 opterr=str(error)) 

598 else: 

599 from lock import Lock 

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

601 lock.acquire() 

602 try: 

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

604 # and nullify its successor 

605 logpath = self._get_cbt_logpath(self.uuid) 

606 parent = self._cbt_op(self.uuid, 

607 cbtutil.get_cbt_parent, logpath) 

608 self._delete_cbt_log() 

609 parent_path = self._get_cbt_logpath(parent) 

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

611 self._cbt_op(parent, cbtutil.set_cbt_child, 

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

613 except Exception as error: 

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

615 finally: 

616 lock.release() 

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

618 finally: 

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

620 

621 def data_destroy(self, sr_uuid, vdi_uuid): 

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

623 

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

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

626 by upper layers 

627 """ 

628 

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

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

631 raise xs_errors.XenError('VDIType', 

632 opterr='Only allowed for snapshot VDIs') 

633 

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

635 

636 def list_changed_blocks(self): 

637 """ List all changed blocks """ 

638 vdi_from = self.uuid 

639 params = self.sr.srcmd.params 

640 _VDI = self.session.xenapi.VDI 

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

642 sr_uuid = params['sr_uuid'] 

643 

644 if vdi_from == vdi_to: 

645 raise xs_errors.XenError('CBTChangedBlocksError', 

646 "Source and target VDI are same") 

647 

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

649 if (self._get_blocktracking_status(vdi_from) and 

650 self._get_blocktracking_status(vdi_to)): 

651 merged_bitmap = None 

652 curr_vdi = vdi_from 

653 vdi_size = 0 

654 logpath = self._get_cbt_logpath(curr_vdi) 

655 

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

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

658 # * We've reached destination VDI 

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

660 while True: 

661 # Check if we have reached end of CBT chain 

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

663 logpath) 

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

665 # VDIs are not part of the same metadata chain 

666 break 

667 else: 

668 curr_vdi = next_vdi 

669 

670 logpath = self._get_cbt_logpath(curr_vdi) 

671 curr_vdi_size = self._cbt_op(curr_vdi, 

672 cbtutil.get_cbt_size, logpath) 

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

674 % (curr_vdi, curr_vdi_size)) 

675 curr_bitmap = bitarray() 

676 curr_bitmap.frombytes(self._cbt_op(curr_vdi, 

677 cbtutil.get_cbt_bitmap, 

678 logpath)) 

679 curr_bitmap.bytereverse() 

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

681 

682 expected_bitmap_len = curr_vdi_size // CBT_BLOCK_SIZE 

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

684 # changed blocks instead of returning corrupt data 

685 if len(curr_bitmap) < expected_bitmap_len: 

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

687 % (len(curr_bitmap), expected_bitmap_len)) 

688 raise xs_errors.XenError('CBTMetadataInconsistent', 

689 "Inconsistent bitmaps") 

690 

691 if merged_bitmap: 

692 # Rule out error conditions 

693 # 1) New VDI size < original VDI size 

694 # 2) New bitmap size < original bitmap size 

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

696 # is not bigger 

697 if (curr_vdi_size < vdi_size or 

698 len(curr_bitmap) < len(merged_bitmap) or 

699 (curr_vdi_size > vdi_size and 

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

701 # Return error: Failure to calculate changed blocks 

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

703 "inconsistent bitmap sizes") 

704 raise xs_errors.XenError('CBTMetadataInconsistent', 

705 "Inconsistent bitmaps") 

706 

707 # Check if disk has been resized 

708 if curr_vdi_size > vdi_size: 

709 vdi_size = curr_vdi_size 

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

711 # Extend merged_bitmap to match size of curr_bitmap 

712 extended_bitmap = extended_size * bitarray('0') 

713 merged_bitmap += extended_bitmap 

714 

715 # At this point bitmap sizes should be same 

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

717 curr_vdi_size == vdi_size): 

718 # This is unusual. Log it but calculate merged 

719 # bitmap by truncating new bitmap 

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

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

722 curr_bitmap = curr_bitmap[:len(merged_bitmap)] 

723 

724 merged_bitmap = merged_bitmap | curr_bitmap 

725 else: 

726 merged_bitmap = curr_bitmap 

727 vdi_size = curr_vdi_size 

728 

729 # Check if we have reached "vdi_to" 

730 if curr_vdi == vdi_to: 

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

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

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

734 # find common ancestor and find difference 

735 

736 # TODO: VDIs are unrelated 

737 # return fully populated bitmap size of to VDI 

738 

739 raise xs_errors.XenError('CBTChangedBlocksError', 

740 "Source and target VDI are unrelated") 

741 

742 def _cbt_snapshot(self, snapshot_uuid, consistency_state): 

743 """ CBT snapshot""" 

744 snap_logpath = self._get_cbt_logpath(snapshot_uuid) 

745 vdi_logpath = self._get_cbt_logpath(self.uuid) 

746 

747 # Rename vdi vdi.cbtlog to snapshot.cbtlog 

748 # and mark it consistent 

749 self._rename(vdi_logpath, snap_logpath) 

750 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_consistency, 

751 snap_logpath, True) 

752 

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

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

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

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

757 # do this 

758 parent = self._cbt_op(snapshot_uuid, 

759 cbtutil.get_cbt_parent, snap_logpath) 

760 parent_path = self._get_cbt_logpath(parent) 

761 if self._cbt_log_exists(parent_path): 

762 self._cbt_op(parent, cbtutil.set_cbt_child, 

763 parent_path, snapshot_uuid) 

764 try: 

765 # Ensure enough space for metadata file 

766 self._ensure_cbt_space() 

767 # Create new vdi.cbtlog 

768 self._create_cbt_log() 

769 # Set previous vdi node consistency status 

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

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

772 vdi_logpath, consistency_state) 

773 # Set relationship pointers 

774 # Save the child of the VDI just snapshotted 

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

776 snap_logpath) 

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

778 vdi_logpath, snapshot_uuid) 

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

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

781 vdi_logpath, curr_child_uuid) 

782 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_child, 

783 snap_logpath, self.uuid) 

784 except Exception as ex: 

785 alert_name = "VDI_CBT_SNAPSHOT_FAILED" 

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

787 % self.uuid) 

788 self._disable_cbt_on_error(alert_name, alert_str) 

789 

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

791 """ Get blocktracking status """ 

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

793 uuid = self.uuid 

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

795 return False 

796 elif 'VDI_CONFIG_CBT' not in util.sr_get_capability( 

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

798 return False 

799 logpath = self._get_cbt_logpath(uuid) 

800 return self._cbt_log_exists(logpath) 

801 

802 def _set_blocktracking_status(self, vdi_ref, enable): 

803 """ Set blocktracking status""" 

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

805 if "cbt_enabled" in vdi_config: 

806 self.session.xenapi.VDI.remove_from_other_config( 

807 vdi_ref, "cbt_enabled") 

808 

809 self.session.xenapi.VDI.add_to_other_config( 

810 vdi_ref, "cbt_enabled", enable) 

811 

812 def _ensure_cbt_space(self) -> None: 

813 """ Ensure enough CBT space """ 

814 pass 

815 

816 def _get_cbt_logname(self, uuid): 

817 """ Get CBT logname """ 

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

819 return logName 

820 

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

822 """ Get CBT logpath """ 

823 logName = self._get_cbt_logname(uuid) 

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

825 

826 def _create_cbt_log(self) -> str: 

827 """ Create CBT log """ 

828 try: 

829 logpath = self._get_cbt_logpath(self.uuid) 

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

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

832 #cbtutil.create_cbt_log(logpath, size) 

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

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

835 except Exception as e: 

836 try: 

837 self._delete_cbt_log() 

838 except: 

839 pass 

840 finally: 

841 raise e 

842 

843 return logpath 

844 

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

846 """Activate CBT log file 

847 

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

849 No-op otherwise 

850 """ 

851 return False 

852 

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

854 """Deactivate CBT log file 

855 

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

857 No-op otherwise 

858 """ 

859 pass 

860 

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

862 # Lock cbtlog operations 

863 from lock import Lock 

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

865 lock.acquire() 

866 

867 try: 

868 logname = self._get_cbt_logname(uuid) 

869 activated = self._activate_cbt_log(logname) 

870 ret = func( * args) 

871 if activated: 

872 self._deactivate_cbt_log(logname) 

873 return ret 

874 finally: 

875 lock.release() 

876 

877 def _disable_cbt_on_error(self, alert_name, alert_str): 

878 util.SMlog(alert_str) 

879 self._delete_cbt_log() 

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

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

882 alert_prio_warning = "3" 

883 alert_obj = "VDI" 

884 alert_uuid = str(self.uuid) 

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

886 alert_prio_warning, 

887 alert_obj, alert_uuid, 

888 alert_str) 

889 

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

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

892 self.session.xenapi.VDI.remove_from_other_config( 

893 vdi_ref, cleanup.VDI.DB_LEAFCLSC) 

894 if secondary is not None: 

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

896 "blocking leaf coalesce") 

897 self.session.xenapi.VDI.add_to_other_config( 

898 vdi_ref, cleanup.VDI.DB_LEAFCLSC, 

899 cleanup.VDI.LEAFCLSC_DISABLED)