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# 

18from io import SEEK_SET 

19 

20import util 

21import metadata 

22import os 

23import xs_errors 

24import 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 vals = from_utf8(header).split(HEADER_SEP) 

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

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

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

185 raise xs_errors.XenError('MetadataError', \ 

186 opterr='Bad header') 

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

188 

189 

190def getSector(s): 

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

192 return sector_fmt % s 

193 

194 

195def buildXMLSector(tagName, value): 

196 # truncate data if we breach the 512 limit 

197 tag_bytes = to_utf8(tagName) 

198 value_bytes = to_utf8(value) 

199 

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

201 if len(elt) > SECTOR_SIZE: 

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

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

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

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

206 

207 return getSector(elt) 

208 

209 

210def buildXMLElement(tag, value_dict): 

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

212 

213 

214def openingTag(tag): 

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

216 

217 

218def closingTag(tag): 

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

220 

221 

222def buildParsableMetadataXML(info): 

223 tag = to_utf8(metadata.XML_TAG) 

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

225 

226 

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

228 minor=metadata.MD_MINOR): 

229 try: 

230 md = file_read_wrapper(fd, 0) 

231 updated_md = buildHeader(length, major, minor) 

232 updated_md += md[SECTOR_SIZE:] 

233 

234 # Now write the new length 

235 file_write_wrapper(fd, 0, updated_md) 

236 except Exception as e: 

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

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

239 raise 

240 

241 

242def getMetadataLength(fd): 

243 try: 

244 sector1 = \ 

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

246 hdr = unpackHeader(sector1) 

247 return int(hdr[1]) 

248 except Exception as e: 

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

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

251 raise 

252 

253 

254# ----------------- # General helper functions - end # ----------------- 

255class MetadataHandler: 

256 

257 VDI_INFO_SIZE_IN_SECTORS = None 

258 

259 # constructor 

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

261 

262 self.fd = None 

263 self.path = path 

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

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

266 

267 def __del__(self): 

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

269 self.fd.close() 

270 

271 @property 

272 def vdi_info_size(self): 

273 return self.VDI_INFO_SIZE_IN_SECTORS * SECTOR_SIZE 

274 

275 def spaceAvailableForVdis(self, count): 

276 raise NotImplementedError("spaceAvailableForVdis is undefined") 

277 

278 # common utility functions 

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

280 try: 

281 sr_info = {} 

282 vdi_info = {} 

283 try: 

284 md = self.getMetadataInternal(params) 

285 sr_info = md['sr_info'] 

286 vdi_info = md['vdi_info'] 

287 except: 

288 # Maybe there is no metadata yet 

289 pass 

290 

291 except Exception as e: 

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

293 raise xs_errors.XenError('MetadataError', \ 

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

295 

296 return (sr_info, vdi_info) 

297 

298 def writeMetadata(self, sr_info, vdi_info): 

299 try: 

300 self.writeMetadataInternal(sr_info, vdi_info) 

301 except Exception as e: 

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

303 raise xs_errors.XenError('MetadataError', \ 

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

305 

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

307 def findMetadataVDI(self): 

308 try: 

309 vdi_info = self.getMetadata()[1] 

310 for offset in vdi_info.keys(): 

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

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

313 return vdi_info[offset][UUID_TAG] 

314 

315 return None 

316 except Exception as e: 

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

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

319 raise xs_errors.XenError('MetadataError', \ 

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

321 

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

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

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

325 # SR name-label 

326 # SR name_description 

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

328 # uuid - mandatory 

329 # name-label 

330 # name_description 

331 # is_a_snapshot 

332 # snapshot_of, if snapshot status is true 

333 # snapshot time 

334 # type (system, user or metadata etc) 

335 # vdi_type: raw or vhd 

336 # read_only 

337 # location 

338 # managed 

339 # metadata_of_pool 

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

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

342 

343 try: 

344 objtype = update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] 

345 del update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] 

346 

347 if objtype == METADATA_OBJECT_TYPE_SR: 

348 self.updateSR(update_map) 

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

350 self.updateVdi(update_map) 

351 except Exception as e: 

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

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

354 raise xs_errors.XenError('MetadataError', \ 

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

356 

357 def deleteVdiFromMetadata(self, vdi_uuid): 

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

359 try: 

360 self.deleteVdi(vdi_uuid) 

361 except Exception as e: 

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

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

364 raise xs_errors.XenError('MetadataError', \ 

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

366 

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

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

369 try: 

370 self.addVdiInternal(vdi_info) 

371 except Exception as e: 

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

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

374 raise xs_errors.XenError('MetadataError', \ 

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

376 

377 def ensureSpaceIsAvailableForVdis(self, count): 

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

379 count) 

380 try: 

381 self.spaceAvailableForVdis(count) 

382 except Exception as e: 

383 raise xs_errors.XenError('MetadataError', \ 

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

385 

386 # common functions 

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

388 util.SMlog("Entering deleteVdi") 

389 try: 

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

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

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

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

394 return 

395 

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

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

398 

399 try: 

400 mdlength = getMetadataLength(self.fd) 

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

402 updateLengthInHeader(self.fd, 

403 mdlength - self.vdi_info_size) 

404 except: 

405 raise 

406 except Exception as e: 

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

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

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

410 

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

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

413 offset=0): 

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

415 return self.getVdiInfo(update_map) 

416 

417 value = b"" 

418 for vdi_offset in vdi_info.keys(): 

419 if vdi_offset < lower: 

420 continue 

421 

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

423 break 

424 

425 vdi_map = vdi_info[vdi_offset] 

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

427 # write passed in VDI info 

428 for key in update_map.keys(): 

429 vdi_map[key] = update_map[key] 

430 

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

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

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

434 

435 return value 

436 

437 def addVdiInternal(self, Dict): 

438 util.SMlog("Entering addVdiInternal") 

439 try: 

440 Dict[VDI_DELETED_TAG] = '0' 

441 mdlength = getMetadataLength(self.fd) 

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

443 if 'foundDeleted' not in md: 

444 md['offset'] = mdlength 

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

446 getBlockAlignedRange(mdlength, self.vdi_info_size) 

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

448 if 'foundDeleted' in md: 

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

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

451 else: 

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

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

454 

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

456 

457 if 'foundDeleted' in md: 

458 updateLengthInHeader(self.fd, mdlength) 

459 else: 

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

461 return True 

462 except Exception as e: 

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

464 (Dict, str(e))) 

465 raise 

466 

467 # Get metadata from the file name passed in 

468 # additional params: 

469 # includeDeletedVdis - include deleted VDIs in the returned metadata 

470 # vdi_uuid - only fetch metadata till a particular VDI 

471 # offset - only fetch metadata till a particular offset 

472 # firstDeleted - get the first deleted VDI 

473 # indexByUuid - index VDIs by uuid 

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

475 # sr_info: dictionary containing sr information 

476 # vdi_info: dictionary containing vdi information indexed by offset 

477 # offset: when passing in vdi_uuid/firstDeleted below 

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

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

480 try: 

481 lower = 0 

482 upper = 0 

483 retmap = {} 

484 sr_info_map = {} 

485 ret_vdi_info = {} 

486 length = getMetadataLength(self.fd) 

487 

488 # Read in the metadata fil 

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

490 

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

492 offset = SECTOR_SIZE + len(XML_HEADER) 

493 sr_info = metadataxml[offset: SECTOR_SIZE * 4] 

494 offset = SECTOR_SIZE * 4 

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

496 

497 parsable_metadata = buildParsableMetadataXML(sr_info) 

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

499 

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

501 if 'offset' in params: 

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

503 else: 

504 upper = length 

505 

506 # Now look at the VDI objects 

507 while offset < upper: 

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

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

510 parsable_metadata = buildParsableMetadataXML(vdi_info) 

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

512 vdi_info_map[OFFSET_TAG] = offset 

513 

514 if 'includeDeletedVdis' not in params and \ 

515 vdi_info_map[VDI_DELETED_TAG] == '1': 

516 offset += self.vdi_info_size 

517 continue 

518 

519 if 'indexByUuid' in params: 

520 ret_vdi_info[vdi_info_map[UUID_TAG]] = vdi_info_map 

521 else: 

522 ret_vdi_info[offset] = vdi_info_map 

523 

524 if 'vdi_uuid' in params: 

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

526 retmap['offset'] = offset 

527 (lower, upper) = \ 

528 getBlockAlignedRange(offset, self.vdi_info_size) 

529 

530 elif 'firstDeleted' in params: 

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

532 retmap['foundDeleted'] = 1 

533 retmap['offset'] = offset 

534 (lower, upper) = \ 

535 getBlockAlignedRange(offset, self.vdi_info_size) 

536 

537 offset += self.vdi_info_size 

538 

539 retmap['lower'] = lower 

540 retmap['upper'] = upper 

541 retmap['vdi_info'] = ret_vdi_info 

542 return retmap 

543 except Exception as e: 

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

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

546 raise 

547 

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

549 # passed in 

550 def updateSR(self, Dict): 

551 util.SMlog('entering updateSR') 

552 

553 value = b"" 

554 

555 # Find the offset depending on what we are updating 

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

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

558 offset = SECTOR_SIZE * 2 

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

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

561 SECTOR_SIZE * (SR_INFO_SIZE_IN_SECTORS - 1)}) 

562 

563 sr_info = md['sr_info'] 

564 vdi_info_by_offset = md['vdi_info'] 

565 

566 # update SR info with Dict 

567 for key in Dict.keys(): 

568 sr_info[key] = Dict[key] 

569 

570 # if lower is less than SR header size 

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

572 # if upper is less than SR header size 

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

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

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

576 else: 

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

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

579 

580 # generate the remaining VDI 

581 value += self.generateVDIsForRange(vdi_info_by_offset, 

582 SR_INFO_SIZE_IN_SECTORS, upper) 

583 else: 

584 # generate the remaining VDI 

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

586 

587 file_write_wrapper(self.fd, lower, value) 

588 else: 

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

590 "parameters: %s" % diff) 

591 

592 def updateVdi(self, Dict): 

593 util.SMlog('entering updateVdi') 

594 try: 

595 mdlength = getMetadataLength(self.fd) 

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

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

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

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

600 return True 

601 except Exception as e: 

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

603 (Dict, str(e))) 

604 raise 

605 

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

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

608 # about the SRs and all its VDIs 

609 def writeMetadataInternal(self, sr_info, vdi_info): 

610 try: 

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

612 

613 # Go over the VDIs passed and for each 

614 for key in vdi_info.keys(): 

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

616 

617 # Now write the metadata on disk. 

618 file_write_wrapper(self.fd, 0, md) 

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

620 

621 except Exception as e: 

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

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

624 raise 

625 

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

627 # a range, lower - upper 

628 # sr and vdi information 

629 # VDI information to update 

630 # an optional offset to the VDI to update 

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

632 offset): 

633 util.SMlog("Entering getMetadataToWrite") 

634 try: 

635 value = b"" 

636 vdi_map = {} 

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 return 

702 

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

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

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

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

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

708 util.SMlog("Entering VDI info") 

709 try: 

710 vdi_info = b"" 

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

712 # which will be called by all classes 

713 # and one specific to this class 

714 if generateSector == 1 or generateSector == 0: 

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

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

717 label_length = len(to_utf8(label)) 

718 desc_length = len(to_utf8(desc)) 

719 

720 if label_length + desc_length > MAX_VDI_NAME_LABEL_DESC_LENGTH: 

721 limit = MAX_VDI_NAME_LABEL_DESC_LENGTH // 2 

722 if label_length > limit: 

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

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

725 '%d to %d bytes' 

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

727 

728 if desc_length > limit: 

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

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

731 '%d to %d bytes' 

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

733 

734 Dict[NAME_LABEL_TAG] = label 

735 Dict[NAME_DESCRIPTION_TAG] = desc 

736 

737 # Fill the open struct and write it 

738 vdi_info += getSector(openingTag(VDI_TAG) 

739 + buildXMLElement(NAME_LABEL_TAG, Dict) 

740 + buildXMLElement(NAME_DESCRIPTION_TAG, 

741 Dict)) 

742 

743 if generateSector == 2 or generateSector == 0: 

744 sector2 = b"" 

745 

746 if VDI_DELETED_TAG not in Dict: 

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

748 

749 for tag in Dict.keys(): 

750 if tag == NAME_LABEL_TAG or tag == NAME_DESCRIPTION_TAG: 

751 continue 

752 sector2 += buildXMLElement(tag, Dict) 

753 

754 sector2 += closingTag(VDI_TAG) 

755 vdi_info += getSector(sector2) 

756 return vdi_info 

757 

758 except Exception as e: 

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

760 (Dict, str(e))) 

761 raise 

762 

763 def getSRInfoForSectors(self, sr_info, range): 

764 srinfo = b"" 

765 

766 try: 

767 # write header, name_labael and description in that function 

768 # as its common to all 

769 # Fill up the first sector 

770 if 0 in range: 

771 srinfo = getSector(buildHeader(SECTOR_SIZE)) 

772 

773 if 1 in range: 

774 srinfo += getSector(XML_HEADER 

775 + buildXMLElement(UUID_TAG, sr_info) 

776 + buildXMLElement(ALLOCATION_TAG, sr_info)) 

777 

778 if 2 in range: 

779 # Fill up the SR name_label 

780 srinfo += buildXMLSector(NAME_LABEL_TAG, 

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

782 

783 if 3 in range: 

784 # Fill the name_description 

785 srinfo += buildXMLSector(NAME_DESCRIPTION_TAG, 

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

787 

788 return srinfo 

789 

790 except Exception as e: 

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

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

793 raise