How to Move (Live) vApps Across Org VDCs

VMware Cloud Director has secret not well known API only feature that allows to move vApps across Org VDCs while they are running. This feature has been purposefully made for the NSX-V to NSX-T Migration Tool, but can be used for other use cases hence the reason here to shed more lights on it.

We should start with mentioning that vApp migration across Org VDCs has been around since forever – in the UI you can select an existing vApp and you will find out Move command in the action menu. But that is something completely different – that method does in the background (vSphere) cloning operation with deletion of the source VM(s). Thus it is slow, requires vApp to be powered off and creates new identity for the vApp and VMs after the move (their UUIDs will change). The UI is using API method POST /vdc/{id}/action/cloneVApp with flag IsSourceDelete set to true.

So the above method is *not* the subject of this article – instead we will talk about API method POST /VDC/{id}/action/moveVApp.

The main differences are:

  • vMotion (e.g. live, share nothing and cross vCenter) is used
  • identity of vApp and VM does not change (UUID is retained)
  • vApp can be in running state
  • VMs can be connected to Named (independent) disks
  • Fast provisioning (linked clones) support

The moveVApp API is fairly new and still evolving. For example VMware Cloud Director 10.3.2 added support for move router vApps. Movement of running encrypted vApps will be supported in the future. So be aware there might be limitations based on your VCD version.

The vApp can be moved across Org VDCs/Provider VDCs/clusters, vCenters of the same tenant but it will not work across associated Orgs for example. It also cannot be used for moving vApps across clusters/resource pools in the same Org VDC (for that use Migrate VM UI/API). Obviously the underlying vSphere platform must support vMotions across the involved clusters or vCenters. NSX backing (V to T) change is also supported.

The API method is using the target Org VDC endpoint with quite elaborate payload that must describe which vApp is being moved, how will the target network configuration look like (obviously parent Org VDC networks will change) and what storage, compute or placement policies will be used by every vApp VM at the target.

Note that if a VM is connected to a media (ISO) it must be accessible to the target Org VDC (the ISO is not migrated).

An example is worth 1000 words:

POST https://{{host}}/api/vdc/5b2abda9-aa2e-4745-a33b-b4b8fa1dc5f4/action/moveVApp

Content-Type:application/vnd.vmware.vcloud.MoveVAppParams+xml
Accept:application/*+xml;version=36.2

<?xml version="1.0"?>
<MoveVAppParams xmlns="http://www.vmware.com/vcloud/v1.5" xmlns:ns7="http://schemas.dmtf.org/ovf/envelope/1" xmlns:ns8="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:ns9="http://www.vmware.com/schema/ovf">
  <Source href="https://vcd-01a.corp.local/api/vApp/vapp-96d3a015-4a08-4c59-93fa-384b41d4e453"/>
  <NetworkConfigSection>
    <ns7:Info>The configuration parameters for logical networks</ns7:Info>
       <NetworkConfig networkName="vApp-192.168.40.0">
            <Configuration>
                <IpScopes>
                    <IpScope>
                        <IsInherited>false</IsInherited>
                        <Gateway>192.168.40.1</Gateway>
                        <Netmask>255.255.255.0</Netmask>
                        <SubnetPrefixLength>24</SubnetPrefixLength>
                        <IsEnabled>true</IsEnabled>
                        <IpRanges>
                            <IpRange>
<StartAddress>192.168.40.2</StartAddress>
<EndAddress>192.168.40.99</EndAddress>
                            </IpRange>
                        </IpRanges>
                    </IpScope>
                </IpScopes>
                <ParentNetwork href="https://vcd-01a.corp.local/api/admin/network/1b8a200b-7ee7-47d5-81a1-a0dcb3161452" id="1b8a200b-7ee7-47d5-81a1-a0dcb3161452" name="Isol_192.168.33.0-v2t"/>
                <FenceMode>natRouted</FenceMode>
                <RetainNetInfoAcrossDeployments>false</RetainNetInfoAcrossDeployments>
                <Features>
                    <FirewallService>
                        <IsEnabled>true</IsEnabled>
                        <DefaultAction>drop</DefaultAction>
                        <LogDefaultAction>false</LogDefaultAction>
                        <FirewallRule>
                            <IsEnabled>true</IsEnabled>
                            <Description>ssh-VM6</Description>
                            <Policy>allow</Policy>
                            <Protocols>
<Tcp>true</Tcp>
                            </Protocols>
                            <DestinationPortRange>22</DestinationPortRange>
                            <DestinationVm>
<VAppScopedVmId>88445b8a-a9c4-43d5-bfd8-3630994a0a88</VAppScopedVmId>
<VmNicId>0</VmNicId>
<IpType>assigned</IpType>
                            </DestinationVm>
                            <SourcePortRange>Any</SourcePortRange>
                            <SourceIp>Any</SourceIp>
                            <EnableLogging>false</EnableLogging>
                        </FirewallRule>
                        <FirewallRule>
                            <IsEnabled>true</IsEnabled>
                            <Description>ssh-VM5</Description>
                            <Policy>allow</Policy>
                            <Protocols>
<Tcp>true</Tcp>
                            </Protocols>
                            <DestinationPortRange>22</DestinationPortRange>
                            <DestinationVm>
<VAppScopedVmId>e61491e5-56c4-48bd-809a-db16b9619d63</VAppScopedVmId>
<VmNicId>0</VmNicId>
<IpType>assigned</IpType>
                            </DestinationVm>
                            <SourcePortRange>Any</SourcePortRange>
                            <SourceIp>Any</SourceIp>
                            <EnableLogging>false</EnableLogging>
                        </FirewallRule>
                        <FirewallRule>
                            <IsEnabled>true</IsEnabled>
                            <Description>Allow all outgoing traffic</Description>
                            <Policy>allow</Policy>
                            <Protocols>
<Any>true</Any>
                            </Protocols>
                            <DestinationPortRange>Any</DestinationPortRange>
                            <DestinationIp>external</DestinationIp>
                            <SourcePortRange>Any</SourcePortRange>
                            <SourceIp>internal</SourceIp>
                            <EnableLogging>false</EnableLogging>
                        </FirewallRule>
                    </FirewallService>
                    <NatService>
                        <IsEnabled>true</IsEnabled>
                        <NatType>portForwarding</NatType>
                        <Policy>allowTraffic</Policy>
                        <NatRule>
                            <Id>65537</Id>
                            <VmRule>
<ExternalIpAddress>192.168.33.2</ExternalIpAddress>
<ExternalPort>2222</ExternalPort>
<VAppScopedVmId>e61491e5-56c4-48bd-809a-db16b9619d63</VAppScopedVmId>
<VmNicId>0</VmNicId>
<InternalPort>22</InternalPort>
<Protocol>TCP</Protocol>
                            </VmRule>
                        </NatRule>
                        <NatRule>
                            <Id>65538</Id>
                            <VmRule>
<ExternalIpAddress>192.168.33.2</ExternalIpAddress>
<ExternalPort>22</ExternalPort>
<VAppScopedVmId>88445b8a-a9c4-43d5-bfd8-3630994a0a88</VAppScopedVmId>
<VmNicId>0</VmNicId>
<InternalPort>22</InternalPort>
<Protocol>TCP</Protocol>
                            </VmRule>
                        </NatRule>
                    </NatService>
                </Features>
                <SyslogServerSettings/>
                <RouterInfo>
                    <ExternalIp>192.168.33.2</ExternalIp>
                </RouterInfo>
                <GuestVlanAllowed>false</GuestVlanAllowed>
                <DualStackNetwork>false</DualStackNetwork>
            </Configuration>
            <IsDeployed>true</IsDeployed>
        </NetworkConfig>
  </NetworkConfigSection>
  <SourcedItem>
    <Source href="https://vcd-01a.corp.local/api/vApp/vm-fa47982a-120a-421a-a321-62e764e10b80"/>
    <InstantiationParams>
      <NetworkConnectionSection>
        <ns7:Info>Network Connection Section</ns7:Info>
        <PrimaryNetworkConnectionIndex>0</PrimaryNetworkConnectionIndex>
                <NetworkConnection network="vApp-192.168.40.0" needsCustomization="false">
                    <NetworkConnectionIndex>0</NetworkConnectionIndex>
                    <IpAddress>192.168.40.2</IpAddress>
                    <IpType>IPV4</IpType>
                    <ExternalIpAddress>192.168.33.3</ExternalIpAddress>
                    <IsConnected>true</IsConnected>
                    <MACAddress>00:50:56:28:00:30</MACAddress>
                    <IpAddressAllocationMode>POOL</IpAddressAllocationMode>
                    <SecondaryIpAddressAllocationMode>NONE</SecondaryIpAddressAllocationMode>
                    <NetworkAdapterType>VMXNET3</NetworkAdapterType>
                </NetworkConnection>
      </NetworkConnectionSection>
    </InstantiationParams>
    <StorageProfile href="https://vcd-01a.corp.local/api/vdcStorageProfile/bdf68bda-8ab9-4ec1-970a-fafc34cdcf5b"/>
  </SourcedItem>
    <SourcedItem>
    <Source href="https://vcd-01a.corp.local/api/vApp/vm-a1f87b29-60e7-45ee-86e2-5b749a81ed19"/>
    <InstantiationParams>
      <NetworkConnectionSection>
        <ns7:Info>Network Connection Section</ns7:Info>
        <PrimaryNetworkConnectionIndex>0</PrimaryNetworkConnectionIndex>
                <NetworkConnection network="vApp-192.168.40.0" needsCustomization="false">
                    <NetworkConnectionIndex>0</NetworkConnectionIndex>
                    <IpAddress>192.168.40.3</IpAddress>
                    <IpType>IPV4</IpType>
                    <ExternalIpAddress>192.168.33.2</ExternalIpAddress>
                    <IsConnected>true</IsConnected>
                    <MACAddress>00:50:56:28:00:37</MACAddress>
                    <IpAddressAllocationMode>POOL</IpAddressAllocationMode>
                    <SecondaryIpAddressAllocationMode>NONE</SecondaryIpAddressAllocationMode>
                    <NetworkAdapterType>VMXNET3</NetworkAdapterType>
                </NetworkConnection>
      </NetworkConnectionSection>
    </InstantiationParams>
    <StorageProfile href="https://vcd-01a.corp.local/api/vdcStorageProfile/bdf68bda-8ab9-4ec1-970a-fafc34cdcf5b"/>
  </SourcedItem>
</MoveVAppParams>

In our case this is routed two VM vApp where both VMs are connected to the same routed vApp network named vApp-192.168.40.0 with set of port forwarding NAT rules and FW policies configured on the vApp router.

  • As said above it is a POST call against the target Org VDC – in our case 5b2abda9-aa2e-4745-a33b-b4b8fa1dc5f4.
  • The payload starts with the source vApp (vapp-96d3a015-4a08-4c59-93fa-384b41d4e453).
  • The follows the NetworkConfig section. Here we are describing the target vApp network topology. In general that section should be identical to the source vApp payload with the only difference being the ParentNetwork must refer to an Org VDC network from the target Org VDC. So in our case we are describing the subnet and IP pools of the vApp network (vApp-192.168.40.0), its new parent Org VDC network (Isol_192.168.33.0-v2t) and the way these two are connected (bridged or natRouted). As we are using routed vApp it is natRouted in our case. Then follow (optional) routed vApp features such as firewall policies or NAT rules. They should be pretty self explanatory and again they are usually identical to the source vApp section from the NetworkConfig. Note that VM object rules use VAppScopedVmId that is random looking UUID that changes every time the vApp is moved.
    We should highlight that IP addresses allocated to the vApp (its VMs or vApp routers) from the source Org VDC network are retained during the migration (and must be available in the target Org VDC network static IP pool).
  • After the NetworkConfigSection follow details of every vApp VM (SourcedItem) – to which vApp network(s) defined above the VM network interface(s) will connect (with which IP/MAC and IPAM mode) and which storage, placement and compute policies (StorageProfile, VdcComputePolicy and ComputePolicy) it should use. For the NIC section you usually take the source VM equivalent info. The vApp network name must be the one defined in the NetworkConfig section. For the policies you must obviously use target Org VDC policies as these will change.
  • BTW storage policy can be also defined at the disk level with DiskSetting element (the followin excerpt shows when named disk is connected)
            <DiskSettings>
                <DiskId>2016</DiskId>
                <SizeMb>8</SizeMb>
                <UnitNumber>0</UnitNumber>
                <BusNumber>1</BusNumber>
                <AdapterType>3</AdapterType>
                <ThinProvisioned>true</ThinProvisioned>
                <Disk href="https://vcd-01a.corp.local/api/disk/567bdd04-4905-4a62-95e7-9f4850f85240" id="urn:vcloud:disk:567bdd04-4905-4a62-95e7-9f4850f85240" type="application/vnd.vmware.vcloud.disk+xml" name="Disk1"/>
                <StorageProfile href="https://vcd-01a.corp.local/api/vdcStorageProfile/1f8bf2df-d28c-4bec-900c-726f20507b5b"/>
                <overrideVmDefault>true</overrideVmDefault>
                <iops>0</iops>
                <VirtualQuantityUnit>byte</VirtualQuantityUnit>
                <resizable>true</resizable>
                <encrypted>false</encrypted>
                <shareable>false</shareable>
                <sharingType>None</sharingType>
            </DiskSettings>

The actual vApp migration triggers async operation that takes some time to complete. If you observe what is happening in VCD and vCenter you will see that a new temporary “-generated” vApp is created in the target Org VDC with the VMs being first migrated there. In case of routed vApps the vApp routers (edge service gateways or Tier-1 gateways) must be deployed as well. When all the vApp VMs are moved the source vApp is removed and the target vApp with the same identity is created and the VMs from generated vApp are relocated there. If all goes as expected the generated vApp is removed.

Shout-out to Julian – the engineering brain behind this feature.

28 thoughts on “How to Move (Live) vApps Across Org VDCs

  1. Thanks for this post. It’s really helpful. By the way, I’m facing a weird problem with a powershell script to automate the movevApp.
    Sometimes it works, sometimes not. API calls always return a success code (202) but sometimes (quite often) the move ends with an “Internal Server Error” on vCloud Side.
    If I take the XML build within the script and paste “as is” into my REST API tools (Insomnia), it work like a charm.
    There is someting weird I cannot figured out. Here is the code, if you are interested of digging. It may be helpful for others :

    #Functions
    function new-cloudapiSession
    {
    $userName = ‘administrator@system’
    $securedValue = $passwordCI
    $password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securedValue))
    $userPass = $username + “:” + $password
    $bytes= [System.Text.Encoding]::UTF8.GetBytes($userpass)
    $encodedlogin=[Convert]::ToBase64String($bytes)

    Write-Host -NoNewline “`nBuilding Headers and verifying API version: ” -ForegroundColor Green
    $global:headers = @{}
    $authheader = “Basic ” + $encodedlogin
    $headers.Add(“Authorization”,$authheader)

    $version_uri = $base_uri + “api/versions”
    try {$get_version = Invoke-RestMethod -Uri $version_uri -Headers $headers -Method GET -ErrorAction Stop}
    catch {$err_mes = $_;$get_version = $false}

    $check_version = $get_version.SupportedVersions.VersionInfo | Where-Object {$_.Version -eq $apiVersion}

    if ($check_version)
    {
    Write-Host “Success” -ForegroundColor White
    $headers.Add(“Accept”,”application/*+xml;version=$apiVersion”)

    Write-Host -NoNewline “Adding Bearer Token authorization: ” -ForegroundColor Green
    $session_uri = $base_uri + “api/sessions”
    try{$get_session = Invoke-WebRequest -Uri $session_uri -Headers $headers -Method POST -ErrorAction Stop}
    catch{$err_mes = $_;$get_version = $false}

    if ($get_session.StatusCode -eq ‘200’)
    {
    Write-Host “Success” -ForegroundColor White
    $bearer_token = $get_session.Headers.Item(‘X-VMWARE-VCLOUD-TOKEN-TYPE’) + ” ” + $get_session.Headers.Item(‘X-VMWARE-VCLOUD-ACCESS-TOKEN’)
    $headers.Remove(“Authorization”)
    $headers.Add(“Authorization”,$bearer_token)
    }
    else{Write-Host “Failed” -ForegroundColor Red;Write-Host $err_mes.Exception -ForegroundColor Red}
    }
    else{Write-Host “Failed” -ForegroundColor Red;Write-Host $err_mes.Exception -ForegroundColor Red}
    }

    function check-apiConnection
    {
    $test_uri = $base_uri + “api/org/”
    Write-Host -NoNewline “Validating connection to Cloud Director API: ” -ForegroundColor Green
    try{$connection_check = Invoke-WebRequest -Uri $test_uri -Headers $headers -Method GET}
    catch{$err_mes = $_}

    if($connection_check.StatusCode -eq ‘200’){Write-Host “Success” -ForegroundColor White}
    else{Write-Host “Failed” -ForegroundColor Red;Write-Host $err_mes.Exception -ForegroundColor Red}
    }

    $cloudDirector =
    $orgname =
    $Source_vDCName =
    $Target_VDCName =

    #connexion vCloud
    connect-ciserver $cloudDirector

    #Connexion Rest API
    $apiVersion = “36.2”
    $base_uri = “https://” + $cloudDirector + “/”

    new-cloudapiSession
    if ($headers.Authorization -like “Bearer*”)
    {
    check-apiConnection
    }

    #Retrieve href vapp source and target
    $counter = 0
    $org = get-org -name $orgname
    $Target_VDC = Get-OrgVdc -name $Target_VDCName
    $Target_VDC_id = ($Target_VDC.id -split “:”)[3]
    $Source_VDC = Get-OrgVdc -name $Source_vDCName
    $Source_vApps = $Source_VDC | Get-CIVApp
    $Target_uri = “$($base_uri)api/vdc/$Target_VDC_id/action/moveVApp”
    Write-host -NoNewline “URI : “-ForegroundColor Green
    Write-host $uri
    $Move_headers = @{}
    $Move_headers = $headers.clone()
    $Move_headers.Add(“Content-Type”,”application/vnd.vmware.vcloud.MoveVAppParams+xml”)
    $Request_headers = @{}
    $Request_headers = $headers.clone()

    $Request_headers.Add(“Content-Type”,”application/vnd.vmware.vcloud.session+xml;version=36.2″)

    #Request each item
    Foreach ($Source_vApp in $Source_vApps){
    $vAppCount = $Source_vApps.count
    $counter += 1
    $Source_vAppName = $Source_vApp.name
    $vapp_href = $Source_vApp.ExtensionData.href
    $vms = $Source_vApp | Get-CIVM

    #Create XML Body
    [System.Xml.XmlDocument]$XML_body = @”

    “@

    #Create element vApp “Source”
    $SourcevApp = $XML_body.CreateElement(“Source”,$XML_body.MoveVAppParams.NamespaceURI)
    $SourcevApp.SetAttribute(“href”,$vapp_href)
    $XML_body.MoveVAppParams.AppendChild($SourcevApp)

    #API Request vApp
    $uri = “$vapp_href/networkConfigSection”
    Write-Host -NoNewline “Invoke vApp Request API: ” -ForegroundColor Green
    try{$vApp_request = Invoke-RestMethod -uri $uri -Headers $Request_headers -Method Get }
    catch{$err_mes = $_}

    #Create node “NetworkConfigSection”
    $NetworkConfigSection=$XML_body.CreateNode(“element”,”NetworkConfigSection”,$XML_body.MoveVAppParams.NamespaceURI)
    $NetworkConfigSection = $XML_body.importnode($vApp_request.NetworkConfigSection, $true)
    $XML_body.MoveVAppParams.appendChild($NetworkConfigSection)

    foreach ($vm in $vms){
    $vm_href = $vm.ExtensionData.Href
    $vm_sp_name = $vm.extensiondata.storageprofile.name
    $Target_vdc_sp_id = (($Target_VDC.ExtensionData.VdcStorageProfiles.VdcStorageProfile | where-object {$_.name -eq $vm_sp_name}).id -split “:”)[3]
    $Target_vdc_sp_href = “$($base_uri)api/vdcStorageProfile/$Target_vdc_sp_id”

    #API Request VM
    $uri = “$vm_href/networkConnectionSection”
    Write-Host -NoNewline “Invoke Request API: ” -ForegroundColor Green
    try{$VM_request = Invoke-RestMethod -uri $uri -Headers $headers -Method Get }
    catch{$err_mes = $_}

    #Create node “SourcedItem”
    $SourceItem=$XML_body.CreateNode(“element”,”SourcedItem”,$XML_body.MoveVAppParams.NamespaceURI)
    $XML_body.MoveVAppParams.appendChild($SourceItem)

    #Create element VM “Source”
    $SourceVM = $XML_body.CreateElement(“Source”,$XML_body.MoveVAppParams.NamespaceURI)
    $SourceVM.SetAttribute(“href”,$vm_href)
    $SourceItem.AppendChild($SourceVM)

    #Create node “InstantiationParams”
    $InstantiationParams=$XML_body.CreateNode(“element”,”InstantiationParams”,$XML_body.MoveVAppParams.NamespaceURI)
    $SourceItem.appendChild($InstantiationParams)

    #Create node “NetworkConnectionSection”
    $NetworkConnectionSection=$XML_body.CreateNode(“element”,”NetworkConnectionSection”,$XML_body.MoveVAppParams.NamespaceURI)
    $NetworkConnectionSection = $XML_body.importnode($VM_request.NetworkConnectionSection, $true)
    $InstantiationParams.appendChild($NetworkConnectionSection)

    #Create element “StorageProfile”
    $StorageProfile = $XML_body.CreateElement(“StorageProfile”,$XML_body.MoveVAppParams.NamespaceURI)
    $StorageProfile.SetAttribute(“href”,$Target_vdc_sp_href)
    $SourceItem.AppendChild($StorageProfile)

    }

    #(Optional)save the file
    $XML_body.save(“C:\Sources\Scripts PowerShell\REST API\Export\XMLExported_$Source_vAppName.xml”)

    Write-Host -NoNewline “Move vApp: ” -ForegroundColor Green
    Write-Host $Source_vAppName
    Write-Host -NoNewline “Invoke Request API: ” -ForegroundColor Green
    try{$request = Invoke-WebRequest -uri $Target_uri -Headers $Move_headers -Method Post -Body $XML_body}
    catch{$err_mes = $_}

    if($request.StatusCode -eq ‘202’){Write-Host “Success” -ForegroundColor White}
    else{Write-Host “Failed” -ForegroundColor Red;Write-Host $err_mes.Exception -ForegroundColor Red}

    if ($counter -ne $vAppCount){Write-Host -NoNewline “Migrating vApp : ” -ForegroundColor Green
    write-host “$counter on $vAppCount”
    write-host “Waiting for next vApp” -ForegroundColor Yellow
    write-host -NoNewline “Target URI : ” -ForegroundColor Green
    Write-host $Target_uri
    #Start-Sleep -s 120
    pause “Press any key to continue”

    } else {
    Write-Host -NoNewline “Migrating vApp : ” -ForegroundColor Green
    Write-host “$counter on $vAppCount”
    write-host -NoNewline “Target URI : ” -ForegroundColor Green
    Write-host $Target_uri
    }
    }

    Disconnect-CIserver * -Confirm:$False

  2. Thank you for this helpful info, but do you have any idea how to move vap with multipule network config not only 1 ?

  3. also if i have 1000 machine inside 1 vapp, will i do the config manual ? or is there is a way to move the Vapp with all the machines in it without entering the details manually ?

  4. i am still having issues running this API calls. May I request for you to provide an example to move a vApp connected to an isolated VDCnetwork please?

  5. I’m using the API call to move a vApp where the source and target vCenters are different. Hence, the storage policies defined in vCD don’t match and the source policy is not available in the target vCenter. I created the body of the payload defining the StorageProfile section with the reference to the target storage policy but it failed with this error:

    “No valid storage containers found for VirtualMachine”

    Is it possible to perform this type of move or is it a restriction of this feature?

      1. Thanks Tomas

        This is the error we’re getting:

        [ cd30f5cd-261d-4750-859a-eb04b0a2d61e ] Internal Server Error
        – VM vmtest5 (b2867b66-5205-45d2-9c4a-df235289255b) failed to update and has been rolled back. The operation failed because no suitable resource was found. Out of 1 candidate hubs:
        1 hubs eliminated because: No valid storage containers found for VirtualMachine “1f48ce7c-a6c9-406d-8c6a-d6d54875dab9”. All 16 available storage containers were filtered out as being invalid. Rejected hubs: resgroup-1679108
        Storage containers were rejected for the following reasons:
        16 storage containers rejected because: They do not support the required storage policy: Tier 1. Rejected storage containers: PlacementException NO_FEASIBLE_PLACEMENT_SOLUTION

        The strange part in this error is that storage policy “Tier 1” is the name on the source cluster, not the destination.

        In the XML payload we are specifying the destination with:

        That 61f5 uuid is correct for the destination OrgVDC storageProfile.

  6. We have two VMware environments (6.7 and 7.0) with vCloud 10.4.0 and we’re migrating workload from old to new.

    Tried to make live migration with this API, but task failed with some errors.

    On vCloud task monitoring, it shows “Internal Server Error – null Underlying system error: com.vmware.vim.binding.vim.fault.CannotAccessNetwork. com.vmware.vcloud.api.presentation.service.InternalServerErrorException: Internal Server Error” -error on task.

    In source vCenter tasks it shows “Currently connected network interface ‘Network adapter 1’ uses network ‘DVSwitch: ‘, which is not accessible.”.

    Does this mean that parent network in XML is invalid? Offline migration works fine using that API and live migrations from vCenter works fine.

    VMware support couldn’t help me with this, so maybe someone can give a hint.

      1. It was misconfiguration in External network configuration in destination vDC.

        We have shared storage between old and new environment and we noticed that it almost always make Storage vMotion even the storage policy remain same. Is it possible to set specific datastore?

  7. Hi Tom, Is it possible to move from one OrgvDC with a Direct Attach OrgvDC Network to an another OrgvDC with a vApp Network using this API? The use case is to move from a Non-NSX vCenter Server to a NSX Integrated vCenter Server with a desired topology.

  8. Hello Tom,

    Above all else thanks a lot for that article, the documentation on this API call is very scarce.

    We’re trying to use the moveVApp API to migrate vApps between two Provider VDCs
    The two OrgVDC are on the same DC Group and are using the same NSX-T T1 which has its scope increased from the Original OrgVDC to the new one.

    The goal was to live migrate the vApps to a new vSphere cluster (each one is attached to a different PVDC)

    I’m currently running some tests with Postman.
    I created my xml, put it in postman (Raw/XML) and ran the POST api call to /api/vdc/{{Target_Org_VDC_UUID}}/action/moveVApp

    I’m getting a “415 unsupported media type” error message.

    1. My question is the following, do you know if this API call still uses XML (then I probably made a mistake somewhere) or should I create a JSON ?

      We’re running VCD 10.4.1.
      I tried with different API versions (version=36.2, 37.0)

    2. EDIT : I think I found what I was doing wrong. So if someone else get the same issue
      I didn’t put the correct headers…

      Content-Type:application/vnd.vmware.vcloud.MoveVAppParams+xml
      Accept:application/*+xml;version=36.2

      Thanks again for documenting this

  9. Hey Tom,

    Thanks for this article. This is working for us for the most part, however when the source VM/s have storage polices assigned to individual disks we are unable to build and acceptable body to send to vCloud. In your example you have a disk HREF as well as some Disk Settings defined. We are able to recreate this, but cannot find a reference to the disk HREF at all.

    Are you able to show me where you found the HREF for the individual disks?

    Also, do you have any internal information/documentation on the API method? We are struggling to find a spec for this and its basically trial and error when working with it.

    Thanks so much!

  10. I just re-read your note above the disk settings post, and you clearly state that this is for a named (independent) disk, so apologies there. Can we assume that this section in the payload is similar for each vdisk just without the HREF?

  11. For anyone having issues with getting this to work: The biggest issue I ran into is that the MoveVAppParams element must have attributes for any “xmlns” namespaces used in any section of the xml body.

    Below is my modified version of the script that Damien posted in one of the above comments. A few notes
    1 – you may need to include other xmlns attributes.
    2 – the formatting on this site uses those odd versions of single and double quotes that powershell doesn’t like. do a replace all for those.
    3 – If you’re using vCD 10.5 you need to have powercli 13.1 for the connection to work.

    $cloudDirector = “XXXX”
    $orgname = “XXXX”
    $vapp_to_move = “XXXX”
    $Source_vDCName = “XXXX”
    $Target_VDCName = “XXXX”
    $creds = (get-credential -username administrator -message “vcd administrator pass”)
    $apiVersion = “36.3”
    #Functions
    function new-cloudapiSession
    {
    $userName = $creds.username + “@system”
    $password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($creds.password))
    $userPass = $username + “:” + $password
    $bytes= [System.Text.Encoding]::UTF8.GetBytes($userpass)
    $encodedlogin=[Convert]::ToBase64String($bytes)
    Write-Host -NoNewline “`nBuilding Headers and verifying API version: ” -ForegroundColor Green
    $global:headers = @{}
    $authheader = “Basic ” + $encodedlogin
    $headers.Add(“Authorization”,$authheader)
    $version_uri = $base_uri + “api/versions”
    try {$get_version = Invoke-RestMethod -Uri $version_uri -Headers $headers -Method GET -ErrorAction Stop}
    catch {$err_mes = $_;$get_version = $false}
    $check_version = $get_version.SupportedVersions.VersionInfo | Where-Object {$_.Version -eq $apiVersion}
    if ($check_version)
    {
    Write-Host “Success” -ForegroundColor White
    $headers.Add(“Accept”,”application/*+xml;version=$apiVersion”)
    Write-Host -NoNewline “Adding Bearer Token authorization: ” -ForegroundColor Green
    $session_uri = $base_uri + “api/sessions”
    try{$get_session = Invoke-WebRequest -Uri $session_uri -Headers $headers -Method POST -ErrorAction Stop}
    catch{$err_mes = $_;$get_version = $false}
    if ($get_session.StatusCode -eq ‘200’)
    {
    Write-Host “Success” -ForegroundColor White
    $bearer_token = $get_session.Headers.Item(‘X-VMWARE-VCLOUD-TOKEN-TYPE’) + ” ” + $get_session.Headers.Item(‘X-VMWARE-VCLOUD-ACCESS-TOKEN’)
    $bearer_token
    $headers.Remove(“Authorization”)
    $headers.Add(“Authorization”,$bearer_token)
    }
    else{Write-Host “Failed” -ForegroundColor Red;Write-Host $err_mes.Exception -ForegroundColor Red}
    }
    else{Write-Host “Failed” -ForegroundColor Red;Write-Host $err_mes.Exception -ForegroundColor Red}
    }
    function check-apiConnection
    {
    $test_uri = $base_uri + “api/org/”
    Write-Host -NoNewline “Validating connection to Cloud Director API: ” -ForegroundColor Green
    try{$connection_check = Invoke-WebRequest -Uri $test_uri -Headers $headers -Method GET}
    catch{$err_mes = $_}
    if($connection_check.StatusCode -eq ‘200’){Write-Host “Success” -ForegroundColor White}
    else{Write-Host “Failed” -ForegroundColor Red;Write-Host $err_mes.Exception -ForegroundColor Red}
    }

    #connexion vCloud
    connect-ciserver $cloudDirector -org system -credential $creds
    #Connexion Rest API
    $base_uri = “https://” + $cloudDirector + “/”
    new-cloudapiSession
    if ($headers.Authorization -like “Bearer*”)
    {
    check-apiConnection
    }
    #Retrieve href vapp source and target
    $counter = 0
    $org = get-org -name $orgname
    $Target_VDC = Get-OrgVdc -name $Target_VDCName
    $Target_VDC_id = ($Target_VDC.id -split “:”)[3]
    $Source_VDC = Get-OrgVdc -name $Source_vDCName
    $Source_vApps = $Source_VDC | Get-CIVApp -name $vapp_to_move
    $Target_uri = “$($base_uri)api/vdc/$Target_VDC_id/action/moveVApp”
    Write-host -NoNewline “URI : “-ForegroundColor Green
    Write-host $uri
    $Move_headers = @{}
    $Move_headers = $headers.clone()
    $Move_headers.Add(“Content-Type”,”application/vnd.vmware.vcloud.MoveVAppParams+xml”)
    $Request_headers = @{}
    $Request_headers = $headers.clone()
    $Request_headers.Add(“Content-Type”,”application/vnd.vmware.vcloud.session+xml;version=36.2″)
    #Request each item
    Foreach ($Source_vApp in $Source_vApps){
    $vAppCount = $Source_vApps.count
    $counter += 1
    $Source_vAppName = $Source_vApp.name
    $vapp_href = $Source_vApp.ExtensionData.href
    $vms = $Source_vApp | Get-CIVM
    #Create XML Body
    $x = New-Object System.Xml.XmlDocument
    $dec = $X.CreateXmlDeclaration(“1.0″,”UTF-8”,$null)
    $X.AppendChild($dec)
    $XMLbody = $X.CreateNode(“element”,”MoveVAppParams”,$null)
    $XMLbody.setattribute(“xmlns”,”http://www.vmware.com/vcloud/v1.5″)
    $XMLbody.setattribute(“xmlns:wbem”,”http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData”)
    $XMLbody.setattribute(“xmlns:ovf”,”http://www.vmware.com/schema/ovf”)
    $XMLbody.setattribute(“xmlns:vmext”,”http://www.vmware.com/vcloud/extension/v1.5″)
    $XMLbody.setattribute(“xmlns:ns9″,”http://www.vmware.com/vcloud/versions”)
    $XMLbody.setattribute(“name”,”Move-vApp”)
    $X.AppendChild($xmlbody)
    $SourcevApp = $x.CreateElement(“Source”,$XMLbody.MoveVAppParams.NamespaceURI)
    $SourcevApp.SetAttribute(“href”,$vapp_href)
    $XMLbody.AppendChild($SourcevApp)
    #API Request vApp
    $uri = “$vapp_href/networkConfigSection”
    Write-Host -NoNewline “Invoke vApp Request API: ” -ForegroundColor Green
    try{$vApp_request = Invoke-RestMethod -uri $vapp_href -Headers $Request_headers -Method Get }
    catch{$err_mes = $_}
    #Create node “NetworkConfigSection”
    $NetworkConfigSection=$x.CreateNode(“element”,”NetworkConfigSection”,$x.MoveVAppParams.NamespaceURI)
    $NetworkConfigSection = $x.importnode($vApp_request.vapp.NetworkConfigSection, $true)
    $XMLbody.appendChild($NetworkConfigSection)
    foreach ($vm in $vms){
    $vm_href = $vm.ExtensionData.Href
    $vm_sp_name = $vm.extensiondata.storageprofile.name
    $Target_vdc_sp_id = (($Target_VDC.ExtensionData.VdcStorageProfiles.VdcStorageProfile | where-object {$_.name -eq $vm_sp_name}).id -split “:”)[3]
    $Target_vdc_sp_href = “$($base_uri)api/vdcStorageProfile/$Target_vdc_sp_id”
    #API Request VM
    $uri = “$vm_href/networkConnectionSection”
    Write-Host -NoNewline “Invoke Request API: ” -ForegroundColor Green
    try{$VM_request = Invoke-RestMethod -uri $vm_href -Headers $headers -Method Get }
    catch{$err_mes = $_}
    #Create node “SourcedItem”
    $SourceItem=$x.CreateNode(“element”,”SourcedItem”,$x.MoveVAppParams.NamespaceURI)
    $XMLbody.appendChild($SourceItem)
    #Create element VM “Source”
    $SourceVM = $x.CreateElement(“Source”,$x.MoveVAppParams.NamespaceURI)
    $SourceVM.SetAttribute(“href”,$vm_href)
    $SourceItem.AppendChild($SourceVM)
    #Create node “InstantiationParams”
    $InstantiationParams=$x.CreateNode(“element”,”InstantiationParams”,$x.MoveVAppParams.NamespaceURI)
    $SourceItem.appendChild($InstantiationParams)
    #Create node “NetworkConnectionSection”
    $NetworkConnectionSection=$x.CreateNode(“element”,”NetworkConnectionSection”,$x.MoveVAppParams.NamespaceURI)
    $NetworkConnectionSection = $x.importnode($VM_request.vm.NetworkConnectionSection, $true)
    $InstantiationParams.appendChild($NetworkConnectionSection)
    #Create element “StorageProfile”
    $StorageProfile = $x.CreateElement(“StorageProfile”,$x.MoveVAppParams.NamespaceURI)
    $StorageProfile.SetAttribute(“href”,$Target_vdc_sp_href)
    $SourceItem.AppendChild($StorageProfile)
    }
    $X.AppendChild($xmlbody)
    #(Optional)save the file
    $desktop = $env:userprofile + “\Desktop”
    $x.save(“$desktop\XMLExported_$Source_vAppName.xml”)
    Write-Host -NoNewline “Move vApp: ” -ForegroundColor Green
    Write-Host $Source_vAppName
    Write-Host -NoNewline “Invoke Request API: ” -ForegroundColor Green
    try{$request = Invoke-WebRequest -uri $Target_uri -Headers $Move_headers -Method Post -Body $XMLbody}
    catch{$err_mes = $_}
    if($request.StatusCode -eq ‘202’){Write-Host “Success” -ForegroundColor White}
    else{Write-Host “Failed” -ForegroundColor Red;Write-Host $err_mes.Exception -ForegroundColor Red}
    if ($counter -ne $vAppCount){Write-Host -NoNewline “Migrating vApp : ” -ForegroundColor Green
    write-host “$counter on $vAppCount”
    write-host “Waiting for next vApp” -ForegroundColor Yellow
    write-host -NoNewline “Target URI : ” -ForegroundColor Green
    Write-host $Target_uri
    #Start-Sleep -s 120
    pause “Press any key to continue”
    } else {
    Write-Host -NoNewline “Migrating vApp : ” -ForegroundColor Green
    Write-host “$counter on $vAppCount”
    write-host -NoNewline “Target URI : ” -ForegroundColor Green
    Write-host $Target_uri
    }
    }
    Disconnect-CIserver * -Confirm:$False

  12. I previously used this alot to move VMs. I just recently tried a move with VCD 10.4.2 and the move was success (vm properly moved to the new org vdc). But I am having a weird glitch where I can see the vApp in the destination org and it shows it contains a virtual machine, but when I go to virtual machine, the moved VM is not showing. in vCenter, the VM was moved properly under the proper resource pool. It seems to be a vCloud glitch.. Anyone else had that same issue and found a solution to prevent/fix this? I do not want to remove the VM from vCloud and re-import it as this will cause downtime to customer and I used the API to prevent downtime

    1. Hello Dominic,

      we have been trying to get the base script to work and are running into issues. Do you have your script on Git that we could reference?

      (we have both 10.4.1.1 and 10.5.1 vCloud deployments)

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.