Coverage for drivers/srmetadata.py : 80%

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
20import util
21import metadata
22import os
23import xs_errors
24import lvutil
25import xml.sax.saxutils
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.
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.
47SECTOR_SIZE = 512
48XML_HEADER = b"<?xml version=\"1.0\" ?>"
49MAX_METADATA_LENGTH_SIZE = 10
50OFFSET_TAG = 'offset'
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
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
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
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))
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))
136def to_utf8(s):
137 return s.encode("utf-8")
140def from_utf8(bs):
141 return bs.decode("utf-8")
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
161 upper = lower + block_size
163 while upper < (lower + length):
164 upper += block_size
166 return (lower, upper)
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))
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])
190def getSector(s):
191 sector_fmt = b"%%-%ds" % SECTOR_SIZE
192 return sector_fmt % s
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)
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)
207 return getSector(elt)
210def buildXMLElement(tag, value_dict):
211 return to_utf8("<%s>%s</%s>" % (tag, value_dict[tag], tag))
214def openingTag(tag):
215 return b"<%s>" % to_utf8(tag)
218def closingTag(tag):
219 return b"</%s>" % to_utf8(tag)
222def buildParsableMetadataXML(info):
223 tag = to_utf8(metadata.XML_TAG)
224 return b"%s<%s>%s</%s>" % (XML_HEADER, tag, info, tag)
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:]
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
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
254# ----------------- # General helper functions - end # -----------------
255class MetadataHandler:
257 VDI_INFO_SIZE_IN_SECTORS = None
259 # constructor
260 def __init__(self, path=None, write=True):
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)
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()
271 @property
272 def vdi_info_size(self):
273 return self.VDI_INFO_SIZE_IN_SECTORS * SECTOR_SIZE
275 def spaceAvailableForVdis(self, count):
276 raise NotImplementedError("spaceAvailableForVdis is undefined")
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
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))
296 return (sr_info, vdi_info)
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))
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]
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))
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)
343 try:
344 objtype = update_map[METADATA_UPDATE_OBJECT_TYPE_TAG]
345 del update_map[METADATA_UPDATE_OBJECT_TYPE_TAG]
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))
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))
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)))
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))
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
396 md['vdi_info'][md['offset']][VDI_DELETED_TAG] = '1'
397 self.updateVdi(md['vdi_info'][md['offset']])
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)))
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)
417 value = b""
418 for vdi_offset in vdi_info.keys():
419 if vdi_offset < lower:
420 continue
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
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]
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)
435 return value
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)
455 file_write_wrapper(self.fd, md['lower'], value)
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
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)
488 # Read in the metadata fil
489 metadataxml = file_read_wrapper(self.fd, 0, length)
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'')
497 parsable_metadata = buildParsableMetadataXML(sr_info)
498 retmap['sr_info'] = metadata._parseXML(parsable_metadata)
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
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
514 if 'includeDeletedVdis' not in params and \
515 vdi_info_map[VDI_DELETED_TAG] == '1':
516 offset += self.vdi_info_size
517 continue
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
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)
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)
537 offset += self.vdi_info_size
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
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')
553 value = b""
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)})
563 sr_info = md['sr_info']
564 vdi_info_by_offset = md['vdi_info']
566 # update SR info with Dict
567 for key in Dict.keys():
568 sr_info[key] = Dict[key]
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))
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)
587 file_write_wrapper(self.fd, lower, value)
588 else:
589 raise Exception("SR Update operation not supported for "
590 "parameters: %s" % diff)
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
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))
613 # Go over the VDIs passed and for each
614 for key in vdi_info.keys():
615 md += self.getVdiInfo(vdi_info[key])
617 # Now write the metadata on disk.
618 file_write_wrapper(self.fd, 0, md)
619 updateLengthInHeader(self.fd, len(md))
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
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 = {}
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))
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
659 # specific functions, to be implement by the child classes
660 def getVdiInfo(self, Dict, generateSector=0):
661 return b""
663 def getSRInfoForSectors(self, sr_info, range):
664 return b""
667class LVMMetadataHandler(MetadataHandler):
669 VDI_INFO_SIZE_IN_SECTORS = 2
671 # constructor
672 def __init__(self, path=None, write=True):
673 lvutil.ensurePathExists(path)
674 MetadataHandler.__init__(self, path, write)
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 }
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
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))
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))))
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))))
734 Dict[NAME_LABEL_TAG] = label
735 Dict[NAME_DESCRIPTION_TAG] = desc
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))
743 if generateSector == 2 or generateSector == 0:
744 sector2 = b""
746 if VDI_DELETED_TAG not in Dict:
747 Dict.update({VDI_DELETED_TAG: '0'})
749 for tag in Dict.keys():
750 if tag == NAME_LABEL_TAG or tag == NAME_DESCRIPTION_TAG:
751 continue
752 sector2 += buildXMLElement(tag, Dict)
754 sector2 += closingTag(VDI_TAG)
755 vdi_info += getSector(sector2)
756 return vdi_info
758 except Exception as e:
759 util.SMlog("Exception generating vdi info: %s. Error: %s" % \
760 (Dict, str(e)))
761 raise
763 def getSRInfoForSectors(self, sr_info, range):
764 srinfo = b""
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))
773 if 1 in range:
774 srinfo += getSector(XML_HEADER
775 + buildXMLElement(UUID_TAG, sr_info)
776 + buildXMLElement(ALLOCATION_TAG, sr_info))
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]))
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]))
788 return srinfo
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