-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-##
-# Copyright 2016-2017 VMware Inc.
-# This file is part of ETSI OSM
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: osslegalrouting@vmware.com
-##
-
-import logging
-import os
-import subprocess
-import yaml
-from lxml import etree as ET
-from command_progress import CommandProgressbar
-
-#file paths
-MODULE_DIR = os.path.dirname(__file__)
-OVF_TEMPLATE_PATH = os.path.join(os.path.dirname(MODULE_DIR),
- "ovf_template/template.xml")
-OS_INFO_FILE_PATH = os.path.join(os.path.dirname(MODULE_DIR),
- "config/os_type.yaml")
-DISK_CONTROLLER_INFO_FILE_PATH = os.path.join(os.path.dirname(MODULE_DIR),
- "config/disk_controller.yaml")
-
-
-#Set logger
-LOG_FILE = os.path.join(os.path.dirname(MODULE_DIR),"logs/ovf_converter.log")
-logger = logging.getLogger(__name__)
-hdlr = logging.FileHandler(LOG_FILE)
-formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-hdlr.setFormatter(formatter)
-logger.addHandler(hdlr)
-logger.setLevel(10)
-
-__version__ = "1.0"
-__description__ = "initial Release"
-
-def get_version(*args, **kwargs):
- """ get version of this application"""
- version = str(__version__ ) +" - "+ str( __description__ )
- return version
-
-#converter class
-class OVFConverter(object):
- """ Class to convert input image into OVF format """
-
- def __init__(self, source_img_path, output_location=None, output_ovf_name=None,
- memory=None, cpu=None, disk=None, os_type=None,
- disk_controller=None,
- options={'subformat':'streamOptimized'}):
- """
- Constructor to initialize object of class OVFConverter
- Args:
- source_img_path - absolute path to source image which will get convert into ovf
- output_location - location where created OVF will be kept. This location
- should have write access. If not given file will get
- created at source location (optional)
- output_ovf_name - name of output ovf.If not given source image name will
- be used (optional)
- memory - required memory for VM in MB (optional)
- cpu - required number of virtual cpus for VM (optional)
- disk - required size of disk for VM in GB (optional)
- os_type- required operating system type as specified in user document
- (default os type other 32 bit) (optional)
- disk_controller - required disk controller type
- (default controller SCSI with lsilogicsas)
- (SATA, IDE, Paravirtual, Buslogic, Lsilogic, Lsilogicsas) (optional)
- options - subformat option for OVF (optional)
-
- Returns:
- Nothing.
- """
- self.logger = logger
- self.ovf_template_path = OVF_TEMPLATE_PATH
-
- self.source_img_path = source_img_path
- file_location, file_extension = os.path.splitext(self.source_img_path)
- self.source_img_location = os.path.dirname(self.source_img_path)
- self.source_format = file_extension[1:]
- self.source_img_filename = os.path.basename(self.source_img_path).split('.')[0]
-
- self.output_format = "ovf"
- self.output_ovf_name = output_ovf_name.split('.')[0] if output_ovf_name else self.source_img_filename
- self.output_location = output_location if output_location else self.source_img_location
- self.output_ovf_name_ext = self.output_ovf_name + "." + self.output_format
- self.output_path = os.path.join(self.output_location , self.output_ovf_name_ext )
-
- self.output_diskimage_format = "vmdk"
- self.output_diskimage_name = self.source_img_filename + "."+ self.output_diskimage_format
- self.output_diskimage_path = os.path.join(self.output_location, self.output_diskimage_name)
-
-
- self.logger.info("Input parameters to Converter: \n ovf_template_path = {}, \n source_img_path = {}, \n"\
- "source_img_location ={} , \n source_format = {}, \n source_img_filename = {}".format(
- self.ovf_template_path,
- self.source_img_path, self.source_img_location,
- self.source_format, self.source_img_filename ))
-
- self.logger.info("Output parameters to Converter: \n output_format = {}, \n output_ovf_name = {}, \n"\
- "output_location ={} , \n output_path = {}, \n output_diskimage_name = {} , \n"\
- " output_diskimage_path = {} ".format(self.output_format, self.output_ovf_name,
- self.output_location, self.output_path,
- self.output_diskimage_name,self.output_diskimage_path ))
-
-
- self.disk_capacity = 1
- self.disk_populated_size = 0
-
- self.vm_name = self.output_ovf_name
- self.memory = str(memory) if memory is not None else None
- self.cpu = str(cpu) if cpu is not None else None
- self.os_type=str(os_type).strip() if os_type else None
-
- if self.os_type:
- self.osID , self.osType = self.__get_osType()
- if self.osID is None or self.osType is None:
- error_msg = "ERROR: Invalid input can not find OS type {} ".format(self.os_type)
- self.__raise_exception(error_msg)
-
- self.disk_controller = str(disk_controller).strip() if disk_controller else None
-
- if self.disk_controller:
- self.disk_controller_info = self.__get_diskcontroller()
-
- if not self.disk_controller_info:
- error_msg = "ERROR: Invalid input can not find Disk Controller {} ".format(self.disk_controller)
- self.__raise_exception(error_msg)
-
- if disk is not None:
- #convert disk size from GB to bytes
- self.disk_size = int(disk) * 1024 * 1024 * 1024
- else:
- self.disk_size = None
-
- self.logger.info("Other input parameters to Converter: \n vm_name = {}, \n memory = {}, \n"\
- "disk_size ={} \n os type = {} \n disk controller = {}".format(
- self.vm_name, self.memory, self.disk_size, self.os_type, self.disk_controller
- ))
-
- #check access for read input location and write output location return none if no access
- if not os.access(self.source_img_path, os.F_OK):
- error_msg = "ERROR: Source image file {} not present".format(self.source_img_path)
- self.__raise_exception(error_msg, exception_type="IO")
-
- elif not os.access(self.source_img_path, os.R_OK):
- error_msg = "ERROR: Cannot read source image file {}".format(self.source_img_path)
- self.__raise_exception(error_msg, exception_type="IO")
-
- if not os.access(self.output_location, os.W_OK):
- error_msg = "ERROR: Not have write access to location {} to write output OVF ".format(self.source_img_path)
- self.__raise_exception(error_msg, exception_type="IO")
-
- def __get_image_info(self):
- """
- Private method to get information about source imager.
- Args : None
- Return : True on success else False
- """
- try:
- print("Getting source image information")
- command = "qemu-img info \t " + self.source_img_path
- output, error, returncode= self.__execute_command(command)
-
- if error or returncode:
- self.logger.error("ERROR: Error occurred while getting information about source image : {} \n "\
- "return code : {} ".format(error, returncode))
- return False
-
- elif output:
- self.logger.info("Get Image Info Output : {} \n ".format(output))
- split_output = output.split("\n")
- for line in split_output:
- line = line.strip()
- if "virtual size" in line:
- virtual_size_info = line.split(":")[1].split()
- if len(virtual_size_info) == 3 and virtual_size_info[2].strip(")") == "bytes":
- self.disk_capacity = int(virtual_size_info[1].strip("("))
- else:
- self.disk_capacity = self.__convert_size(virtual_size_info[0])
-
- elif "disk size" in line:
- size = line.split(":")[1].split()[0]
- self.disk_populated_size = self.__convert_size(size)
- elif "file format" in line:
- self.source_format = line.split(":")[1]
-
- self.logger.info("Updated source image virtual disk capacity : {} ,"\
- "Updated source image populated size: {}".format(self.disk_capacity,
- self.disk_populated_size))
- return True
- except Exception as exp:
- error_msg = "ERROR: Error occurred while getting information about source image : {}".format(exp)
- self.logger.error(error_msg)
- print(error_msg)
- return False
-
- def __convert_image(self):
- """
- Private method to convert source disk image into .vmdk disk image.
- Args : None
- Return : True on success else False
- """
-
- print("Converting source disk image to .vmdk ")
-
- progress = CommandProgressbar()
- progress.start_progressbar()
-
- command = "qemu-img convert -f "+ self.source_format +" -O " + self.output_diskimage_format + \
- " -o subformat=streamOptimized " + self.source_img_path + "\t" + self.output_diskimage_path
-
- output, error , returncode = self.__execute_command(command)
-
- progress.stop_progressbar()
-
- if error or returncode :
- error_msg = "ERROR: Error occurred while converting source disk image into vmdk : {} \n "\
- "return code : {} ".format(error, returncode)
- self.logger.error(error_msg)
- print(error_msg)
- return False
- else:
- if os.path.isfile(self.output_diskimage_path):
- self.logger.info("Successfully converted source image {} into {} \n "\
- "return code : {} ".format(self.source_img_path,
- self.output_diskimage_path,
- returncode))
- result = self.__make_image_bootable()
- if result:
- self.logger.info("Made {} bootable".format(self.output_diskimage_path))
- return True
- else:
- self.logger.error("Cannot make {} bootable".format(self.output_diskimage_path))
- print("ERROR: Fail to convert source image into .vmdk")
- return False
- else:
- self.logger.error("Converted vmdk disk file {} is not present \n ".format(
- self.output_diskimage_path))
- print("Fail to convert source image into .vmdk")
- return False
-
- def __make_image_bootable(self):
- """
- Private method to make source disk image bootable.
- Args : None
- Return : True on success else False
- """
- command = "printf '\x03' | dd conv=notrunc of="+ self.output_diskimage_path + "\t bs=1 seek=$((0x4))"
- output, error, returncode = self.__execute_command(command)
-
- if error and returncode :
- error_msg = "ERROR:Error occurred while making source disk image bootable : {} \n "\
- "return code : {} ".format(error, returncode)
- self.logger.error(error_msg)
- print(error_msg)
- return False
- else:
- self.logger.info("Make Image Bootable Output : {} ".format(output))
- return True
-
-
- def __edit_ovf_template(self):
- """
- Private method to create new OVF file by editing OVF template
- Args : None
- Return : True on success else False
- """
- try:
- print("\nCreating OVF")
- #Read OVF template file
- OVF_tree = ET.parse(self.ovf_template_path)
- root = OVF_tree.getroot()
-
- #Collect namespaces
- nsmap = {k:v for k,v in root.nsmap.iteritems() if k}
- nsmap["xmlns"]= "http://schemas.dmtf.org/ovf/envelope/1"
-
- #Edit OVF template
- references = root.find('xmlns:References',nsmap)
- if references is not None:
- file_tag = references.find('xmlns:File', nsmap)
- if file_tag is not None:
- file_tag.attrib['{'+nsmap['ovf']+'}href'] = self.output_diskimage_name
-
- disksection = root.find('xmlns:DiskSection',nsmap)
- if disksection is not None:
- diak_tag = disksection.find('xmlns:Disk', nsmap)
- if diak_tag is not None:
- if self.disk_size and self.disk_size > self.disk_capacity:
- self.disk_capacity = self.disk_size
-
- diak_tag.attrib['{'+nsmap['ovf']+'}capacity'] = str(self.disk_capacity)
- diak_tag.attrib['{'+nsmap['ovf']+'}populatedSize'] = str(self.disk_populated_size)
-
- virtuasystem = root.find('xmlns:VirtualSystem',nsmap)
- if virtuasystem is not None:
- name_tag = virtuasystem.find('xmlns:Name', nsmap)
- if name_tag is not None:
- name_tag.text = self.vm_name
-
- if self.os_type is not None:
- operatingSystemSection = virtuasystem.find('xmlns:OperatingSystemSection', nsmap)
- if self.osID and self.osType:
- operatingSystemSection.attrib['{'+nsmap['ovf']+'}id'] = self.osID
- os_discription_tag = operatingSystemSection.find('xmlns:Description', nsmap)
- os_discription_tag.text = self.osType
-
- virtualHardwareSection = virtuasystem.find('xmlns:VirtualHardwareSection', nsmap)
- system = virtualHardwareSection.find('xmlns:System', nsmap)
- virtualSystemIdentifier = system.find('vssd:VirtualSystemIdentifier', nsmap)
- if virtualSystemIdentifier is not None:
- virtualSystemIdentifier.text = self.vm_name
-
- if self.memory is not None or self.cpu is not None or self.disk_controller is not None:
- for item in virtualHardwareSection.iterfind('xmlns:Item',nsmap):
- description = item.find("rasd:Description",nsmap)
-
- if self.cpu is not None:
- if description is not None and description.text == "Number of Virtual CPUs":
- cpu_item = item.find("rasd:VirtualQuantity", nsmap)
- name_item = item.find("rasd:ElementName", nsmap)
- if cpu_item is not None:
- cpu_item.text = self.cpu
- name_item.text = self.cpu+" virtual CPU(s)"
-
- if self.memory is not None:
- if description is not None and description.text == "Memory Size":
- mem_item = item.find("rasd:VirtualQuantity", nsmap)
- name_item = item.find("rasd:ElementName", nsmap)
- if mem_item is not None:
- mem_item.text = self.memory
- name_item.text = self.memory + " MB of memory"
-
- if self.disk_controller is not None:
- if description is not None and description.text == "SCSI Controller":
- if self.disk_controller_info is not None:
- name_item = item.find("rasd:ElementName", nsmap)
- name_item.text = str(self.disk_controller_info["controllerName"])+"0"
-
- resource_type = item.find("rasd:ResourceType", nsmap)
- resource_type.text = self.disk_controller_info["resourceType"]
-
- description.text = self.disk_controller_info["controllerName"]
- resource_subtype = item.find("rasd:ResourceSubType", nsmap)
- if self.disk_controller_info["controllerName"] == "IDE Controller":
- #Remove resource subtype item
- resource_subtype.getparent().remove(resource_subtype)
- if "resourceSubType" in self.disk_controller_info:
- resource_subtype.text = self.disk_controller_info["resourceSubType"]
-
- #Save output OVF
- OVF_tree.write(self.output_path, xml_declaration=True,encoding='utf-8',
- method="xml" )
-
- if os.path.isfile(self.output_path):
- logger.info("Successfully written output OVF at {}".format(self.output_path))
- print("Output OVF is at : {}".format(self.output_path))
- return self.output_path
- else:
- error_msg = "ERROR: Error occurred while creating OVF file"
- print(error_msg)
- return False
-
- except Exception as exp:
- error_msg = "ERROR: Error occurred while editing OVF template : {}".format(exp)
- self.logger.error(error_msg)
- print(error_msg)
- return False
-
-
- def __convert_size(self,size):
- """
- Private method to convert disk size from GB,MB to bytes.
- Args :
- size : disk size with prefix 'G' for GB and 'M' for MB
- Return : disk size in bytes
- """
- byte_size= 0
- try:
- if not size:
- self.logger.error("No size {} to convert in bytes".format(size))
- else:
- size = str(size)
- disk_size = float(size[:-1])
- input_type = size[-1].strip()
-
- self.logger.info("Disk size : {} , size type : {} ".format(disk_size,input_type))
-
- if input_type == "G":
- byte_size = disk_size * 1024 * 1024 *1024
- elif input_type == "M":
- byte_size = disk_size * 1024 * 1024
-
- self.logger.info("Disk size in bytes: {} ".format(byte_size))
-
- return int(byte_size)
-
- except Exception as exp:
- error_msg = "ERROR:Error occurred while converting disk size in bytes : {}".format(exp)
- self.logger.error(error_msg)
- print(error_msg)
- return False
-
- def __get_osType(self):
- """
- Private method to get OS ID and Type
- Args :
- None
- Return :
- osID : OS ID
- osType: OS Type
- """
- osID = None
- osType = None
- os_info = self.__read_yaml_file(OS_INFO_FILE_PATH)
- try:
- if self.os_type and os_info:
- for os_id , os_type in os_info.iteritems():
- if self.os_type.lower() == os_type.lower():
- osID = os_id
- osType = os_type
- break
- except Exception as exp:
- error_msg = "ERROR:Error occurred while getting OS details : {}".format(exp)
- self.logger.error(error_msg)
- print(error_msg)
-
- return osID, osType
-
-
- def __get_diskcontroller(self):
- """
- Private method to get details of Disk Controller
- Args :
- None
- Return :
- disk_controller : dict with details of Disk Controller
- """
- disk_controller = {}
- scsi_subtype = None
- if self.disk_controller.lower() in ["paravirtual", "lsilogic", "buslogic", "lsilogicsas"]:
- scsi_subtype = self.disk_controller
- self.disk_controller = "SCSI"
-
- disk_controller_info = self.__read_yaml_file(DISK_CONTROLLER_INFO_FILE_PATH)
- try:
- if self.disk_controller and disk_controller_info:
- for key , value in disk_controller_info.iteritems():
- if self.disk_controller.lower() in key.lower():
- disk_controller['controllerName'] = key
- disk_controller['resourceType'] = str(value["ResourceType"])
- resourceSubTypes = value["ResourceSubTypes"] if "ResourceSubTypes" in value else None
- if key == "SATA Controller":
- disk_controller["resourceSubType"] = resourceSubTypes[0]
- elif key == "SCSI Controller":
- if scsi_subtype:
- if scsi_subtype.lower() == "paravirtual":
- scsi_subtype = "VirtualSCSI"
- for subtype in resourceSubTypes:
- if scsi_subtype.lower() == subtype.lower():
- disk_controller["resourceSubType"] = subtype
- break
- else:
- error_msg = "ERROR: Invalid inputs can not "\
- "find SCSI subtype {}".format(scsi_subtype)
- self.__raise_exception(error_msg)
-
- except KeyError as exp:
- error_msg = "ERROR:Error occurred while getting Disk Controller details : {}".format(exp)
- self.logger.error(error_msg)
- print(error_msg)
-
- return disk_controller
-
- def __read_yaml_file(self, file_path):
- """
- Private method to execute command
- Args :
- command : command to execute
- Return :
- Dict of yaml data
- """
- with open(file_path) as data_file:
- data = yaml.load(data_file)
- return data
-
- def __raise_exception(self, error_msg , exception_type="Generic"):
- """
- Private method to execute command
- Args :
- command : command to execute
- Return :
- None
- """
- if error_msg:
- self.logger.debug(error_msg)
- print(error_msg)
- if exception_type == "Generic":
- raise Exception(error_msg)
- elif exception_type == "IO":
- raise Exception(error_msg)
-
- def __execute_command(self, command):
- """
- Private method to execute command
- Args :
- command : command to execute
- Return :
- stdout : output of command
- stderr: error occurred while executing command if any
- returncode : return code of command execution
- """
- try:
- self.logger.info("Execute command: {} ".format(command))
-
- proc = subprocess.Popen(command , stdout = subprocess.PIPE, stdin = subprocess.PIPE,
- stderr = subprocess.PIPE,shell=True)
- stdout, stderr = proc.communicate()
- returncode = proc.returncode
-
- except Exception as exp:
- self.logger.error("Error {} occurred while executing command {} ".format(exp,command))
-
- return stdout, stderr , returncode
-
-
- def create_ovf(self):
- """
- Method to convert source image into OVF
- Args : None
- Return : True on success else False
- """
- #check output format
- if self.source_format == self.output_format:
- self.logger.info("Source format is OVF. No need to convert: {} ")
- return self.source_img_path
-
- #Get source img properties
- img_info = self.__get_image_info()
- if img_info:
-
- #Create vmdk disk image
- disk_img = self.__convert_image()
- if disk_img:
-
- #Edit OVF tempalte
- ovf_path = self.__edit_ovf_template()
- return ovf_path
- else:
- self.logger.error("Error in getting image information cannot convert image")
- raise Exception("Error in getting image information cannot convert image")
- return False
-