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#
19from sm_typing import ClassVar, override
21from abc import abstractmethod
23from io import SEEK_SET
25import util
26import metadata
27import os
28import xs_errors
29import lvutil
30import xml.sax.saxutils
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.
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.
52SECTOR_SIZE = 512
53XML_HEADER = b"<?xml version=\"1.0\" ?>"
54MAX_METADATA_LENGTH_SIZE = 10
55OFFSET_TAG = 'offset'
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
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
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
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))
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))
141def to_utf8(s):
142 return s.encode("utf-8")
145def from_utf8(bs):
146 return bs.decode("utf-8")
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
166 upper = lower + block_size
168 while upper < (lower + length):
169 upper += block_size
171 return (lower, upper)
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))
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])
195def getSector(s):
196 sector_fmt = b"%%-%ds" % SECTOR_SIZE
197 return sector_fmt % s
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)
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)
212 return getSector(elt)
215def buildXMLElement(tag, value_dict):
216 return to_utf8("<%s>%s</%s>" % (tag, value_dict[tag], tag))
219def openingTag(tag):
220 return b"<%s>" % to_utf8(tag)
223def closingTag(tag):
224 return b"</%s>" % to_utf8(tag)
227def buildParsableMetadataXML(info):
228 tag = to_utf8(metadata.XML_TAG)
229 return b"%s<%s>%s</%s>" % (XML_HEADER, tag, info, tag)
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:]
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
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
259# ----------------- # General helper functions - end # -----------------
260class MetadataHandler:
262 VDI_INFO_SIZE_IN_SECTORS: ClassVar[int]
264 # constructor
265 def __init__(self, path=None, write=True):
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)
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()
276 @property
277 def vdi_info_size(self):
278 return self.VDI_INFO_SIZE_IN_SECTORS * SECTOR_SIZE
280 @abstractmethod
281 def spaceAvailableForVdis(self, count) -> None:
282 pass
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
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))
302 return (sr_info, vdi_info)
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))
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]
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))
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)
349 try:
350 objtype = update_map[METADATA_UPDATE_OBJECT_TYPE_TAG]
351 del update_map[METADATA_UPDATE_OBJECT_TYPE_TAG]
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))
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))
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)))
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))
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
402 md['vdi_info'][md['offset']][VDI_DELETED_TAG] = '1'
403 self.updateVdi(md['vdi_info'][md['offset']])
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)))
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)
423 value = b""
424 for vdi_offset in vdi_info.keys():
425 if vdi_offset < lower:
426 continue
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
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]
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)
441 return value
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)
461 file_write_wrapper(self.fd, md['lower'], value)
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
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)
494 # Read in the metadata fil
495 metadataxml = file_read_wrapper(self.fd, 0, length)
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'')
503 parsable_metadata = buildParsableMetadataXML(sr_info)
504 retmap['sr_info'] = metadata._parseXML(parsable_metadata)
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
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
520 if 'includeDeletedVdis' not in params and \
521 vdi_info_map[VDI_DELETED_TAG] == '1':
522 offset += self.vdi_info_size
523 continue
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
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)
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)
543 offset += self.vdi_info_size
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
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')
559 value = b""
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)})
569 sr_info = md['sr_info']
570 vdi_info_by_offset = md['vdi_info']
572 # update SR info with Dict
573 for key in Dict.keys():
574 sr_info[key] = Dict[key]
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))
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)
593 file_write_wrapper(self.fd, lower, value)
594 else:
595 raise Exception("SR Update operation not supported for "
596 "parameters: %s" % diff)
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
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))
619 # Go over the VDIs passed and for each
620 for key in vdi_info.keys():
621 md += self.getVdiInfo(vdi_info[key])
623 # Now write the metadata on disk.
624 file_write_wrapper(self.fd, 0, md)
625 updateLengthInHeader(self.fd, len(md))
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
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 = {}
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))
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
665 # specific functions, to be implement by the child classes
666 def getVdiInfo(self, Dict, generateSector=0) -> bytes:
667 return b""
669 def getSRInfoForSectors(self, sr_info, range) -> bytes:
670 return b""
673class LVMMetadataHandler(MetadataHandler):
675 VDI_INFO_SIZE_IN_SECTORS = 2
677 # constructor
678 def __init__(self, path=None, write=True):
679 lvutil.ensurePathExists(path)
680 MetadataHandler.__init__(self, path, write)
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 }
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
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))
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))))
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))))
742 Dict[NAME_LABEL_TAG] = label
743 Dict[NAME_DESCRIPTION_TAG] = desc
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))
751 if generateSector == 2 or generateSector == 0:
752 sector2 = b""
754 if VDI_DELETED_TAG not in Dict:
755 Dict.update({VDI_DELETED_TAG: '0'})
757 for tag in Dict.keys():
758 if tag == NAME_LABEL_TAG or tag == NAME_DESCRIPTION_TAG:
759 continue
760 sector2 += buildXMLElement(tag, Dict)
762 sector2 += closingTag(VDI_TAG)
763 vdi_info += getSector(sector2)
764 return vdi_info
766 except Exception as e:
767 util.SMlog("Exception generating vdi info: %s. Error: %s" % \
768 (Dict, str(e)))
769 raise
771 @override
772 def getSRInfoForSectors(self, sr_info, range) -> bytes:
773 srinfo = b""
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))
782 if 1 in range:
783 srinfo += getSector(XML_HEADER
784 + buildXMLElement(UUID_TAG, sr_info)
785 + buildXMLElement(ALLOCATION_TAG, sr_info))
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]))
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]))
797 return srinfo
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