Coverage for libs/sm/srmetadata.py: 81%

430 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2026-07-10 07:59 +0000

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# Functions to read and write SR metadata 

17# 

18from io import SEEK_SET 

19 

20from sm.core import util 

21from sm import metadata 

22import os 

23from sm.core import xs_errors 

24from sm import lvutil 

25import xml.sax.saxutils 

26 

27# A metadata file is considered to be made up of 512 byte sectors. 

28# Most of the information in it is in the form of fragments of XML. 

29# The first four contain SR information - well, the first actually 

30# contains a header, and the next three contain bits of XML representing SR 

31# info, but the four are treated as a unit. Information in the header includes 

32# the length of the part of the file that's in use. 

33# Subsequent sectors, if they are in use, contain VDI information - in the LVM 

34# case they take two sectors each. VDI information might mark the VDI as 

35# having been deleted, in which case the sectors used to contain this info can 

36# potentially be reused when a new VDI is subsequently added. 

37 

38# String data in this module takes the form of normal Python unicode `str` 

39# instances, or UTF-8 encoded `bytes`, depending on circumstance. In `dict` 

40# instances such as are used to represent SR and VDI info, `str` is used (as 

41# these may be returned to, or have been supplied by, this module's callers). 

42# Data going into or taken from a metadata file is `bytes`. XML and XML 

43# fragments come under this category, so are `bytes`. XML tag names are `str` 

44# instances, as these are also used as `dict` keys. 

45 

46 

47SECTOR_SIZE = 512 

48XML_HEADER = b"<?xml version=\"1.0\" ?>" 

49MAX_METADATA_LENGTH_SIZE = 10 

50OFFSET_TAG = 'offset' 

51 

52# define xml tags for metadata 

53ALLOCATION_TAG = 'allocation' 

54NAME_LABEL_TAG = 'name_label' 

55NAME_DESCRIPTION_TAG = 'name_description' 

56VDI_TAG = 'vdi' 

57VDI_DELETED_TAG = 'deleted' 

58UUID_TAG = 'uuid' 

59IS_A_SNAPSHOT_TAG = 'is_a_snapshot' 

60SNAPSHOT_OF_TAG = 'snapshot_of' 

61TYPE_TAG = 'type' 

62VDI_TYPE_TAG = 'vdi_type' 

63READ_ONLY_TAG = 'read_only' 

64MANAGED_TAG = 'managed' 

65SNAPSHOT_TIME_TAG = 'snapshot_time' 

66METADATA_OF_POOL_TAG = 'metadata_of_pool' 

67SVID_TAG = 'svid' 

68LUN_LABEL_TAG = 'll' 

69MAX_VDI_NAME_LABEL_DESC_LENGTH = SECTOR_SIZE - 2 * len(NAME_LABEL_TAG) - \ 

70 2 * len(NAME_DESCRIPTION_TAG) - len(VDI_TAG) - 12 

71 

72ATOMIC_UPDATE_PARAMS_AND_OFFSET = {NAME_LABEL_TAG: 2, 

73 NAME_DESCRIPTION_TAG: 3} 

74SR_INFO_SIZE_IN_SECTORS = 4 

75HEADER_SEP = ':' 

76METADATA_UPDATE_OBJECT_TYPE_TAG = 'objtype' 

77METADATA_OBJECT_TYPE_SR = 'sr' 

78METADATA_OBJECT_TYPE_VDI = 'vdi' 

79METADATA_BLK_SIZE = 512 

80 

81 

82# ----------------- # General helper functions - begin # ----------------- 

83def open_file(path, write=False): 

84 if write: 

85 try: 

86 file_p = open(path, 'wb+') 

87 except OSError as e: 

88 raise OSError( 

89 "Failed to open file %s for read-write. Error: %s" % 

90 (path, e.errno)) 

91 else: 

92 try: 

93 file_p = open(path, 'rb') 

94 except OSError as e: 

95 raise OSError( 

96 "Failed to open file %s for read. Error: %s" % 

97 (path, e.errno)) 

98 return file_p 

99 

100 

101def file_write_wrapper(fd, offset, data): 

102 """ 

103 Writes data to a file at a given offset. Padding (consisting of spaces) 

104 may be written out after the given data to ensure that complete blocks are 

105 written. 

106 """ 

107 try: 

108 blocksize = METADATA_BLK_SIZE 

109 length = len(data) 

110 newlength = length 

111 if length % blocksize: 

112 newlength = length + (blocksize - length % blocksize) 

113 fd.seek(offset, SEEK_SET) 

114 to_write = data + b' ' * (newlength - length) 

115 return fd.write(to_write) 

116 except OSError as e: 

117 raise OSError( 

118 "Failed to write file with params %s. Error: %s" % 

119 ([fd, offset, blocksize, data], e.errno)) 

120 

121 

122def file_read_wrapper(fd, offset, bytesToRead=METADATA_BLK_SIZE): 

123 """ 

124 Reads data from a file at a given offset. If not specified, the amount of 

125 data to read defaults to one block. 

126 """ 

127 try: 

128 fd.seek(offset, SEEK_SET) 

129 return fd.read(bytesToRead) 

130 except OSError as e: 

131 raise OSError( 

132 "Failed to read file with params %s. Error: %s" % 

133 ([fd, offset, bytesToRead], e.errno)) 

134 

135 

136def to_utf8(s): 

137 return s.encode("utf-8") 

138 

139 

140def from_utf8(bs): 

141 return bs.decode("utf-8") 

142 

143 

144# get a range which is block aligned, contains 'offset' and allows 

145# length bytes to be written 

146def getBlockAlignedRange(offset, length): 

147 # It looks like offsets and lengths are in reality always sector aligned, 

148 # and since a block and a sector are the same size we could probably do 

149 # without this code. 

150 # There methods elsewhere in this module (updateSR, getMetadataForWrite) 

151 # that appear try to cope with the possibility of the block-aligned range 

152 # for SR info also containing VDI info, or vice versa. On the face of it, 

153 # that's impossible, and so there's scope for simplification there too. 

154 block_size = METADATA_BLK_SIZE 

155 lower = 0 

156 if offset % block_size == 0: 156 ↛ 159line 156 didn't jump to line 159, because the condition on line 156 was never false

157 lower = offset 

158 else: 

159 lower = offset - offset % block_size 

160 

161 upper = lower + block_size 

162 

163 while upper < (lower + length): 

164 upper += block_size 

165 

166 return (lower, upper) 

167 

168 

169def buildHeader(length, major=metadata.MD_MAJOR, minor=metadata.MD_MINOR): 

170 len_fmt = "%%-%ds" % MAX_METADATA_LENGTH_SIZE 

171 return to_utf8(metadata.HDR_STRING 

172 + HEADER_SEP 

173 + (len_fmt % length) 

174 + HEADER_SEP 

175 + str(major) 

176 + HEADER_SEP 

177 + str(minor)) 

178 

179 

180def unpackHeader(header): 

181 decoded = from_utf8(header) 

182 if len(decoded.rstrip('\x00')) == 0: 

183 raise xs_errors.XenError('MetadataError', opterr='Empty header') 

184 vals = decoded.split(HEADER_SEP) 

185 if len(vals) != 4 or vals[0] != metadata.HDR_STRING: 

186 util.SMlog(f"Exception unpacking metadata header: Error: Bad header {header!r}") 

187 raise xs_errors.XenError('MetadataError', opterr='Bad header') 

188 return (vals[0], vals[1], vals[2], vals[3]) 

189 

190 

191def getSector(s): 

192 sector_fmt = b"%%-%ds" % SECTOR_SIZE 

193 return sector_fmt % s 

194 

195 

196def buildXMLSector(tagName, value): 

197 # truncate data if we breach the 512 limit 

198 tag_bytes = to_utf8(tagName) 

199 value_bytes = to_utf8(value) 

200 

201 elt = b"<%s>%s</%s>" % (tag_bytes, value_bytes, tag_bytes) 

202 if len(elt) > SECTOR_SIZE: 

203 length = util.unictrunc(value_bytes, SECTOR_SIZE - 2 * len(tag_bytes) - 5) 

204 util.SMlog('warning: SR %s truncated from %d to %d bytes' 

205 % (tagName, len(value_bytes), length)) 

206 elt = b"<%s>%s</%s>" % (tag_bytes, value_bytes[:length], tag_bytes) 

207 

208 return getSector(elt) 

209 

210 

211def buildXMLElement(tag, value_dict): 

212 return to_utf8("<%s>%s</%s>" % (tag, value_dict[tag], tag)) 

213 

214 

215def openingTag(tag): 

216 return b"<%s>" % to_utf8(tag) 

217 

218 

219def closingTag(tag): 

220 return b"</%s>" % to_utf8(tag) 

221 

222 

223def buildParsableMetadataXML(info): 

224 tag = to_utf8(metadata.XML_TAG) 

225 return b"%s<%s>%s</%s>" % (XML_HEADER, tag, info, tag) 

226 

227 

228def updateLengthInHeader(fd, length, major=metadata.MD_MAJOR, \ 

229 minor=metadata.MD_MINOR): 

230 try: 

231 md = file_read_wrapper(fd, 0) 

232 updated_md = buildHeader(length, major, minor) 

233 updated_md += md[SECTOR_SIZE:] 

234 

235 # Now write the new length 

236 file_write_wrapper(fd, 0, updated_md) 

237 except Exception as e: 

238 util.SMlog("Exception updating metadata length with length: %d." 

239 "Error: %s" % (length, str(e))) 

240 raise 

241 

242 

243def getMetadataLength(fd): 

244 try: 

245 sector1 = \ 

246 file_read_wrapper(fd, 0, SECTOR_SIZE).strip() 

247 hdr = unpackHeader(sector1) 

248 return int(hdr[1]) 

249 except Exception as e: 

250 util.SMlog("Exception getting metadata length: " 

251 "Error: %s" % str(e)) 

252 raise 

253 

254 

255# ----------------- # General helper functions - end # ----------------- 

256class MetadataHandler: 

257 

258 VDI_INFO_SIZE_IN_SECTORS = None 

259 

260 # constructor 

261 def __init__(self, path=None, write=True): 

262 

263 self.fd = None 

264 self.path = path 

265 if self.path is not None: 265 ↛ exitline 265 didn't return from function '__init__', because the condition on line 265 was never false

266 self.fd = open_file(self.path, write) 

267 

268 def __del__(self): 

269 if self.fd: 269 ↛ exitline 269 didn't return from function '__del__', because the condition on line 269 was never false

270 self.fd.close() 

271 

272 @property 

273 def vdi_info_size(self): 

274 return self.VDI_INFO_SIZE_IN_SECTORS * SECTOR_SIZE 

275 

276 def spaceAvailableForVdis(self, count): 

277 raise NotImplementedError("spaceAvailableForVdis is undefined") 

278 

279 # common utility functions 

280 def getMetadata(self, params={}): 

281 try: 

282 sr_info = {} 

283 vdi_info = {} 

284 try: 

285 md = self.getMetadataInternal(params) 

286 sr_info = md['sr_info'] 

287 vdi_info = md['vdi_info'] 

288 except: 

289 # Maybe there is no metadata yet 

290 pass 

291 

292 except Exception as e: 

293 util.SMlog('Exception getting metadata. Error: %s' % str(e)) 

294 raise xs_errors.XenError('MetadataError', \ 

295 opterr='%s' % str(e)) 

296 

297 return (sr_info, vdi_info) 

298 

299 def writeMetadata(self, sr_info, vdi_info): 

300 try: 

301 self.writeMetadataInternal(sr_info, vdi_info) 

302 except Exception as e: 

303 util.SMlog('Exception writing metadata. Error: %s' % str(e)) 

304 raise xs_errors.XenError('MetadataError', \ 

305 opterr='%s' % str(e)) 

306 

307 # read metadata for this SR and find if a metadata VDI exists 

308 def findMetadataVDI(self): 

309 try: 

310 vdi_info = self.getMetadata()[1] 

311 for offset in vdi_info.keys(): 

312 if vdi_info[offset][TYPE_TAG] == 'metadata' and \ 

313 vdi_info[offset][IS_A_SNAPSHOT_TAG] == '0': 

314 return vdi_info[offset][UUID_TAG] 

315 

316 return None 

317 except Exception as e: 

318 util.SMlog('Exception checking if SR metadata a metadata VDI.' \ 

319 'Error: %s' % str(e)) 

320 raise xs_errors.XenError('MetadataError', \ 

321 opterr='%s' % str(e)) 

322 

323 # update the SR information or one of the VDIs information 

324 # the passed in map would have a key 'objtype', either sr or vdi. 

325 # if the key is sr, the following might be passed in 

326 # SR name-label 

327 # SR name_description 

328 # if the key is vdi, the following information per VDI may be passed in 

329 # uuid - mandatory 

330 # name-label 

331 # name_description 

332 # is_a_snapshot 

333 # snapshot_of, if snapshot status is true 

334 # snapshot time 

335 # type (system, user or metadata etc) 

336 # vdi_type: raw or vhd 

337 # read_only 

338 # location 

339 # managed 

340 # metadata_of_pool 

341 def updateMetadata(self, update_map={}): 

342 util.SMlog("Updating metadata : %s" % update_map) 

343 

344 try: 

345 objtype = update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] 

346 del update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] 

347 

348 if objtype == METADATA_OBJECT_TYPE_SR: 

349 self.updateSR(update_map) 

350 elif objtype == METADATA_OBJECT_TYPE_VDI: 350 ↛ exitline 350 didn't return from function 'updateMetadata', because the condition on line 350 was never false

351 self.updateVdi(update_map) 

352 except Exception as e: 

353 util.SMlog('Error updating Metadata Volume with update' \ 

354 'map: %s. Error: %s' % (update_map, str(e))) 

355 raise xs_errors.XenError('MetadataError', \ 

356 opterr='%s' % str(e)) 

357 

358 def deleteVdiFromMetadata(self, vdi_uuid): 

359 util.SMlog("Deleting vdi: %s" % vdi_uuid) 

360 try: 

361 self.deleteVdi(vdi_uuid) 

362 except Exception as e: 

363 util.SMlog('Error deleting vdi %s from the metadata. ' \ 

364 'Error: %s' % (vdi_uuid, str(e))) 

365 raise xs_errors.XenError('MetadataError', \ 

366 opterr='%s' % str(e)) 

367 

368 def addVdi(self, vdi_info={}): 

369 util.SMlog("Adding VDI with info: %s" % vdi_info) 

370 try: 

371 self.addVdiInternal(vdi_info) 

372 except Exception as e: 

373 util.SMlog('Error adding VDI to Metadata Volume with ' \ 

374 'update map: %s. Error: %s' % (vdi_info, str(e))) 

375 raise xs_errors.XenError('MetadataError', \ 

376 opterr='%s' % (str(e))) 

377 

378 def ensureSpaceIsAvailableForVdis(self, count): 

379 util.SMlog("Checking if there is space in the metadata for %d VDI." % \ 

380 count) 

381 try: 

382 self.spaceAvailableForVdis(count) 

383 except Exception as e: 

384 raise xs_errors.XenError('MetadataError', \ 

385 opterr='%s' % str(e)) 

386 

387 # common functions 

388 def deleteVdi(self, vdi_uuid, offset=0): 

389 util.SMlog("Entering deleteVdi") 

390 try: 

391 md = self.getMetadataInternal({'vdi_uuid': vdi_uuid}) 

392 if 'offset' not in md: 392 ↛ 393line 392 didn't jump to line 393, because the condition on line 392 was never true

393 util.SMlog("Metadata for VDI %s not present, or already removed, " \ 

394 "no further deletion action required." % vdi_uuid) 

395 return 

396 

397 md['vdi_info'][md['offset']][VDI_DELETED_TAG] = '1' 

398 self.updateVdi(md['vdi_info'][md['offset']]) 

399 

400 try: 

401 mdlength = getMetadataLength(self.fd) 

402 if (mdlength - md['offset']) == self.vdi_info_size: 

403 updateLengthInHeader(self.fd, 

404 mdlength - self.vdi_info_size) 

405 except: 

406 raise 

407 except Exception as e: 

408 raise Exception("VDI delete operation failed for " \ 

409 "parameters: %s, %s. Error: %s" % \ 

410 (self.path, vdi_uuid, str(e))) 

411 

412 # common functions with some details derived from the child class 

413 def generateVDIsForRange(self, vdi_info, lower, upper, update_map={}, \ 

414 offset=0): 

415 if not len(vdi_info.keys()) or offset not in vdi_info: 

416 return self.getVdiInfo(update_map) 

417 

418 value = b"" 

419 for vdi_offset in vdi_info.keys(): 

420 if vdi_offset < lower: 

421 continue 

422 

423 if len(value) >= (upper - lower): 423 ↛ 424line 423 didn't jump to line 424, because the condition on line 423 was never true

424 break 

425 

426 vdi_map = vdi_info[vdi_offset] 

427 if vdi_offset == offset: 427 ↛ 432line 427 didn't jump to line 432, because the condition on line 427 was never false

428 # write passed in VDI info 

429 for key in update_map.keys(): 

430 vdi_map[key] = update_map[key] 

431 

432 for i in range(1, self.VDI_INFO_SIZE_IN_SECTORS + 1): 

433 if len(value) < (upper - lower): 433 ↛ 432line 433 didn't jump to line 432, because the condition on line 433 was never false

434 value += self.getVdiInfo(vdi_map, i) 

435 

436 return value 

437 

438 def addVdiInternal(self, Dict): 

439 util.SMlog("Entering addVdiInternal") 

440 try: 

441 Dict[VDI_DELETED_TAG] = '0' 

442 mdlength = getMetadataLength(self.fd) 

443 md = self.getMetadataInternal({'firstDeleted': 1, 'includeDeletedVdis': 1}) 

444 if 'foundDeleted' not in md: 

445 md['offset'] = mdlength 

446 (md['lower'], md['upper']) = \ 

447 getBlockAlignedRange(mdlength, self.vdi_info_size) 

448 # If this has created a new VDI, update metadata length 

449 if 'foundDeleted' in md: 

450 value = self.getMetadataToWrite(md['sr_info'], md['vdi_info'], \ 

451 md['lower'], md['upper'], Dict, md['offset']) 

452 else: 

453 value = self.getMetadataToWrite(md['sr_info'], md['vdi_info'], \ 

454 md['lower'], md['upper'], Dict, mdlength) 

455 

456 file_write_wrapper(self.fd, md['lower'], value) 

457 

458 if 'foundDeleted' in md: 

459 updateLengthInHeader(self.fd, mdlength) 

460 else: 

461 updateLengthInHeader(self.fd, mdlength + self.vdi_info_size) 

462 return True 

463 except Exception as e: 

464 util.SMlog("Exception adding vdi with info: %s. Error: %s" % \ 

465 (Dict, str(e))) 

466 raise 

467 

468 # Get metadata from the file name passed in 

469 # additional params: 

470 # includeDeletedVdis - include deleted VDIs in the returned metadata 

471 # vdi_uuid - only fetch metadata till a particular VDI 

472 # offset - only fetch metadata till a particular offset 

473 # firstDeleted - get the first deleted VDI 

474 # indexByUuid - index VDIs by uuid 

475 # the return value of this function is a dictionary having the following keys 

476 # sr_info: dictionary containing sr information 

477 # vdi_info: dictionary containing vdi information indexed by offset 

478 # offset: when passing in vdi_uuid/firstDeleted below 

479 # deleted - true if deleted VDI found to be replaced 

480 def getMetadataInternal(self, params={}): 

481 try: 

482 lower = 0 

483 upper = 0 

484 retmap = {} 

485 sr_info_map = {} 

486 ret_vdi_info = {} 

487 length = getMetadataLength(self.fd) 

488 

489 # Read in the metadata fil 

490 metadataxml = file_read_wrapper(self.fd, 0, length) 

491 

492 # At this point we have the complete metadata in metadataxml 

493 offset = SECTOR_SIZE + len(XML_HEADER) 

494 sr_info = metadataxml[offset: SECTOR_SIZE * 4] 

495 offset = SECTOR_SIZE * 4 

496 sr_info = sr_info.replace(b'\x00', b'') 

497 

498 parsable_metadata = buildParsableMetadataXML(sr_info) 

499 retmap['sr_info'] = metadata._parseXML(parsable_metadata) 

500 

501 # At this point we check if an offset has been passed in 

502 if 'offset' in params: 

503 upper = getBlockAlignedRange(params['offset'], 0)[1] 

504 else: 

505 upper = length 

506 

507 # Now look at the VDI objects 

508 while offset < upper: 

509 vdi_info = metadataxml[offset:offset + self.vdi_info_size] 

510 vdi_info = vdi_info.replace(b'\x00', b'') 

511 parsable_metadata = buildParsableMetadataXML(vdi_info) 

512 vdi_info_map = metadata._parseXML(parsable_metadata)[VDI_TAG] 

513 vdi_info_map[OFFSET_TAG] = offset 

514 

515 if 'includeDeletedVdis' not in params and \ 

516 vdi_info_map[VDI_DELETED_TAG] == '1': 

517 offset += self.vdi_info_size 

518 continue 

519 

520 if 'indexByUuid' in params: 

521 ret_vdi_info[vdi_info_map[UUID_TAG]] = vdi_info_map 

522 else: 

523 ret_vdi_info[offset] = vdi_info_map 

524 

525 if 'vdi_uuid' in params: 

526 if vdi_info_map[UUID_TAG] == params['vdi_uuid']: 

527 retmap['offset'] = offset 

528 (lower, upper) = \ 

529 getBlockAlignedRange(offset, self.vdi_info_size) 

530 

531 elif 'firstDeleted' in params: 

532 if vdi_info_map[VDI_DELETED_TAG] == '1': 

533 retmap['foundDeleted'] = 1 

534 retmap['offset'] = offset 

535 (lower, upper) = \ 

536 getBlockAlignedRange(offset, self.vdi_info_size) 

537 

538 offset += self.vdi_info_size 

539 

540 retmap['lower'] = lower 

541 retmap['upper'] = upper 

542 retmap['vdi_info'] = ret_vdi_info 

543 return retmap 

544 except Exception as e: 

545 util.SMlog("Exception getting metadata with params" \ 

546 "%s. Error: %s" % (params, str(e))) 

547 raise 

548 

549 # This function expects both sr name_label and sr name_description to be 

550 # passed in 

551 def updateSR(self, Dict): 

552 util.SMlog('entering updateSR') 

553 

554 value = b"" 

555 

556 # Find the offset depending on what we are updating 

557 diff = set(Dict.keys()) - set(ATOMIC_UPDATE_PARAMS_AND_OFFSET.keys()) 

558 if diff == set([]): 558 ↛ 590line 558 didn't jump to line 590, because the condition on line 558 was never false

559 offset = SECTOR_SIZE * 2 

560 (lower, upper) = getBlockAlignedRange(offset, SECTOR_SIZE * 2) 

561 md = self.getMetadataInternal({'offset': \ 

562 SECTOR_SIZE * (SR_INFO_SIZE_IN_SECTORS - 1)}) 

563 

564 sr_info = md['sr_info'] 

565 vdi_info_by_offset = md['vdi_info'] 

566 

567 # update SR info with Dict 

568 for key in Dict.keys(): 

569 sr_info[key] = Dict[key] 

570 

571 # if lower is less than SR header size 

572 if lower < SR_INFO_SIZE_IN_SECTORS * SECTOR_SIZE: 572 ↛ 586line 572 didn't jump to line 586, because the condition on line 572 was never false

573 # if upper is less than SR header size 

574 if upper <= SR_INFO_SIZE_IN_SECTORS * SECTOR_SIZE: 574 ↛ 578line 574 didn't jump to line 578, because the condition on line 574 was never false

575 for i in range(lower // SECTOR_SIZE, upper // SECTOR_SIZE): 

576 value += self.getSRInfoForSectors(sr_info, range(i, i + 1)) 

577 else: 

578 for i in range(lower // SECTOR_SIZE, SR_INFO_SIZE_IN_SECTORS): 

579 value += self.getSRInfoForSectors(sr_info, range(i, i + 1)) 

580 

581 # generate the remaining VDI 

582 value += self.generateVDIsForRange(vdi_info_by_offset, 

583 SR_INFO_SIZE_IN_SECTORS, upper) 

584 else: 

585 # generate the remaining VDI 

586 value += self.generateVDIsForRange(vdi_info_by_offset, lower, upper) 

587 

588 file_write_wrapper(self.fd, lower, value) 

589 else: 

590 raise Exception("SR Update operation not supported for " 

591 "parameters: %s" % diff) 

592 

593 def updateVdi(self, Dict): 

594 util.SMlog('entering updateVdi') 

595 try: 

596 mdlength = getMetadataLength(self.fd) 

597 md = self.getMetadataInternal({'vdi_uuid': Dict[UUID_TAG]}) 

598 value = self.getMetadataToWrite(md['sr_info'], md['vdi_info'], \ 

599 md['lower'], md['upper'], Dict, md['offset']) 

600 file_write_wrapper(self.fd, md['lower'], value) 

601 return True 

602 except Exception as e: 

603 util.SMlog("Exception updating vdi with info: %s. Error: %s" % \ 

604 (Dict, str(e))) 

605 raise 

606 

607 # This should be called only in the cases where we are initially writing 

608 # metadata, the function would expect a dictionary which had all information 

609 # about the SRs and all its VDIs 

610 def writeMetadataInternal(self, sr_info, vdi_info): 

611 try: 

612 md = self.getSRInfoForSectors(sr_info, range(0, SR_INFO_SIZE_IN_SECTORS)) 

613 

614 # Go over the VDIs passed and for each 

615 for key in vdi_info.keys(): 

616 md += self.getVdiInfo(vdi_info[key]) 

617 

618 # Now write the metadata on disk. 

619 file_write_wrapper(self.fd, 0, md) 

620 updateLengthInHeader(self.fd, len(md)) 

621 

622 except Exception as e: 

623 util.SMlog("Exception writing metadata with info: %s, %s. " \ 

624 "Error: %s" % (sr_info, vdi_info, str(e))) 

625 raise 

626 

627 # generates metadata info to write taking the following parameters: 

628 # a range, lower - upper 

629 # sr and vdi information 

630 # VDI information to update 

631 # an optional offset to the VDI to update 

632 def getMetadataToWrite(self, sr_info, vdi_info, lower, upper, update_map, \ 

633 offset): 

634 util.SMlog("Entering getMetadataToWrite") 

635 try: 

636 value = b"" 

637 

638 # if lower is less than SR info 

639 if lower < SECTOR_SIZE * SR_INFO_SIZE_IN_SECTORS: 639 ↛ 641line 639 didn't jump to line 641, because the condition on line 639 was never true

640 # generate SR info 

641 for i in range(lower // SECTOR_SIZE, SR_INFO_SIZE_IN_SECTORS): 

642 value += self.getSRInfoForSectors(sr_info, range(i, i + 1)) 

643 

644 # generate the rest of the VDIs till upper 

645 value += self.generateVDIsForRange(vdi_info, \ 

646 SECTOR_SIZE * SR_INFO_SIZE_IN_SECTORS, upper, update_map, offset) 

647 else: 

648 # skip till you get a VDI with lower as the offset, then generate 

649 value += self.generateVDIsForRange(vdi_info, lower, upper, \ 

650 update_map, offset) 

651 return value 

652 except Exception as e: 

653 util.SMlog("Exception generating metadata to write with info: " \ 

654 "sr_info: %s, vdi_info: %s, lower: %d, upper: %d, " \ 

655 "update_map: %s, offset: %d. Error: %s" % \ 

656 (sr_info, vdi_info, lower, upper, update_map, offset, str(e))) 

657 raise 

658 

659 # specific functions, to be implement by the child classes 

660 def getVdiInfo(self, Dict, generateSector=0): 

661 return b"" 

662 

663 def getSRInfoForSectors(self, sr_info, range): 

664 return b"" 

665 

666 

667class LVMMetadataHandler(MetadataHandler): 

668 

669 VDI_INFO_SIZE_IN_SECTORS = 2 

670 

671 # constructor 

672 def __init__(self, path=None, write=True): 

673 lvutil.ensurePathExists(path) 

674 MetadataHandler.__init__(self, path, write) 

675 

676 def spaceAvailableForVdis(self, count): 

677 created = False 

678 try: 

679 # The easiest way to do this, is to create a dummy vdi and write it 

680 uuid = util.gen_uuid() 

681 vdi_info = {UUID_TAG: uuid, 

682 NAME_LABEL_TAG: 'dummy vdi for space check', 

683 NAME_DESCRIPTION_TAG: 'dummy vdi for space check', 

684 IS_A_SNAPSHOT_TAG: 0, 

685 SNAPSHOT_OF_TAG: '', 

686 SNAPSHOT_TIME_TAG: '', 

687 TYPE_TAG: 'user', 

688 VDI_TYPE_TAG: 'vhd', 

689 READ_ONLY_TAG: 0, 

690 MANAGED_TAG: 0, 

691 'metadata_of_pool': '' 

692 } 

693 

694 created = self.addVdiInternal(vdi_info) 

695 except IOError as e: 

696 raise 

697 finally: 

698 if created: 698 ↛ exitline 698 didn't except from function 'spaceAvailableForVdis', because the raise on line 696 wasn't executed or line 698 didn't return from function 'spaceAvailableForVdis', because the condition on line 698 was never false

699 # Now delete the dummy VDI created above 

700 self.deleteVdi(uuid) 

701 

702 # This function generates VDI info based on the passed in information 

703 # it also takes in a parameter to determine whether both the sector 

704 # or only one sector needs to be generated, and which one 

705 # generateSector - can be 1 or 2, defaults to 0 and generates both sectors 

706 def getVdiInfo(self, Dict, generateSector=0): 

707 util.SMlog("Entering VDI info") 

708 try: 

709 vdi_info = b"" 

710 # HP split into 2 functions, 1 for generating the first 2 sectors, 

711 # which will be called by all classes 

712 # and one specific to this class 

713 if generateSector == 1 or generateSector == 0: 

714 label = xml.sax.saxutils.escape(util.to_plain_string(Dict[NAME_LABEL_TAG])) 

715 desc = xml.sax.saxutils.escape(util.to_plain_string(Dict[NAME_DESCRIPTION_TAG])) 

716 label_length = len(to_utf8(label)) 

717 desc_length = len(to_utf8(desc)) 

718 

719 if label_length + desc_length > MAX_VDI_NAME_LABEL_DESC_LENGTH: 

720 limit = MAX_VDI_NAME_LABEL_DESC_LENGTH // 2 

721 if label_length > limit: 

722 label = label[:util.unictrunc(label, limit)] 

723 util.SMlog('warning: name-label truncated from ' 

724 '%d to %d bytes' 

725 % (label_length, len(to_utf8(label)))) 

726 

727 if desc_length > limit: 

728 desc = desc[:util.unictrunc(desc, limit)] 

729 util.SMlog('warning: description truncated from ' 

730 '%d to %d bytes' 

731 % (desc_length, len(to_utf8(desc)))) 

732 

733 Dict[NAME_LABEL_TAG] = label 

734 Dict[NAME_DESCRIPTION_TAG] = desc 

735 

736 # Fill the open struct and write it 

737 vdi_info += getSector(openingTag(VDI_TAG) 

738 + buildXMLElement(NAME_LABEL_TAG, Dict) 

739 + buildXMLElement(NAME_DESCRIPTION_TAG, 

740 Dict)) 

741 

742 if generateSector == 2 or generateSector == 0: 

743 sector2 = b"" 

744 

745 if VDI_DELETED_TAG not in Dict: 

746 Dict.update({VDI_DELETED_TAG: '0'}) 

747 

748 for tag in Dict.keys(): 

749 if tag == NAME_LABEL_TAG or tag == NAME_DESCRIPTION_TAG: 

750 continue 

751 sector2 += buildXMLElement(tag, Dict) 

752 

753 sector2 += closingTag(VDI_TAG) 

754 vdi_info += getSector(sector2) 

755 return vdi_info 

756 

757 except Exception as e: 

758 util.SMlog("Exception generating vdi info: %s. Error: %s" % \ 

759 (Dict, str(e))) 

760 raise 

761 

762 def getSRInfoForSectors(self, sr_info, range): 

763 srinfo = b"" 

764 

765 try: 

766 # write header, name_labael and description in that function 

767 # as its common to all 

768 # Fill up the first sector 

769 if 0 in range: 

770 srinfo = getSector(buildHeader(SECTOR_SIZE)) 

771 

772 if 1 in range: 

773 srinfo += getSector(XML_HEADER 

774 + buildXMLElement(UUID_TAG, sr_info) 

775 + buildXMLElement(ALLOCATION_TAG, sr_info)) 

776 

777 if 2 in range: 

778 # Fill up the SR name_label 

779 srinfo += buildXMLSector(NAME_LABEL_TAG, 

780 xml.sax.saxutils.escape(sr_info[NAME_LABEL_TAG])) 

781 

782 if 3 in range: 

783 # Fill the name_description 

784 srinfo += buildXMLSector(NAME_DESCRIPTION_TAG, 

785 xml.sax.saxutils.escape(sr_info[NAME_DESCRIPTION_TAG])) 

786 

787 return srinfo 

788 

789 except Exception as e: 

790 util.SMlog("Exception getting SR info with parameters: sr_info: %s," \ 

791 "range: %s. Error: %s" % (sr_info, range, str(e))) 

792 raise