VDS Import fails with UTF-8 decoding error - Pure Python solution needed
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Needs clarification
- Activity status
- Active
- Tech stack
- python
- Domain
- api, networking
Research direction
Start with the complete reproducible script, especially parse_xml_entity and import_vds, then trace the DVSManager.ImportEntity call in pyVmomi. Reproduce the failure with the supplied environment and inspect how EntityBackup.Config values and the returned SOAP response are handled. Done means a pure Python VDS import works and rejected requests expose a usable error.
Written by the indexing model from the issue text.
Description
Is your feature request related to a problem? Please describe.
When attempting to import a Distributed Virtual Switch (VDS) configuration using pure Python and pyVmomi, the DVSManager.ImportEntity method fails with a UTF-8 decoding error:
ERROR: Import failed: DVSManagerImportEntity_Task failed for 'dvs-production-01': 'utf-8' codec can't decode byte 0x9c in position 5: invalid start byte
Technical Analysis:
- The byte
0x9cis the zlib compression header - vCenter rejects the import request and returns a compressed SOAP fault
- pyVmomi's HTTP client fails to decompress the response before parsing
- This masks the actual vCenter error message, preventing troubleshooting
Environment:
- Python: 3.13.x
- OS: Windows 11(PoC) but Linux for Production.
- pyVmomi: 9.1.0.0
- vCenter: VMware vSphere 8.0.3.01000
Current Status:
- ✅ Export functionality works perfectly
- Import functionality fails with UTF-8 decoding error
- ❌ Generated XML is not compatible with vSphere Web UI (expects ZIP format)
The same operation works flawlessly using PowerCLI, confirming the API itself is functional.
Describe the solution you'd like
I need a pure Python/pyVmomi solution that:
-
Exports VDS configuration (already works ✅)
-
Imports VDS configuration without UTF-8 decoding errors
-
Handles proper serialization of
EntityBackupConfigobjects including:configBlobas raw bytes (not list or string)configVersionas string (pyVmomi requirement)- Proper handling of the
containerfield forapplyToEntitySpecifiedmode
-
Provides clear error messages when vCenter rejects requests (proper decompression of SOAP faults)
Specific Questions:
-
How should
configBlobbe serialized when callingDVSManagerImportEntity?- Raw bytes?
- Base64 string?
- Compressed (zlib/gzip)?
-
What is the correct type for
configVersion?- pyVmomi validation requires
str, but is this consistent with vSphere 8.0.x WSDL?
- pyVmomi validation requires
-
Should the
containerfield be populated when usingimportType="applyToEntitySpecified"?- Documentation suggests
entityTypeandkeyare sufficient
- Documentation suggests
-
How to handle vCenter's compressed error responses?
- Is there a configuration to auto-decompress gzip/zlib responses?
-
Is there a working example of VDS import using pure pyVmomi (not PowerCLI)?
Describe alternatives you've considered
PowerCLI/PowerShell Module:
- The original PowerCLI script by Luc Dekens works perfectly
- However, I require a pure Python solution for cross-platform compatibility
- PowerShell is not available or preferred in our Linux-based automation environment
vSphere Web UI Manual Import:
- Requires ZIP format with specific structure (separate .bak files + manifest)
- Not suitable for automated workflows
- The Python script generates a single XML file with inline base64 blobs
Direct HTTP/REST API calls:
- Would bypass pyVmomi entirely
- Extremely complex to implement properly
- Would lose the benefits of pyVmomi's type safety and object model
Preferred Solution:
A working pyVmomi implementation that properly handles the SOAP serialization and HTTP compression, maintaining compatibility with the existing vSphere API.
Additional context
Complete Reproducible Script
Below is the complete, self-contained Python script that demonstrates the issue. Maintainers can copy, paste, and run this directly.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
VDS Backup & Restore Manager (XML) - Minimal Reproducible Example
Demonstrates the UTF-8 decoding crash during DVSManager.ImportEntity in pyVmomi.
"""
import os
import sys
import ssl
import base64
import argparse
import urllib3
from datetime import datetime
from xml.etree import ElementTree as ET
try:
from pyVmomi import vim, vmodl, VmomiSupport
from pyVim import connect as vmware_connect
except ImportError as e:
print(f"ERROR: Cannot import pyVmomi: {e}")
sys.exit(1)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
ssl._create_default_https_context = ssl._create_unverified_context
BACKUP_ROOT = r"C:\backups\VDS"
IMPORT_TYPE = "applyToEntitySpecified"
def sanitize_filename(name: str) -> str:
invalid = '<>:"/\\|?*\x00-\x1f'
for ch in invalid:
name = name.replace(ch, '_')
return name.strip(' .') or '_'
def create_xml_entity(entity):
elem = ET.Element("entity")
elem.set("type", getattr(entity, 'entityType', ''))
elem.set("key", getattr(entity, 'key', ''))
elem.set("name", getattr(entity, 'name', ''))
container = getattr(entity, 'container', None)
if container and hasattr(container, '_moId'):
elem.set("container", container._moId)
else:
elem.set("container", str(container) if container else '')
elem.set("configVersion", str(getattr(entity, 'configVersion', '0')))
if getattr(entity, 'configBlob', None):
blob_b64 = base64.b64encode(entity.configBlob).decode('ascii')
blob_elem = ET.SubElement(elem, "configBlob")
blob_elem.text = blob_b64
else:
blob_elem = ET.SubElement(elem, "configBlob")
blob_elem.text = ""
return elem
def parse_xml_entity(elem):
config_type = VmomiSupport.GetVmodlType("vim.dvs.EntityBackup.Config")
config = config_type()
config.entityType = elem.get("type", "")
config.key = elem.get("key", "")
config.name = elem.get("name", "")
config_version_str = elem.get("configVersion", "0")
config.configVersion = str(config_version_str) if config_version_str else "0"
blob_text = elem.findtext("configBlob", default="")
if blob_text:
config.configBlob = base64.b64decode(blob_text)
else:
config.configBlob = b""
return config
def connect_to_vcenter(host, user, pwd):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
si = vmware_connect.SmartConnect(host=host, user=user, pwd=pwd, port=443, sslContext=ctx)
return si, si.RetrieveContent()
except Exception as e:
raise RuntimeError(f"Connection to '{host}' failed: {e}")
def export_vds(content, dvs_name, output_dir):
container_view = content.viewManager.CreateContainerView(content.rootFolder, [vim.DistributedVirtualSwitch], True)
dvs = next((d for d in container_view.view if d.name == dvs_name), None)
container_view.Destroy()
if not dvs:
raise RuntimeError(f"VDS '{dvs_name}' not found")
selection_sets = []
dvs_ss = vim.dvs.DistributedVirtualSwitchSelection()
dvs_ss.dvsUuid = dvs.uuid
selection_sets.append(dvs_ss)
for pg in dvs.portgroup:
pg_ss = vim.dvs.DistributedVirtualPortgroupSelection()
pg_ss.dvsUuid = dvs.uuid
pg_ss.portgroupKey = [pg.key]
selection_sets.append(pg_ss)
try:
task = content.dvSwitchManager.DVSManagerExportEntity_Task(selection_sets)
while task.info.state not in [vim.TaskInfo.State.success, vim.TaskInfo.State.error]:
import time; time.sleep(0.5)
if task.info.state == vim.TaskInfo.State.error:
raise RuntimeError(f"Export failed: {task.info.error.msg}")
export_result = task.info.result
except Exception as e:
raise RuntimeError(f"DVSManagerExportEntity_Task failed: {e}")
os.makedirs(output_dir, exist_ok=True)
root = ET.Element("vdsBackup")
root.set("dvsName", dvs.name)
root.set("dvsUuid", dvs.uuid)
root.set("exportTime", datetime.now().isoformat())
entities_elem = ET.SubElement(root, "entities")
for entity in export_result:
entities_elem.append(create_xml_entity(entity))
filepath = os.path.join(output_dir, f"VDS_{sanitize_filename(dvs.name)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xml")
ET.ElementTree(root).write(filepath, encoding="utf-8", xml_declaration=True)
print(f"SUCCESS: Exported to {filepath}")
return filepath
def import_vds(content, backup_file, dvs_name):
if not os.path.isfile(backup_file):
raise FileNotFoundError(f"File not found: {backup_file}")
tree = ET.parse(backup_file)
root = tree.getroot()
entities_elem = root.find("entities")
if entities_elem is None:
raise ValueError("Invalid XML: missing 'entities' element")
entity_list = [parse_xml_entity(elem) for elem in entities_elem.findall("entity")]
if not entity_list:
raise ValueError("No entities found in XML file")
try:
content.dvSwitchManager.ImportEntity(
entityBackup=entity_list,
importType=IMPORT_TYPE
)
print(f"SUCCESS: Imported VDS '{dvs_name}'")
except vim.fault.NotFound:
raise RuntimeError(f"VDS '{dvs_name}' not found. '{IMPORT_TYPE}' requires the VDS to exist.")
except Exception as e:
err_msg = str(e)
if "utf-8" in err_msg.lower() or "codec" in err_msg.lower() or "decode" in err_msg.lower():
print("\n" + "="*60)
print("[DEBUG] pyVmomi parsing crash intercepted.")
print("[DEBUG] vCenter rejected the request and returned a compressed (zlib) SOAP fault.")
print("[DEBUG] pyVmomi failed to decompress it, masking the actual vCenter error.")
print("="*60 + "\n")
raise RuntimeError(f"ImportEntity failed for '{dvs_name}': {e}")
def main():
parser = argparse.ArgumentParser(description="VDS Backup & Restore - Minimal Reproducible Example")
parser.add_argument("--export", action="store_true", help="Export VDS")
parser.add_argument("--import", dest="import_mode", action="store_true", help="Import VDS")
parser.add_argument("--vcenter", required=True, help="vCenter FQDN")
parser.add_argument("--vds", required=True, help="VDS Name")
parser.add_argument("--path", help="Path for import/export")
parser.add_argument("--user", required=True, help="vCenter Username")
parser.add_argument("--password", required=True, help="vCenter Password")
args = parser.parse_args()
try:
si, content = connect_to_vcenter(args.vcenter, args.user, args.password)
print(f"SUCCESS: Connected to '{args.vcenter}'")
if args.export:
out_dir = args.path or os.path.join(BACKUP_ROOT, sanitize_filename(args.vcenter), sanitize_filename(args.vds))
export_vds(content, args.vds, out_dir)
elif args.import_mode:
if not args.path:
parser.error("--import requires --path")
import_vds(content, args.path, args.vds)
except Exception as e:
print(f"ERROR: {e}")
sys.exit(1)
finally:
if 'si' in locals() and si:
try: vmware_connect.Disconnect(si)
except: pass
if __name__ == "__main__":
main()
Sample Generated XML Snippet:
<?xml version='1.0' encoding='utf-8'?>
<vdsBackup dvsName="dvs-production-01" dvsUuid="50 39 18 c4 04 fc cf 76-97 50 1f ee e9 0c 0a 27" exportTime="2026-08-31T18:21:11.676457">
<entities>
<entity type="distributedVirtualSwitch" key="50 39 18 c4 04 fc cf 76-97 50 1f ee e9 0c 0a 27" name="dvs-production-01" container="group-n11" configVersion="0">
<configBlob>AEUAAHic7RzZbuM48leEPCxmgY3lI4njrNpAzulgciFOB4t9adASbbNDiWqKcpz9+i2S...</configBlob>
</entity>
<entity type="distributedVirtualPortgroup" key="dvportgroup-1234" name="PG-Production-01" container="dvs-837" configVersion="0">
<configBlob>DBQAAHic7Vjbkto4EP0VF++DuQwzMOVxVeayW1ObSaiFUPuWEpYMCrLklWQD+fptWwZs...</configBlob>
</entity>
</entities>
</vdsBackup>
References
-
vSphere API Reference: DistributedVirtualSwitchManager
-
pyVmomi Community Samples: configure_dvs_port_group.py
-
Community Context: The equivalent operation is widely documented in the VMware community via Luc Dekens' PowerCLI functions, which successfully utilize
$dvSwMgr.DVSManagerImportEntity($dvswImport, "applyToEntitySpecified").
- Dominant language
- Python
- Stars
- 2.3k
- Forks
- 763
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from vmware/pyvmomi
-
bug
Difficulty 4/5 3-5 days Newbie friendliness 48/100
-
enhancement
Difficulty 3/5 1-2 days Newbie friendliness 35/100
-
enhancement
Difficulty 3/5 1-2 days Newbie friendliness 45/100
-
enhancement
Difficulty 4/5 3-5 days Newbie friendliness 35/100
-
enhancement
Difficulty 3/5 1-2 days Newbie friendliness 45/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
bancolombia/sentinel#23 ·
-
test md OpenCI
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
-
integration:quickjs org:external priority:backlog topic:code-interpreter topic:middleware type:feature
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
langchain-ai/deepagents#6450 ·
-
bug client
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100