Coverage for drivers/srmetadata.py : 81%
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 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])
196def getSector(s):
197 sector_fmt = b"%%-%ds" % SECTOR_SIZE
198 return sector_fmt % s
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)
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)
213 return getSector(elt)
216def buildXMLElement(tag, value_dict):
217 return to_utf8("<%s>%s</%s>" % (tag, value_dict[tag], tag))
220def openingTag(tag):
221 return b"<%s>" % to_utf8(tag)
224def closingTag(tag):
225 return b"</%s>" % to_utf8(tag)
228def buildParsableMetadataXML(info):
229 tag = to_utf8(metadata.XML_TAG)
230 return b"%s<%s>%s</%s>" % (XML_HEADER, tag, info, tag)
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:]
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
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
260# ----------------- # General helper functions - end # -----------------
261class MetadataHandler:
263 VDI_INFO_SIZE_IN_SECTORS: ClassVar[int]
265 # constructor
266 def __init__(self, path=None, write=True):
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)
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()
277 @property
278 def vdi_info_size(self):
279 return self.VDI_INFO_SIZE_IN_SECTORS * SECTOR_SIZE
281 @abstractmethod
282 def spaceAvailableForVdis(self, count) -> None:
283 pass
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
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))
303 return (sr_info, vdi_info)
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))
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]
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))
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, vhd or qcow2
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)
350 try:
351 objtype = update_map[METADATA_UPDATE_OBJECT_TYPE_TAG]
352 del update_map[METADATA_UPDATE_OBJECT_TYPE_TAG]
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))
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))
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)))
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))
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
403 md['vdi_info'][md['offset']][VDI_DELETED_TAG] = '1'
404 self.updateVdi(md['vdi_info'][md['offset']])
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)))
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)
424 value = b""
425 for vdi_offset in vdi_info.keys():
426 if vdi_offset < lower:
427 continue
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
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]
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)
442 return value
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)
462 file_write_wrapper(self.fd, md['lower'], value)
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
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)
495 # Read in the metadata fil
496 metadataxml = file_read_wrapper(self.fd, 0, length)
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'')
504 parsable_metadata = buildParsableMetadataXML(sr_info)
505 retmap['sr_info'] = metadata._parseXML(parsable_metadata)
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
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
521 if 'includeDeletedVdis' not in params and \
522 vdi_info_map[VDI_DELETED_TAG] == '1':
523 offset += self.vdi_info_size
524 continue
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
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)
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)
544 offset += self.vdi_info_size
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
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')
560 value = b""
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)})
570 sr_info = md['sr_info']
571 vdi_info_by_offset = md['vdi_info']
573 # update SR info with Dict
574 for key in Dict.keys():
575 sr_info[key] = Dict[key]
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))
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)
594 file_write_wrapper(self.fd, lower, value)
595 else:
596 raise Exception("SR Update operation not supported for "
597 "parameters: %s" % diff)
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
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))
620 # Go over the VDIs passed and for each
621 for key in vdi_info.keys():
622 md += self.getVdiInfo(vdi_info[key])
624 # Now write the metadata on disk.
625 file_write_wrapper(self.fd, 0, md)
626 updateLengthInHeader(self.fd, len(md))
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
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 = {}
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))
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
666 # specific functions, to be implement by the child classes
667 def getVdiInfo(self, Dict, generateSector=0) -> bytes:
668 return b""
670 def getSRInfoForSectors(self, sr_info, range) -> bytes:
671 return b""
674class LVMMetadataHandler(MetadataHandler):
676 VDI_INFO_SIZE_IN_SECTORS = 2
678 # constructor
679 def __init__(self, path=None, write=True):
680 lvutil.ensurePathExists(path)
681 MetadataHandler.__init__(self, path, write)
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 }
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
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))
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))))
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))))
743 Dict[NAME_LABEL_TAG] = label
744 Dict[NAME_DESCRIPTION_TAG] = desc
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))
752 if generateSector == 2 or generateSector == 0:
753 sector2 = b""
755 if VDI_DELETED_TAG not in Dict:
756 Dict.update({VDI_DELETED_TAG: '0'})
758 for tag in Dict.keys():
759 if tag == NAME_LABEL_TAG or tag == NAME_DESCRIPTION_TAG:
760 continue
761 sector2 += buildXMLElement(tag, Dict)
763 sector2 += closingTag(VDI_TAG)
764 vdi_info += getSector(sector2)
765 return vdi_info
767 except Exception as e:
768 util.SMlog("Exception generating vdi info: %s. Error: %s" % \
769 (Dict, str(e)))
770 raise
772 @override
773 def getSRInfoForSectors(self, sr_info, range) -> bytes:
774 srinfo = b""
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))
783 if 1 in range:
784 srinfo += getSector(XML_HEADER
785 + buildXMLElement(UUID_TAG, sr_info)
786 + buildXMLElement(ALLOCATION_TAG, sr_info))
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]))
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]))
798 return srinfo
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