Постепенно всё больше пользователей начинают использовать ресурсы нашего внутреннего облака, но не всем командам подходят стандартные серверы. Недавно появилась задача: создавать виртуальные машины по запросу из vRA с подключением дополнительных PCI-устройств. Сделать это можно двумя способами (ручную настройку VM как возможное решение рассматривать не будем):
- Подготовить отдельный шаблон VM с уже проброшенным устройством.
Минусы: 1. это ещё один шаблон, которому нужна поддержка и обновление; 2. если PCI-устройств несколько, то автоматически выбрать свободное при развертывании VM не получится.
Плюсы: быстро и просто. - Программно изменить конфигурацию обычной виртуальной машины.
Минусы: нужно написать скрипт реконфигурации VM.
Плюсы: вы получите универсальное решение, работающее со всеми шаблонами VM и различными устройствами. 
В нашем случае задача состоит в пробросе одного из четырёх GPU NVIDIA Tesla M6, устройства разные (разные ID), поэтому подходит второй вариант. Будем разрабатывать процесс изменения конфигурации VM в vRO.
Современный графический ускоритель можно пробросить в виртуальную машину как полностью, так и частично (vGPU), распределив ресурсы одного GPU между несколькими клиентами. Разберем оба варианта.
Предоставление VM части ресурсов GPU (режим vGPU)
Настройка vGPU в vCenter (HTML Client): Host -> Configure -> Grafics
var vmSpec = new VcVirtualMachineConfigSpec(); vmSpec.deviceChange = [new VcVirtualDeviceConfigSpec()]; vmSpec.deviceChange[0].operation = VcVirtualDeviceConfigSpecOperation.add; vmSpec.deviceChange[0].device = new VcVirtualPCIPassthrough(); vmSpec.deviceChange[0].device.deviceInfo = new VcDescription(); vmSpec.deviceChange[0].device.deviceInfo.summary = ''; vmSpec.deviceChange[0].device.deviceInfo.label = 'Tesla M6 vGPU'; vmSpec.deviceChange[0].device.backing = new VcVirtualPCIPassthroughVmiopBackingInfo(); vmSpec.deviceChange[0].device.backing.vgpu = vgpuType; vmSpec.memoryReservationLockedToMax = true; vm.reconfigVM_Task(vmSpec);
В первой строке создаются новые параметры конфигурации vmSpec (VcVirtualMachineConfigSpec) для добавления устройства (VcVirtualDeviceConfigSpec) в виртуальную машину. Затем Вы описываете все изменения устройств в массиве deviceChange (VcVirtualDeviceConfigSpec). Для каждого устройства определяете тип операции (add/edit/remove), тип устройства и необходимые параметры. Подробно о доступных полях и их типах читайте в описании VcVirtualMachineConfigSpec.
В vmSpec.deviceChange[0].device.backing.vgpu передаётся тип vGPU (зависит от модели устройства, объёма выделяемой клиенту памяти GPU и вида лицензирования vGPU). Для просмотра всех доступных Вам типов vGPU можете выполнить следующее:
var vgpuTypes = host.config.sharedPassthruGpuTypes; System.log(vgpuTypes); // результат - массив строк с типами vGPU
Не забывайте про особенности работы VM с проброшенным устройством:
- необходимо полностью зарезервировать память VM, без этого она просто не включится;
 - отсутствует поддержка некоторых технологий, таких как: vMotion, HA, FT, DRS (кроме Cisco UCS);
 - отсутствует горячее добавление устройств, приостановление и возобновление работы VM, создание снимков VM (кроме Cisco UCS).
 
Если с прекращением работы некоторых технологий нужно просто смириться, то без резервирования памяти VM не будет работать. Настройку автоматического включения Memory Reservation выполняет строка vmSpec.memoryReservationLockedToMax = true.
Прямой проброс GPU в VM (VMDirectPath I/O)
Настройка DirectPath I/O PCI в vCenter (HTML Client): Host -> Configure -> PCI Devices
Следующий скрипт очень похож на предыдущий, только вместо типа устройства VcVirtualPCIPassthroughVmiopBackingInfo используется тип VcVirtualPCIPassthroughDeviceBackingInfo.
var freePciId = System.getModule("my.cloud").asGetFreeGpuId(vmhost);
if (freePciId == undefined) {
	throw "Free PCI device not found"
} else {
	System.log("Found free PCI device: " + freePciId);
	var vmSpec = new VcVirtualMachineConfigSpec();
	vmSpec.deviceChange = [new VcVirtualDeviceConfigSpec()];
	vmSpec.deviceChange[0].operation = VcVirtualDeviceConfigSpecOperation.add;
	vmSpec.deviceChange[0].device = new VcVirtualPCIPassthrough();
	vmSpec.deviceChange[0].device.backing = new VcVirtualPCIPassthroughDeviceBackingInfo();
	vmSpec.deviceChange[0].device.backing.deviceId = "13f3";
	vmSpec.deviceChange[0].device.backing.systemId = "5a8292d1-d421-b231-ddc0-48df372226c1";
	vmSpec.deviceChange[0].device.backing.deviceName = "PCI device 0";
	vmSpec.deviceChange[0].device.backing.vendorId = 4318;
	vmSpec.deviceChange[0].device.backing.id = freePciId;
	vmSpec.memoryReservationLockedToMax = true;
	var task = vm.reconfigVM_Task(vmSpec);
}
В скрипте используется дополнительный action, который возвращает ID первого свободного графического ускорителя:
// Action asGetFreeGpuId
// Получаем в массив pciIds список всех доступных для проброса устройств в кластере
var pciIds = [];
var ptArray = cluster.EnvironmentBrowser.queryConfigTarget(host).pciPassthrough;
for (var i = 0; i < ptArray.length; ++i) {
	pciIds.push(ptArray[i].pciDevice.id);
}
// Для отдельного хоста этот список можно сформировать так:
/*var ptArray = host.config.pciPassthruInfo;
for (var i = 0; i < ptArray.length; ++i) {
	if (ptArray[i].passthruEnabled == true) {
		pciIds.push(ptArray[i].id);
	}
}*/
// После выполнения предыдущего блока мой массив содержал следующие значения:
// var pciIds = ["0000:8a:00.0", "0000:8d:00.0", "0000:0f:00.0", "0000:0c:00.0"];
// Определяем, какие из устройств свободны, анализируя конфигурацию всех VM на хосте
// Если знаете более изящный способ, то обязательно поделитесь
var vms = host.vm;
for each (vm in vms) {
	if (vm.config != undefined) {
		var device = vm.config.hardware.device;
		for (var i = 0; i < device.length; ++i) {
			if (device[i].deviceInfo.label == "PCI device 0") {
				// из массива ID устройств удаляем все уже задействованные
				delete pciIds[pciIds.indexOf(device[i].backing.id)];
			}
		}
	}
}
// возвращаем ID первого из свободных устройств
return first(pciIds);
function first(p){
	for (var i in p)
		return p[i];
}
Вызов cluster.EnvironmentBrowser.queryConfigTarget(host).pciPassthrough возвращает массив с описанием каждого из устройств:
[VcVirtualMachinePciPassthroughInfo]-[class com.vmware.o11n.plugin.vsphere_gen.PciPassthroughInfo_Wrapper] -- 
VALUE : (vim.vm.PciPassthroughInfo) {
   dynamicType = null,
   dynamicProperty = null,
   name = ,
   configurationTag = null,
   pciDevice = (vim.host.PciDevice) {
      dynamicType = null,
      dynamicProperty = null,
      id = 0000:0c:00.0,
      classId = 768,
      bus = 12,
      slot = 0,
      function = 0,
      vendorId = 4318,
      subVendorId = 4318,
      vendorName = NVIDIA Corporation,
      deviceId = 5107,
      subDeviceId = 4419,
      parentBridge = 0000:0a:08.0,
      deviceName = NVIDIANVIDIA TESLA M6
   },
   systemId = 5a8292d1-d421-b231-ddc0-48df372226c1
}
Эти данные можно использовать при заполнении параметров устройства в скрипте конфигурации. Для однотипных устройств все параметры, кроме ID, будут одинаковые. Наш готовый процесс в vRealize Orchestrator включает следующие основные этапы:
- Подготовка входных параметров;
 - Выключение VM;
 - Проверка на необходимость миграции и миграция VM (если в кластере не все хосты имеют необходимое для проброса оборудование, то VM нужно переместить на подходящий хост);
 - Реконфигурация VM;
 - Включение VM.
 
