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 

19import SR 

20import xmlrpc.client 

21import xs_errors 

22import util 

23import vhdutil 

24import cbtutil 

25import os 

26import base64 

27from constants import CBTLOG_TAG 

28from bitarray import bitarray 

29import uuid 

30 

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

32 

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

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

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

36CBT_BLOCK_SIZE = (64 * 1024) 

37 

38 

39def VDIMetadataSize(type, virtualsize): 

40 size = 0 

41 if type == 'vhd': 

42 size_mb = virtualsize // (1024 * 1024) 

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

44 size = 3 * 1024 

45 

46 # BAT 4 Bytes per block segment 

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

48 size = util.roundup(512, size) 

49 

50 # BATMAP 1 bit per block segment 

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

52 size = util.roundup(4096, size) 

53 

54 # Segment bitmaps + Page align offsets 

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

56 

57 return size 

58 

59 

60class VDI(object): 

61 """Virtual Disk Instance descriptor. 

62 

63 Attributes: 

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

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

66 description: string, longer user generated description string 

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

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

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

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

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

72 CoW instance 

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

74 e.g. shared OCFS disk 

75 attached: boolean, whether VDI is attached 

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

77 """ 

78 

79 def __init__(self, sr, uuid): 

80 self.sr = sr 

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

82 # ever come of this. 

83 if uuid is not None: 

84 self.uuid = uuid 

85 self.location = uuid 

86 self.path = None 

87 else: 

88 # We assume that children class initializors calling without 

89 # uuid will set these attributes themselves somewhere. They 

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

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

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

93 pass 

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

95 # ommitted from the XML output 

96 

97 self.label = '' 

98 self.description = '' 

99 self.vbds = [] 

100 self.size = 0 

101 self.utilisation = 0 

102 self.vdi_type = '' 

103 self.has_child = 0 

104 self.parent = None 

105 self.shareable = False 

106 self.attached = False 

107 self.status = 0 

108 self.read_only = False 

109 self.xenstore_data = {} 

110 self.deleted = False 

111 self.session = sr.session 

112 self.managed = True 

113 self.sm_config_override = {} 

114 self.sm_config_keep = ["key_hash"] 

115 self.ty = "user" 

116 self.cbt_enabled = False 

117 

118 self.load(uuid) 

119 

120 @staticmethod 

121 def from_uuid(session, vdi_uuid): 

122 

123 _VDI = session.xenapi.VDI 

124 vdi_ref = _VDI.get_by_uuid(vdi_uuid) 

125 sr_ref = _VDI.get_SR(vdi_ref) 

126 

127 _SR = session.xenapi.SR 

128 sr_uuid = _SR.get_uuid(sr_ref) 

129 

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

131 

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

133 return sr.vdi(vdi_uuid) 

134 

135 def create(self, sr_uuid, vdi_uuid, size): 

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

137 

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

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

140 be explicitly attached via the attach() command following 

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

142 requested size if the substrate requires a size in multiples 

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

144 size. 

145 """ 

146 raise xs_errors.XenError('Unimplemented') 

147 

148 def update(self, sr_uuid, vdi_uuid): 

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

150 

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

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

153 this call is made. 

154 """ 

155 # no-op unless individual backends implement it 

156 return 

157 

158 def introduce(self, sr_uuid, vdi_uuid): 

159 """Explicitly introduce a particular VDI. 

160 

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

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

163 object and then creates the XenAPI VDI object. 

164 """ 

165 raise xs_errors.XenError('Unimplemented') 

166 

167 def attach(self, sr_uuid, vdi_uuid): 

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

169 state required to access the VDI. 

170 

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

172 attached or if the VDI is already attached. 

173 

174 Returns: 

175 string, local device path. 

176 """ 

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

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

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

180 

181 def detach(self, sr_uuid, vdi_uuid): 

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

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

184 

185 This operation is idempotent. 

186 """ 

187 raise xs_errors.XenError('Unimplemented') 

188 

189 def clone(self, sr_uuid, vdi_uuid): 

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

191 

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

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

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

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

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

197 cloning this operation should raise SRUnsupportedOperation. 

198 

199 Arguments: 

200 Raises: 

201 SRUnsupportedOperation 

202 """ 

203 raise xs_errors.XenError('Unimplemented') 

204 

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

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

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

208 raise xs_errors.XenError('Unimplemented') 

209 

210 def generate_config(self, sr_uuid, vdi_uuid): 

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

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

213 vdi_attach_from_config() SMAPI call. 

214 """ 

215 raise xs_errors.XenError('Unimplemented') 

216 

217 def compose(self, sr_uuid, vdi1, vdi2): 

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

219 [vdi2]. 

220 

221 Raises: 

222 SRUnsupportedOperation 

223 """ 

224 raise xs_errors.XenError('Unimplemented') 

225 

226 def attach_from_config(self, sr_uuid, vdi_uuid): 

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

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

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

230 """ 

231 raise xs_errors.XenError('Unimplemented') 

232 

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

234 cloneOp=False, secondary=None, cbtlog=None): 

235 raise xs_errors.XenError('Unimplemented') 

236 

237 def _delete_cbt_log(self): 

238 raise xs_errors.XenError('Unimplemented') 

239 

240 def _rename(self, old, new): 

241 raise xs_errors.XenError('Unimplemented') 

242 

243 def _cbt_log_exists(self, logpath): 

244 """Check if CBT log file exists 

245 

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

247 """ 

248 raise xs_errors.XenError('Unimplemented') 

249 

250 def resize(self, sr_uuid, vdi_uuid, size): 

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

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

253 the current value. 

254 

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

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

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

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

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

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

261 the disk such as the filesystem. Responsibility for resizing 

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

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

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

265 advance. 

266 """ 

267 raise xs_errors.XenError('Unimplemented') 

268 

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

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

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

272 the current value. 

273 

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

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

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

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

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

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

280 the disk such as the filesystem. Responsibility for resizing 

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

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

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

284 advance. 

285 """ 

286 try: 

287 if self._get_blocktracking_status(): 

288 logpath = self._get_cbt_logpath(vdi_uuid) 

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

290 except util.CommandException as ex: 

291 alert_name = "VDI_CBT_RESIZE_FAILED" 

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

293 % vdi_uuid) 

294 self._disable_cbt_on_error(alert_name, alert_str) 

295 

296 def delete(self, sr_uuid, vdi_uuid, data_only=False): 

297 """Delete this VDI. 

298 

299 This operation IS idempotent and should succeed if the VDI 

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

301 the responsibility of the higher-level management tool to 

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

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

304 disk is still attached. 

305 """ 

306 import blktap2 

307 from lock import Lock 

308 

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

310 logpath = self._get_cbt_logpath(vdi_uuid) 

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

312 logpath) 

313 parent_path = self._get_cbt_logpath(parent_uuid) 

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

315 child_path = self._get_cbt_logpath(child_uuid) 

316 

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

318 

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

320 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

321 parent_path, child_uuid) 

322 

323 if self._cbt_log_exists(child_path): 

324 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

325 child_path, parent_uuid) 

326 lock.acquire() 

327 try: 

328 # Coalesce contents of bitmap with child's bitmap 

329 # Check if child bitmap is currently attached 

330 paused_for_coalesce = False 

331 consistent = self._cbt_op(child_uuid, 

332 cbtutil.get_cbt_consistency, 

333 child_path) 

334 if not consistent: 

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

336 sr_uuid, child_uuid): 

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

338 paused_for_coalesce = True 

339 self._activate_cbt_log(self._get_cbt_logname(vdi_uuid)) 

340 self._cbt_op(child_uuid, cbtutil.coalesce_bitmap, 

341 logpath, child_path) 

342 lock.release() 

343 except util.CommandException: 

344 # If there is an exception in coalescing, 

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

346 # to what they were 

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

348 " restoring to previous state") 

349 try: 

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

351 self._cbt_op(parent_uuid, cbtutil.set_cbt_child, 

352 parent_path, vdi_uuid) 

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

354 self._cbt_op(child_uuid, cbtutil.set_cbt_parent, 

355 child_path, vdi_uuid) 

356 finally: 

357 lock.release() 

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

359 return 

360 finally: 

361 # Unpause tapdisk if it wasn't originally paused 

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

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

364 child_uuid) 

365 lock.acquire() 

366 try: 

367 self._delete_cbt_log() 

368 finally: 

369 lock.release() 

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

371 

372 def snapshot(self, sr_uuid, vdi_uuid): 

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

374 

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

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

377 be explicitly attached via the vdi_attach() command following 

378 creation. If the driver does not support snapshotting this 

379 operation should raise SRUnsupportedOperation 

380 

381 Arguments: 

382 Raises: 

383 SRUnsupportedOperation 

384 """ 

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

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

387 # in both cases, unless driver_params overrides it 

388 snapType = SNAPSHOT_DOUBLE 

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

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

391 snapType = SNAPSHOT_SINGLE 

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

393 snapType = SNAPSHOT_INTERNAL 

394 

395 secondary = None 

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

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

398 

399 if self._get_blocktracking_status(): 

400 cbtlog = self._get_cbt_logpath(self.uuid) 

401 else: 

402 cbtlog = None 

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

404 secondary=secondary, cbtlog=cbtlog) 

405 

406 def activate(self, sr_uuid, vdi_uuid): 

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

408 if self._get_blocktracking_status(): 

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

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

411 if read_write == "false": 

412 # Disk is being attached in RO mode, 

413 # don't attach metadata log file 

414 return None 

415 

416 from lock import Lock 

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

418 lock.acquire() 

419 

420 try: 

421 logpath = self._get_cbt_logpath(vdi_uuid) 

422 logname = self._get_cbt_logname(vdi_uuid) 

423 

424 # Activate CBT log file, if required 

425 self._activate_cbt_log(logname) 

426 finally: 

427 lock.release() 

428 

429 # Check and update consistency 

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

431 logpath) 

432 if not consistent: 

433 alert_name = "VDI_CBT_METADATA_INCONSISTENT" 

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

435 " for disk %s." % vdi_uuid) 

436 self._disable_cbt_on_error(alert_name, alert_str) 

437 return None 

438 

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

440 logpath, False) 

441 return {'cbtlog': logpath} 

442 return None 

443 

444 def deactivate(self, sr_uuid, vdi_uuid): 

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

446 if self._get_blocktracking_status(): 

447 from lock import Lock 

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

449 lock.acquire() 

450 

451 try: 

452 logpath = self._get_cbt_logpath(vdi_uuid) 

453 logname = self._get_cbt_logname(vdi_uuid) 

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

455 # Finally deactivate log file 

456 self._deactivate_cbt_log(logname) 

457 finally: 

458 lock.release() 

459 

460 def get_params(self): 

461 """ 

462 Returns: 

463 XMLRPC response containing a single struct with fields 

464 'location' and 'uuid' 

465 """ 

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

467 'uuid': self.uuid} 

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

469 

470 def load(self, vdi_uuid): 

471 """Post-init hook""" 

472 pass 

473 

474 def _db_introduce(self): 

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

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

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

478 for key in SM_CONFIG_PASS_THROUGH_FIELDS: 

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

480 if val: 

481 sm_config[key] = val 

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

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

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

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

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

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

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

489 return vdi 

490 

491 def _db_forget(self): 

492 self.sr.forget_vdi(self.uuid) 

493 

494 def _override_sm_config(self, sm_config): 

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

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

497 continue 

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

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

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

501 sm_config[key] = val 

502 elif key in sm_config: 

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

504 del sm_config[key] 

505 

506 def _db_update_sm_config(self, ref, sm_config): 

507 import cleanup 

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

509 smconfig_protected_keys = [ 

510 cleanup.VDI.DB_VDI_PAUSED, 

511 cleanup.VDI.DB_VHD_BLOCKS, 

512 cleanup.VDI.DB_VDI_RELINKING, 

513 cleanup.VDI.DB_VDI_ACTIVATING] 

514 

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

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

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

518 key in smconfig_protected_keys): 

519 continue 

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

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

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

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

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

525 

526 for key in current_sm_config.keys(): 

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

528 key in smconfig_protected_keys or 

529 key in self.sm_config_keep): 

530 continue 

531 if not sm_config.get(key): 

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

533 (self.uuid, key)) 

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

535 

536 def _db_update(self): 

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

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

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

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

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

542 self._override_sm_config(sm_config) 

543 self._db_update_sm_config(vdi, sm_config) 

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

545 self._get_blocktracking_status()) 

546 

547 def in_sync_with_xenapi_record(self, x): 

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

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

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

551 return False 

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

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

554 return False 

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

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

557 return False 

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

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

560 return False 

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

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

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

564 return False 

565 for k in sm_config.keys(): 

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

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

568 return False 

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

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

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

572 return False 

573 return True 

574 

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

576 """Function for configuring blocktracking""" 

577 import blktap2 

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

579 

580 # Check if raw VDI or snapshot 

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

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

583 raise xs_errors.XenError('VDIType', 

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

585 

586 # Check if already enabled 

587 if self._get_blocktracking_status() == enable: 

588 return 

589 

590 # Save disk state before pause 

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

592 

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

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

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

596 logfile = None 

597 

598 try: 

599 if enable: 

600 try: 

601 # Check available space 

602 self._ensure_cbt_space() 

603 logfile = self._create_cbt_log() 

604 # Set consistency 

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

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

607 % self.uuid) 

608 logpath = self._get_cbt_logpath(self.uuid) 

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

610 logpath, False) 

611 except Exception as error: 

612 self._delete_cbt_log() 

613 raise xs_errors.XenError('CBTActivateFailed', 

614 opterr=str(error)) 

615 else: 

616 from lock import Lock 

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

618 lock.acquire() 

619 try: 

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

621 # and nullify its successor 

622 logpath = self._get_cbt_logpath(self.uuid) 

623 parent = self._cbt_op(self.uuid, 

624 cbtutil.get_cbt_parent, logpath) 

625 self._delete_cbt_log() 

626 parent_path = self._get_cbt_logpath(parent) 

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

628 self._cbt_op(parent, cbtutil.set_cbt_child, 

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

630 except Exception as error: 

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

632 finally: 

633 lock.release() 

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

635 finally: 

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

637 

638 def data_destroy(self, sr_uuid, vdi_uuid): 

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

640 

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

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

643 by upper layers 

644 """ 

645 

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

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

648 raise xs_errors.XenError('VDIType', 

649 opterr='Only allowed for snapshot VDIs') 

650 

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

652 

653 def list_changed_blocks(self): 

654 """ List all changed blocks """ 

655 vdi_from = self.uuid 

656 params = self.sr.srcmd.params 

657 _VDI = self.session.xenapi.VDI 

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

659 sr_uuid = params['sr_uuid'] 

660 

661 if vdi_from == vdi_to: 

662 raise xs_errors.XenError('CBTChangedBlocksError', 

663 "Source and target VDI are same") 

664 

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

666 if (self._get_blocktracking_status(vdi_from) and 

667 self._get_blocktracking_status(vdi_to)): 

668 merged_bitmap = None 

669 curr_vdi = vdi_from 

670 vdi_size = 0 

671 logpath = self._get_cbt_logpath(curr_vdi) 

672 

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

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

675 # * We've reached destination VDI 

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

677 while True: 

678 # Check if we have reached end of CBT chain 

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

680 logpath) 

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

682 # VDIs are not part of the same metadata chain 

683 break 

684 else: 

685 curr_vdi = next_vdi 

686 

687 logpath = self._get_cbt_logpath(curr_vdi) 

688 curr_vdi_size = self._cbt_op(curr_vdi, 

689 cbtutil.get_cbt_size, logpath) 

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

691 % (curr_vdi, curr_vdi_size)) 

692 curr_bitmap = bitarray() 

693 curr_bitmap.frombytes(self._cbt_op(curr_vdi, 

694 cbtutil.get_cbt_bitmap, 

695 logpath)) 

696 curr_bitmap.bytereverse() 

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

698 

699 expected_bitmap_len = curr_vdi_size // CBT_BLOCK_SIZE 

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

701 # changed blocks instead of returning corrupt data 

702 if len(curr_bitmap) < expected_bitmap_len: 

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

704 % (len(curr_bitmap), expected_bitmap_len)) 

705 raise xs_errors.XenError('CBTMetadataInconsistent', 

706 "Inconsistent bitmaps") 

707 

708 if merged_bitmap: 

709 # Rule out error conditions 

710 # 1) New VDI size < original VDI size 

711 # 2) New bitmap size < original bitmap size 

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

713 # is not bigger 

714 if (curr_vdi_size < vdi_size or 

715 len(curr_bitmap) < len(merged_bitmap) or 

716 (curr_vdi_size > vdi_size and 

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

718 # Return error: Failure to calculate changed blocks 

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

720 "inconsistent bitmap sizes") 

721 raise xs_errors.XenError('CBTMetadataInconsistent', 

722 "Inconsistent bitmaps") 

723 

724 # Check if disk has been resized 

725 if curr_vdi_size > vdi_size: 

726 vdi_size = curr_vdi_size 

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

728 # Extend merged_bitmap to match size of curr_bitmap 

729 extended_bitmap = extended_size * bitarray('0') 

730 merged_bitmap += extended_bitmap 

731 

732 # At this point bitmap sizes should be same 

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

734 curr_vdi_size == vdi_size): 

735 # This is unusual. Log it but calculate merged 

736 # bitmap by truncating new bitmap 

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

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

739 curr_bitmap = curr_bitmap[:len(merged_bitmap)] 

740 

741 merged_bitmap = merged_bitmap | curr_bitmap 

742 else: 

743 merged_bitmap = curr_bitmap 

744 vdi_size = curr_vdi_size 

745 

746 # Check if we have reached "vdi_to" 

747 if curr_vdi == vdi_to: 

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

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

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

751 # find common ancestor and find difference 

752 

753 # TODO: VDIs are unrelated 

754 # return fully populated bitmap size of to VDI 

755 

756 raise xs_errors.XenError('CBTChangedBlocksError', 

757 "Source and target VDI are unrelated") 

758 

759 def _cbt_snapshot(self, snapshot_uuid, consistency_state): 

760 """ CBT snapshot""" 

761 snap_logpath = self._get_cbt_logpath(snapshot_uuid) 

762 vdi_logpath = self._get_cbt_logpath(self.uuid) 

763 

764 # Rename vdi vdi.cbtlog to snapshot.cbtlog 

765 # and mark it consistent 

766 self._rename(vdi_logpath, snap_logpath) 

767 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_consistency, 

768 snap_logpath, True) 

769 

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

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

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

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

774 # do this 

775 parent = self._cbt_op(snapshot_uuid, 

776 cbtutil.get_cbt_parent, snap_logpath) 

777 parent_path = self._get_cbt_logpath(parent) 

778 if self._cbt_log_exists(parent_path): 

779 self._cbt_op(parent, cbtutil.set_cbt_child, 

780 parent_path, snapshot_uuid) 

781 try: 

782 # Ensure enough space for metadata file 

783 self._ensure_cbt_space() 

784 # Create new vdi.cbtlog 

785 self._create_cbt_log() 

786 # Set previous vdi node consistency status 

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

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

789 vdi_logpath, consistency_state) 

790 # Set relationship pointers 

791 # Save the child of the VDI just snapshotted 

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

793 snap_logpath) 

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

795 vdi_logpath, snapshot_uuid) 

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

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

798 vdi_logpath, curr_child_uuid) 

799 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_child, 

800 snap_logpath, self.uuid) 

801 except Exception as ex: 

802 alert_name = "VDI_CBT_SNAPSHOT_FAILED" 

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

804 % self.uuid) 

805 self._disable_cbt_on_error(alert_name, alert_str) 

806 

807 def _get_blocktracking_status(self, uuid=None): 

808 """ Get blocktracking status """ 

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

810 uuid = self.uuid 

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

812 return False 

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

814 return False 

815 logpath = self._get_cbt_logpath(uuid) 

816 return self._cbt_log_exists(logpath) 

817 

818 def _set_blocktracking_status(self, vdi_ref, enable): 

819 """ Set blocktracking status""" 

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

821 if "cbt_enabled" in vdi_config: 

822 self.session.xenapi.VDI.remove_from_other_config( 

823 vdi_ref, "cbt_enabled") 

824 

825 self.session.xenapi.VDI.add_to_other_config( 

826 vdi_ref, "cbt_enabled", enable) 

827 

828 def _ensure_cbt_space(self): 

829 """ Ensure enough CBT space """ 

830 pass 

831 

832 def _get_cbt_logname(self, uuid): 

833 """ Get CBT logname """ 

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

835 return logName 

836 

837 def _get_cbt_logpath(self, uuid): 

838 """ Get CBT logpath """ 

839 logName = self._get_cbt_logname(uuid) 

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

841 

842 def _create_cbt_log(self): 

843 """ Create CBT log """ 

844 try: 

845 logpath = self._get_cbt_logpath(self.uuid) 

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

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

848 #cbtutil.create_cbt_log(logpath, size) 

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

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

851 except Exception as e: 

852 try: 

853 self._delete_cbt_log() 

854 except: 

855 pass 

856 finally: 

857 raise e 

858 

859 return logpath 

860 

861 def _activate_cbt_log(self, logname): 

862 """Activate CBT log file 

863 

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

865 No-op otherwise 

866 """ 

867 return False 

868 

869 def _deactivate_cbt_log(self, logname): 

870 """Deactivate CBT log file 

871 

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

873 No-op otherwise 

874 """ 

875 pass 

876 

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

878 # Lock cbtlog operations 

879 from lock import Lock 

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

881 lock.acquire() 

882 

883 try: 

884 logname = self._get_cbt_logname(uuid) 

885 activated = self._activate_cbt_log(logname) 

886 ret = func( * args) 

887 if activated: 887 ↛ 888line 887 didn't jump to line 888, because the condition on line 887 was never true

888 self._deactivate_cbt_log(logname) 

889 return ret 

890 finally: 

891 lock.release() 

892 

893 def _disable_cbt_on_error(self, alert_name, alert_str): 

894 util.SMlog(alert_str) 

895 self._delete_cbt_log() 

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

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

898 alert_prio_warning = "3" 

899 alert_obj = "VDI" 

900 alert_uuid = str(self.uuid) 

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

902 alert_prio_warning, 

903 alert_obj, alert_uuid, 

904 alert_str)