Skip to content

Instantly share code, notes, and snippets.

@swill
Last active May 2, 2017 12:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save swill/f6b54762ffcce85772535a490a9c8cbe to your computer and use it in GitHub Desktop.
Save swill/f6b54762ffcce85772535a490a9c8cbe to your computer and use it in GitHub Desktop.
A function extracted from a VMware migration tool to illustrate splitting an OVA file into one OVA per disk.
def split_ova(vm_id):
split_ok = True
conf.read(['./running.conf'])
vms = json.loads(conf.get('STATE', 'vms'))
## this script is designed to work with Python 2.5+, so we are not using anything from ElementTree 1.3, only 1.2...
## this is important in order to support CentOS at the time of this writing.
ns = {}
ns['ns'] = 'http://schemas.dmtf.org/ovf/envelope/1'
ns['ovf'] = 'http://schemas.dmtf.org/ovf/envelope/1'
ns['cim'] = 'http://schemas.dmtf.org/wbem/wscim/1/common'
ns['rasd'] = 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData'
ns['vmw'] = 'http://www.vmware.com/schema/ovf'
ns['vssd'] = 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData'
ns['xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'
ET._namespace_map[ns['ovf']] = 'ovf'
ET._namespace_map[ns['cim']] = 'cim'
ET._namespace_map[ns['rasd']] = 'rasd'
ET._namespace_map[ns['vmw']] = 'vmw'
ET._namespace_map[ns['vssd']] = 'vssd'
ET._namespace_map[ns['xsi']] = 'xsi'
DISK_RESOURCE_TYPE = 17
src_ova_file = '%s.ova' % (vms[vm_id]['clean_name'])
src_ova_base = vms[vm_id]['clean_name']
print('\nExtracting %s...' % (src_ova_file))
cmd = 'cd %s; rm -rf %s; mkdir %s; tar xvf %s -C %s' % (
conf.get('FILESERVER', 'files_path'), src_ova_base, src_ova_base, src_ova_file, src_ova_base)
ret = subprocess.call(cmd, shell=True)
if ret == 0:
src_ovf_file = None
for f in os.listdir('%s/%s' % (conf.get('FILESERVER', 'files_path'), src_ova_base)):
if f.endswith('.ovf'):
src_ovf_file = '%s/%s/%s' % (conf.get('FILESERVER', 'files_path'), src_ova_base, f)
if src_ovf_file:
src_dom = ET.parse(src_ovf_file)
src_tree = src_dom.getroot()
log.info('Extracting and evaluating the ova file. Creating a new ova file for each disk...')
for index in xrange(len(src_tree.findall('{%(ns)s}DiskSection/{%(ns)s}Disk' % ns))):
dom = ET.parse(src_ovf_file)
tree = dom.getroot()
split_base = None
items_to_remove = []
# get the values we care about for this iteration
disk_el = tree.findall('{%(ns)s}DiskSection/{%(ns)s}Disk' % ns)[index]
disk_id = disk_el.attrib.get('{%(ovf)s}diskId' % ns, None)
file_id = disk_el.attrib.get('{%(ovf)s}fileRef' % ns, None)
file_nm = None
for f in tree.findall('{%(ns)s}References/{%(ns)s}File' % ns):
if f.attrib.get('{%(ovf)s}id' % ns, None) == file_id:
file_nm = f.attrib.get('{%(ovf)s}href' % ns, None)
split_base = os.path.splitext(file_nm)[0]
# get the controller type
controller_id = None
controller_type = None
for i in tree.findall('{%(ns)s}VirtualSystem/{%(ns)s}VirtualHardwareSection/{%(ns)s}Item' % ns):
if int(i.find('{%(rasd)s}ResourceType' % ns).text) == DISK_RESOURCE_TYPE:
if i.find('{%(rasd)s}HostResource' % ns).text.endswith(disk_id):
controller_id = i.find('{%(rasd)s}Parent' % ns).text
for i in tree.findall('{%(ns)s}VirtualSystem/{%(ns)s}VirtualHardwareSection/{%(ns)s}Item' % ns):
if i.find('{%(rasd)s}InstanceID' % ns).text == controller_id:
controller_type = i.find('{%(rasd)s}Description' % ns).text
if 'IDE' in controller_type:
log.info('Disk %s is using an IDE controller\n' % (split_base))
log.warning('The IDE controller is not fully supported. The VM will need to be manually verified to be working after the migration completes.\n')
# loop through the different elements and remove the elements we don't want
for d in tree.findall('{%(ns)s}DiskSection/{%(ns)s}Disk' % ns):
if d.attrib.get('{%(ovf)s}diskId' % ns, None) != disk_id:
parent = tree.find('{%(ns)s}DiskSection' % ns)
parent.remove(d)
for f in tree.findall('{%(ns)s}References/{%(ns)s}File' % ns):
if f.attrib.get('{%(ovf)s}id' % ns, None) != file_id:
items_to_remove.append(f.attrib.get('{%(ovf)s}id' % ns))
parent = tree.find('{%(ns)s}References' % ns)
parent.remove(f)
for i in tree.findall('{%(ns)s}VirtualSystem/{%(ns)s}VirtualHardwareSection/{%(ns)s}Item' % ns):
if int(i.find('{%(rasd)s}ResourceType' % ns).text) == DISK_RESOURCE_TYPE:
if not i.find('{%(rasd)s}HostResource' % ns).text.endswith(disk_id):
parent = tree.find('{%(ns)s}VirtualSystem/{%(ns)s}VirtualHardwareSection' % ns)
parent.remove(i)
# remove extra Items associated with deleted elements
for d in tree.findall('{%(ns)s}VirtualSystem/{%(ns)s}VirtualHardwareSection/{%(ns)s}Item' % ns):
if d.find('{%(rasd)s}HostResource' % ns) != None:
for item in items_to_remove:
if d.find('{%(rasd)s}HostResource' % ns).text.endswith(item):
parent = tree.find('{%(ns)s}VirtualSystem/{%(ns)s}VirtualHardwareSection' % ns)
parent.remove(d)
# update elements that require specific values
for c in tree.findall('{%(ns)s}VirtualSystem/{%(ns)s}VirtualHardwareSection/{%(vmw)s}Config' % ns):
if c.attrib.get('{%(vmw)s}key' % ns, None) == 'tools.toolsUpgradePolicy':
c.set('{%(vmw)s}value' % ns, 'manual')
split_ofv_file = '%s/%s/%s.ovf' % (conf.get('FILESERVER', 'files_path'), src_ova_base, split_base)
with open(split_ofv_file, 'w') as f:
f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
dom.write(f, encoding='utf-8')
## since the '' and 'ovf' namespaces have the same url, we have to keep the 'ovf' on attributes, but not on tags.
cmd = "perl -pi -e 's,<ovf:,<,g' %s" % (split_ofv_file)
ret = subprocess.call(cmd, shell=True)
cmd = "perl -pi -e 's,</ovf:,</,g' %s" % (split_ofv_file)
ret = subprocess.call(cmd, shell=True)
## apparently the namespaces need to be exactly as specified and can't be re-saved. replace the Envelope. no id passed...
ns_str = ''
for k, v in ns.items():
if k == 'ns':
ns_str = '%s xmlns="%s"' % (ns_str, v)
else:
ns_str = '%s xmlns:%s="%s"' % (ns_str, k, v)
cmd = "perl -pi -e 's,<Envelope.*>,%s,g' %s" % (
'<Envelope%s>' % (ns_str),
split_ofv_file)
ret = subprocess.call(cmd, shell=True)
print('\nCreating %s.ova...' % (split_base))
cmd = 'cd %s/%s; rm -rf ../%s.ova; tar cvf ../%s.ova %s.ovf %s' % (
conf.get('FILESERVER', 'files_path'), src_ova_base, split_base, split_base, split_base, file_nm)
ret = subprocess.call(cmd, shell=True)
if ret == 0:
log.info('Created %s.ova' % (split_base))
if len(vms[vm_id]['src_disks']) > index:
vms[vm_id]['src_disks'][index]['ova'] = '%s.ova' % (split_base)
vms[vm_id]['src_disks'][index]['url'] = '%s://%s:%s%s%s' % (
'https' if conf.get('FILESERVER', 'port') == '443' else 'http',
conf.get('FILESERVER', 'host'),
conf.get('FILESERVER', 'port'),
conf.get('FILESERVER', 'base_uri'),
'%s.ova' % (split_base))
conf.set('STATE', 'vms', json.dumps(vms))
with open('running.conf', 'wb') as f:
conf.write(f) # update the file to include the changes we have made
else:
log.error('Could not save the ova to the vms disk due to index out of bound')
split_ok = False
else:
log.error('Failed to create %s.ova' % (split_base))
split_ok = False
else:
log.error('Failed to locate the source ovf file %s/%s/%s.ovf' % (
conf.get('FILESERVER', 'files_path'), src_ova_base, src_ova_base))
split_ok = False
# remove the directory we used to create the new OVA files
cmd = 'cd %s; rm -rf %s' % (conf.get('FILESERVER', 'files_path'), src_ova_base)
ret = subprocess.call(cmd, shell=True)
if ret == 0:
log.info('Successfully removed temporary disk files')
else:
log.warning('Failed to remove temporary disk files. Consider cleaning up the directory "%s" after the migration.' % (
conf.get('FILESERVER', 'files_path')))
else:
log.error('Failed to extract the ova file %s/%s' % (conf.get('FILESERVER', 'files_path'), src_ova_file))
split_ok = False
return split_ok
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment