SOAP vs. REST for performing tasks in VMware environments

In a previous article, we learned from Luc Dekens about the REST API in VMware environments. Luc is a vExpert and MVP and is interested in all things automation, and more specifically through PowerShell and PowerCLI. He is a co-author of the PowerCLI Reference and a regular speaker at conferences. Be sure to check out Luc’s blog and his Twitter page . In this article, Luc builds on the earlier one by comparing how SOAP and REST work in performing a task in a VMware environment.

SOAP vs. REST API

REST

We now have a broad picture of REST API in VMware environments and how to consume those from PowerShell, but what does it actually mean in practice? Why should we convert from calling SOAP in favor of calling REST? As an example, I’m going to show how to perform the same action twice, once via the SOAP API and once via the REST API. The differences between these two should make it clear where your gain, as the consumer/coder, is located.

The task

In a VMware vSphere environment, when you add a hard disk to a virtual machine with PowerCLI, the system decides which unit number the hard disk will get. You can select the SCSI controller, but not the unit number of the device. For some applications, or even just as a requirement for VM management, it can be required to be able to select a predefined unit number. But with our current automation tool of choice, VMware PowerCLI, the New-HardDisk cmdlet doesn’t allow that.

SOAP

In the pre-REST age, we had to fall back to calling the vSphere API, based on SOAP, from within a PowerCLI script. When using the vSphere API the vSphere API Reference is indispensable. The drawback — this document can be daunting when you need to use it for the first couple of times.

All accessibility questions aside, the method we are looking for is ReconfigVM. Via a construction of nested objects, we can tell the vSphere Server exactly what we want to do:


$vmName = ‘VM1’
$hdSizeGB = 1
$unitNumber = 6
$busNumber = 0

$vm = Get-VM -Name $vmName

# Find the key of the SCSI controller that has bus 0

$ctrl = $vm.ExtensionData.Config.Hardware.Device |
where{$_ -is [VMware.Vim.VirtualLsiLogicSASController] -and
$_.BusNumber -eq $busNumber} |
Select -ExpandProperty Key

# Construct specification for adding the hard disk

$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$dev = New-Object VMware.Vim.VirtualDeviceConfigSpec
$disk = New-Object VMware.Vim.VirtualDisk
$back = New-Object VMware.Vim.VirtualDiskFlatVer2BackingInfo

$back.FileName = ‘‘
$back.ThinProvisioned = $true
$back.DiskMode = [VMware.Vim.VirtualDiskMode]::independent_persistent

$disk.Key = -100
$disk.ControllerKey = $ctrl
$disk.CapacityInKB = $hdSizeGB*1GB/1KB
$disk.UnitNumber = $unitNumber # <== define the unit number
$disk.Backing = $back

$dev.Operation = [VMware.Vim.VirtualDeviceConfigSpecOperation]::add
$dev.FileOperation = [VMware.Vim.VirtualDeviceConfigSpecFileOperation]::create
$dev.Device = $disk

$spec.DeviceChange += $dev

# Add the hard disk

$vm.ExtensionData.ReconfigVM($spec)

# Verification

Get-VM -Name $vmName | Get-HardDisk |
select Name,FileName,@{N=‘Unit’;E={$_.ExtensionData.UnitNumber}}


 

As you can see in the code, we can explicitly specify which unit number ($disk.UnitNumber) the new hard disk will have.

REST

When we do the same with a REST API, the code might look like this:


$vmName = ‘VM1’
$hdSizeGB = 1
$unitNumber = 6
$busNumber = 0

$vCenterName = ‘vcsa.local.lab’
$auth = @{
‘vmware-api-session-id’ = ‘8dd2aa8f186187949ceb7ce4318a9472’
}

# Find the VM

$uriFind = “https://$($vCenterName)/rest/vcenter/vm?filter.names=$($vmName)”
$vm = Invoke-RestMethod -Uri $uriFind -Headers $auth

# Create the new HD

$uriHD = “https://$($vCenterName)/rest/vcenter/vm/$($vm.value[0].vm)/hardware/disk”

$sRest = @{
Uri = $uriHD
Method = ‘Post’
Headers = $auth
ContentType = ‘application/json’
Body = @{
spec = @{
new_vmdk = @{
capacity = $hdSizeGB * 1GB
}
scsi = @{
bus = $busNumber
unit = $unitNumber
}
type = ‘SCSI’
}
} | ConvertTo-Json
}

$hd = Invoke-RestMethod @sRest

# Verification

Get-VM -Name $vmName | Get-HardDisk |
select Name,FileName,@{N=‘Unit’;E={$_.ExtensionData.UnitNumber}}


 

The big difference here, at least for me, is the way in which we tell the system what to do. The second Invoke-RestMethod, which creates the new hard disk, is told what we want to do through a combination of the URI (which VM we are targeting) and the Body structure, a straightforward hash table that we convert to JSON.

My conclusion

Working with REST API definitely simplifies the structures/objects we need to set up to make a call to the vSphere environment. Everything is defined in “text format,” the URI and the eventual parameters we pass through the JSON text. That is in my book one step closer to SDDC, a software defined datacenter.

Does this mean that we can abandon our scripts based on SOAP in favor of REST API? No, not really. At least not for now!
Remember that the vSphere REST API are a work in progress, not 100 percent there yet. To demonstrate that, notice how the SOAP script asked for a Thin Provision hard disk, and as far as I know, that feature is not yet available in the REST API, as they stand today. So until we have a 100 percent feature-compatible REST API implementation, don’t throw away your SOAP code just yet.

The rCisTag module

When you have a new set of API that offers functionality that was before only available to a single tool I use, then it was obvious that I wanted to explore the possibilities. The Tag feature in vSphere is something that has a great potential, but lacks a bit of flexibility. There are indeed cmdlets in PowerCLI to work with Tags, Tag Categories and Tag Assignments, but they tend be relatively slow under certain conditions. And the REST API offered an extensive set of functions to work with Tags.

What about Connect-CisServer and Get-CisService? One of the questions I got early on as a reaction to my rCisTag module: “Why didn’t you use the Connect-CisServer and Get-CisService cmdlets that are available in PowerCLI?” Well, there are a number of reasons, why I went for pure REST.

  • Avoid REST hiding: More and more products offer a REST API nowadays. The basic concept of calling REST API, and how to handle the returned data, is universal and can be used by all REST API interfaces. The Cis Service, as available in PowerCLI, hides the underlying REST API interface for the better part, which might be ok for users who do not want to get too deep into REST API, but you will not be able to use your more general REST API code.
  • Avoid PowerCLI dependency: Since the REST API use http(s), these API can be called from platforms where there is no PowerCLI. By eliminating this dependency, the code becomes more portable. And no, I’m not saying that you can run PowerShell from any platform, but it allows you to prototype your code in PowerShell, by eliminating the PowerCLI dependency.
  • Speed: The Tag related cmdlets tended to be somewhat slow in certain circumstances. The rCisTag module was in part written, because early trials showed that access to the Tag functionality tended to be faster with the REST API.
  • Why do people climb mountains? Because the mountains are there, and the people can.

But be assured, I have no problem with the Cis Services as implemented in PowerCLI. They are an easy and fast way to experiment with the REST API.

CRUD functions

When I write a module that is intended to work with specific objects, or in a specific environment, I always try to provide all CRUD functionality. CRUD, which stands for Create-Read-Update-Delete, is just a fancy word to indicate that something has all functionality to manage the complete life-cycle of the targets. In this case we have three kinds of target objects, Tags, Tag Categories and Tag Assignments. A quick check in the API Explorer showed that all that functionality is available through the REST API, except Updating Tag Assignments. But then again, that action in itself makes not a lot of sense.

How to write a module

When you start writing a module from scratch, it is always advisable to have a battle plan. In this case, calling the REST API and receiving the result(s), is of course the centerpiece. For that I tried to create a function that could handle all possible REST API calls. That way I could contain all the actual REST API calling in one function. When you look at a typical REST API call, you’ll notice straightaway that there are in fact three distinctive parts:

  • The uri, and more specifically the request part of the uri.
  • The method.
  • The body (in some cases).

When I am writing functions, I tend to start with the bare minimum, and add the nice features once the basic functionality is working. My original REST API caller function looked like this:


function Invoke-vCisRest{
param (
[String]$Method,
[String]$Request,
[PSObject]$Body
)

Process
{
$sRest = @{
Uri = “https:/”,$Script:CisServer.Server,’rest’,$Request -join ‘/’
Method = $Method
Body = &{if($Body){$Body | ConvertTo-Json -Depth 32}}
ContentType = ‘application/json’
Headers = @{
‘vmware-api-session-id’ = “$($vmware_api_session_id’)”
}
}
Invoke-RestMethod @sRest
}
}


 

As you can see, nothing fancy, just the basics. The session ID was stored in a variable upon the connect. Note that since we are using a PowerShell object to define the content of the Body, and since we define the Content s JSON, we have to convert the object to JSON. Nothing too difficult with the built-in ConvertTo-Json cmdlet. Just remember that the default depth of the cmdlet might not be sufficient to handle complex objects, hence the -Depth 32 that was added. Once I had this function working as designed, I could start developing the actual CRUD functions.

Getting a tag

When you are developing functions it is important to think about the parameters you are accepting. In the case of the Get-rCisTag function I wanted to allow fetching a Tag by its Name, but also through its internal Id. PowerShell has Parameter Sets for this, so use them.

Another nifty feature in PowerShell is the pipeline. So when possible try building that into your functions. In this function, I provided pipeline support for the Category parameter. That allowed me to have line like this:

Get-rCisTagCategory -Name TCat2,TCat1 | Get-rCisTag

The function ultimately looked something like this:


function Get-rCisTag{`
[CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = ‘Low’,
DefaultParameterSetName=‘Name’)]
param (
[Parameter(Position = 1, ParameterSetName=‘Name’)]
[String[]]$Name,
[Parameter(Position = 2, ParameterSetName=‘Name’,ValueFromPipeline = $true)]
[PSObject[]]$Category,
[Parameter(Mandatory = $True, Position = 1, ParameterSetName=‘Id’)]
[String[]]$Id
)

Process
{
if($PSCmdlet.ParameterSetName -eq ‘Name’){
if($Category){
$tagIds = $Category | %{
$categoryIds = &{if($_ -is [string]){
(Get-rCisTagCategory -Name $_).Id
}
else{
$_.Id
}}
$categoryIds | %{
# Get all tags in categories
$sRest = @{
Method = ‘Post’
Request = “com/vmware/cis/tagging/tag/” +
“id:$([uri]::EscapeDataString($_))” +
“?~action=list-tags-for-category”
}
(Invoke-vCisRest @sRest).value
}
}
}
else{
$sRest = @{
Method = ‘Get’
Request = ‘com/vmware/cis/tagging/tag’
}
$tagIds = (Invoke-vCisRest @sRest).value
}
}
else{
$tagIds = $Id
}

# Get category details
$out = @()
$tagIds | where{($PSCmdlet.ParameterSetName -eq ‘Id’ -and $Id -contains $_) -or
$PSCmdlet.ParameterSetName -eq ‘Name’} | %{
$sRest = @{
Method = ‘Get’
Request = “com/vmware/cis/tagging/tag/id:$([uri]::EscapeDataString($_))”
}
$result = Invoke-vCisRest @sRest

if($PSCmdlet.ParameterSetName -eq ‘Id’ -or ($PSCmdlet.ParameterSetName -eq ‘Name’ -and
($Name -eq $null -or $Name -contains $result.value.name))){
$out += New-Object PSObject -Property @{
Description = $result.value.description
Id = $result.value.id
Name = $result.value.name
Category = (Get-rCisTagCategory -Id $result.value.category_id).Name
Uid = “$($global:defaultviserver.Id)Tag=$($result.value.id)/”
Client = $global:defaultviserver.Client
}
}
}
$out | Select-Object Category,Description,Id,Name,Uid,Client
}
}


 

One point worth noting is how to pass the internal IDs in an URI. These internal IDs contain some special characters that cause problems in a URI. Such an ID looks like this:

‘urn:vmomi:InventoryServiceCategory:3919cc14-406c-4bfe-a156-fed27e277dc6:GLOBAL’

Luckily, PowerShell gives you access to all .Net functionality, so I could use the EscapeDataString method to convert such an Id into something like this:

‘urn%3Avmomi%3AInventoryServiceCategory%3A3919cc14-406c-4bfe-a156-fed27e277dc6%3AGLOBAL’

Which poses no problem in an URI.

The result

I invite you to have a look at the resulting code in the rCisTag module, which I made available on VMware’s PowerCLI Community Repository.

PS C:\> Get-Command -Module rCisTag

rest

Note that the module is currently still in its first public draft, and any feedback is more than welcome.

Enjoy!

Photo credit: Flickr / Chris Parfitt

About The Author

2 thoughts on “SOAP vs. REST for performing tasks in VMware environments”

  1. Thanks for the post. But PowerCLI provides methods which can get me mapping between VM and its ESXi host, data center, cluster etc. I believe this is missing in REST APIs, I can list all VM, hosts, data centers, cluster but no way to find mapping between them. Let me know if I am incorrect.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Scroll to Top