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 decoded = from_utf8(header) 

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

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

189 vals = decoded.split(HEADER_SEP) 

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

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

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

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

194 

195 

196def getSector(s): 

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

198 return sector_fmt % s 

199 

200 

201def buildXMLSector(tagName, value): 

202 # truncate data if we breach the 512 limit 

203 tag_bytes = to_utf8(tagName) 

204 value_bytes = to_utf8(value) 

205 

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

207 if len(elt) > SECTOR_SIZE: 

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

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

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

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

212 

213 return getSector(elt) 

214 

215 

216def buildXMLElement(tag, value_dict): 

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

218 

219 

220def openingTag(tag): 

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

222 

223 

224def closingTag(tag): 

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

226 

227 

228def buildParsableMetadataXML(info): 

229 tag = to_utf8(metadata.XML_TAG) 

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

231 

232 

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

234 minor=metadata.MD_MINOR): 

235 try: 

236 md = file_read_wrapper(fd, 0) 

237 updated_md = buildHeader(length, major, minor) 

238 updated_md += md[SECTOR_SIZE:] 

239 

240 # Now write the new length 

241 file_write_wrapper(fd, 0, updated_md) 

242 except Exception as e: 

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

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

245 raise 

246 

247 

248def getMetadataLength(fd): 

249 try: 

250 sector1 = \ 

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

252 hdr = unpackHeader(sector1) 

253 return int(hdr[1]) 

254 except Exception as e: 

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

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

257 raise 

258 

259 

260# ----------------- # General helper functions - end # ----------------- 

261class MetadataHandler: 

262 

263 VDI_INFO_SIZE_IN_SECTORS: ClassVar[int] 

264 

265 # constructor 

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

267 

268 self.fd = None 

269 self.path = path 

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

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

272 

273 def __del__(self): 

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

275 self.fd.close() 

276 

277 @property 

278 def vdi_info_size(self): 

279 return self.VDI_INFO_SIZE_IN_SECTORS * SECTOR_SIZE 

280 

281 @abstractmethod 

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

283 pass 

284 

285 # common utility functions 

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

287 try: 

288 sr_info = {} 

289 vdi_info = {} 

290 try: 

291 md = self.getMetadataInternal(params) 

292 sr_info = md['sr_info'] 

293 vdi_info = md['vdi_info'] 

294 except: 

295 # Maybe there is no metadata yet 

296 pass 

297 

298 except Exception as e: 

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

300 raise xs_errors.XenError('MetadataError', \ 

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

302 

303 return (sr_info, vdi_info) 

304 

305 def writeMetadata(self, sr_info, vdi_info): 

306 try: 

307 self.writeMetadataInternal(sr_info, vdi_info) 

308 except Exception as e: 

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

310 raise xs_errors.XenError('MetadataError', \ 

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

312 

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

314 def findMetadataVDI(self): 

315 try: 

316 vdi_info = self.getMetadata()[1] 

317 for offset in vdi_info.keys(): 

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

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

320 return vdi_info[offset][UUID_TAG] 

321 

322 return None 

323 except Exception as e: 

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

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

326 raise xs_errors.XenError('MetadataError', \ 

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

328 

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

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

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

332 # SR name-label 

333 # SR name_description 

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

335 # uuid - mandatory 

336 # name-label 

337 # name_description 

338 # is_a_snapshot 

339 # snapshot_of, if snapshot status is true 

340 # snapshot time 

341 # type (system, user or metadata etc) 

342 # vdi_type: raw or vhd 

343 # read_only 

344 # location 

345 # managed 

346 # metadata_of_pool 

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

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

349 

350 try: 

351 objtype = update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] 

352 del update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] 

353 

354 if objtype == METADATA_OBJECT_TYPE_SR: 

355 self.updateSR(update_map) 

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

357 self.updateVdi(update_map) 

358 except Exception as e: 

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

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

361 raise xs_errors.XenError('MetadataError', \ 

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

363 

364 def deleteVdiFromMetadata(self, vdi_uuid): 

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

366 try: 

367 self.deleteVdi(vdi_uuid) 

368 except Exception as e: 

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

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

371 raise xs_errors.XenError('MetadataError', \ 

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

373 

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

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

376 try: 

377 self.addVdiInternal(vdi_info) 

378 except Exception as e: 

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

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

381 raise xs_errors.XenError('MetadataError', \ 

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

383 

384 def ensureSpaceIsAvailableForVdis(self, count): 

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

386 count) 

387 try: 

388 self.spaceAvailableForVdis(count) 

389 except Exception as e: 

390 raise xs_errors.XenError('MetadataError', \ 

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

392 

393 # common functions 

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

395 util.SMlog("Entering deleteVdi") 

396 try: 

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

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

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

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

401 return 

402 

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

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

405 

406 try: 

407 mdlength = getMetadataLength(self.fd) 

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

409 updateLengthInHeader(self.fd, 

410 mdlength - self.vdi_info_size) 

411 except: 

412 raise 

413 except Exception as e: 

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

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

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

417 

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

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

420 offset=0): 

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

422 return self.getVdiInfo(update_map) 

423 

424 value = b"" 

425 for vdi_offset in vdi_info.keys(): 

426 if vdi_offset < lower: 

427 continue 

428 

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

430 break 

431 

432 vdi_map = vdi_info[vdi_offset] 

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

434 # write passed in VDI info 

435 for key in update_map.keys(): 

436 vdi_map[key] = update_map[key] 

437 

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

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

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

441 

442 return value 

443 

444 def addVdiInternal(self, Dict): 

445 util.SMlog("Entering addVdiInternal") 

446 try: 

447 Dict[VDI_DELETED_TAG] = '0' 

448 mdlength = getMetadataLength(self.fd) 

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

450 if 'foundDeleted' not in md: 

451 md['offset'] = mdlength 

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

453 getBlockAlignedRange(mdlength, self.vdi_info_size) 

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

455 if 'foundDeleted' in md: 

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

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

458 else: 

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

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

461 

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

463 

464 if 'foundDeleted' in md: 

465 updateLengthInHeader(self.fd, mdlength) 

466 else: 

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

468 return True 

469 except Exception as e: 

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

471 (Dict, str(e))) 

472 raise 

473 

474 # Get metadata from the file name passed in 

475 # additional params: 

476 # includeDeletedVdis - include deleted VDIs in the returned metadata 

477 # vdi_uuid - only fetch metadata till a particular VDI 

478 # offset - only fetch metadata till a particular offset 

479 # firstDeleted - get the first deleted VDI 

480 # indexByUuid - index VDIs by uuid 

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

482 # sr_info: dictionary containing sr information 

483 # vdi_info: dictionary containing vdi information indexed by offset 

484 # offset: when passing in vdi_uuid/firstDeleted below 

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

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

487 try: 

488 lower = 0 

489 upper = 0 

490 retmap = {} 

491 sr_info_map = {} 

492 ret_vdi_info = {} 

493 length = getMetadataLength(self.fd) 

494 

495 # Read in the metadata fil 

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

497 

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

499 offset = SECTOR_SIZE + len(XML_HEADER) 

500 sr_info = metadataxml[offset: SECTOR_SIZE * 4] 

501 offset = SECTOR_SIZE * 4 

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

503 

504 parsable_metadata = buildParsableMetadataXML(sr_info) 

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

506 

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

508 if 'offset' in params: 

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

510 else: 

511 upper = length 

512 

513 # Now look at the VDI objects 

514 while offset < upper: 

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

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

517 parsable_metadata = buildParsableMetadataXML(vdi_info) 

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

519 vdi_info_map[OFFSET_TAG] = offset 

520 

521 if 'includeDeletedVdis' not in params and \ 

522 vdi_info_map[VDI_DELETED_TAG] == '1': 

523 offset += self.vdi_info_size 

524 continue 

525 

526 if 'indexByUuid' in params: 

527 ret_vdi_info[vdi_info_map[UUID_TAG]] = vdi_info_map 

528 else: 

529 ret_vdi_info[offset] = vdi_info_map 

530 

531 if 'vdi_uuid' in params: 

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

533 retmap['offset'] = offset 

534 (lower, upper) = \ 

535 getBlockAlignedRange(offset, self.vdi_info_size) 

536 

537 elif 'firstDeleted' in params: 

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

539 retmap['foundDeleted'] = 1 

540 retmap['offset'] = offset 

541 (lower, upper) = \ 

542 getBlockAlignedRange(offset, self.vdi_info_size) 

543 

544 offset += self.vdi_info_size 

545 

546 retmap['lower'] = lower 

547 retmap['upper'] = upper 

548 retmap['vdi_info'] = ret_vdi_info 

549 return retmap 

550 except Exception as e: 

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

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

553 raise 

554 

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

556 # passed in 

557 def updateSR(self, Dict): 

558 util.SMlog('entering updateSR') 

559 

560 value = b"" 

561 

562 # Find the offset depending on what we are updating 

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

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

565 offset = SECTOR_SIZE * 2 

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

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

568 SECTOR_SIZE * (SR_INFO_SIZE_IN_SECTORS - 1)}) 

569 

570 sr_info = md['sr_info'] 

571 vdi_info_by_offset = md['vdi_info'] 

572 

573 # update SR info with Dict 

574 for key in Dict.keys(): 

575 sr_info[key] = Dict[key] 

576 

577 # if lower is less than SR header size 

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

579 # if upper is less than SR header size 

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

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

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

583 else: 

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

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

586 

587 # generate the remaining VDI 

588 value += self.generateVDIsForRange(vdi_info_by_offset, 

589 SR_INFO_SIZE_IN_SECTORS, upper) 

590 else: 

591 # generate the remaining VDI 

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

593 

594 file_write_wrapper(self.fd, lower, value) 

595 else: 

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

597 "parameters: %s" % diff) 

598 

599 def updateVdi(self, Dict): 

600 util.SMlog('entering updateVdi') 

601 try: 

602 mdlength = getMetadataLength(self.fd) 

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

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

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

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

607 return True 

608 except Exception as e: 

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

610 (Dict, str(e))) 

611 raise 

612 

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

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

615 # about the SRs and all its VDIs 

616 def writeMetadataInternal(self, sr_info, vdi_info): 

617 try: 

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

619 

620 # Go over the VDIs passed and for each 

621 for key in vdi_info.keys(): 

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

623 

624 # Now write the metadata on disk. 

625 file_write_wrapper(self.fd, 0, md) 

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

627 

628 except Exception as e: 

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

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

631 raise 

632 

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

634 # a range, lower - upper 

635 # sr and vdi information 

636 # VDI information to update 

637 # an optional offset to the VDI to update 

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

639 offset): 

640 util.SMlog("Entering getMetadataToWrite") 

641 try: 

642 value = b"" 

643 vdi_map = {} 

644 

645 # if lower is less than SR info 

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

647 # generate SR info 

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

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

650 

651 # generate the rest of the VDIs till upper 

652 value += self.generateVDIsForRange(vdi_info, \ 

653 SECTOR_SIZE * SR_INFO_SIZE_IN_SECTORS, upper, update_map, offset) 

654 else: 

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

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

657 update_map, offset) 

658 return value 

659 except Exception as e: 

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

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

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

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

664 raise 

665 

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

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

668 return b"" 

669 

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

671 return b"" 

672 

673 

674class LVMMetadataHandler(MetadataHandler): 

675 

676 VDI_INFO_SIZE_IN_SECTORS = 2 

677 

678 # constructor 

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

680 lvutil.ensurePathExists(path) 

681 MetadataHandler.__init__(self, path, write) 

682 

683 @override 

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

685 created = False 

686 try: 

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

688 uuid = util.gen_uuid() 

689 vdi_info = {UUID_TAG: uuid, 

690 NAME_LABEL_TAG: 'dummy vdi for space check', 

691 NAME_DESCRIPTION_TAG: 'dummy vdi for space check', 

692 IS_A_SNAPSHOT_TAG: 0, 

693 SNAPSHOT_OF_TAG: '', 

694 SNAPSHOT_TIME_TAG: '', 

695 TYPE_TAG: 'user', 

696 VDI_TYPE_TAG: 'vhd', 

697 READ_ONLY_TAG: 0, 

698 MANAGED_TAG: 0, 

699 'metadata_of_pool': '' 

700 } 

701 

702 created = self.addVdiInternal(vdi_info) 

703 except IOError as e: 

704 raise 

705 finally: 

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

707 # Now delete the dummy VDI created above 

708 self.deleteVdi(uuid) 

709 return 

710 

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

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

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

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

715 @override 

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

717 util.SMlog("Entering VDI info") 

718 try: 

719 vdi_info = b"" 

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

721 # which will be called by all classes 

722 # and one specific to this class 

723 if generateSector == 1 or generateSector == 0: 

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

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

726 label_length = len(to_utf8(label)) 

727 desc_length = len(to_utf8(desc)) 

728 

729 if label_length + desc_length > MAX_VDI_NAME_LABEL_DESC_LENGTH: 

730 limit = MAX_VDI_NAME_LABEL_DESC_LENGTH // 2 

731 if label_length > limit: 

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

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

734 '%d to %d bytes' 

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

736 

737 if desc_length > limit: 

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

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

740 '%d to %d bytes' 

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

742 

743 Dict[NAME_LABEL_TAG] = label 

744 Dict[NAME_DESCRIPTION_TAG] = desc 

745 

746 # Fill the open struct and write it 

747 vdi_info += getSector(openingTag(VDI_TAG) 

748 + buildXMLElement(NAME_LABEL_TAG, Dict) 

749 + buildXMLElement(NAME_DESCRIPTION_TAG, 

750 Dict)) 

751 

752 if generateSector == 2 or generateSector == 0: 

753 sector2 = b"" 

754 

755 if VDI_DELETED_TAG not in Dict: 

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

757 

758 for tag in Dict.keys(): 

759 if tag == NAME_LABEL_TAG or tag == NAME_DESCRIPTION_TAG: 

760 continue 

761 sector2 += buildXMLElement(tag, Dict) 

762 

763 sector2 += closingTag(VDI_TAG) 

764 vdi_info += getSector(sector2) 

765 return vdi_info 

766 

767 except Exception as e: 

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

769 (Dict, str(e))) 

770 raise 

771 

772 @override 

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

774 srinfo = b"" 

775 

776 try: 

777 # write header, name_labael and description in that function 

778 # as its common to all 

779 # Fill up the first sector 

780 if 0 in range: 

781 srinfo = getSector(buildHeader(SECTOR_SIZE)) 

782 

783 if 1 in range: 

784 srinfo += getSector(XML_HEADER 

785 + buildXMLElement(UUID_TAG, sr_info) 

786 + buildXMLElement(ALLOCATION_TAG, sr_info)) 

787 

788 if 2 in range: 

789 # Fill up the SR name_label 

790 srinfo += buildXMLSector(NAME_LABEL_TAG, 

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

792 

793 if 3 in range: 

794 # Fill the name_description 

795 srinfo += buildXMLSector(NAME_DESCRIPTION_TAG, 

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

797 

798 return srinfo 

799 

800 except Exception as e: 

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

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

803 raise