VMware and SCSI

There comes the time you want to automate stuff, or just need the information, which VMware vmdk Disk belongs to which OS Device inside the OS, depending on your architecture, you can be in quite a trouble, when picking the right VMware backing for your OS Devices.

After some google-foo, you might find some easy and shiningly trivial approaches, like counting OS sg Devices and hoping that they correspond to the vmdk files, like in this older blog post here…

https://www.unixarena.com/2015/08/how-to-map-the-vmware-virtual-disks-for-linux-vm.html/

From what many people have found out: This is not reliable and should not be depended on to be accurate!

A good start

There is already a quite good base of a Workflow that enumerates all the SCSI Host Adapters for a given VM and also finds out the Unit number of the Disk.

// Source is iiliev
 on the VMware Community Forum:
// https://communities.vmware.com/t5/vRealize-Orchestrator/Get-all-virtual-disk-from-a-VM-in-workflow/m-p/964488/highlight/true#M7429

var devices = vm.config.hardware.device;

for each (controller in devices) {
  var is_scsi = controller instanceof VcVirtualBusLogicController || controller instanceof VcVirtualLsiLogicController
       || controller instanceof VcParaVirtualSCSIController || controller instanceof VcVirtualLsiLogicSASController;
  if (!is_scsi) {
    continue;
  }
  var controller_label = controller.deviceInfo.label;
  System.log("SCSI controller found: " + controller_label);
  for each (device in devices) {
    if (device.controllerKey == controller.key) {
      var scsi_id = controller.busNumber + ":" + device.unitNumber;
      System.log("    device found:  '" + device.deviceInfo.label + "'  'SCSI (" + scsi_id + ")'");
    }
  }
}

Unfortunately those numbers do not correlate to anything of what is inside the VM, and are not really of any use, but we have a vRO skeleton to work with (I am lazy and don’t want to reinvent the wheel if not necessary).

VMware To The Rescue!

Well, VMware provides an answer on how to handle this and has published this KB article, one should be able to resolve which Disk SCSI Host is which inside the Guest.

https://kb.vmware.com/s/article/2047927

This basically uses the vmx configuration file and pull out the pciSlotNumbers of the devices and looks it up for each device, mixing in the bridges as well.

In the Example VMware uses some esoteric number-shenanigans to maps the Slot ID “1216” to “00h:16h:01h at Dev.Func 00h:0h“. Fortunately they also provide an example python implementation, which does the heavy listing on a code level and is not too far from JavaScript.

Now in vRO

Now, this is all fine and nice, but mostly you have a vRO or a vRA that handles things for you, so you want it automated in JavaScript.

Good news is, that all the necessary inputs are available for vRO from the vCenter, you just have to look for it.

// This script maps the PV SCSI Devices to SCSI ID-s inside the PCI Root of the VM Container
// Logic is a derivate work of the official VMware-provided python Script based on the Article
// on https://kb.vmware.com/s/article/2047927

// Input: <vm> of type <Vc:VirtualMachine>
// Output: whatever you'd like to use with the given data.

var dev2slot = {};
var scsidevs = [];
var slot2id = new Properties(); 

// Unfortunately we need to access all the config items via extraConfig because 
// the devices does not contain the necessary information, but using that we can
// build the mapping table for all devices.

for each (cfg in vm.config.extraConfig) {
	var found = cfg.key.match(/^([^.]*)\.pciSlotNumber/i);
	if (!found) { continue; }
	var devname = found[1];
	if (!devname.match(/^pci|^scsi/) ) { continue; }
	dev2slot[devname.toLowerCase()] = cfg.value;
	if (devname.match(/scsi/)) { scsidevs.push(devname); }
}

// This logic is taken from the python Script, without the sanity checks
function getGuestPCIPath(dev) {
	var func = 0;
	var origdev = dev;
	var origslot = dev2slot[dev];
	while (true) {
		var slot = dev2slot[dev];
		var bus = slot >> 5;
		var devNum = slot & 0x1F;
		var bridgeNr = bus & 0x1F;
		if (bridgeNr == 0) { break; }
		func = bus >> 5;
		dev = ('pcibridge' + parseInt(bridgeNr - 1)).toLowerCase();
	}
	devNumHex = parseInt(devNum).toString(16);
	funcHex = parseInt(func).toString(16);
	var loc = devNumHex + "." + funcHex;
	slot2id.put(origslot, loc);
}

for (var i = 0; i < scsidevs.length; i++) {
	var dev = scsidevs[i];
	getGuestPCIPath(dev);
}

var out = [];

var devices = vm.config.hardware.device;
for each (controller in devices) {
	var is_scsi = controller instanceof VcParaVirtualSCSIController;
	if (!is_scsi) { continue; }
	var label = controller.deviceInfo.label;
	var slot = controller.slotInfo.pciSlotNumber;
	System.log("SCSI controller found: label='" + label + "' slot=" + slot + " pci_id=" + slot2id.get(slot));

    for each (device in devices) {
      if (device.controllerKey == controller.key) {
        var scsi_id = controller.busNumber + ":" + device.unitNumber;
        var sizeGB = device.capacityInBytes / 1024 / 1024 / 1024;
        System.log(" - device found:  '" + device.deviceInfo.label + "': pci_id=" + slot2id.get(slot) + " unit=" + device.unitNumber  + "' " + sizeGB + " GB");
		out.push({ pciid: slot2id.get(slot), unit: device.unitNumber , sizeGB: sizeGB, label: device.deviceInfo.label, ctrl: label });
      }
    }
}

System.log(JSON.stringify(out));

This gives us an output JSON mapping that looks like this, which you could use on the VM itself to map this to a Block device (output formatted to ease parsing by humanoids)

[
  { "pciid": "15.0", "unit": 1,  "sizeGB": 50, "label": "Hard disk 2" },
  { "pciid": "18.0", "unit": 0,  "sizeGB": 16, "label": "Hard disk 3" },
  { "pciid": "18.0", "unit": 19, "sizeGB": 19, "label": "Hard disk 6" },
  { "pciid": "15.1", "unit": 3,  "sizeGB": 3,  "label": "Hard disk 7" }
]

Note: you don’t really need the sizeGB field, I just included it to have an additional sanity check during development and did not bother to remove it.

How to map inside a Linux VM

Now that we have this information, we want to get what device this is, we need to resolve this to a block device. I have found that the easiest way is to simply look in the /sys tree with some very heave bash-style star-globbing to find the proper device directory:

linux:~ $ basename -a /sys/devices/pci0000:00/0000:00:15.0/*/*/target*/*:*:1:*/block/sd*
sdb


linux:~ $ basename -a /sys/devices/pci0000:00/0000:00:18.0/*/*/target*/*:*:0:*/block/sd*
sdd


linux:~ $ basename -a /sys/devices/pci0000:00/0000:00:18.0/*/*/target*/*:*:19:*/block/sd*
sdf

linux:~ $ basename -a /sys/devices/pci0000:00/0000:00:15.1/*/*/target*/*:*:3:*/block/sd*
sdg

Note: I am using a Linux System with Kernel 4.12.14, depending on kernel versions, your paths can be different.

This shows us that Hard Disks 2,3,6,7 correspond to OS devices sdb,sdd,sdf,sdg.

Easy like Sunday morning, isn’t it?

Disclaimer

The code is provided on an only a Proof-of-Concept and comes “AS IS”, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND. It does not include any of the safety checks that are present in Python, and should only be used as a base.

I have not found any copyright notes on the original Python Script, so I am also only including this footer text. It is a derivate work based on a referenced VMware KB Article, and no copyright infringements were intended.

The vRO Script also just handles PVSCSI Host based devices because I just needed them. Adjusting it to other devices is given as homework if required.