Last active
May 2, 2017 12:24
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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