Coverage for libs/sm/srmetadata.py: 81%
430 statements
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
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
20from sm.core import util
21from sm import metadata
22import os
23from sm.core import xs_errors
24from sm import 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 decoded = from_utf8(header)
182 if len(decoded.rstrip('\x00')) == 0:
183 raise xs_errors.XenError('MetadataError', opterr='Empty header')
184 vals = decoded.split(HEADER_SEP)
185 if len(vals) != 4 or vals[0] != metadata.HDR_STRING:
186 util.SMlog(f"Exception unpacking metadata header: Error: Bad header {header!r}")
187 raise xs_errors.XenError('MetadataError', opterr='Bad header')
188 return (vals[0], vals[1], vals[2], vals[3])
191def getSector(s):
192 sector_fmt = b"%%-%ds" % SECTOR_SIZE
193 return sector_fmt % s
196def buildXMLSector(tagName, value):
197 # truncate data if we breach the 512 limit
198 tag_bytes = to_utf8(tagName)
199 value_bytes = to_utf8(value)
201 elt = b"<%s>%s</%s>" % (tag_bytes, value_bytes, tag_bytes)
202 if len(elt) > SECTOR_SIZE:
203 length = util.unictrunc(value_bytes, SECTOR_SIZE - 2 * len(tag_bytes) - 5)
204 util.SMlog('warning: SR %s truncated from %d to %d bytes'
205 % (tagName, len(value_bytes), length))
206 elt = b"<%s>%s</%s>" % (tag_bytes, value_bytes[:length], tag_bytes)
208 return getSector(elt)
211def buildXMLElement(tag, value_dict):
212 return to_utf8("<%s>%s</%s>" % (tag, value_dict[tag], tag))
215def openingTag(tag):
216 return b"<%s>" % to_utf8(tag)
219def closingTag(tag):
220 return b"</%s>" % to_utf8(tag)
223def buildParsableMetadataXML(info):
224 tag = to_utf8(metadata.XML_TAG)
225 return b"%s<%s>%s</%s>" % (XML_HEADER, tag, info, tag)
228def updateLengthInHeader(fd, length, major=metadata.MD_MAJOR, \
229 minor=metadata.MD_MINOR):
230 try:
231 md = file_read_wrapper(fd, 0)
232 updated_md = buildHeader(length, major, minor)
233 updated_md += md[SECTOR_SIZE:]
235 # Now write the new length
236 file_write_wrapper(fd, 0, updated_md)
237 except Exception as e:
238 util.SMlog("Exception updating metadata length with length: %d."
239 "Error: %s" % (length, str(e)))
240 raise
243def getMetadataLength(fd):
244 try:
245 sector1 = \
246 file_read_wrapper(fd, 0, SECTOR_SIZE).strip()
247 hdr = unpackHeader(sector1)
248 return int(hdr[1])
249 except Exception as e:
250 util.SMlog("Exception getting metadata length: "
251 "Error: %s" % str(e))
252 raise
255# ----------------- # General helper functions - end # -----------------
256class MetadataHandler:
258 VDI_INFO_SIZE_IN_SECTORS = None
260 # constructor
261 def __init__(self, path=None, write=True):
263 self.fd = None
264 self.path = path
265 if self.path is not None: 265 ↛ exitline 265 didn't return from function '__init__', because the condition on line 265 was never false
266 self.fd = open_file(self.path, write)
268 def __del__(self):
269 if self.fd: 269 ↛ exitline 269 didn't return from function '__del__', because the condition on line 269 was never false
270 self.fd.close()
272 @property
273 def vdi_info_size(self):
274 return self.VDI_INFO_SIZE_IN_SECTORS * SECTOR_SIZE
276 def spaceAvailableForVdis(self, count):
277 raise NotImplementedError("spaceAvailableForVdis is undefined")
279 # common utility functions
280 def getMetadata(self, params={}):
281 try:
282 sr_info = {}
283 vdi_info = {}
284 try:
285 md = self.getMetadataInternal(params)
286 sr_info = md['sr_info']
287 vdi_info = md['vdi_info']
288 except:
289 # Maybe there is no metadata yet
290 pass
292 except Exception as e:
293 util.SMlog('Exception getting metadata. Error: %s' % str(e))
294 raise xs_errors.XenError('MetadataError', \
295 opterr='%s' % str(e))
297 return (sr_info, vdi_info)
299 def writeMetadata(self, sr_info, vdi_info):
300 try:
301 self.writeMetadataInternal(sr_info, vdi_info)
302 except Exception as e:
303 util.SMlog('Exception writing metadata. Error: %s' % str(e))
304 raise xs_errors.XenError('MetadataError', \
305 opterr='%s' % str(e))
307 # read metadata for this SR and find if a metadata VDI exists
308 def findMetadataVDI(self):
309 try:
310 vdi_info = self.getMetadata()[1]
311 for offset in vdi_info.keys():
312 if vdi_info[offset][TYPE_TAG] == 'metadata' and \
313 vdi_info[offset][IS_A_SNAPSHOT_TAG] == '0':
314 return vdi_info[offset][UUID_TAG]
316 return None
317 except Exception as e:
318 util.SMlog('Exception checking if SR metadata a metadata VDI.' \
319 'Error: %s' % str(e))
320 raise xs_errors.XenError('MetadataError', \
321 opterr='%s' % str(e))
323 # update the SR information or one of the VDIs information
324 # the passed in map would have a key 'objtype', either sr or vdi.
325 # if the key is sr, the following might be passed in
326 # SR name-label
327 # SR name_description
328 # if the key is vdi, the following information per VDI may be passed in
329 # uuid - mandatory
330 # name-label
331 # name_description
332 # is_a_snapshot
333 # snapshot_of, if snapshot status is true
334 # snapshot time
335 # type (system, user or metadata etc)
336 # vdi_type: raw or vhd
337 # read_only
338 # location
339 # managed
340 # metadata_of_pool
341 def updateMetadata(self, update_map={}):
342 util.SMlog("Updating metadata : %s" % update_map)
344 try:
345 objtype = update_map[METADATA_UPDATE_OBJECT_TYPE_TAG]
346 del update_map[METADATA_UPDATE_OBJECT_TYPE_TAG]
348 if objtype == METADATA_OBJECT_TYPE_SR:
349 self.updateSR(update_map)
350 elif objtype == METADATA_OBJECT_TYPE_VDI: 350 ↛ exitline 350 didn't return from function 'updateMetadata', because the condition on line 350 was never false
351 self.updateVdi(update_map)
352 except Exception as e:
353 util.SMlog('Error updating Metadata Volume with update' \
354 'map: %s. Error: %s' % (update_map, str(e)))
355 raise xs_errors.XenError('MetadataError', \
356 opterr='%s' % str(e))
358 def deleteVdiFromMetadata(self, vdi_uuid):
359 util.SMlog("Deleting vdi: %s" % vdi_uuid)
360 try:
361 self.deleteVdi(vdi_uuid)
362 except Exception as e:
363 util.SMlog('Error deleting vdi %s from the metadata. ' \
364 'Error: %s' % (vdi_uuid, str(e)))
365 raise xs_errors.XenError('MetadataError', \
366 opterr='%s' % str(e))
368 def addVdi(self, vdi_info={}):
369 util.SMlog("Adding VDI with info: %s" % vdi_info)
370 try:
371 self.addVdiInternal(vdi_info)
372 except Exception as e:
373 util.SMlog('Error adding VDI to Metadata Volume with ' \
374 'update map: %s. Error: %s' % (vdi_info, str(e)))
375 raise xs_errors.XenError('MetadataError', \
376 opterr='%s' % (str(e)))
378 def ensureSpaceIsAvailableForVdis(self, count):
379 util.SMlog("Checking if there is space in the metadata for %d VDI." % \
380 count)
381 try:
382 self.spaceAvailableForVdis(count)
383 except Exception as e:
384 raise xs_errors.XenError('MetadataError', \
385 opterr='%s' % str(e))
387 # common functions
388 def deleteVdi(self, vdi_uuid, offset=0):
389 util.SMlog("Entering deleteVdi")
390 try:
391 md = self.getMetadataInternal({'vdi_uuid': vdi_uuid})
392 if 'offset' not in md: 392 ↛ 393line 392 didn't jump to line 393, because the condition on line 392 was never true
393 util.SMlog("Metadata for VDI %s not present, or already removed, " \
394 "no further deletion action required." % vdi_uuid)
395 return
397 md['vdi_info'][md['offset']][VDI_DELETED_TAG] = '1'
398 self.updateVdi(md['vdi_info'][md['offset']])
400 try:
401 mdlength = getMetadataLength(self.fd)
402 if (mdlength - md['offset']) == self.vdi_info_size:
403 updateLengthInHeader(self.fd,
404 mdlength - self.vdi_info_size)
405 except:
406 raise
407 except Exception as e:
408 raise Exception("VDI delete operation failed for " \
409 "parameters: %s, %s. Error: %s" % \
410 (self.path, vdi_uuid, str(e)))
412 # common functions with some details derived from the child class
413 def generateVDIsForRange(self, vdi_info, lower, upper, update_map={}, \
414 offset=0):
415 if not len(vdi_info.keys()) or offset not in vdi_info:
416 return self.getVdiInfo(update_map)
418 value = b""
419 for vdi_offset in vdi_info.keys():
420 if vdi_offset < lower:
421 continue
423 if len(value) >= (upper - lower): 423 ↛ 424line 423 didn't jump to line 424, because the condition on line 423 was never true
424 break
426 vdi_map = vdi_info[vdi_offset]
427 if vdi_offset == offset: 427 ↛ 432line 427 didn't jump to line 432, because the condition on line 427 was never false
428 # write passed in VDI info
429 for key in update_map.keys():
430 vdi_map[key] = update_map[key]
432 for i in range(1, self.VDI_INFO_SIZE_IN_SECTORS + 1):
433 if len(value) < (upper - lower): 433 ↛ 432line 433 didn't jump to line 432, because the condition on line 433 was never false
434 value += self.getVdiInfo(vdi_map, i)
436 return value
438 def addVdiInternal(self, Dict):
439 util.SMlog("Entering addVdiInternal")
440 try:
441 Dict[VDI_DELETED_TAG] = '0'
442 mdlength = getMetadataLength(self.fd)
443 md = self.getMetadataInternal({'firstDeleted': 1, 'includeDeletedVdis': 1})
444 if 'foundDeleted' not in md:
445 md['offset'] = mdlength
446 (md['lower'], md['upper']) = \
447 getBlockAlignedRange(mdlength, self.vdi_info_size)
448 # If this has created a new VDI, update metadata length
449 if 'foundDeleted' in md:
450 value = self.getMetadataToWrite(md['sr_info'], md['vdi_info'], \
451 md['lower'], md['upper'], Dict, md['offset'])
452 else:
453 value = self.getMetadataToWrite(md['sr_info'], md['vdi_info'], \
454 md['lower'], md['upper'], Dict, mdlength)
456 file_write_wrapper(self.fd, md['lower'], value)
458 if 'foundDeleted' in md:
459 updateLengthInHeader(self.fd, mdlength)
460 else:
461 updateLengthInHeader(self.fd, mdlength + self.vdi_info_size)
462 return True
463 except Exception as e:
464 util.SMlog("Exception adding vdi with info: %s. Error: %s" % \
465 (Dict, str(e)))
466 raise
468 # Get metadata from the file name passed in
469 # additional params:
470 # includeDeletedVdis - include deleted VDIs in the returned metadata
471 # vdi_uuid - only fetch metadata till a particular VDI
472 # offset - only fetch metadata till a particular offset
473 # firstDeleted - get the first deleted VDI
474 # indexByUuid - index VDIs by uuid
475 # the return value of this function is a dictionary having the following keys
476 # sr_info: dictionary containing sr information
477 # vdi_info: dictionary containing vdi information indexed by offset
478 # offset: when passing in vdi_uuid/firstDeleted below
479 # deleted - true if deleted VDI found to be replaced
480 def getMetadataInternal(self, params={}):
481 try:
482 lower = 0
483 upper = 0
484 retmap = {}
485 sr_info_map = {}
486 ret_vdi_info = {}
487 length = getMetadataLength(self.fd)
489 # Read in the metadata fil
490 metadataxml = file_read_wrapper(self.fd, 0, length)
492 # At this point we have the complete metadata in metadataxml
493 offset = SECTOR_SIZE + len(XML_HEADER)
494 sr_info = metadataxml[offset: SECTOR_SIZE * 4]
495 offset = SECTOR_SIZE * 4
496 sr_info = sr_info.replace(b'\x00', b'')
498 parsable_metadata = buildParsableMetadataXML(sr_info)
499 retmap['sr_info'] = metadata._parseXML(parsable_metadata)
501 # At this point we check if an offset has been passed in
502 if 'offset' in params:
503 upper = getBlockAlignedRange(params['offset'], 0)[1]
504 else:
505 upper = length
507 # Now look at the VDI objects
508 while offset < upper:
509 vdi_info = metadataxml[offset:offset + self.vdi_info_size]
510 vdi_info = vdi_info.replace(b'\x00', b'')
511 parsable_metadata = buildParsableMetadataXML(vdi_info)
512 vdi_info_map = metadata._parseXML(parsable_metadata)[VDI_TAG]
513 vdi_info_map[OFFSET_TAG] = offset
515 if 'includeDeletedVdis' not in params and \
516 vdi_info_map[VDI_DELETED_TAG] == '1':
517 offset += self.vdi_info_size
518 continue
520 if 'indexByUuid' in params:
521 ret_vdi_info[vdi_info_map[UUID_TAG]] = vdi_info_map
522 else:
523 ret_vdi_info[offset] = vdi_info_map
525 if 'vdi_uuid' in params:
526 if vdi_info_map[UUID_TAG] == params['vdi_uuid']:
527 retmap['offset'] = offset
528 (lower, upper) = \
529 getBlockAlignedRange(offset, self.vdi_info_size)
531 elif 'firstDeleted' in params:
532 if vdi_info_map[VDI_DELETED_TAG] == '1':
533 retmap['foundDeleted'] = 1
534 retmap['offset'] = offset
535 (lower, upper) = \
536 getBlockAlignedRange(offset, self.vdi_info_size)
538 offset += self.vdi_info_size
540 retmap['lower'] = lower
541 retmap['upper'] = upper
542 retmap['vdi_info'] = ret_vdi_info
543 return retmap
544 except Exception as e:
545 util.SMlog("Exception getting metadata with params" \
546 "%s. Error: %s" % (params, str(e)))
547 raise
549 # This function expects both sr name_label and sr name_description to be
550 # passed in
551 def updateSR(self, Dict):
552 util.SMlog('entering updateSR')
554 value = b""
556 # Find the offset depending on what we are updating
557 diff = set(Dict.keys()) - set(ATOMIC_UPDATE_PARAMS_AND_OFFSET.keys())
558 if diff == set([]): 558 ↛ 590line 558 didn't jump to line 590, because the condition on line 558 was never false
559 offset = SECTOR_SIZE * 2
560 (lower, upper) = getBlockAlignedRange(offset, SECTOR_SIZE * 2)
561 md = self.getMetadataInternal({'offset': \
562 SECTOR_SIZE * (SR_INFO_SIZE_IN_SECTORS - 1)})
564 sr_info = md['sr_info']
565 vdi_info_by_offset = md['vdi_info']
567 # update SR info with Dict
568 for key in Dict.keys():
569 sr_info[key] = Dict[key]
571 # if lower is less than SR header size
572 if lower < SR_INFO_SIZE_IN_SECTORS * SECTOR_SIZE: 572 ↛ 586line 572 didn't jump to line 586, because the condition on line 572 was never false
573 # if upper is less than SR header size
574 if upper <= SR_INFO_SIZE_IN_SECTORS * SECTOR_SIZE: 574 ↛ 578line 574 didn't jump to line 578, because the condition on line 574 was never false
575 for i in range(lower // SECTOR_SIZE, upper // SECTOR_SIZE):
576 value += self.getSRInfoForSectors(sr_info, range(i, i + 1))
577 else:
578 for i in range(lower // SECTOR_SIZE, SR_INFO_SIZE_IN_SECTORS):
579 value += self.getSRInfoForSectors(sr_info, range(i, i + 1))
581 # generate the remaining VDI
582 value += self.generateVDIsForRange(vdi_info_by_offset,
583 SR_INFO_SIZE_IN_SECTORS, upper)
584 else:
585 # generate the remaining VDI
586 value += self.generateVDIsForRange(vdi_info_by_offset, lower, upper)
588 file_write_wrapper(self.fd, lower, value)
589 else:
590 raise Exception("SR Update operation not supported for "
591 "parameters: %s" % diff)
593 def updateVdi(self, Dict):
594 util.SMlog('entering updateVdi')
595 try:
596 mdlength = getMetadataLength(self.fd)
597 md = self.getMetadataInternal({'vdi_uuid': Dict[UUID_TAG]})
598 value = self.getMetadataToWrite(md['sr_info'], md['vdi_info'], \
599 md['lower'], md['upper'], Dict, md['offset'])
600 file_write_wrapper(self.fd, md['lower'], value)
601 return True
602 except Exception as e:
603 util.SMlog("Exception updating vdi with info: %s. Error: %s" % \
604 (Dict, str(e)))
605 raise
607 # This should be called only in the cases where we are initially writing
608 # metadata, the function would expect a dictionary which had all information
609 # about the SRs and all its VDIs
610 def writeMetadataInternal(self, sr_info, vdi_info):
611 try:
612 md = self.getSRInfoForSectors(sr_info, range(0, SR_INFO_SIZE_IN_SECTORS))
614 # Go over the VDIs passed and for each
615 for key in vdi_info.keys():
616 md += self.getVdiInfo(vdi_info[key])
618 # Now write the metadata on disk.
619 file_write_wrapper(self.fd, 0, md)
620 updateLengthInHeader(self.fd, len(md))
622 except Exception as e:
623 util.SMlog("Exception writing metadata with info: %s, %s. " \
624 "Error: %s" % (sr_info, vdi_info, str(e)))
625 raise
627 # generates metadata info to write taking the following parameters:
628 # a range, lower - upper
629 # sr and vdi information
630 # VDI information to update
631 # an optional offset to the VDI to update
632 def getMetadataToWrite(self, sr_info, vdi_info, lower, upper, update_map, \
633 offset):
634 util.SMlog("Entering getMetadataToWrite")
635 try:
636 value = b""
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)
702 # This function generates VDI info based on the passed in information
703 # it also takes in a parameter to determine whether both the sector
704 # or only one sector needs to be generated, and which one
705 # generateSector - can be 1 or 2, defaults to 0 and generates both sectors
706 def getVdiInfo(self, Dict, generateSector=0):
707 util.SMlog("Entering VDI info")
708 try:
709 vdi_info = b""
710 # HP split into 2 functions, 1 for generating the first 2 sectors,
711 # which will be called by all classes
712 # and one specific to this class
713 if generateSector == 1 or generateSector == 0:
714 label = xml.sax.saxutils.escape(util.to_plain_string(Dict[NAME_LABEL_TAG]))
715 desc = xml.sax.saxutils.escape(util.to_plain_string(Dict[NAME_DESCRIPTION_TAG]))
716 label_length = len(to_utf8(label))
717 desc_length = len(to_utf8(desc))
719 if label_length + desc_length > MAX_VDI_NAME_LABEL_DESC_LENGTH:
720 limit = MAX_VDI_NAME_LABEL_DESC_LENGTH // 2
721 if label_length > limit:
722 label = label[:util.unictrunc(label, limit)]
723 util.SMlog('warning: name-label truncated from '
724 '%d to %d bytes'
725 % (label_length, len(to_utf8(label))))
727 if desc_length > limit:
728 desc = desc[:util.unictrunc(desc, limit)]
729 util.SMlog('warning: description truncated from '
730 '%d to %d bytes'
731 % (desc_length, len(to_utf8(desc))))
733 Dict[NAME_LABEL_TAG] = label
734 Dict[NAME_DESCRIPTION_TAG] = desc
736 # Fill the open struct and write it
737 vdi_info += getSector(openingTag(VDI_TAG)
738 + buildXMLElement(NAME_LABEL_TAG, Dict)
739 + buildXMLElement(NAME_DESCRIPTION_TAG,
740 Dict))
742 if generateSector == 2 or generateSector == 0:
743 sector2 = b""
745 if VDI_DELETED_TAG not in Dict:
746 Dict.update({VDI_DELETED_TAG: '0'})
748 for tag in Dict.keys():
749 if tag == NAME_LABEL_TAG or tag == NAME_DESCRIPTION_TAG:
750 continue
751 sector2 += buildXMLElement(tag, Dict)
753 sector2 += closingTag(VDI_TAG)
754 vdi_info += getSector(sector2)
755 return vdi_info
757 except Exception as e:
758 util.SMlog("Exception generating vdi info: %s. Error: %s" % \
759 (Dict, str(e)))
760 raise
762 def getSRInfoForSectors(self, sr_info, range):
763 srinfo = b""
765 try:
766 # write header, name_labael and description in that function
767 # as its common to all
768 # Fill up the first sector
769 if 0 in range:
770 srinfo = getSector(buildHeader(SECTOR_SIZE))
772 if 1 in range:
773 srinfo += getSector(XML_HEADER
774 + buildXMLElement(UUID_TAG, sr_info)
775 + buildXMLElement(ALLOCATION_TAG, sr_info))
777 if 2 in range:
778 # Fill up the SR name_label
779 srinfo += buildXMLSector(NAME_LABEL_TAG,
780 xml.sax.saxutils.escape(sr_info[NAME_LABEL_TAG]))
782 if 3 in range:
783 # Fill the name_description
784 srinfo += buildXMLSector(NAME_DESCRIPTION_TAG,
785 xml.sax.saxutils.escape(sr_info[NAME_DESCRIPTION_TAG]))
787 return srinfo
789 except Exception as e:
790 util.SMlog("Exception getting SR info with parameters: sr_info: %s," \
791 "range: %s. Error: %s" % (sr_info, range, str(e)))
792 raise