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

17# 

18 

19from sm_typing import ClassVar, override 

20 

21from abc import abstractmethod 

22 

23from io import SEEK_SET 

24 

25import util 

26import metadata 

27import os 

28import xs_errors 

29import lvutil 

30import xml.sax.saxutils 

31 

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

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

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

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

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

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

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

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

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

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

42 

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

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

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

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

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

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

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

50 

51 

52SECTOR_SIZE = 512 

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

54MAX_METADATA_LENGTH_SIZE = 10 

55OFFSET_TAG = 'offset' 

56 

57# define xml tags for metadata 

58ALLOCATION_TAG = 'allocation' 

59NAME_LABEL_TAG = 'name_label' 

60NAME_DESCRIPTION_TAG = 'name_description' 

61VDI_TAG = 'vdi' 

62VDI_DELETED_TAG = 'deleted' 

63UUID_TAG = 'uuid' 

64IS_A_SNAPSHOT_TAG = 'is_a_snapshot' 

65SNAPSHOT_OF_TAG = 'snapshot_of' 

66TYPE_TAG = 'type' 

67VDI_TYPE_TAG = 'vdi_type' 

68READ_ONLY_TAG = 'read_only' 

69MANAGED_TAG = 'managed' 

70SNAPSHOT_TIME_TAG = 'snapshot_time' 

71METADATA_OF_POOL_TAG = 'metadata_of_pool' 

72SVID_TAG = 'svid' 

73LUN_LABEL_TAG = 'll' 

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

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

76 

77ATOMIC_UPDATE_PARAMS_AND_OFFSET = {NAME_LABEL_TAG: 2, 

78 NAME_DESCRIPTION_TAG: 3} 

79SR_INFO_SIZE_IN_SECTORS = 4 

80HEADER_SEP = ':' 

81METADATA_UPDATE_OBJECT_TYPE_TAG = 'objtype' 

82METADATA_OBJECT_TYPE_SR = 'sr' 

83METADATA_OBJECT_TYPE_VDI = 'vdi' 

84METADATA_BLK_SIZE = 512 

85 

86 

87# ----------------- # General helper functions - begin # ----------------- 

88def open_file(path, write=False): 

89 if write: 

90 try: 

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

92 except OSError as e: 

93 raise OSError( 

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

95 (path, e.errno)) 

96 else: 

97 try: 

98 file_p = open(path, 'rb') 

99 except OSError as e: 

100 raise OSError( 

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

102 (path, e.errno)) 

103 return file_p 

104 

105 

106def file_write_wrapper(fd, offset, data): 

107 """ 

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

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

110 written. 

111 """ 

112 try: 

113 blocksize = METADATA_BLK_SIZE 

114 length = len(data) 

115 newlength = length 

116 if length % blocksize: 

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

118 fd.seek(offset, SEEK_SET) 

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

120 return fd.write(to_write) 

121 except OSError as e: 

122 raise OSError( 

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

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

125 

126 

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

128 """ 

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

130 data to read defaults to one block. 

131 """ 

132 try: 

133 fd.seek(offset, SEEK_SET) 

134 return fd.read(bytesToRead) 

135 except OSError as e: 

136 raise OSError( 

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

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

139 

140 

141def to_utf8(s): 

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

143 

144 

145def from_utf8(bs): 

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

147 

148 

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

150# length bytes to be written 

151def getBlockAlignedRange(offset, length): 

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

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

154 # without this code. 

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

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

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

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

159 block_size = METADATA_BLK_SIZE 

160 lower = 0 

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

162 lower = offset 

163 else: 

164 lower = offset - offset % block_size 

165 

166 upper = lower + block_size 

167 

168 while upper < (lower + length): 

169 upper += block_size 

170 

171 return (lower, upper) 

172 

173 

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

175 len_fmt = "%%-%ds" % MAX_METADATA_LENGTH_SIZE 

176 return to_utf8(metadata.HDR_STRING 

177 + HEADER_SEP 

178 + (len_fmt % length) 

179 + HEADER_SEP 

180 + str(major) 

181 + HEADER_SEP 

182 + str(minor)) 

183 

184 

185def unpackHeader(header): 

186 vals = from_utf8(header).split(HEADER_SEP) 

187 if len(vals) != 4 or vals[0] != metadata.HDR_STRING: 187 ↛ 188line 187 didn't jump to line 188, because the condition on line 187 was never true

188 util.SMlog("Exception unpacking metadata header: " 

189 "Error: Bad header '%s'" % (header)) 

190 raise xs_errors.XenError('MetadataError', \ 

191 opterr='Bad header') 

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

193 

194 

195def getSector(s): 

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

197 return sector_fmt % s 

198 

199 

200def buildXMLSector(tagName, value): 

201 # truncate data if we breach the 512 limit 

202 tag_bytes = to_utf8(tagName) 

203 value_bytes = to_utf8(value) 

204 

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

206 if len(elt) > SECTOR_SIZE: 

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

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

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

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

211 

212 return getSector(elt) 

213 

214 

215def buildXMLElement(tag, value_dict): 

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

217 

218 

219def openingTag(tag): 

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

221 

222 

223def closingTag(tag): 

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

225 

226 

227def buildParsableMetadataXML(info): 

228 tag = to_utf8(metadata.XML_TAG) 

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

230 

231 

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

233 minor=metadata.MD_MINOR): 

234 try: 

235 md = file_read_wrapper(fd, 0) 

236 updated_md = buildHeader(length, major, minor) 

237 updated_md += md[SECTOR_SIZE:] 

238 

239 # Now write the new length 

240 file_write_wrapper(fd, 0, updated_md) 

241 except Exception as e: 

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

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

244 raise 

245 

246 

247def getMetadataLength(fd): 

248 try: 

249 sector1 = \ 

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

251 hdr = unpackHeader(sector1) 

252 return int(hdr[1]) 

253 except Exception as e: 

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

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

256 raise 

257 

258 

259# ----------------- # General helper functions - end # ----------------- 

260class MetadataHandler: 

261 

262 VDI_INFO_SIZE_IN_SECTORS: ClassVar[int] 

263 

264 # constructor 

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

266 

267 self.fd = None 

268 self.path = path 

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

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

271 

272 def __del__(self): 

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

274 self.fd.close() 

275 

276 @property 

277 def vdi_info_size(self): 

278 return self.VDI_INFO_SIZE_IN_SECTORS * SECTOR_SIZE 

279 

280 @abstractmethod 

281 def spaceAvailableForVdis(self, count) -> None: 

282 pass 

283 

284 # common utility functions 

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

286 try: 

287 sr_info = {} 

288 vdi_info = {} 

289 try: 

290 md = self.getMetadataInternal(params) 

291 sr_info = md['sr_info'] 

292 vdi_info = md['vdi_info'] 

293 except: 

294 # Maybe there is no metadata yet 

295 pass 

296 

297 except Exception as e: 

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

299 raise xs_errors.XenError('MetadataError', \ 

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

301 

302 return (sr_info, vdi_info) 

303 

304 def writeMetadata(self, sr_info, vdi_info): 

305 try: 

306 self.writeMetadataInternal(sr_info, vdi_info) 

307 except Exception as e: 

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

309 raise xs_errors.XenError('MetadataError', \ 

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

311 

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

313 def findMetadataVDI(self): 

314 try: 

315 vdi_info = self.getMetadata()[1] 

316 for offset in vdi_info.keys(): 

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

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

319 return vdi_info[offset][UUID_TAG] 

320 

321 return None 

322 except Exception as e: 

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

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

325 raise xs_errors.XenError('MetadataError', \ 

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

327 

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

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

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

331 # SR name-label 

332 # SR name_description 

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

334 # uuid - mandatory 

335 # name-label 

336 # name_description 

337 # is_a_snapshot 

338 # snapshot_of, if snapshot status is true 

339 # snapshot time 

340 # type (system, user or metadata etc) 

341 # vdi_type: raw or vhd 

342 # read_only 

343 # location 

344 # managed 

345 # metadata_of_pool 

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

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

348 

349 try: 

350 objtype = update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] 

351 del update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] 

352 

353 if objtype == METADATA_OBJECT_TYPE_SR: 

354 self.updateSR(update_map) 

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

356 self.updateVdi(update_map) 

357 except Exception as e: 

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

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

360 raise xs_errors.XenError('MetadataError', \ 

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

362 

363 def deleteVdiFromMetadata(self, vdi_uuid): 

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

365 try: 

366 self.deleteVdi(vdi_uuid) 

367 except Exception as e: 

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

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

370 raise xs_errors.XenError('MetadataError', \ 

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

372 

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

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

375 try: 

376 self.addVdiInternal(vdi_info) 

377 except Exception as e: 

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

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

380 raise xs_errors.XenError('MetadataError', \ 

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

382 

383 def ensureSpaceIsAvailableForVdis(self, count): 

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

385 count) 

386 try: 

387 self.spaceAvailableForVdis(count) 

388 except Exception as e: 

389 raise xs_errors.XenError('MetadataError', \ 

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

391 

392 # common functions 

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

394 util.SMlog("Entering deleteVdi") 

395 try: 

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

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

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

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

400 return 

401 

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

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

404 

405 try: 

406 mdlength = getMetadataLength(self.fd) 

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

408 updateLengthInHeader(self.fd, 

409 mdlength - self.vdi_info_size) 

410 except: 

411 raise 

412 except Exception as e: 

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

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

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

416 

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

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

419 offset=0): 

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

421 return self.getVdiInfo(update_map) 

422 

423 value = b"" 

424 for vdi_offset in vdi_info.keys(): 

425 if vdi_offset < lower: 

426 continue 

427 

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

429 break 

430 

431 vdi_map = vdi_info[vdi_offset] 

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

433 # write passed in VDI info 

434 for key in update_map.keys(): 

435 vdi_map[key] = update_map[key] 

436 

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

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

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

440 

441 return value 

442 

443 def addVdiInternal(self, Dict): 

444 util.SMlog("Entering addVdiInternal") 

445 try: 

446 Dict[VDI_DELETED_TAG] = '0' 

447 mdlength = getMetadataLength(self.fd) 

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

449 if 'foundDeleted' not in md: 

450 md['offset'] = mdlength 

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

452 getBlockAlignedRange(mdlength, self.vdi_info_size) 

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

454 if 'foundDeleted' in md: 

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

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

457 else: 

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

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

460 

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

462 

463 if 'foundDeleted' in md: 

464 updateLengthInHeader(self.fd, mdlength) 

465 else: 

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

467 return True 

468 except Exception as e: 

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

470 (Dict, str(e))) 

471 raise 

472 

473 # Get metadata from the file name passed in 

474 # additional params: 

475 # includeDeletedVdis - include deleted VDIs in the returned metadata 

476 # vdi_uuid - only fetch metadata till a particular VDI 

477 # offset - only fetch metadata till a particular offset 

478 # firstDeleted - get the first deleted VDI 

479 # indexByUuid - index VDIs by uuid 

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

481 # sr_info: dictionary containing sr information 

482 # vdi_info: dictionary containing vdi information indexed by offset 

483 # offset: when passing in vdi_uuid/firstDeleted below 

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

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

486 try: 

487 lower = 0 

488 upper = 0 

489 retmap = {} 

490 sr_info_map = {} 

491 ret_vdi_info = {} 

492 length = getMetadataLength(self.fd) 

493 

494 # Read in the metadata fil 

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

496 

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

498 offset = SECTOR_SIZE + len(XML_HEADER) 

499 sr_info = metadataxml[offset: SECTOR_SIZE * 4] 

500 offset = SECTOR_SIZE * 4 

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

502 

503 parsable_metadata = buildParsableMetadataXML(sr_info) 

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

505 

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

507 if 'offset' in params: 

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

509 else: 

510 upper = length 

511 

512 # Now look at the VDI objects 

513 while offset < upper: 

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

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

516 parsable_metadata = buildParsableMetadataXML(vdi_info) 

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

518 vdi_info_map[OFFSET_TAG] = offset 

519 

520 if 'includeDeletedVdis' not in params and \ 

521 vdi_info_map[VDI_DELETED_TAG] == '1': 

522 offset += self.vdi_info_size 

523 continue 

524 

525 if 'indexByUuid' in params: 

526 ret_vdi_info[vdi_info_map[UUID_TAG]] = vdi_info_map 

527 else: 

528 ret_vdi_info[offset] = vdi_info_map 

529 

530 if 'vdi_uuid' in params: 

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

532 retmap['offset'] = offset 

533 (lower, upper) = \ 

534 getBlockAlignedRange(offset, self.vdi_info_size) 

535 

536 elif 'firstDeleted' in params: 

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

538 retmap['foundDeleted'] = 1 

539 retmap['offset'] = offset 

540 (lower, upper) = \ 

541 getBlockAlignedRange(offset, self.vdi_info_size) 

542 

543 offset += self.vdi_info_size 

544 

545 retmap['lower'] = lower 

546 retmap['upper'] = upper 

547 retmap['vdi_info'] = ret_vdi_info 

548 return retmap 

549 except Exception as e: 

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

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

552 raise 

553 

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

555 # passed in 

556 def updateSR(self, Dict): 

557 util.SMlog('entering updateSR') 

558 

559 value = b"" 

560 

561 # Find the offset depending on what we are updating 

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

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

564 offset = SECTOR_SIZE * 2 

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

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

567 SECTOR_SIZE * (SR_INFO_SIZE_IN_SECTORS - 1)}) 

568 

569 sr_info = md['sr_info'] 

570 vdi_info_by_offset = md['vdi_info'] 

571 

572 # update SR info with Dict 

573 for key in Dict.keys(): 

574 sr_info[key] = Dict[key] 

575 

576 # if lower is less than SR header size 

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

578 # if upper is less than SR header size 

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

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

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

582 else: 

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

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

585 

586 # generate the remaining VDI 

587 value += self.generateVDIsForRange(vdi_info_by_offset, 

588 SR_INFO_SIZE_IN_SECTORS, upper) 

589 else: 

590 # generate the remaining VDI 

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

592 

593 file_write_wrapper(self.fd, lower, value) 

594 else: 

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

596 "parameters: %s" % diff) 

597 

598 def updateVdi(self, Dict): 

599 util.SMlog('entering updateVdi') 

600 try: 

601 mdlength = getMetadataLength(self.fd) 

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

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

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

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

606 return True 

607 except Exception as e: 

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

609 (Dict, str(e))) 

610 raise 

611 

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

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

614 # about the SRs and all its VDIs 

615 def writeMetadataInternal(self, sr_info, vdi_info): 

616 try: 

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

618 

619 # Go over the VDIs passed and for each 

620 for key in vdi_info.keys(): 

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

622 

623 # Now write the metadata on disk. 

624 file_write_wrapper(self.fd, 0, md) 

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

626 

627 except Exception as e: 

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

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

630 raise 

631 

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

633 # a range, lower - upper 

634 # sr and vdi information 

635 # VDI information to update 

636 # an optional offset to the VDI to update 

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

638 offset): 

639 util.SMlog("Entering getMetadataToWrite") 

640 try: 

641 value = b"" 

642 vdi_map = {} 

643 

644 # if lower is less than SR info 

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

646 # generate SR info 

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

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

649 

650 # generate the rest of the VDIs till upper 

651 value += self.generateVDIsForRange(vdi_info, \ 

652 SECTOR_SIZE * SR_INFO_SIZE_IN_SECTORS, upper, update_map, offset) 

653 else: 

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

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

656 update_map, offset) 

657 return value 

658 except Exception as e: 

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

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

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

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

663 raise 

664 

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

666 def getVdiInfo(self, Dict, generateSector=0) -> bytes: 

667 return b"" 

668 

669 def getSRInfoForSectors(self, sr_info, range) -> bytes: 

670 return b"" 

671 

672 

673class LVMMetadataHandler(MetadataHandler): 

674 

675 VDI_INFO_SIZE_IN_SECTORS = 2 

676 

677 # constructor 

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

679 lvutil.ensurePathExists(path) 

680 MetadataHandler.__init__(self, path, write) 

681 

682 @override 

683 def spaceAvailableForVdis(self, count) -> None: 

684 created = False 

685 try: 

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

687 uuid = util.gen_uuid() 

688 vdi_info = {UUID_TAG: uuid, 

689 NAME_LABEL_TAG: 'dummy vdi for space check', 

690 NAME_DESCRIPTION_TAG: 'dummy vdi for space check', 

691 IS_A_SNAPSHOT_TAG: 0, 

692 SNAPSHOT_OF_TAG: '', 

693 SNAPSHOT_TIME_TAG: '', 

694 TYPE_TAG: 'user', 

695 VDI_TYPE_TAG: 'vhd', 

696 READ_ONLY_TAG: 0, 

697 MANAGED_TAG: 0, 

698 'metadata_of_pool': '' 

699 } 

700 

701 created = self.addVdiInternal(vdi_info) 

702 except IOError as e: 

703 raise 

704 finally: 

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

706 # Now delete the dummy VDI created above 

707 self.deleteVdi(uuid) 

708 return 

709 

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

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

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

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

714 @override 

715 def getVdiInfo(self, Dict, generateSector=0) -> bytes: 

716 util.SMlog("Entering VDI info") 

717 try: 

718 vdi_info = b"" 

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

720 # which will be called by all classes 

721 # and one specific to this class 

722 if generateSector == 1 or generateSector == 0: 

723 label = xml.sax.saxutils.escape(Dict[NAME_LABEL_TAG]) 

724 desc = xml.sax.saxutils.escape(Dict[NAME_DESCRIPTION_TAG]) 

725 label_length = len(to_utf8(label)) 

726 desc_length = len(to_utf8(desc)) 

727 

728 if label_length + desc_length > MAX_VDI_NAME_LABEL_DESC_LENGTH: 

729 limit = MAX_VDI_NAME_LABEL_DESC_LENGTH // 2 

730 if label_length > limit: 

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

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

733 '%d to %d bytes' 

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

735 

736 if desc_length > limit: 

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

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

739 '%d to %d bytes' 

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

741 

742 Dict[NAME_LABEL_TAG] = label 

743 Dict[NAME_DESCRIPTION_TAG] = desc 

744 

745 # Fill the open struct and write it 

746 vdi_info += getSector(openingTag(VDI_TAG) 

747 + buildXMLElement(NAME_LABEL_TAG, Dict) 

748 + buildXMLElement(NAME_DESCRIPTION_TAG, 

749 Dict)) 

750 

751 if generateSector == 2 or generateSector == 0: 

752 sector2 = b"" 

753 

754 if VDI_DELETED_TAG not in Dict: 

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

756 

757 for tag in Dict.keys(): 

758 if tag == NAME_LABEL_TAG or tag == NAME_DESCRIPTION_TAG: 

759 continue 

760 sector2 += buildXMLElement(tag, Dict) 

761 

762 sector2 += closingTag(VDI_TAG) 

763 vdi_info += getSector(sector2) 

764 return vdi_info 

765 

766 except Exception as e: 

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

768 (Dict, str(e))) 

769 raise 

770 

771 @override 

772 def getSRInfoForSectors(self, sr_info, range) -> bytes: 

773 srinfo = b"" 

774 

775 try: 

776 # write header, name_labael and description in that function 

777 # as its common to all 

778 # Fill up the first sector 

779 if 0 in range: 

780 srinfo = getSector(buildHeader(SECTOR_SIZE)) 

781 

782 if 1 in range: 

783 srinfo += getSector(XML_HEADER 

784 + buildXMLElement(UUID_TAG, sr_info) 

785 + buildXMLElement(ALLOCATION_TAG, sr_info)) 

786 

787 if 2 in range: 

788 # Fill up the SR name_label 

789 srinfo += buildXMLSector(NAME_LABEL_TAG, 

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

791 

792 if 3 in range: 

793 # Fill the name_description 

794 srinfo += buildXMLSector(NAME_DESCRIPTION_TAG, 

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

796 

797 return srinfo 

798 

799 except Exception as e: 

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

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

802 raise