2 # -*- coding: utf-8 -*-
5 # Copyright 2016-2017 VMware Inc.
6 # This file is part of ETSI OSM
9 # Licensed under the Apache License, Version 2.0 (the "License"); you may
10 # not use this file except in compliance with the License. You may obtain
11 # a copy of the License at
13 # http://www.apache.org/licenses/LICENSE-2.0
15 # Unless required by applicable law or agreed to in writing, software
16 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18 # License for the specific language governing permissions and limitations
21 # For those usages not covered by the Apache License, Version 2.0 please
22 # contact: osslegalrouting@vmware.com
29 from lxml
import etree
as ET
30 from command_progress
import CommandProgressbar
33 MODULE_DIR
= os
.path
.dirname(__file__
)
34 OVF_TEMPLATE_PATH
= os
.path
.join(os
.path
.dirname(MODULE_DIR
),
35 "ovf_template/template.xml")
36 IDE_CDROM_XML_PATH
= os
.path
.join(os
.path
.dirname(MODULE_DIR
),
37 "ovf_template/ide_cdrom.xml")
38 OS_INFO_FILE_PATH
= os
.path
.join(os
.path
.dirname(MODULE_DIR
),
39 "config/os_type.yaml")
40 DISK_CONTROLLER_INFO_FILE_PATH
= os
.path
.join(os
.path
.dirname(MODULE_DIR
),
41 "config/disk_controller.yaml")
45 LOG_FILE
= os
.path
.join(os
.path
.dirname(MODULE_DIR
),"logs/ovf_converter.log")
46 logger
= logging
.getLogger(__name__
)
47 hdlr
= logging
.FileHandler(LOG_FILE
)
48 formatter
= logging
.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
49 hdlr
.setFormatter(formatter
)
50 logger
.addHandler(hdlr
)
54 __description__
= "initial Release"
56 def get_version(*args
, **kwargs
):
57 """ get version of this application"""
58 version
= str(__version__
) +" - "+ str( __description__
)
62 class OVFConverter(object):
63 """ Class to convert input image into OVF format """
65 def __init__(self
, source_img_path
, output_location
=None, output_ovf_name
=None,
66 memory
=None, cpu
=None, disk
=None, os_type
=None,
67 disk_controller
=None, cdrom
=None,
68 options
={'subformat':'streamOptimized'}):
70 Constructor to initialize object of class OVFConverter
72 source_img_path - absolute path to source image which will get convert into ovf
73 output_location - location where created OVF will be kept. This location
74 should have write access. If not given file will get
75 created at source location (optional)
76 output_ovf_name - name of output ovf.If not given source image name will
78 memory - required memory for VM in MB (optional)
79 cpu - required number of virtual cpus for VM (optional)
80 disk - required size of disk for VM in GB (optional)
81 os_type- required operating system type as specified in user document
82 (default os type other 32 bit) (optional)
83 disk_controller - required disk controller type
84 (default controller SCSI with lsilogicsas)
85 (SATA, IDE, Paravirtual, Buslogic, Lsilogic, Lsilogicsas) (optional)
86 options - subformat option for OVF (optional)
92 self
.ovf_template_path
= OVF_TEMPLATE_PATH
94 self
.source_img_path
= source_img_path
95 file_location
, file_extension
= os
.path
.splitext(self
.source_img_path
)
96 self
.source_img_location
= os
.path
.dirname(self
.source_img_path
)
97 self
.source_format
= file_extension
[1:]
98 self
.source_img_filename
= os
.path
.basename(self
.source_img_path
).split('.')[0]
100 self
.output_format
= "ovf"
101 self
.output_ovf_name
= output_ovf_name
.split('.')[0] if output_ovf_name
else self
.source_img_filename
102 self
.output_location
= output_location
if output_location
else self
.source_img_location
103 self
.output_ovf_name_ext
= self
.output_ovf_name
+ "." + self
.output_format
104 self
.output_path
= os
.path
.join(self
.output_location
, self
.output_ovf_name_ext
)
106 self
.output_diskimage_format
= "vmdk"
107 self
.output_diskimage_name
= self
.source_img_filename
+ "."+ self
.output_diskimage_format
108 self
.output_diskimage_path
= os
.path
.join(self
.output_location
, self
.output_diskimage_name
)
111 self
.logger
.info("Input parameters to Converter: \n ovf_template_path = {}, \n source_img_path = {}, \n"\
112 "source_img_location ={} , \n source_format = {}, \n source_img_filename = {}".format(
113 self
.ovf_template_path
,
114 self
.source_img_path
, self
.source_img_location
,
115 self
.source_format
, self
.source_img_filename
))
117 self
.logger
.info("Output parameters to Converter: \n output_format = {}, \n output_ovf_name = {}, \n"\
118 "output_location ={} , \n output_path = {}, \n output_diskimage_name = {} , \n"\
119 " output_diskimage_path = {} ".format(self
.output_format
, self
.output_ovf_name
,
120 self
.output_location
, self
.output_path
,
121 self
.output_diskimage_name
,self
.output_diskimage_path
))
124 self
.disk_capacity
= 1
125 self
.disk_populated_size
= 0
127 self
.vm_name
= self
.output_ovf_name
128 self
.memory
= str(memory
) if memory
is not None else None
129 self
.cpu
= str(cpu
) if cpu
is not None else None
130 self
.os_type
=str(os_type
).strip() if os_type
else None
134 self
.osID
, self
.osType
= self
.__get
_osType
()
135 if self
.osID
is None or self
.osType
is None:
136 error_msg
= "ERROR: Invalid input can not find OS type {} ".format(self
.os_type
)
137 self
.__raise
_exception
(error_msg
)
139 self
.disk_controller
= str(disk_controller
).strip() if disk_controller
else None
141 if self
.disk_controller
:
142 self
.disk_controller_info
= self
.__get
_diskcontroller
()
144 if not self
.disk_controller_info
:
145 error_msg
= "ERROR: Invalid input can not find Disk Controller {} ".format(self
.disk_controller
)
146 self
.__raise
_exception
(error_msg
)
149 #convert disk size from GB to bytes
150 self
.disk_size
= int(disk
) * 1024 * 1024 * 1024
152 self
.disk_size
= None
154 self
.logger
.info("Other input parameters to Converter: \n vm_name = {}, \n memory = {}, \n"\
155 "disk_size ={} \n os type = {} \n disk controller = {}".format(
156 self
.vm_name
, self
.memory
, self
.disk_size
, self
.os_type
, self
.disk_controller
159 #check access for read input location and write output location return none if no access
160 if not os
.access(self
.source_img_path
, os
.F_OK
):
161 error_msg
= "ERROR: Source image file {} not present".format(self
.source_img_path
)
162 self
.__raise
_exception
(error_msg
, exception_type
="IO")
164 elif not os
.access(self
.source_img_path
, os
.R_OK
):
165 error_msg
= "ERROR: Cannot read source image file {}".format(self
.source_img_path
)
166 self
.__raise
_exception
(error_msg
, exception_type
="IO")
168 if not os
.access(self
.output_location
, os
.W_OK
):
169 error_msg
= "ERROR: Not have write access to location {} to write output OVF ".format(self
.source_img_path
)
170 self
.__raise
_exception
(error_msg
, exception_type
="IO")
172 def __get_image_info(self
):
174 Private method to get information about source imager.
176 Return : True on success else False
179 print("Getting source image information")
180 command
= "qemu-img info \t " + self
.source_img_path
181 output
, error
, returncode
= self
.__execute
_command
(command
)
183 if error
or returncode
:
184 self
.logger
.error("ERROR: Error occurred while getting information about source image : {} \n "\
185 "return code : {} ".format(error
, returncode
))
189 self
.logger
.info("Get Image Info Output : {} \n ".format(output
))
190 split_output
= output
.split("\n")
191 for line
in split_output
:
193 if "virtual size" in line
:
194 virtual_size_info
= line
.split(":")[1].split()
195 if len(virtual_size_info
) == 3 and virtual_size_info
[2].strip(")") == "bytes":
196 self
.disk_capacity
= int(virtual_size_info
[1].strip("("))
198 self
.disk_capacity
= self
.__convert
_size
(virtual_size_info
[0])
200 elif "disk size" in line
:
201 size
= line
.split(":")[1].split()[0]
202 self
.disk_populated_size
= self
.__convert
_size
(size
)
203 elif "file format" in line
:
204 self
.source_format
= line
.split(":")[1]
206 self
.logger
.info("Updated source image virtual disk capacity : {} ,"\
207 "Updated source image populated size: {}".format(self
.disk_capacity
,
208 self
.disk_populated_size
))
210 except Exception as exp
:
211 error_msg
= "ERROR: Error occurred while getting information about source image : {}".format(exp
)
212 self
.logger
.error(error_msg
)
216 def __convert_image(self
):
218 Private method to convert source disk image into .vmdk disk image.
220 Return : True on success else False
223 print("Converting source disk image to .vmdk ")
225 progress
= CommandProgressbar()
226 progress
.start_progressbar()
228 command
= "qemu-img convert -f "+ self
.source_format
+" -O " + self
.output_diskimage_format
+ \
229 " -o subformat=streamOptimized " + self
.source_img_path
+ "\t" + self
.output_diskimage_path
231 output
, error
, returncode
= self
.__execute
_command
(command
)
233 progress
.stop_progressbar()
235 if error
or returncode
:
236 error_msg
= "ERROR: Error occurred while converting source disk image into vmdk : {} \n "\
237 "return code : {} ".format(error
, returncode
)
238 self
.logger
.error(error_msg
)
242 if os
.path
.isfile(self
.output_diskimage_path
):
243 self
.logger
.info("Successfully converted source image {} into {} \n "\
244 "return code : {} ".format(self
.source_img_path
,
245 self
.output_diskimage_path
,
247 result
= self
.__make
_image
_bootable
()
249 self
.logger
.info("Made {} bootable".format(self
.output_diskimage_path
))
252 self
.logger
.error("Cannot make {} bootable".format(self
.output_diskimage_path
))
253 print("ERROR: Fail to convert source image into .vmdk")
256 self
.logger
.error("Converted vmdk disk file {} is not present \n ".format(
257 self
.output_diskimage_path
))
258 print("Fail to convert source image into .vmdk")
261 def __make_image_bootable(self
):
263 Private method to make source disk image bootable.
265 Return : True on success else False
267 command
= "printf '\x03' | dd conv=notrunc of="+ self
.output_diskimage_path
+ "\t bs=1 seek=$((0x4))"
268 output
, error
, returncode
= self
.__execute
_command
(command
)
270 if error
and returncode
:
271 error_msg
= "ERROR:Error occurred while making source disk image bootable : {} \n "\
272 "return code : {} ".format(error
, returncode
)
273 self
.logger
.error(error_msg
)
277 self
.logger
.info("Make Image Bootable Output : {} ".format(output
))
281 def __edit_ovf_template(self
):
283 Private method to create new OVF file by editing OVF template
285 Return : True on success else False
288 print("\nCreating OVF")
289 #Read OVF template file
290 OVF_tree
= ET
.parse(self
.ovf_template_path
)
291 root
= OVF_tree
.getroot()
294 nsmap
= {k
:v
for k
,v
in root
.nsmap
.iteritems() if k
}
295 nsmap
["xmlns"]= "http://schemas.dmtf.org/ovf/envelope/1"
298 references
= root
.find('xmlns:References',nsmap
)
299 if references
is not None:
300 file_tag
= references
.find('xmlns:File', nsmap
)
301 if file_tag
is not None:
302 file_tag
.attrib
['{'+nsmap
['ovf']+'}href'] = self
.output_diskimage_name
304 disksection
= root
.find('xmlns:DiskSection',nsmap
)
305 if disksection
is not None:
306 diak_tag
= disksection
.find('xmlns:Disk', nsmap
)
307 if diak_tag
is not None:
308 if self
.disk_size
and self
.disk_size
> self
.disk_capacity
:
309 self
.disk_capacity
= self
.disk_size
311 diak_tag
.attrib
['{'+nsmap
['ovf']+'}capacity'] = str(self
.disk_capacity
)
312 diak_tag
.attrib
['{'+nsmap
['ovf']+'}populatedSize'] = str(self
.disk_populated_size
)
314 virtuasystem
= root
.find('xmlns:VirtualSystem',nsmap
)
315 if virtuasystem
is not None:
316 name_tag
= virtuasystem
.find('xmlns:Name', nsmap
)
317 if name_tag
is not None:
318 name_tag
.text
= self
.vm_name
320 if self
.os_type
is not None:
321 operatingSystemSection
= virtuasystem
.find('xmlns:OperatingSystemSection', nsmap
)
322 if self
.osID
and self
.osType
:
323 operatingSystemSection
.attrib
['{'+nsmap
['ovf']+'}id'] = self
.osID
324 os_discription_tag
= operatingSystemSection
.find('xmlns:Description', nsmap
)
325 os_discription_tag
.text
= self
.osType
327 virtualHardwareSection
= virtuasystem
.find('xmlns:VirtualHardwareSection', nsmap
)
328 system
= virtualHardwareSection
.find('xmlns:System', nsmap
)
329 virtualSystemIdentifier
= system
.find('vssd:VirtualSystemIdentifier', nsmap
)
330 if virtualSystemIdentifier
is not None:
331 virtualSystemIdentifier
.text
= self
.vm_name
333 if self
.memory
is not None or self
.cpu
is not None or self
.disk_controller
is not None:
334 for item
in virtualHardwareSection
.iterfind('xmlns:Item',nsmap
):
335 description
= item
.find("rasd:Description",nsmap
)
337 if self
.cpu
is not None:
338 if description
is not None and description
.text
== "Number of Virtual CPUs":
339 cpu_item
= item
.find("rasd:VirtualQuantity", nsmap
)
340 name_item
= item
.find("rasd:ElementName", nsmap
)
341 if cpu_item
is not None:
342 cpu_item
.text
= self
.cpu
343 name_item
.text
= self
.cpu
+" virtual CPU(s)"
345 if self
.memory
is not None:
346 if description
is not None and description
.text
== "Memory Size":
347 mem_item
= item
.find("rasd:VirtualQuantity", nsmap
)
348 name_item
= item
.find("rasd:ElementName", nsmap
)
349 if mem_item
is not None:
350 mem_item
.text
= self
.memory
351 name_item
.text
= self
.memory
+ " MB of memory"
353 if self
.disk_controller
is not None:
354 if description
is not None and description
.text
== "SCSI Controller":
355 if self
.disk_controller_info
is not None:
356 name_item
= item
.find("rasd:ElementName", nsmap
)
357 name_item
.text
= str(self
.disk_controller_info
["controllerName"])+"0"
359 resource_type
= item
.find("rasd:ResourceType", nsmap
)
360 resource_type
.text
= self
.disk_controller_info
["resourceType"]
362 description
.text
= self
.disk_controller_info
["controllerName"]
363 resource_subtype
= item
.find("rasd:ResourceSubType", nsmap
)
364 if self
.disk_controller_info
["controllerName"] == "IDE Controller":
365 #Remove resource subtype item
366 resource_subtype
.getparent().remove(resource_subtype
)
367 if "resourceSubType" in self
.disk_controller_info
:
368 resource_subtype
.text
= self
.disk_controller_info
["resourceSubType"]
370 last_item
= list(virtualHardwareSection
.iterfind('xmlns:Item',nsmap
))[-1]
371 ide_cdrom_items_etree
= ET
.parse(IDE_CDROM_XML_PATH
)
372 ide_cdrom_items
= list(ide_cdrom_items_etree
.iterfind('Item'))
373 for item
in ide_cdrom_items
:
374 last_item
.addnext(item
)
377 OVF_tree
.write(self
.output_path
, xml_declaration
=True,encoding
='utf-8',
380 if os
.path
.isfile(self
.output_path
):
381 logger
.info("Successfully written output OVF at {}".format(self
.output_path
))
382 print("Output OVF is at : {}".format(self
.output_path
))
383 return self
.output_path
385 error_msg
= "ERROR: Error occurred while creating OVF file"
389 except Exception as exp
:
390 error_msg
= "ERROR: Error occurred while editing OVF template : {}".format(exp
)
391 self
.logger
.error(error_msg
)
396 def __convert_size(self
,size
):
398 Private method to convert disk size from GB,MB to bytes.
400 size : disk size with prefix 'G' for GB and 'M' for MB
401 Return : disk size in bytes
406 self
.logger
.error("No size {} to convert in bytes".format(size
))
409 disk_size
= float(size
[:-1])
410 input_type
= size
[-1].strip()
412 self
.logger
.info("Disk size : {} , size type : {} ".format(disk_size
,input_type
))
414 if input_type
== "G":
415 byte_size
= disk_size
* 1024 * 1024 *1024
416 elif input_type
== "M":
417 byte_size
= disk_size
* 1024 * 1024
419 self
.logger
.info("Disk size in bytes: {} ".format(byte_size
))
421 return int(byte_size
)
423 except Exception as exp
:
424 error_msg
= "ERROR:Error occurred while converting disk size in bytes : {}".format(exp
)
425 self
.logger
.error(error_msg
)
429 def __get_osType(self
):
431 Private method to get OS ID and Type
440 os_info
= self
.__read
_yaml
_file
(OS_INFO_FILE_PATH
)
442 if self
.os_type
and os_info
:
443 for os_id
, os_type
in os_info
.iteritems():
444 if self
.os_type
.lower() == os_type
.lower():
448 except Exception as exp
:
449 error_msg
= "ERROR:Error occurred while getting OS details : {}".format(exp
)
450 self
.logger
.error(error_msg
)
456 def __get_diskcontroller(self
):
458 Private method to get details of Disk Controller
462 disk_controller : dict with details of Disk Controller
466 if self
.disk_controller
.lower() in ["paravirtual", "lsilogic", "buslogic", "lsilogicsas"]:
467 scsi_subtype
= self
.disk_controller
468 self
.disk_controller
= "SCSI"
470 disk_controller_info
= self
.__read
_yaml
_file
(DISK_CONTROLLER_INFO_FILE_PATH
)
472 if self
.disk_controller
and disk_controller_info
:
473 for key
, value
in disk_controller_info
.iteritems():
474 if self
.disk_controller
.lower() in key
.lower():
475 disk_controller
['controllerName'] = key
476 disk_controller
['resourceType'] = str(value
["ResourceType"])
477 resourceSubTypes
= value
["ResourceSubTypes"] if "ResourceSubTypes" in value
else None
478 if key
== "SATA Controller":
479 disk_controller
["resourceSubType"] = resourceSubTypes
[0]
480 elif key
== "SCSI Controller":
482 if scsi_subtype
.lower() == "paravirtual":
483 scsi_subtype
= "VirtualSCSI"
484 for subtype
in resourceSubTypes
:
485 if scsi_subtype
.lower() == subtype
.lower():
486 disk_controller
["resourceSubType"] = subtype
489 error_msg
= "ERROR: Invalid inputs can not "\
490 "find SCSI subtype {}".format(scsi_subtype
)
491 self
.__raise
_exception
(error_msg
)
493 except KeyError as exp
:
494 error_msg
= "ERROR:Error occurred while getting Disk Controller details : {}".format(exp
)
495 self
.logger
.error(error_msg
)
498 return disk_controller
500 def __read_yaml_file(self
, file_path
):
502 Private method to execute command
504 command : command to execute
508 with
open(file_path
) as data_file
:
509 data
= yaml
.load(data_file
)
512 def __raise_exception(self
, error_msg
, exception_type
="Generic"):
514 Private method to execute command
516 command : command to execute
521 self
.logger
.debug(error_msg
)
523 if exception_type
== "Generic":
524 raise Exception(error_msg
)
525 elif exception_type
== "IO":
526 raise Exception(error_msg
)
528 def __execute_command(self
, command
):
530 Private method to execute command
532 command : command to execute
534 stdout : output of command
535 stderr: error occurred while executing command if any
536 returncode : return code of command execution
539 self
.logger
.info("Execute command: {} ".format(command
))
541 proc
= subprocess
.Popen(command
, stdout
= subprocess
.PIPE
, stdin
= subprocess
.PIPE
,
542 stderr
= subprocess
.PIPE
,shell
=True)
543 stdout
, stderr
= proc
.communicate()
544 returncode
= proc
.returncode
546 except Exception as exp
:
547 self
.logger
.error("Error {} occurred while executing command {} ".format(exp
,command
))
549 return stdout
, stderr
, returncode
552 def create_ovf(self
):
554 Method to convert source image into OVF
556 Return : True on success else False
559 if self
.source_format
== self
.output_format
:
560 self
.logger
.info("Source format is OVF. No need to convert: {} ")
561 return self
.source_img_path
563 #Get source img properties
564 img_info
= self
.__get
_image
_info
()
567 #Create vmdk disk image
568 disk_img
= self
.__convert
_image
()
572 ovf_path
= self
.__edit
_ovf
_template
()
575 self
.logger
.error("Error in getting image information cannot convert image")
576 raise Exception("Error in getting image information cannot convert image")