# Copyright 2014 Eucalyptus Systems, Inc.
#
# Redistribution and use of this software in source and binary forms,
# with or without modification, are permitted provided that the following
# conditions are met:
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import logging
import lxml.etree
import lxml.objectify
import euca2ools
class ImportManifest(object):
def __init__(self, loglevel=None):
self.log = logging.getLogger(self.__class__.__name__)
if loglevel is not None:
self.log.level = loglevel
self.file_format = None
self.self_destruct_url = None
self.image_size = None
self.volume_size = None
self.image_parts = []
@classmethod
def read_from_file(cls, manifest_filename):
with open(manifest_filename) as manifest_fileobj:
return cls.read_from_fileobj(manifest_fileobj)
@classmethod
def read_from_fileobj(cls, manifest_fileobj):
xml = lxml.objectify.parse(manifest_fileobj).getroot()
manifest = cls()
manifest.file_format = xml['file-format'].text
manifest.self_destruct_url = xml['self-destruct-url'].text
manifest.image_size = int(xml['import'].size)
manifest.volume_size = int(xml['import']['volume-size'])
manifest.image_parts = [None] * int(xml['import']['parts']
.get('count'))
for part in xml['import']['parts']['part']:
part_index = int(part.get('index'))
part_obj = ImportImagePart()
part_obj.index = part_index
part_obj.start = int(part['byte-range'].get('start'))
part_obj.end = int(part['byte-range'].get('end'))
part_obj.key = part['key'].text
part_obj.head_url = part['head-url'].text
part_obj.get_url = part['get-url'].text
part_obj.delete_url = part['delete-url'].text
manifest.image_parts[part_index] = part_obj
assert None not in manifest.image_parts, 'part missing from manifest'
return manifest
def dump_to_str(self, pretty_print=False):
xml = lxml.objectify.Element('manifest')
# Manifest version
xml.version = '2010-11-15'
# File format
xml['file-format'] = self.file_format
# Our version
xml.importer = None
xml.importer.name = 'euca2ools'
xml.importer.version = euca2ools.__version__
xml.importer.release = 0
# Import and image part info
xml['self-destruct-url'] = self.self_destruct_url
xml['import'] = None
xml['import']['size'] = self.image_size
xml['import']['volume-size'] = self.volume_size
xml['import']['parts'] = None
xml['import']['parts'].set('count', str(len(self.image_parts)))
for part in self.image_parts:
xml['import']['parts'].append(part.dump_to_xml())
# Cleanup
lxml.objectify.deannotate(xml, xsi_nil=True)
lxml.etree.cleanup_namespaces(xml)
self.log.debug('-- manifest content --\n', extra={'append': True})
pretty_manifest = lxml.etree.tostring(xml, pretty_print=True).strip()
self.log.debug('%s', pretty_manifest, extra={'append': True})
self.log.debug('-- end of manifest content --')
return lxml.etree.tostring(xml, pretty_print=pretty_print,
encoding='UTF-8', standalone=True,
xml_declaration=True).strip()
def dump_to_fileobj(self, fileobj, pretty_print=False):
fileobj.write(self.dump_to_str(pretty_print=pretty_print))
class ImportImagePart(object):
def __init__(self):
self.index = None
self.start = None
self.end = None
self.key = None
self.head_url = None
self.get_url = None
self.delete_url = None
def dump_to_xml(self):
xml = lxml.objectify.Element('part', index=str(self.index))
xml['byte-range'] = None
xml['byte-range'].set('start', str(self.start))
xml['byte-range'].set('end', str(self.end))
xml['key'] = self.key
xml['head-url'] = self.head_url
xml['get-url'] = self.get_url
xml['delete-url'] = self.delete_url
return xml