A migrated VM can boot, have the right disks and still be broken. A license server may refuse to start because the host ID changed. A domain controller may reset part of its replication state. A Linux guest may come up with no network because its config names an interface that no longer exists. Monitoring may show the VM as a new host while the old one alerts as down.

All of these problems have the same cause: the VM’s identity changed. This post lists the identifiers a VMware VM carries, explains what depends on each one, and shows how each maps to KVM (libvirt/QEMU) and KubeVirt.

What “identity” means for a VM#

Identifier Where it lives on VMware Who reads it
BIOS (SMBIOS) UUID uuid.bios in the .vmx; config.uuid in the API The guest (dmidecode -s system-uuid, /sys/class/dmi/id/product_uuid, Win32_ComputerSystemProduct), license managers, backup and endpoint agents, CMDB discovery
Instance UUID vc.uuid; config.instanceUuid vCenter and tools that talk to vCenter: backup, DR, automation, CMDB connectors
MAC addresses Each virtual NIC DHCP reservations, firewall and NAC rules, MAC-bound licenses, guest network configs that match by MAC
VM Generation ID vm.genid / vm.genidX in the .vmx Windows, especially Active Directory domain controllers
NIC device name Derived by the guest from the virtual PCI slot (ens192) Linux network configs, firewall rules, scripts, monitoring checks
Management object ID vCenter moref (vm-42) Anything that stores vCenter references: scripts, backup jobs, CMDB records

The vSphere API describes uuid as the “128-bit SMBIOS UUID of a virtual machine”. It describes instanceUuid as a vCenter-specific UUID used “to uniquely identify all virtual machine instances, including those that may share the same SMBIOS UUID”. This distinction matters because cloned VMs sometimes share a BIOS UUID. In that case the BIOS UUID is not a reliable unique key, but the instance UUID is.

Record all of these before you move anything. vCenter forgets a guest’s IP configuration when VMware Tools stop, so capture guest IPs while the VM is still running.

The SMBIOS UUID#

Guests and agents treat the SMBIOS system UUID as the hardware’s serial identity. Some software licenses bind to it, and some backup, EDR and inventory agents use it to recognize a machine they have seen before. If it changes, these tools may count the VM as a new machine. Windows may also treat a hardware identity change as new hardware for activation purposes.

On libvirt, the domain’s <uuid> is presented to the guest as the SMBIOS system UUID. You can also set it explicitly in <sysinfo>. If you set both, libvirt requires them to match.

<domain type='kvm'>
  <name>web01</name>
  <uuid>4231a6c2-8f0e-4b7d-9c1a-2f5e6d7c8b90</uuid>
  <sysinfo type='smbios'>
    <system>
      <entry name='uuid'>4231a6c2-8f0e-4b7d-9c1a-2f5e6d7c8b90</entry>
    </system>
  </sysinfo>
  <os>
    <type arch='x86_64' machine='q35'>hvm</type>
    <smbios mode='sysinfo'/>
  </os>
  ...
</domain>

On KubeVirt, set spec.domain.firmware.uuid on the VirtualMachine’s template, and serial if the guest also cares about the SMBIOS serial number:

spec:
  template:
    spec:
      domain:
        firmware:
          uuid: 4231a6c2-8f0e-4b7d-9c1a-2f5e6d7c8b90

Verify the result from inside the guest after the first boot:

sudo dmidecode -s system-uuid
cat /sys/class/dmi/id/product_uuid

On Windows, run (Get-CimInstance Win32_ComputerSystemProduct).UUID.

MAC addresses#

DHCP reservations, switch port security, NAC policies, firewall objects and some license managers key on MAC addresses. Keeping them avoids touching all of those systems.

In libvirt, the MAC is set on each <interface>. Any unicast address works. The 52:54:00 prefix is only libvirt’s default for addresses it generates.

<interface type='bridge'>
  <mac address='00:50:56:9a:12:34'/>
  <source bridge='br-prod'/>
  <model type='virtio'/>
</interface>

In KubeVirt, set macAddress on the interface in spec.domain.devices.interfaces. Note how the binding type affects this. With bridge binding, the documented default is that the pod interface’s MAC is passed to the VM when you don’t set one, so set it explicitly. Masquerade binding NATs the guest behind the pod IP, so external systems never see the guest MAC anyway.

virt-v2v keeps MACs. Its manual says the MAC “is part of the guest metadata and must remain the same on source and target hypervisors”. Its --mac option maps each MAC to a target network or bridge.

Keeping the MAC doesn’t keep the NIC. vmxnet3 becomes virtio, so the guest sees new hardware with the old address. That leads to the next two problems.

Linux NIC names#

On systemd-based distributions, interface names are derived from firmware and bus information. VMware places NICs in PCI slots that produce names like ens192, where 192 is the slot number. On KVM the virtio NIC sits at a different PCI address, so it gets a different name, such as enp1s0. Every config that says ens192 (an ifcfg file, a netplan stanza, an nftables rule, a monitoring check) now points at an interface that doesn’t exist, and the VM boots without network.

There are two fixes, and you can use both:

  1. Bind the configuration to the MAC instead of the name. netplan supports match: macaddress: with set-name:. NetworkManager keyfiles support mac-address= under [ethernet]. systemd-networkd supports [Match] MACAddress=.
  2. Pin the old name to the MAC with a systemd .link file. For each device, the first .link file that matches (in lexical order) applies, so a low number sorts it ahead of 99-default.link:
# /etc/systemd/network/10-ens192.link
[Match]
MACAddress=00:50:56:9a:12:34

[Link]
Name=ens192

Don’t pin names in the kernel’s own namespace, such as eth0. The systemd.link manual warns against names the kernel might assign to another device. Also check whether your distribution copies .link files into the initramfs. If it does, rebuild the initramfs after adding one.

Also look for stale state from the old platform:

  • /etc/udev/rules.d/70-persistent-net.rules on older guests;
  • cloud-init network configuration that will regenerate DHCP config for the “new” NIC on first boot.

virt-v2v doesn’t do this part for you. Its manual says it “cannot currently reconfigure a guest’s network configuration”.

Windows NICs and static IPs#

Windows ties TCP/IP settings to the adapter instance, not to the MAC address. After conversion, the virtio adapter shows up as a new connection (for example “Ethernet 2”) with DHCP. The old vmxnet3 adapter becomes a hidden device that still holds the static address. Trying to assign the same IP to the new adapter then triggers a warning that the address is already in use by another adapter.

virt-v2v’s manual describes Windows as unable to “preserve MAC to static IP address mappings automatically”. Its --mac MAC:ip:ADDR,GW,LEN,DNS form sets the static address on the new adapter at first boot. If you handle it yourself:

  1. Remove the hidden adapter: in Device Manager, choose View > Show hidden devices, then uninstall the greyed-out vmxnet3 adapter.
  2. Set the address on the virtio adapter, which you can find by its MAC.
  3. Check DNS suffixes and adapter binding order, which also belong to the old adapter.

VM Generation ID and domain controllers#

VM Generation ID is a 128-bit value the hypervisor exposes to the guest, defined by Microsoft’s specification. The hypervisor changes it when the VM’s timeline changes: when a snapshot is applied or a VM is created from a copy. QEMU implements it as the vmgenid device:

-device vmgenid,guid=324e6eaf-d1d1-4bf6-bf41-b9bb6c91fb87

libvirt (since 4.4.0) exposes it as <genid>. An empty <genid/> makes libvirt generate a value and save it. A value you supply is kept.

<genid>8f0c5a6e-3b7d-4e21-9a4c-1d2e3f4a5b6c</genid>

Active Directory is the main reason to care about this ID. Microsoft’s documentation explains that a DC stores the VM Generation ID in its database (the msDS-GenerationId attribute). Whenever the current value differs from the stored one, the DC resets its invocation ID and discards its RID pool. This protects against USN rollback after a snapshot restore and against duplicate security principals after cloning.

For a migration, which is a move rather than a copy, that gives two cases:

  • The ID changes. The DC applies those safeguards and resynchronizes SYSVOL non-authoritatively from a partner. This is safe, but it adds replication work and is better avoided for a move.
  • The target exposes no Generation ID. Microsoft notes that the DC then behaves like a pre-2012 virtual DC. You lose snapshot-rollback protection on the new platform. Also, if a DCCloneConfig.xml is present, the DC boots into Directory Services Restore Mode.

Keeping the same value is the clean option. virt-v2v reads vm.genid/vm.genidX from VMX sources and writes <genid> for libvirt and local outputs. KubeVirt does not offer a Generation ID device, so a DC moved to KubeVirt loses this protection. Plan DC moves accordingly. Often the simplest approach is to build a new DC on the target and demote the old one.

Licensing, monitoring and CMDB keys#

Before the move, find out which external records refer to each VM, and by which key:

  • Licensing. Ask each vendor what their host ID is: MAC, SMBIOS UUID, a hash of several hardware values, or the hostname. Keeping the MAC and UUID covers the common cases, but not every scheme.
  • Monitoring. Agents often register by hostname plus a machine ID. VMware-integrated monitoring may instead key on the instance UUID or the moref.
  • CMDB. Discovery tools reconcile on serial number, UUID, MAC or hostname. If the key changes, you get a duplicate CI and an orphaned one.
  • Backup and DR. Jobs bound to vCenter morefs or instance UUIDs won’t follow the VM to a new platform. Plan to recreate them, and keep the old restore points until they expire.

The management object ID (vCenter’s moref) cannot survive a move to a different control plane. Plan a mapping from old IDs to new ones and update the records that use them.

Checklist#

Before

  • Export per VM: BIOS UUID, instance UUID, every MAC, vm.genid/vm.genidX, the NIC PCI slot and guest name, IPs, gateways, DNS (captured while the VM runs), and the moref.
  • Flag duplicate BIOS UUIDs (clones) and decide which VM keeps each one.
  • List software licensed to hardware identity and confirm each vendor’s host ID.
  • Find DCs and other Generation-ID-aware workloads; decide between migrating and rebuilding.
  • Find bonds, bridges and VLAN interfaces inside guests. They need manual handling.

Target configuration

  • SMBIOS UUID set (<uuid>/<sysinfo>, or firmware.uuid).
  • MAC set on every interface.
  • <genid> set to the source value where supported.
  • Linux: configs matched by MAC, old names pinned with .link files, cloud-init networking disabled if it would interfere.
  • Windows: static IP reapplied on the virtio adapter and the ghost adapter removed.

After the first boot

  • dmidecode -s system-uuid matches the source.
  • ip -br link shows the expected names and MACs, and the gateway is reachable.
  • License servers start, and agents report as the same host rather than a new one.
  • CMDB, monitoring and backup records point at the new management ID.

Where Victor fits#

Victor’s cold migration from vCenter (validated so far against a vCenter simulator) keeps each VM’s BIOS UUID, instance UUID and MACs, plus the VM Generation ID on the classic KVM runtime; KubeVirt has no Generation ID. Migrated VMs get a new Victor managed-object ID, and the plan records which vCenter moref it replaced. For Linux guests Victor rewrites the network configuration by MAC and pins old names such as ens192 with a .link file; reapplying static IPs on Windows guests is an Enterprise feature.

See Victor's migration path