mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
is attached a hard disk drive is created on the scsi controller. On detach the data disk is removed from the drive but the disk drive is left behind. On reattach the agent was again trying to create a disk drive while it was already present. Fixed the agent code to look up for disk drive while attaching and if one is not found then only to create the drive for attaching a data disk.
2413 lines
107 KiB
C#
2413 lines
107 KiB
C#
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using CloudStack.Plugin.WmiWrappers.ROOT.VIRTUALIZATION.V2;
|
|
using log4net;
|
|
using System.Globalization;
|
|
using System.Management;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using CloudStack.Plugin.WmiWrappers.ROOT.CIMV2;
|
|
using System.IO;
|
|
using System.Net.NetworkInformation;
|
|
using System.Net;
|
|
|
|
namespace HypervResource
|
|
{
|
|
public class WmiCallsV2 : IWmiCallsV2
|
|
{
|
|
public static String CloudStackUserDataKey = "cloudstack-vm-userdata";
|
|
|
|
/// <summary>
|
|
/// Defines the migration types.
|
|
/// </summary>
|
|
public enum MigrationType
|
|
{
|
|
VirtualSystem = 32768,
|
|
Storage = 32769,
|
|
Staged = 32770,
|
|
VirtualSystemAndStorage = 32771
|
|
};
|
|
|
|
/// <summary>
|
|
/// Defines migration transport types.
|
|
/// </summary>
|
|
public enum TransportType
|
|
{
|
|
TCP = 5,
|
|
SMB = 32768
|
|
};
|
|
|
|
public static void Initialize()
|
|
{
|
|
// Trigger assembly load into curren appdomain
|
|
}
|
|
|
|
private static ILog logger = LogManager.GetLogger(typeof(WmiCallsV2));
|
|
|
|
/// <summary>
|
|
/// Returns ping status of the given ip
|
|
/// </summary>
|
|
public static String PingHost(String ip)
|
|
{
|
|
return "Success";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns ComputerSystem lacking any NICs and VOLUMEs
|
|
/// </summary>
|
|
public ComputerSystem AddUserData(ComputerSystem vm, string userData)
|
|
{
|
|
// Obtain controller for Hyper-V virtualisation subsystem
|
|
VirtualSystemManagementService vmMgmtSvc = GetVirtualisationSystemManagementService();
|
|
|
|
// Create object to hold the data.
|
|
KvpExchangeDataItem kvpItem = KvpExchangeDataItem.CreateInstance();
|
|
kvpItem.LateBoundObject["Name"] = WmiCallsV2.CloudStackUserDataKey;
|
|
kvpItem.LateBoundObject["Data"] = userData;
|
|
kvpItem.LateBoundObject["Source"] = 0;
|
|
logger.Debug("VM " + vm.Name + " gets userdata " + userData);
|
|
|
|
// Update the resource settings for the VM.
|
|
System.Management.ManagementBaseObject kvpMgmtObj = kvpItem.LateBoundObject;
|
|
System.Management.ManagementPath jobPath;
|
|
String kvpStr = kvpMgmtObj.GetText(System.Management.TextFormat.CimDtd20);
|
|
uint ret_val = vmMgmtSvc.AddKvpItems(new String[] { kvpStr }, vm.Path, out jobPath);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to update VM {0} (GUID {1}) due to {2} (ModifyVirtualSystem call), existing VM not deleted",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
return vm;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns ComputerSystem lacking any NICs and VOLUMEs
|
|
/// </summary>
|
|
public ComputerSystem CreateVM(string name, long memory_mb, int vcpus)
|
|
{
|
|
// Obtain controller for Hyper-V virtualisation subsystem
|
|
VirtualSystemManagementService vmMgmtSvc = GetVirtualisationSystemManagementService();
|
|
|
|
// Create VM with correct name and default resources
|
|
ComputerSystem vm = CreateDefaultVm(vmMgmtSvc, name);
|
|
|
|
// Update the resource settings for the VM.
|
|
|
|
// Resource settings are referenced through the Msvm_VirtualSystemSettingData object.
|
|
VirtualSystemSettingData vmSettings = GetVmSettings(vm);
|
|
|
|
// For memory settings, there is no Dynamic Memory, so reservation, limit and quantity are identical.
|
|
MemorySettingData memSettings = GetMemSettings(vmSettings);
|
|
memSettings.LateBoundObject["VirtualQuantity"] = memory_mb;
|
|
memSettings.LateBoundObject["Reservation"] = memory_mb;
|
|
memSettings.LateBoundObject["Limit"] = memory_mb;
|
|
|
|
// Update the processor settings for the VM, static assignment of 100% for CPU limit
|
|
ProcessorSettingData procSettings = GetProcSettings(vmSettings);
|
|
procSettings.LateBoundObject["VirtualQuantity"] = vcpus;
|
|
procSettings.LateBoundObject["Reservation"] = vcpus;
|
|
procSettings.LateBoundObject["Limit"] = 100000;
|
|
|
|
ModifyVmResources(vmMgmtSvc, vm, new String[] {
|
|
memSettings.LateBoundObject.GetText(TextFormat.CimDtd20),
|
|
procSettings.LateBoundObject.GetText(TextFormat.CimDtd20)
|
|
});
|
|
logger.InfoFormat("VM with display name {0} has GUID {1}", vm.ElementName, vm.Name);
|
|
logger.DebugFormat("Resources for vm {0}: {1} MB memory, {2} vcpus", name, memory_mb, vcpus);
|
|
|
|
return vm;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a (synthetic) nic, and attach it to the vm
|
|
/// </summary>
|
|
/// <param name="vm"></param>
|
|
/// <param name="mac"></param>
|
|
/// <param name="vlan"></param>
|
|
/// <returns></returns>
|
|
public SyntheticEthernetPortSettingData CreateNICforVm(ComputerSystem vm, string mac)
|
|
{
|
|
logger.DebugFormat("Creating nic for VM {0} (GUID {1})", vm.ElementName, vm.Name);
|
|
|
|
// Obtain controller for Hyper-V networking subsystem
|
|
var vmNetMgmtSvc = GetVirtualSwitchManagementService();
|
|
|
|
// Create NIC resource by cloning the default NIC
|
|
var synthNICsSettings = SyntheticEthernetPortSettingData.GetInstances(vmNetMgmtSvc.Scope, "InstanceID LIKE \"%Default\"");
|
|
|
|
// Assert
|
|
if (synthNICsSettings.Count != 1)
|
|
{
|
|
var errMsg = string.Format("Internal error, coudl not find default SyntheticEthernetPort instance");
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
var defaultSynthNICSettings = synthNICsSettings.OfType<SyntheticEthernetPortSettingData>().First();
|
|
|
|
var newSynthNICSettings = new SyntheticEthernetPortSettingData((ManagementBaseObject)defaultSynthNICSettings.LateBoundObject.Clone());
|
|
|
|
// Assign configuration to new NIC
|
|
string normalisedMAC = string.Join("", (mac.Split(new char[] { ':' })));
|
|
newSynthNICSettings.LateBoundObject["ElementName"] = vm.ElementName;
|
|
newSynthNICSettings.LateBoundObject["Address"] = normalisedMAC;
|
|
newSynthNICSettings.LateBoundObject["StaticMacAddress"] = "TRUE";
|
|
newSynthNICSettings.LateBoundObject["VirtualSystemIdentifiers"] = new string[] { "{" + Guid.NewGuid().ToString() + "}" };
|
|
newSynthNICSettings.CommitObject();
|
|
|
|
// Insert NIC into vm
|
|
string[] newResources = new string[] { newSynthNICSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20)};
|
|
ManagementPath[] newResourcePaths = AddVirtualResource(newResources, vm );
|
|
|
|
// assert
|
|
if (newResourcePaths.Length != 1)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to properly insert a single NIC on VM {0} (GUID {1}): number of resource created {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
newResourcePaths.Length);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
return new SyntheticEthernetPortSettingData(newResourcePaths[0]);
|
|
}
|
|
|
|
public const string IDE_CONTROLLER = "Microsoft:Hyper-V:Emulated IDE Controller";
|
|
public const string SCSI_CONTROLLER = "Microsoft:Hyper-V:Synthetic SCSI Controller";
|
|
public const string HARDDISK_DRIVE = "Microsoft:Hyper-V:Synthetic Disk Drive";
|
|
public const string ISO_DRIVE = "Microsoft:Hyper-V:Synthetic DVD Drive";
|
|
|
|
// TODO: names harvested from Msvm_ResourcePool, not clear how to create new instances
|
|
public const string ISO_DISK = "Microsoft:Hyper-V:Virtual CD/DVD Disk"; // For IDE_ISO_DRIVE
|
|
public const string HARDDISK_DISK = "Microsoft:Hyper-V:Virtual Hard Disk"; // For IDE_HARDDISK_DRIVE
|
|
|
|
/// <summary>
|
|
/// Create new VM. By default we start it.
|
|
/// </summary>
|
|
public ComputerSystem DeployVirtualMachine(dynamic jsonObj, string systemVmIso)
|
|
{
|
|
var vmInfo = jsonObj.vm;
|
|
string vmName = vmInfo.name;
|
|
var nicInfo = vmInfo.nics;
|
|
int vcpus = vmInfo.cpus;
|
|
int memSize = vmInfo.maxRam / 1048576;
|
|
string errMsg = vmName;
|
|
var diskDrives = vmInfo.disks;
|
|
var bootArgs = vmInfo.bootArgs;
|
|
|
|
// assert
|
|
errMsg = vmName + ": missing disk information, array empty or missing, agent expects *at least* one disk for a VM";
|
|
if (diskDrives == null)
|
|
{
|
|
logger.Error(errMsg);
|
|
throw new ArgumentException(errMsg);
|
|
}
|
|
// assert
|
|
errMsg = vmName + ": missing NIC information, array empty or missing, agent expects at least an empty array.";
|
|
if (nicInfo == null )
|
|
{
|
|
logger.Error(errMsg);
|
|
throw new ArgumentException(errMsg);
|
|
}
|
|
|
|
|
|
// For existing VMs, return when we spot one of this name not stopped. In the meantime, remove any existing VMs of same name.
|
|
ComputerSystem vmWmiObj = null;
|
|
while ((vmWmiObj = GetComputerSystem(vmName)) != null)
|
|
{
|
|
logger.WarnFormat("Create request for existing vm, name {0}", vmName);
|
|
if (vmWmiObj.EnabledState == EnabledState.Disabled)
|
|
{
|
|
logger.InfoFormat("Deleting existing VM with name {0}, before we go on to create a VM with the same name", vmName);
|
|
DestroyVm(vmName);
|
|
}
|
|
else if (vmWmiObj.EnabledState == EnabledState.Enabled)
|
|
{
|
|
string infoMsg = string.Format("Create VM discovered there exists a VM with name {0}, state {1}",
|
|
vmName,
|
|
EnabledState.ToString(vmWmiObj.EnabledState));
|
|
logger.Info(infoMsg);
|
|
return vmWmiObj;
|
|
}
|
|
else
|
|
{
|
|
errMsg = string.Format("Create VM failing, because there exists a VM with name {0}, state {1}",
|
|
vmName,
|
|
EnabledState.ToString(vmWmiObj.EnabledState));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
// Create vm carcase
|
|
logger.DebugFormat("Going ahead with create VM {0}, {1} vcpus, {2}MB RAM", vmName, vcpus, memSize);
|
|
var newVm = CreateVM(vmName, memSize, vcpus);
|
|
|
|
// Add a SCSI controller for attaching/detaching data volumes.
|
|
AddScsiController(newVm);
|
|
|
|
foreach (var diskDrive in diskDrives)
|
|
{
|
|
string vhdFile = null;
|
|
string diskName = null;
|
|
string isoPath = null;
|
|
VolumeObjectTO volInfo = VolumeObjectTO.ParseJson(diskDrive.data);
|
|
TemplateObjectTO templateInfo = TemplateObjectTO.ParseJson(diskDrive.data);
|
|
|
|
if (volInfo != null)
|
|
{
|
|
// assert
|
|
errMsg = vmName + ": volume missing primaryDataStore for disk " + diskDrive.ToString();
|
|
if (volInfo.primaryDataStore == null)
|
|
{
|
|
logger.Error(errMsg);
|
|
throw new ArgumentException(errMsg);
|
|
}
|
|
diskName = volInfo.name;
|
|
|
|
// assert
|
|
errMsg = vmName + ": can't deal with DataStore type for disk " + diskDrive.ToString();
|
|
if (volInfo.primaryDataStore == null)
|
|
{
|
|
logger.Error(errMsg);
|
|
throw new ArgumentException(errMsg);
|
|
}
|
|
errMsg = vmName + ": Malformed PrimaryDataStore for disk " + diskDrive.ToString();
|
|
if (String.IsNullOrEmpty(volInfo.primaryDataStore.Path))
|
|
{
|
|
logger.Error(errMsg);
|
|
throw new ArgumentException(errMsg);
|
|
}
|
|
errMsg = vmName + ": Missing folder PrimaryDataStore for disk " + diskDrive.ToString() + ", missing path: " + volInfo.primaryDataStore.Path;
|
|
if (!Directory.Exists(volInfo.primaryDataStore.Path))
|
|
{
|
|
logger.Error(errMsg);
|
|
throw new ArgumentException(errMsg);
|
|
}
|
|
|
|
vhdFile = volInfo.FullFileName;
|
|
if (!System.IO.File.Exists(vhdFile))
|
|
{
|
|
errMsg = vmName + ": non-existent volume, missing " + vhdFile + " for drive " + diskDrive.ToString();
|
|
logger.Error(errMsg);
|
|
throw new ArgumentException(errMsg);
|
|
}
|
|
logger.Debug("Going to create " + vmName + " with attached voluem " + diskName + " at " + vhdFile);
|
|
}
|
|
else if (templateInfo != null && templateInfo.nfsDataStoreTO != null)
|
|
{
|
|
NFSTO share = templateInfo.nfsDataStoreTO;
|
|
Utils.ConnectToRemote(share.UncPath, share.Domain, share.User, share.Password);
|
|
// The share is mapped, now attach the iso
|
|
isoPath = Utils.NormalizePath(Path.Combine(share.UncPath, templateInfo.path));
|
|
}
|
|
|
|
string driveType = diskDrive.type;
|
|
string ideCtrllr = "0";
|
|
string driveResourceType = null;
|
|
switch (driveType) {
|
|
case "ROOT":
|
|
ideCtrllr = "0";
|
|
driveResourceType = HARDDISK_DRIVE;
|
|
break;
|
|
case "ISO":
|
|
ideCtrllr = "1";
|
|
driveResourceType = ISO_DRIVE;
|
|
break;
|
|
case "DATADISK":
|
|
break;
|
|
default:
|
|
// TODO: double check exception type
|
|
errMsg = string.Format("Unknown disk type {0} for disk {1}, vm named {2}",
|
|
string.IsNullOrEmpty(driveType) ? "NULL" : driveType,
|
|
string.IsNullOrEmpty(diskName) ? "NULL" : diskName, vmName);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
logger.DebugFormat("Create disk type {1} (Named: {0}), on vm {2} {3}", diskName, driveResourceType, vmName,
|
|
string.IsNullOrEmpty(vhdFile) ? " no disk to insert" : ", inserting disk" + vhdFile);
|
|
if (driveType.Equals("DATADISK"))
|
|
{
|
|
AttachDisk(vmName, vhdFile, (string)diskDrive.diskSeq);
|
|
}
|
|
else
|
|
{
|
|
AddDiskDriveToIdeController(newVm, vhdFile, ideCtrllr, driveResourceType);
|
|
if (isoPath != null)
|
|
{
|
|
AttachIso(vmName, isoPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
String publicIpAddress = "";
|
|
int nicCount = 0;
|
|
// Add the Nics to the VM in the deviceId order.
|
|
foreach (var nc in nicInfo)
|
|
{
|
|
foreach (var nic in nicInfo)
|
|
{
|
|
|
|
int nicid = nic.deviceId;
|
|
string mac = nic.mac;
|
|
string vlan = null;
|
|
string isolationUri = nic.isolationUri;
|
|
string broadcastUri = nic.broadcastUri;
|
|
if ( (broadcastUri != null ) || (isolationUri != null && isolationUri.StartsWith("vlan://")) && !isolationUri.Equals("vlan://untagged"))
|
|
{
|
|
if (broadcastUri != null && broadcastUri.StartsWith("storage"))
|
|
{
|
|
vlan = broadcastUri.Substring("storage://".Length);
|
|
}
|
|
else
|
|
{
|
|
vlan = isolationUri.Substring("vlan://".Length);
|
|
}
|
|
int tmp;
|
|
if (!int.TryParse(vlan, out tmp))
|
|
{
|
|
// TODO: double check exception type
|
|
errMsg = string.Format("Invalid VLAN value {0} for on vm {1} for nic uuid {2}", isolationUri, vmName, nic.uuid);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
if (nicCount == 2)
|
|
{
|
|
publicIpAddress = nic.ip;
|
|
}
|
|
|
|
if (nicid == nicCount)
|
|
{
|
|
// Create network adapter
|
|
var newAdapter = CreateNICforVm(newVm, mac);
|
|
String switchName ="";
|
|
if (nic.name != null)
|
|
{
|
|
switchName = nic.name;
|
|
}
|
|
|
|
// connection to vswitch
|
|
var portSettings = AttachNicToPort(newVm, newAdapter, switchName);
|
|
|
|
// set vlan
|
|
if (vlan != null)
|
|
{
|
|
SetPortVlan(vlan, portSettings);
|
|
}
|
|
|
|
logger.DebugFormat("Created adapter {0} on port {1}, {2}",
|
|
newAdapter.Path, portSettings.Path, (vlan == null ? "No VLAN" : "VLAN " + vlan));
|
|
}
|
|
}
|
|
nicCount++;
|
|
}
|
|
|
|
// pass the boot args for the VM using KVP component.
|
|
// We need to pass the boot args to system vm's to get them configured with cloudstack configuration.
|
|
// Add new user data
|
|
var vm = GetComputerSystem(vmName);
|
|
if (bootArgs != null && !String.IsNullOrEmpty((string)bootArgs))
|
|
{
|
|
|
|
String bootargs = bootArgs;
|
|
AddUserData(vm, bootargs);
|
|
|
|
// Verify key added to subsystem
|
|
//kvpInfo = GetKvpSettings(vmSettings);
|
|
|
|
// HostExchangesItems are embedded objects in the sense that the object value is stored and not a reference to the object.
|
|
//kvpProps = kvpInfo.HostExchangeItems;
|
|
|
|
}
|
|
// call patch systemvm iso only for systemvms
|
|
if (vmName.StartsWith("r-") || vmName.StartsWith("s-") || vmName.StartsWith("v-"))
|
|
{
|
|
if (systemVmIso != null && systemVmIso.Length != 0)
|
|
{
|
|
patchSystemVmIso(vmName, systemVmIso);
|
|
}
|
|
}
|
|
|
|
logger.DebugFormat("Starting VM {0}", vmName);
|
|
SetState(newVm, RequiredState.Enabled);
|
|
|
|
// we need to reboot to get the hv kvp daemon get started vr gets configured.
|
|
if (vmName.StartsWith("r-") || vmName.StartsWith("s-") || vmName.StartsWith("v-"))
|
|
{
|
|
System.Threading.Thread.Sleep(90000);
|
|
// wait for the second boot and then return with sucesss
|
|
//if publicIPAddress is empty or null don't ping the ip
|
|
/*if (publicIpAddress.Equals("") == true)
|
|
{
|
|
System.Threading.Thread.Sleep(90000);
|
|
}
|
|
else
|
|
{
|
|
pingResource(publicIpAddress);
|
|
}*/
|
|
}
|
|
logger.InfoFormat("Started VM {0}", vmName);
|
|
return newVm;
|
|
}
|
|
|
|
public static Boolean pingResource(String ip)
|
|
{
|
|
PingOptions pingOptions = null;
|
|
PingReply pingReply = null;
|
|
IPAddress ipAddress = null;
|
|
Ping pingSender = new Ping();
|
|
int numberOfPings = 6;
|
|
int pingTimeout = 1000;
|
|
int byteSize = 32;
|
|
byte[] buffer = new byte[byteSize];
|
|
ipAddress = IPAddress.Parse(ip);
|
|
pingOptions = new PingOptions();
|
|
for (int i = 0; i < numberOfPings; i++)
|
|
{
|
|
pingReply = pingSender.Send(ipAddress, pingTimeout, buffer, pingOptions);
|
|
if (pingReply.Status == IPStatus.Success)
|
|
{
|
|
System.Threading.Thread.Sleep(30000);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// wait for the second boot and then return with suces
|
|
System.Threading.Thread.Sleep(30000);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private EthernetPortAllocationSettingData AttachNicToPort(ComputerSystem newVm, SyntheticEthernetPortSettingData newAdapter, String vSwitchName)
|
|
{
|
|
// Get the virtual switch
|
|
VirtualEthernetSwitch vSwitch = GetExternalVirtSwitch(vSwitchName);
|
|
//check the the recevied vSwitch is the same as vSwitchName.
|
|
if (!vSwitchName.Equals("") && !vSwitch.ElementName.Equals(vSwitchName))
|
|
{
|
|
var errMsg = string.Format("Internal error, coudl not find Virtual Switch with the name : " +vSwitchName);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
// Create port for adapter
|
|
var defaultEthernetPortSettings = EthernetPortAllocationSettingData.GetInstances(vSwitch.Scope, "InstanceID LIKE \"%Default\"");
|
|
|
|
// assert
|
|
if (defaultEthernetPortSettings.Count != 1)
|
|
{
|
|
var errMsg = string.Format("Internal error, coudl not find default EthernetPortAllocationSettingData instance");
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
var defaultEthernetPortSettingsObj = defaultEthernetPortSettings.OfType<EthernetPortAllocationSettingData>().First();
|
|
var newEthernetPortSettings = new EthernetPortAllocationSettingData((ManagementBaseObject)defaultEthernetPortSettingsObj.LateBoundObject.Clone());
|
|
newEthernetPortSettings.LateBoundObject["Parent"] = newAdapter.Path.Path;
|
|
newEthernetPortSettings.LateBoundObject["HostResource"] = new string[] { vSwitch.Path.Path };
|
|
|
|
// Insert NIC into vm
|
|
string[] newResources = new string[] { newEthernetPortSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20) };
|
|
ManagementPath[] newResourcePaths = AddVirtualResource(newResources, newVm);
|
|
|
|
// assert
|
|
if (newResourcePaths.Length != 1)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to properly insert a single NIC on VM {0} (GUID {1}): number of resource created {2}",
|
|
newVm.ElementName,
|
|
newVm.Name,
|
|
newResourcePaths.Length);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
return new EthernetPortAllocationSettingData(newResourcePaths[0]);
|
|
}
|
|
|
|
/// this method is to add a dvd drive and attach the systemvm iso.
|
|
///
|
|
public void patchSystemVmIso(String vmName, String systemVmIso)
|
|
{
|
|
ComputerSystem vmObject = GetComputerSystem(vmName);
|
|
AddDiskDriveToIdeController(vmObject, "", "1", ISO_DRIVE);
|
|
AttachIso(vmName, systemVmIso);
|
|
}
|
|
|
|
public void AttachDisk(string vmName, string diskPath, string addressOnController)
|
|
{
|
|
logger.DebugFormat("Got request to attach disk {0} to vm {1}", diskPath, vmName);
|
|
|
|
ComputerSystem vm = GetComputerSystem(vmName);
|
|
if (vm == null)
|
|
{
|
|
logger.DebugFormat("VM {0} not found", vmName);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ManagementPath newDrivePath = GetDiskDriveOnScsiController(vm, addressOnController);
|
|
if (newDrivePath == null)
|
|
{
|
|
newDrivePath = AttachDiskDriveToScsiController(vm, addressOnController);
|
|
}
|
|
InsertDiskImage(vm, diskPath, HARDDISK_DISK, newDrivePath);
|
|
}
|
|
}
|
|
|
|
/// </summary>
|
|
/// <param name="vm"></param>
|
|
/// <param name="cntrllerAddr"></param>
|
|
/// <param name="driveResourceType">IDE_HARDDISK_DRIVE or IDE_ISO_DRIVE</param>
|
|
public ManagementPath AddDiskDriveToIdeController(ComputerSystem vm, string vhdfile, string cntrllerAddr, string driveResourceType)
|
|
{
|
|
logger.DebugFormat("Creating DISK for VM {0} (GUID {1}) by attaching {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
vhdfile);
|
|
|
|
// Determine disk type for drive and assert drive type valid
|
|
string diskResourceSubType = null;
|
|
switch(driveResourceType) {
|
|
case HARDDISK_DRIVE:
|
|
diskResourceSubType = HARDDISK_DISK;
|
|
break;
|
|
case ISO_DRIVE:
|
|
diskResourceSubType = ISO_DISK;
|
|
break;
|
|
default:
|
|
var errMsg = string.Format(
|
|
"Unrecognised disk drive type {0} for VM {1} (GUID {2})",
|
|
string.IsNullOrEmpty(driveResourceType) ? "NULL": driveResourceType,
|
|
vm.ElementName,
|
|
vm.Name);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
ManagementPath newDrivePath = AttachNewDrive(vm, cntrllerAddr, driveResourceType);
|
|
|
|
// If there's not disk to insert, we are done.
|
|
if (String.IsNullOrEmpty(vhdfile))
|
|
{
|
|
logger.DebugFormat("No disk to be added to drive, disk drive {0} is complete", newDrivePath.Path);
|
|
}
|
|
else
|
|
{
|
|
InsertDiskImage(vm, vhdfile, diskResourceSubType, newDrivePath);
|
|
}
|
|
return newDrivePath;
|
|
}
|
|
|
|
|
|
public void DetachDisk(string displayName, string diskFileName)
|
|
{
|
|
logger.DebugFormat("Got request to detach virtual disk {0} from vm {1}", diskFileName, displayName);
|
|
|
|
ComputerSystem vm = GetComputerSystem(displayName);
|
|
if (vm == null)
|
|
{
|
|
logger.DebugFormat("VM {0} not found", displayName);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
RemoveStorageImage(vm, diskFileName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a disk image from a drive, but does not remove the drive itself.
|
|
/// </summary>
|
|
/// <param name="vm"></param>
|
|
/// <param name="diskFileName"></param>
|
|
private void RemoveStorageImage(ComputerSystem vm, string diskFileName)
|
|
{
|
|
// Obtain StorageAllocationSettingData for disk
|
|
StorageAllocationSettingData.StorageAllocationSettingDataCollection storageSettingsObjs = StorageAllocationSettingData.GetInstances();
|
|
|
|
StorageAllocationSettingData imageToRemove = null;
|
|
foreach (StorageAllocationSettingData item in storageSettingsObjs)
|
|
{
|
|
if (item.HostResource == null || item.HostResource.Length != 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string hostResource = item.HostResource[0];
|
|
if (Path.Equals(hostResource, diskFileName))
|
|
{
|
|
imageToRemove = item;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// assert
|
|
if (imageToRemove == null)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to remove disk image {0} from VM {1} (GUID {2}): the disk image is not attached.",
|
|
diskFileName,
|
|
vm.ElementName,
|
|
vm.Name);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
RemoveStorageResource(imageToRemove.Path, vm);
|
|
|
|
logger.InfoFormat("Removed disk image {0} from VM {1} (GUID {2}): the disk image is not attached.",
|
|
diskFileName,
|
|
vm.ElementName,
|
|
vm.Name);
|
|
}
|
|
|
|
private ManagementPath AttachNewDrive(ComputerSystem vm, string cntrllerAddr, string driveType)
|
|
{
|
|
// Disk drives are attached to a 'Parent' IDE controller. We IDE Controller's settings for the 'Path', which our new Disk drive will use to reference it.
|
|
VirtualSystemSettingData vmSettings = GetVmSettings(vm);
|
|
var ctrller = GetIDEControllerSettings(vmSettings, cntrllerAddr);
|
|
|
|
// A description of the drive is created by modifying a clone of the default ResourceAllocationSettingData for that drive type
|
|
string defaultDriveQuery = String.Format("ResourceSubType LIKE \"{0}\" AND InstanceID LIKE \"%Default\"", driveType);
|
|
var newDiskDriveSettings = CloneResourceAllocationSetting(defaultDriveQuery);
|
|
|
|
// Set IDE controller and address on the controller for the new drive
|
|
newDiskDriveSettings.LateBoundObject["Parent"] = ctrller.Path.ToString();
|
|
newDiskDriveSettings.LateBoundObject["AddressOnParent"] = "0";
|
|
newDiskDriveSettings.CommitObject();
|
|
|
|
// Add this new disk drive to the VM
|
|
logger.DebugFormat("Creating disk drive type {0}, parent IDE controller is {1} and address on controller is {2}",
|
|
newDiskDriveSettings.ResourceSubType,
|
|
newDiskDriveSettings.Parent,
|
|
newDiskDriveSettings.AddressOnParent);
|
|
string[] newDriveResource = new string[] { newDiskDriveSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20) };
|
|
ManagementPath[] newDrivePaths = AddVirtualResource(newDriveResource, vm);
|
|
|
|
// assert
|
|
if (newDrivePaths.Length != 1)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to add disk drive type {3} to VM {0} (GUID {1}): number of resource created {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
newDrivePaths.Length,
|
|
driveType);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
logger.DebugFormat("New disk drive type {0} WMI path is {1}s",
|
|
newDiskDriveSettings.ResourceSubType,
|
|
newDrivePaths[0].Path);
|
|
return newDrivePaths[0];
|
|
}
|
|
|
|
private ManagementPath AddScsiController(ComputerSystem vm)
|
|
{
|
|
// A description of the controller is created by modifying a clone of the default ResourceAllocationSettingData for scsi controller
|
|
string scsiQuery = String.Format("ResourceSubType LIKE \"{0}\" AND InstanceID LIKE \"%Default\"", SCSI_CONTROLLER);
|
|
var scsiSettings = CloneResourceAllocationSetting(scsiQuery);
|
|
|
|
scsiSettings.LateBoundObject["ElementName"] = "SCSI Controller";
|
|
scsiSettings.CommitObject();
|
|
|
|
// Insert SCSI controller into vm
|
|
string[] newResources = new string[] { scsiSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20) };
|
|
ManagementPath[] newResourcePaths = AddVirtualResource(newResources, vm);
|
|
|
|
// assert
|
|
if (newResourcePaths.Length != 1)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to add scsi controller to VM {0} (GUID {1}): number of resource created {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
newResourcePaths.Length);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
logger.DebugFormat("New controller type {0} WMI path is {1}s",
|
|
scsiSettings.ResourceSubType,
|
|
newResourcePaths[0].Path);
|
|
return newResourcePaths[0];
|
|
}
|
|
|
|
private ManagementPath GetDiskDriveOnScsiController(ComputerSystem vm, string addrOnController)
|
|
{
|
|
VirtualSystemSettingData vmSettings = GetVmSettings(vm);
|
|
var wmiObjCollection = GetResourceAllocationSettings(vmSettings);
|
|
foreach (ResourceAllocationSettingData wmiObj in wmiObjCollection)
|
|
{
|
|
if (wmiObj.ResourceSubType == HARDDISK_DRIVE)
|
|
{
|
|
ResourceAllocationSettingData parent = new ResourceAllocationSettingData(new ManagementObject(wmiObj.Parent));
|
|
if (parent.ResourceSubType == SCSI_CONTROLLER && wmiObj.AddressOnParent == addrOnController)
|
|
{
|
|
return wmiObj.Path;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private ManagementPath AttachDiskDriveToScsiController(ComputerSystem vm, string addrOnController)
|
|
{
|
|
// Disk drives are attached to a 'Parent' Scsi controller.
|
|
VirtualSystemSettingData vmSettings = GetVmSettings(vm);
|
|
var ctrller = GetScsiControllerSettings(vmSettings);
|
|
|
|
// A description of the drive is created by modifying a clone of the default ResourceAllocationSettingData for that drive type
|
|
string defaultDriveQuery = String.Format("ResourceSubType LIKE \"{0}\" AND InstanceID LIKE \"%Default\"", HARDDISK_DRIVE);
|
|
var newDiskDriveSettings = CloneResourceAllocationSetting(defaultDriveQuery);
|
|
|
|
// Set IDE controller and address on the controller for the new drive
|
|
newDiskDriveSettings.LateBoundObject["Parent"] = ctrller.Path.ToString();
|
|
newDiskDriveSettings.LateBoundObject["AddressOnParent"] = addrOnController;
|
|
newDiskDriveSettings.CommitObject();
|
|
|
|
// Add this new disk drive to the VM
|
|
logger.DebugFormat("Creating disk drive type {0}, parent IDE controller is {1} and address on controller is {2}",
|
|
newDiskDriveSettings.ResourceSubType,
|
|
newDiskDriveSettings.Parent,
|
|
newDiskDriveSettings.AddressOnParent);
|
|
string[] newDriveResource = new string[] { newDiskDriveSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20) };
|
|
ManagementPath[] newDrivePaths = AddVirtualResource(newDriveResource, vm);
|
|
|
|
// assert
|
|
if (newDrivePaths.Length != 1)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to add disk drive type {3} to VM {0} (GUID {1}): number of resource created {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
newDrivePaths.Length,
|
|
HARDDISK_DRIVE);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
logger.DebugFormat("New disk drive type {0} WMI path is {1}s",
|
|
newDiskDriveSettings.ResourceSubType,
|
|
newDrivePaths[0].Path);
|
|
return newDrivePaths[0];
|
|
}
|
|
|
|
|
|
private void InsertDiskImage(ComputerSystem vm, string diskImagePath, string diskResourceSubType, ManagementPath drivePath)
|
|
{
|
|
// A description of the disk is created by modifying a clone of the default ResourceAllocationSettingData for that disk type
|
|
string defaultDiskQuery = String.Format("ResourceSubType LIKE \"{0}\" AND InstanceID LIKE \"%Default\"", diskResourceSubType);
|
|
var newDiskSettings = CloneStorageAllocationSetting(defaultDiskQuery);
|
|
|
|
// Set file containing the disk image
|
|
newDiskSettings.LateBoundObject["Parent"] = drivePath.Path;
|
|
|
|
// V2 API uses HostResource to specify image, see http://msdn.microsoft.com/en-us/library/hh859775(v=vs.85).aspx
|
|
newDiskSettings.LateBoundObject["HostResource"] = new string[] { diskImagePath };
|
|
newDiskSettings.CommitObject();
|
|
|
|
// Add the new Msvm_StorageAllocationSettingData object as a virtual hard disk to the vm.
|
|
string[] newDiskResource = new string[] { newDiskSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20) };
|
|
ManagementPath[] newDiskPaths = AddStorageResource(newDiskResource, vm);
|
|
// assert
|
|
if (newDiskPaths.Length != 1)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to add disk image type {3} to VM {0} (GUID {1}): number of resource created {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
newDiskPaths.Length,
|
|
diskResourceSubType);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
logger.InfoFormat("Created disk {2} for VM {0} (GUID {1}), image {3} ",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
newDiskPaths[0].Path,
|
|
diskImagePath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create Msvm_StorageAllocationSettingData corresponding to the ISO image, and
|
|
/// associate this with the VM's DVD drive.
|
|
/// </summary>
|
|
private void AttachIso(ComputerSystem vm, string isoPath)
|
|
{
|
|
// Disk drives are attached to a 'Parent' IDE controller. We IDE Controller's settings for the 'Path', which our new Disk drive will use to reference it.
|
|
VirtualSystemSettingData vmSettings = GetVmSettings(vm);
|
|
var driveWmiObj = GetDvdDriveSettings(vmSettings);
|
|
InsertDiskImage(vm, isoPath, ISO_DISK, driveWmiObj.Path);
|
|
}
|
|
|
|
private static ResourceAllocationSettingData CloneResourceAllocationSetting(string wmiQuery)
|
|
{
|
|
var defaultDiskDriveSettingsObjs = ResourceAllocationSettingData.GetInstances(wmiQuery);
|
|
|
|
// assert
|
|
if (defaultDiskDriveSettingsObjs.Count != 1)
|
|
{
|
|
var errMsg = string.Format("Failed to find Msvm_ResourceAllocationSettingData for the query {0}", wmiQuery);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
ResourceAllocationSettingData defaultDiskDriveSettings = defaultDiskDriveSettingsObjs.OfType<ResourceAllocationSettingData>().First();
|
|
return new ResourceAllocationSettingData((ManagementBaseObject)defaultDiskDriveSettings.LateBoundObject.Clone());
|
|
}
|
|
|
|
public void AttachIso(string displayName, string iso)
|
|
{
|
|
logger.DebugFormat("Got request to attach iso {0} to vm {1}", iso, displayName);
|
|
|
|
ComputerSystem vm = GetComputerSystem(displayName);
|
|
if (vm == null)
|
|
{
|
|
logger.DebugFormat("VM {0} not found", displayName);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
AttachIso(vm, iso);
|
|
}
|
|
}
|
|
|
|
public void DestroyVm(dynamic jsonObj)
|
|
{
|
|
string vmToDestroy = jsonObj.vmName;
|
|
DestroyVm(vmToDestroy);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all VMs and all SwitchPorts with the displayName. VHD gets deleted elsewhere.
|
|
/// </summary>
|
|
/// <param name="displayName"></param>
|
|
public void DestroyVm(string displayName)
|
|
{
|
|
logger.DebugFormat("Got request to destroy vm {0}", displayName);
|
|
|
|
var vm = GetComputerSystem(displayName);
|
|
if ( vm == null )
|
|
{
|
|
logger.DebugFormat("VM {0} already destroyed (or never existed)", displayName);
|
|
return;
|
|
}
|
|
|
|
// Stop VM
|
|
logger.DebugFormat("Stop VM {0} (GUID {1})", vm.ElementName, vm.Name);
|
|
SetState(vm, RequiredState.Disabled);
|
|
|
|
// Delete SwitchPort
|
|
logger.DebugFormat("Remove associated switch ports for VM {0} (GUID {1})", vm.ElementName, vm.Name);
|
|
DeleteSwitchPort(vm.ElementName);
|
|
|
|
// Delete VM
|
|
var virtSysMgmtSvc = GetVirtualisationSystemManagementService();
|
|
ManagementPath jobPath;
|
|
|
|
do
|
|
{
|
|
logger.DebugFormat("Delete VM {0} (GUID {1})", vm.ElementName, vm.Name);
|
|
var ret_val = virtSysMgmtSvc.DestroySystem(vm.Path, out jobPath);
|
|
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed Delete VM {0} (GUID {1}) due to {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
vm = GetComputerSystem(displayName);
|
|
}
|
|
while (vm != null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Migrates a vm to the given destination host
|
|
/// </summary>
|
|
/// <param name="desplayName"></param>
|
|
/// <param name="destination host"></param>
|
|
public void MigrateVm(string vmName, string destination)
|
|
{
|
|
ComputerSystem vm = GetComputerSystem(vmName);
|
|
VirtualSystemMigrationSettingData migrationSettingData = VirtualSystemMigrationSettingData.CreateInstance();
|
|
VirtualSystemMigrationService service = GetVirtualisationSystemMigrationService();
|
|
|
|
IPAddress addr = IPAddress.Parse(destination);
|
|
IPHostEntry entry = Dns.GetHostEntry(addr);
|
|
string[] destinationHost = new string[] { destination };
|
|
|
|
migrationSettingData.LateBoundObject["MigrationType"] = MigrationType.VirtualSystem;
|
|
migrationSettingData.LateBoundObject["TransportType"] = TransportType.TCP;
|
|
migrationSettingData.LateBoundObject["DestinationIPAddressList"] = destinationHost;
|
|
string migrationSettings = migrationSettingData.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20);
|
|
|
|
ManagementPath jobPath;
|
|
var ret_val = service.MigrateVirtualSystemToHost(vm.Path, entry.HostName, migrationSettings, null, null, out jobPath);
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
MigrationJobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed migrating VM {0} (GUID {1}) due to {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create new storage media resources, e.g. hard disk images and ISO disk images
|
|
/// see http://msdn.microsoft.com/en-us/library/hh859775(v=vs.85).aspx
|
|
/// </summary>
|
|
/// <param name="wmiQuery"></param>
|
|
/// <returns></returns>
|
|
private static StorageAllocationSettingData CloneStorageAllocationSetting(string wmiQuery)
|
|
{
|
|
var defaultDiskImageSettingsObjs = StorageAllocationSettingData.GetInstances(wmiQuery);
|
|
|
|
// assert
|
|
if (defaultDiskImageSettingsObjs.Count != 1)
|
|
{
|
|
var errMsg = string.Format("Failed to find Msvm_StorageAllocationSettingData for the query {0}", wmiQuery);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
StorageAllocationSettingData defaultDiskDriveSettings = defaultDiskImageSettingsObjs.OfType<StorageAllocationSettingData>().First();
|
|
return new StorageAllocationSettingData((ManagementBaseObject)defaultDiskDriveSettings.LateBoundObject.Clone());
|
|
}
|
|
|
|
/// < summary>
|
|
/// Removes a storage resource from a computer system.
|
|
/// </summary>
|
|
/// <param name="storageSettings">Path that uniquely identifies the resource.</param>
|
|
/// <param name="vm">VM to which the disk image will be attached.</param>
|
|
// Add new
|
|
private void RemoveNetworkResource(ManagementPath resourcePath)
|
|
{
|
|
var virtSwitchMgmtSvc = GetVirtualSwitchManagementService();
|
|
ManagementPath jobPath;
|
|
var ret_val = virtSwitchMgmtSvc.RemoveResourceSettings(
|
|
new ManagementPath[] { resourcePath },
|
|
out jobPath);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to remove network resources {0} from switch due to {1}",
|
|
resourcePath.Path,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
/// < summary>
|
|
/// Removes a storage resource from a computer system.
|
|
/// </summary>
|
|
/// <param name="storageSettings">Path that uniquely identifies the resource.</param>
|
|
/// <param name="vm">VM to which the disk image will be attached.</param>
|
|
private void RemoveStorageResource(ManagementPath resourcePath, ComputerSystem vm)
|
|
{
|
|
var virtSysMgmtSvc = GetVirtualisationSystemManagementService();
|
|
|
|
ManagementPath jobPath;
|
|
var ret_val = virtSysMgmtSvc.RemoveResourceSettings(
|
|
new ManagementPath[] { resourcePath },
|
|
out jobPath);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to remove resource {0} from VM {1} (GUID {2}) due to {3}",
|
|
resourcePath.Path,
|
|
vm.ElementName,
|
|
vm.Name,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
public void SetState(ComputerSystem vm, ushort requiredState)
|
|
{
|
|
logger.InfoFormat(
|
|
"Changing state of {0} (GUID {1}) to {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
RequiredState.ToString(requiredState));
|
|
|
|
ManagementPath jobPath;
|
|
// DateTime is unused
|
|
var ret_val = vm.RequestStateChange(requiredState, new DateTime(), out jobPath);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val == 32775)
|
|
{ // TODO: check
|
|
logger.InfoFormat("RequestStateChange returned 32775, which means vm in wrong state for requested state change. Treating as if requested state was reached");
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to change state of VM {0} (GUID {1}) to {2} due to {3}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
RequiredState.ToString(requiredState),
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
logger.InfoFormat(
|
|
"Successfully changed vm state of {0} (GUID {1} to requested state {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
requiredState);
|
|
}
|
|
|
|
|
|
//TODO: Write method to delete SwitchPort based on Name
|
|
/// <summary>
|
|
/// Delete switch port by removing settings from the switch
|
|
/// </summary>
|
|
/// <param name="elementName"></param>
|
|
/// <returns></returns>
|
|
public void DeleteSwitchPort(string elementName)
|
|
{
|
|
// Get NIC path
|
|
var condition = string.Format("ElementName=\"{0}\"", elementName);
|
|
var virtSwitchMgmtSvc = GetVirtualSwitchManagementService();
|
|
|
|
var switchPortCollection = EthernetSwitchPort.GetInstances(virtSwitchMgmtSvc.Scope, condition);
|
|
if (switchPortCollection.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (EthernetSwitchPort port in switchPortCollection)
|
|
{
|
|
var settings = GetSyntheticEthernetPortSettings(port);
|
|
RemoveNetworkResource(settings.Path);
|
|
}
|
|
}
|
|
|
|
public SyntheticEthernetPortSettingData GetSyntheticEthernetPortSettings(EthernetSwitchPort port)
|
|
{
|
|
// An ASSOCIATOR object provides the cross reference from the EthernetSwitchPort and the
|
|
// SyntheticEthernetPortSettingData, but generated wrappers do not expose a ASSOCIATOR OF query as a method.
|
|
// Instead, we use the System.Management to code the equivalant of
|
|
// string query = string.Format( "ASSOCIATORS OF {{{0}}} WHERE ResultClass = {1}", vm.path, resultclassName);
|
|
//
|
|
var wmiObjQuery = new RelatedObjectQuery(port.Path.Path, SyntheticEthernetPortSettingData.CreatedClassName);
|
|
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var wmiObjectSearch = new ManagementObjectSearcher(port.Scope, wmiObjQuery);
|
|
var wmiObjCollection = new SyntheticEthernetPortSettingData.SyntheticEthernetPortSettingDataCollection(wmiObjectSearch.Get());
|
|
|
|
// When snapshots are taken into account, there can be multiple settings objects
|
|
// take the first one that isn't a snapshot
|
|
foreach (SyntheticEthernetPortSettingData wmiObj in wmiObjCollection)
|
|
{
|
|
return wmiObj;
|
|
}
|
|
|
|
var errMsg = string.Format("No SyntheticEthernetPortSettingData for port {0}, path {1}", port.ElementName, port.Path.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds storage images to coputer system (disk image, iso image).
|
|
/// </summary>
|
|
/// <param name="storageSettings">Msvm_StorageAllocationSettings with HostResource configured with image
|
|
/// file and Parent set to a controller associated with the ComputerSystem</param>
|
|
/// <param name="vm">VM to which the disk image will be attached.</param>
|
|
// Add new
|
|
private ManagementPath[] AddStorageResource(string[] storageSettings, ComputerSystem vm)
|
|
{
|
|
return AddVirtualResource(storageSettings, vm);
|
|
}
|
|
|
|
private ManagementPath[] AddVirtualResource(string[] resourceSettings, ComputerSystem vm )
|
|
{
|
|
var virtSysMgmtSvc = GetVirtualisationSystemManagementService();
|
|
|
|
ManagementPath jobPath;
|
|
ManagementPath[] resourcePaths;
|
|
var ret_val = virtSysMgmtSvc.AddResourceSettings(
|
|
vm.Path,
|
|
resourceSettings,
|
|
out jobPath,
|
|
out resourcePaths);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to add resources to VM {0} (GUID {1}) due to {2}",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
return resourcePaths;
|
|
}
|
|
|
|
private ManagementPath[] AddFeatureSettings(string[] featureSettings, ManagementPath affectedConfiguration)
|
|
{
|
|
var virtSysMgmtSvc = GetVirtualisationSystemManagementService();
|
|
|
|
ManagementPath jobPath;
|
|
ManagementPath[] resultSettings;
|
|
var ret_val = virtSysMgmtSvc.AddFeatureSettings(
|
|
affectedConfiguration,
|
|
featureSettings,
|
|
out jobPath,
|
|
out resultSettings);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to add features settings {0} to resource {1} due to {2}",
|
|
featureSettings,
|
|
affectedConfiguration,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
return resultSettings;
|
|
}
|
|
|
|
private ManagementPath SetPortVlan(string vlan, EthernetPortAllocationSettingData portPath)
|
|
{
|
|
logger.DebugFormat("Setting VLAN to {0}", vlan);
|
|
|
|
var vmVirtMgmtSvc = GetVirtualisationSystemManagementService();
|
|
EthernetSwitchPortVlanSettingData.GetInstances();
|
|
|
|
// Create NIC resource by cloning the default NIC
|
|
var vlanSettings = EthernetSwitchPortVlanSettingData.GetInstances(vmVirtMgmtSvc.Scope, "InstanceID LIKE \"%Default\"");
|
|
|
|
// Assert
|
|
if (vlanSettings.Count != 1)
|
|
{
|
|
var errMsg = string.Format("Internal error, could not find default EthernetSwitchPortVlanSettingData instance");
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
var defaultVlanSettings = vlanSettings.OfType<EthernetSwitchPortVlanSettingData>().First();
|
|
|
|
var newVlanSettings = new EthernetSwitchPortVlanSettingData((ManagementBaseObject)defaultVlanSettings.LateBoundObject.Clone());
|
|
|
|
// Assign configuration to new NIC
|
|
newVlanSettings.LateBoundObject["AccessVlanId"] = vlan;
|
|
newVlanSettings.LateBoundObject["OperationMode"] = 1; // Access=1, trunk=2, private=3 ;
|
|
newVlanSettings.CommitObject();
|
|
|
|
// Insert NIC into vm
|
|
string[] newResources = new string[] { newVlanSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20) };
|
|
ManagementPath[] newResourcePaths = AddFeatureSettings(newResources, portPath.Path);
|
|
|
|
// assert
|
|
if (newResourcePaths.Length != 1)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to properly set VLAN to {0} for NIC on port {1}",
|
|
vlan,
|
|
portPath.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
return newResourcePaths[0];
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// External VSwitch has an external NIC, and we assume there is only one external NIC and one external vswitch.
|
|
/// </summary>
|
|
/// <param name="vmSettings"></param>
|
|
/// <returns></returns>
|
|
/// <throw>Throws if there is no vswitch</throw>
|
|
/// <remarks>
|
|
/// With V1 API, external ethernet port was attached to the land endpoint, which was attached to the switch.
|
|
/// e.g. Msvm_ExternalEthernetPort -> SwitchLANEndpoint -> SwitchPort -> VirtualSwitch
|
|
///
|
|
/// With V2 API, there are two kinds of lan endpoint: one on the computer system and one on the switch
|
|
/// e.g. Msvm_ExternalEthernetPort -> LANEndpoint -> LANEdnpoint -> EthernetSwitchPort -> VirtualEthernetSwitch
|
|
/// </remarks>
|
|
public static VirtualEthernetSwitch GetExternalVirtSwitch(String vSwitchName)
|
|
{
|
|
// Work back from the first *bound* external NIC we find.
|
|
var externNICs = ExternalEthernetPort.GetInstances("IsBound = TRUE");
|
|
VirtualEthernetSwitch vSwitch = null;
|
|
// Assert
|
|
if (externNICs.Count == 0 )
|
|
{
|
|
var errMsg = "No ExternalEthernetPort available to Hyper-V";
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
foreach(ExternalEthernetPort externNIC in externNICs.OfType<ExternalEthernetPort>()) {
|
|
// A sequence of ASSOCIATOR objects need to be traversed to get from external NIC the vswitch.
|
|
// We use ManagementObjectSearcher objects to execute this sequence of questions
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var endpointQuery = new RelatedObjectQuery(externNIC.Path.Path, LANEndpoint.CreatedClassName);
|
|
var endpointSearch = new ManagementObjectSearcher(externNIC.Scope, endpointQuery);
|
|
var endpointCollection = new LANEndpoint.LANEndpointCollection(endpointSearch.Get());
|
|
|
|
// assert
|
|
if (endpointCollection.Count < 1 )
|
|
{
|
|
var errMsg = string.Format("No adapter-based LANEndpoint for external NIC {0} on Hyper-V server", externNIC.Name);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
LANEndpoint adapterEndPoint = endpointCollection.OfType<LANEndpoint>().First();
|
|
var switchEndpointQuery = new RelatedObjectQuery(adapterEndPoint.Path.Path, LANEndpoint.CreatedClassName);
|
|
var switchEndpointSearch = new ManagementObjectSearcher(externNIC.Scope, switchEndpointQuery);
|
|
var switchEndpointCollection = new LANEndpoint.LANEndpointCollection(switchEndpointSearch.Get());
|
|
|
|
// assert
|
|
if (endpointCollection.Count < 1)
|
|
{
|
|
var errMsg = string.Format("No Switch-based LANEndpoint for external NIC {0} on Hyper-V server", externNIC.Name);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
LANEndpoint switchEndPoint = switchEndpointCollection.OfType<LANEndpoint>().First();
|
|
var switchPortQuery = new RelatedObjectQuery(switchEndPoint.Path.Path, EthernetSwitchPort.CreatedClassName);
|
|
var switchPortSearch = new ManagementObjectSearcher(switchEndPoint.Scope, switchPortQuery);
|
|
var switchPortCollection = new EthernetSwitchPort.EthernetSwitchPortCollection(switchPortSearch.Get());
|
|
|
|
// assert
|
|
if (switchPortCollection.Count < 1 )
|
|
{
|
|
var errMsg = string.Format("No SwitchPort for external NIC {0} on Hyper-V server", externNIC.Name);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
EthernetSwitchPort switchPort = switchPortCollection.OfType<EthernetSwitchPort>().First();
|
|
var vSwitchQuery = new RelatedObjectQuery(switchPort.Path.Path, VirtualEthernetSwitch.CreatedClassName);
|
|
var vSwitchSearch = new ManagementObjectSearcher(externNIC.Scope, vSwitchQuery);
|
|
var vSwitchCollection = new VirtualEthernetSwitch.VirtualEthernetSwitchCollection(vSwitchSearch.Get());
|
|
|
|
// assert
|
|
if (vSwitchCollection.Count < 1)
|
|
{
|
|
var errMsg = string.Format("No virtual switch for external NIC {0} on Hyper-V server", externNIC.Name);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
vSwitch = vSwitchCollection.OfType<VirtualEthernetSwitch>().First();
|
|
if (vSwitch.ElementName.Equals(vSwitchName) == true)
|
|
{
|
|
return vSwitch;
|
|
}
|
|
}
|
|
return vSwitch;
|
|
}
|
|
|
|
private static void ModifyVmResources(VirtualSystemManagementService vmMgmtSvc, ComputerSystem vm, string[] resourceSettings)
|
|
{
|
|
// Resource settings are changed through the management service
|
|
System.Management.ManagementPath jobPath;
|
|
System.Management.ManagementPath[] results;
|
|
|
|
var ret_val = vmMgmtSvc.ModifyResourceSettings(
|
|
resourceSettings,
|
|
out jobPath,
|
|
out results);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to update VM {0} (GUID {1}) due to {2} (ModifyVirtualSystem call), existing VM not deleted",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
public void DeleteHostKvpItem(ComputerSystem vm, string key)
|
|
{
|
|
// Obtain controller for Hyper-V virtualisation subsystem
|
|
VirtualSystemManagementService vmMgmtSvc = GetVirtualisationSystemManagementService();
|
|
|
|
// Create object to hold the data.
|
|
KvpExchangeDataItem kvpItem = KvpExchangeDataItem.CreateInstance();
|
|
kvpItem.LateBoundObject["Name"] = WmiCallsV2.CloudStackUserDataKey;
|
|
kvpItem.LateBoundObject["Data"] = "dummy";
|
|
kvpItem.LateBoundObject["Source"] = 0;
|
|
logger.Debug("VM " + vm.Name + " will have KVP key " + key + " removed.");
|
|
|
|
String kvpStr = kvpItem.LateBoundObject.GetText(TextFormat.CimDtd20);
|
|
|
|
// Update the resource settings for the VM.
|
|
ManagementPath jobPath;
|
|
|
|
uint ret_val = vmMgmtSvc.RemoveKvpItems(new String[] { kvpStr }, vm.Path, out jobPath);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to update VM {0} (GUID {1}) due to {2} (ModifyVirtualSystem call), existing VM not deleted",
|
|
vm.ElementName,
|
|
vm.Name,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
private static ComputerSystem CreateDefaultVm(VirtualSystemManagementService vmMgmtSvc, string name)
|
|
{
|
|
// Tweak default settings by basing new VM on default global setting object
|
|
// with designed display name.
|
|
|
|
VirtualSystemSettingData vs_gs_data = VirtualSystemSettingData.CreateInstance();
|
|
vs_gs_data.LateBoundObject["ElementName"] = name;
|
|
|
|
System.Management.ManagementPath jobPath;
|
|
System.Management.ManagementPath defined_sys;
|
|
var ret_val = vmMgmtSvc.DefineSystem(
|
|
null,
|
|
new string[0],
|
|
vs_gs_data.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20),
|
|
out jobPath,
|
|
out defined_sys);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
JobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to create VM {0} due to {1} (DefineVirtualSystem call)",
|
|
name, ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
logger.DebugFormat(CultureInfo.InvariantCulture, "Created VM {0}", name);
|
|
|
|
// Is the defined_system real?
|
|
var vm = new ComputerSystem(defined_sys);
|
|
|
|
// Assertion
|
|
if (vm.ElementName.CompareTo(name) != 0)
|
|
{
|
|
var errMsg = string.Format(
|
|
"New VM created with wrong name (is {0}, should be {1}, GUID {2})",
|
|
vm.ElementName,
|
|
name,
|
|
vm.Name);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
return vm;
|
|
}
|
|
|
|
public VirtualEthernetSwitchManagementService GetVirtualSwitchManagementService()
|
|
{
|
|
// VirtualSwitchManagementService is a singleton, most anonymous way of lookup is by asking for the set
|
|
// of local instances, which should be size 1.
|
|
var virtSwtichSvcCollection = VirtualEthernetSwitchManagementService.GetInstances();
|
|
foreach (VirtualEthernetSwitchManagementService item in virtSwtichSvcCollection)
|
|
{
|
|
return item;
|
|
}
|
|
|
|
var errMsg = string.Format("No Hyper-V subsystem on server");
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Always produces a VHDX.
|
|
/// </summary>
|
|
/// <param name="MaxInternalSize"></param>
|
|
/// <param name="Path"></param>
|
|
public void CreateDynamicVirtualHardDisk(ulong MaxInternalSize, string Path)
|
|
{
|
|
// Produce description of the virtual disk in the format of a Msvm_VirtualHardDiskSettings object
|
|
|
|
// Example at http://www.getcodesamples.com/src/FC025DDC/76689747, but
|
|
// Is there a template we can use to fill in the settings?
|
|
var newVirtHDSettings = VirtualHardDiskSettingData.CreateInstance();
|
|
newVirtHDSettings.LateBoundObject["Type"] = 3; // Dynamic
|
|
newVirtHDSettings.LateBoundObject["Format"] = 2; // VHD
|
|
newVirtHDSettings.LateBoundObject["Path"] = Path;
|
|
newVirtHDSettings.LateBoundObject["MaxInternalSize"] = MaxInternalSize;
|
|
newVirtHDSettings.LateBoundObject["BlockSize"] = 0; // Use defaults
|
|
newVirtHDSettings.LateBoundObject["LogicalSectorSize"] = 0; // Use defaults
|
|
newVirtHDSettings.LateBoundObject["PhysicalSectorSize"] = 0; // Use defaults
|
|
|
|
// Optional: newVirtHDSettings.CommitObject();
|
|
|
|
// Add the new vhd object as a virtual hard disk to the vm.
|
|
string newVirtHDSettingsString = newVirtHDSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20);
|
|
|
|
// Resource settings are changed through the management service
|
|
System.Management.ManagementPath jobPath;
|
|
var imgMgr = GetImageManagementService();
|
|
var ret_val = imgMgr.CreateVirtualHardDisk(newVirtHDSettingsString, out jobPath);
|
|
|
|
// If the Job is done asynchronously
|
|
if (ret_val == ReturnCode.Started)
|
|
{
|
|
StorageJobCompleted(jobPath);
|
|
}
|
|
else if (ret_val != ReturnCode.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Failed to CreateVirtualHardDisk size {0}, path {1} due to {2}",
|
|
MaxInternalSize,
|
|
Path,
|
|
ReturnCode.ToString(ret_val));
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
public ImageManagementService GetImageManagementService()
|
|
{
|
|
// VirtualSystemManagementService is a singleton, most anonymous way of lookup is by asking for the set
|
|
// of local instances, which should be size 1.
|
|
|
|
var coll = ImageManagementService.GetInstances();
|
|
foreach (ImageManagementService item in coll)
|
|
{
|
|
return item;
|
|
}
|
|
|
|
var errMsg = string.Format("No Hyper-V subsystem on server");
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
|
|
public VirtualSystemManagementService GetVirtualisationSystemManagementService()
|
|
{
|
|
// VirtualSystemManagementService is a singleton, most anonymous way of lookup is by asking for the set
|
|
// of local instances, which should be size 1.
|
|
|
|
var virtSysMgmtSvcCollection = VirtualSystemManagementService.GetInstances();
|
|
foreach (VirtualSystemManagementService item in virtSysMgmtSvcCollection)
|
|
{
|
|
return item;
|
|
}
|
|
|
|
var errMsg = string.Format("No Hyper-V subsystem on server");
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
public VirtualSystemMigrationService GetVirtualisationSystemMigrationService()
|
|
{
|
|
|
|
var virtSysMigSvcCollection = VirtualSystemMigrationService.GetInstances();
|
|
foreach (VirtualSystemMigrationService item in virtSysMigSvcCollection)
|
|
{
|
|
return item;
|
|
}
|
|
|
|
var errMsg = string.Format("No Hyper-V migration service subsystem on server");
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Similar to http://msdn.microsoft.com/en-us/library/hh850031%28v=vs.85%29.aspx
|
|
/// </summary>
|
|
/// <param name="jobPath"></param>
|
|
/// <returns></returns>
|
|
private static void JobCompleted(ManagementPath jobPath)
|
|
{
|
|
ConcreteJob jobObj = null;
|
|
for(;;)
|
|
{
|
|
jobObj = new ConcreteJob(jobPath);
|
|
if (jobObj.JobState != JobState.Starting && jobObj.JobState != JobState.Running)
|
|
{
|
|
break;
|
|
}
|
|
logger.InfoFormat("In progress... {0}% completed.", jobObj.PercentComplete);
|
|
System.Threading.Thread.Sleep(1000);
|
|
}
|
|
|
|
if (jobObj.JobState != JobState.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Hyper-V Job failed, Error Code:{0}, Description: {1}",
|
|
jobObj.ErrorCode,
|
|
jobObj.ErrorDescription);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
logger.DebugFormat("WMI job succeeded: {0}, Elapsed={1}", jobObj.Description, jobObj.ElapsedTime);
|
|
}
|
|
|
|
private static void MigrationJobCompleted(ManagementPath jobPath)
|
|
{
|
|
MigrationJob jobObj = null;
|
|
for (;;)
|
|
{
|
|
jobObj = new MigrationJob(jobPath);
|
|
if (jobObj.JobState != JobState.Starting && jobObj.JobState != JobState.Running)
|
|
{
|
|
break;
|
|
}
|
|
logger.InfoFormat("In progress... {0}% completed.", jobObj.PercentComplete);
|
|
System.Threading.Thread.Sleep(1000);
|
|
}
|
|
|
|
if (jobObj.JobState != JobState.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Hyper-V Job failed, Error Code:{0}, Description: {1}",
|
|
jobObj.ErrorCode,
|
|
jobObj.ErrorDescription);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
private static void StorageJobCompleted(ManagementPath jobPath)
|
|
{
|
|
StorageJob jobObj = null;
|
|
for (; ; )
|
|
{
|
|
jobObj = new StorageJob(jobPath);
|
|
if (jobObj.JobState != JobState.Starting && jobObj.JobState != JobState.Running)
|
|
{
|
|
break;
|
|
}
|
|
logger.InfoFormat("In progress... {0}% completed.", jobObj.PercentComplete);
|
|
System.Threading.Thread.Sleep(1000);
|
|
}
|
|
|
|
if (jobObj.JobState != JobState.Completed)
|
|
{
|
|
var errMsg = string.Format(
|
|
"Hyper-V Job failed, Error Code:{0}, Description: {1}",
|
|
jobObj.ErrorCode,
|
|
jobObj.ErrorDescription);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
public void GetProcessorResources(out uint sockets, out uint cores, out uint mhz)
|
|
{
|
|
// Processor processors
|
|
cores = 0;
|
|
mhz = 0;
|
|
sockets = 0;
|
|
Processor.ProcessorCollection procCol = Processor.GetInstances();
|
|
foreach (Processor procInfo in procCol)
|
|
{
|
|
cores += procInfo.NumberOfCores;
|
|
mhz = procInfo.MaxClockSpeed;
|
|
sockets++;
|
|
}
|
|
}
|
|
|
|
public void GetProcessorUsageInfo(out double cpuUtilization)
|
|
{
|
|
PerfFormattedData_Counters_ProcessorInformation.PerfFormattedData_Counters_ProcessorInformationCollection coll =
|
|
PerfFormattedData_Counters_ProcessorInformation.GetInstances("Name=\"_Total\"");
|
|
cpuUtilization = 100;
|
|
// Use the first one
|
|
foreach (PerfFormattedData_Counters_ProcessorInformation procInfo in coll)
|
|
{
|
|
// Idle during a given internal
|
|
// See http://library.wmifun.net/cimv2/win32_perfformatteddata_counters_processorinformation.html
|
|
cpuUtilization = 100.0 - (double)procInfo.PercentIdleTime;
|
|
}
|
|
}
|
|
|
|
|
|
public void GetMemoryResources(out ulong physicalRamKBs, out ulong freeMemoryKBs)
|
|
{
|
|
OperatingSystem0 os = new OperatingSystem0();
|
|
physicalRamKBs = os.TotalVisibleMemorySize;
|
|
freeMemoryKBs = os.FreePhysicalMemory;
|
|
}
|
|
|
|
public string GetDefaultVirtualDiskFolder()
|
|
{
|
|
VirtualSystemManagementServiceSettingData.VirtualSystemManagementServiceSettingDataCollection coll = VirtualSystemManagementServiceSettingData.GetInstances();
|
|
string defaultVirtualHardDiskPath = null;
|
|
foreach (VirtualSystemManagementServiceSettingData settings in coll)
|
|
{
|
|
defaultVirtualHardDiskPath = settings.DefaultVirtualHardDiskPath;
|
|
}
|
|
|
|
// assert
|
|
if (!System.IO.Directory.Exists(defaultVirtualHardDiskPath) ){
|
|
var errMsg = string.Format(
|
|
"Hyper-V DefaultVirtualHardDiskPath is invalid!");
|
|
logger.Error(errMsg);
|
|
return null;
|
|
}
|
|
|
|
return defaultVirtualHardDiskPath;
|
|
}
|
|
|
|
public ComputerSystem GetComputerSystem(string displayName)
|
|
{
|
|
var wmiQuery = String.Format("ElementName=\"{0}\"", displayName);
|
|
ComputerSystem.ComputerSystemCollection vmCollection = ComputerSystem.GetInstances(wmiQuery);
|
|
|
|
// Return the first one
|
|
foreach (ComputerSystem vm in vmCollection)
|
|
{
|
|
return vm;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public Dictionary<String, VmState> GetVmSync(String privateIpAddress)
|
|
{
|
|
List<String> vms = GetVmElementNames();
|
|
Dictionary<String, VmState> vmSyncStates = new Dictionary<string, VmState>();
|
|
String vmState;
|
|
foreach (String vm in vms)
|
|
{
|
|
vmState = EnabledState.ToCloudStackState(GetComputerSystem(vm).EnabledState);
|
|
vmSyncStates.Add(vm, new VmState(vmState, privateIpAddress));
|
|
}
|
|
return vmSyncStates;
|
|
}
|
|
|
|
public List<string> GetVmElementNames()
|
|
{
|
|
List<string> result = new List<string>();
|
|
ComputerSystem.ComputerSystemCollection vmCollection = ComputerSystem.GetInstances();
|
|
|
|
// Return the first one
|
|
foreach (ComputerSystem vm in vmCollection)
|
|
{
|
|
if (vm.Caption.StartsWith("Hosting Computer System") )
|
|
{
|
|
continue;
|
|
}
|
|
result.Add(vm.ElementName);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public ProcessorSettingData GetProcSettings(VirtualSystemSettingData vmSettings)
|
|
{
|
|
// An ASSOCIATOR object provides the cross reference from the VirtualSystemSettingData and the
|
|
// ProcessorSettingData, but generated wrappers do not expose a ASSOCIATOR OF query as a method.
|
|
// Instead, we use the System.Management to code the equivalant of
|
|
// string query = string.Format( "ASSOCIATORS OF {{{0}}} WHERE ResultClass = {1}", vmSettings.path, resultclassName);
|
|
//
|
|
var wmiObjQuery = new RelatedObjectQuery(vmSettings.Path.Path, ProcessorSettingData.CreatedClassName);
|
|
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var wmiObjectSearch = new ManagementObjectSearcher(vmSettings.Scope, wmiObjQuery);
|
|
var wmiObjCollection = new ProcessorSettingData.ProcessorSettingDataCollection(wmiObjectSearch.Get());
|
|
|
|
foreach (ProcessorSettingData wmiObj in wmiObjCollection)
|
|
{
|
|
return wmiObj;
|
|
}
|
|
|
|
var errMsg = string.Format("No ProcessorSettingData in VirtualSystemSettingData {0}", vmSettings.Path.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
public MemorySettingData GetMemSettings(VirtualSystemSettingData vmSettings)
|
|
{
|
|
// An ASSOCIATOR object provides the cross reference from the VirtualSystemSettingData and the
|
|
// MemorySettingData, but generated wrappers do not expose a ASSOCIATOR OF query as a method.
|
|
// Instead, we use the System.Management to code the equivalant of
|
|
// string query = string.Format( "ASSOCIATORS OF {{{0}}} WHERE ResultClass = {1}", vmSettings.path, resultclassName);
|
|
//
|
|
var wmiObjQuery = new RelatedObjectQuery(vmSettings.Path.Path, MemorySettingData.CreatedClassName);
|
|
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var wmiObjectSearch = new ManagementObjectSearcher(vmSettings.Scope, wmiObjQuery);
|
|
var wmiObjCollection = new MemorySettingData.MemorySettingDataCollection(wmiObjectSearch.Get());
|
|
|
|
foreach (MemorySettingData wmiObj in wmiObjCollection)
|
|
{
|
|
return wmiObj;
|
|
}
|
|
|
|
var errMsg = string.Format("No MemorySettingData in VirtualSystemSettingData {0}", vmSettings.Path.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
|
|
public ResourceAllocationSettingData GetDvdDriveSettings(VirtualSystemSettingData vmSettings)
|
|
{
|
|
var wmiObjCollection = GetResourceAllocationSettings(vmSettings);
|
|
|
|
foreach (ResourceAllocationSettingData wmiObj in wmiObjCollection)
|
|
{
|
|
// DVD drive is '16', see http://msdn.microsoft.com/en-us/library/hh850200(v=vs.85).aspx
|
|
if (wmiObj.ResourceType == 16)
|
|
{
|
|
return wmiObj;
|
|
}
|
|
}
|
|
|
|
var errMsg = string.Format(
|
|
"Cannot find the Dvd drive in VirtualSystemSettingData {0}",
|
|
vmSettings.Path.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
public ResourceAllocationSettingData GetIDEControllerSettings(VirtualSystemSettingData vmSettings, string cntrllerAddr)
|
|
{
|
|
var wmiObjCollection = GetResourceAllocationSettings(vmSettings);
|
|
|
|
foreach (ResourceAllocationSettingData wmiObj in wmiObjCollection)
|
|
{
|
|
if (wmiObj.ResourceSubType == IDE_CONTROLLER && wmiObj.Address == cntrllerAddr)
|
|
{
|
|
return wmiObj;
|
|
}
|
|
}
|
|
|
|
var errMsg = string.Format(
|
|
"Cannot find the Microsoft Emulated IDE Controlle at address {0} in VirtualSystemSettingData {1}",
|
|
cntrllerAddr,
|
|
vmSettings.Path.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
public ResourceAllocationSettingData GetScsiControllerSettings(VirtualSystemSettingData vmSettings)
|
|
{
|
|
var wmiObjCollection = GetResourceAllocationSettings(vmSettings);
|
|
|
|
foreach (ResourceAllocationSettingData wmiObj in wmiObjCollection)
|
|
{
|
|
if (wmiObj.ResourceSubType == SCSI_CONTROLLER)
|
|
{
|
|
return wmiObj;
|
|
}
|
|
}
|
|
|
|
var errMsg = string.Format(
|
|
"Cannot find the Microsoft Synthetic SCSI Controller in VirtualSystemSettingData {1}",
|
|
vmSettings.Path.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
/// <summary>
|
|
/// VM resources, typically hardware a described by a generic MSVM_ResourceAllocationSettingData object. The hardware type being
|
|
/// described is identified in two ways: in general terms using an enum in the ResourceType field, and in terms of the implementation
|
|
/// using text in the ResourceSubType field.
|
|
/// See http://msdn.microsoft.com/en-us/library/cc136877%28v=vs.85%29.aspx
|
|
/// </summary>
|
|
/// <param name="vmSettings"></param>
|
|
/// <returns></returns>
|
|
public ResourceAllocationSettingData.ResourceAllocationSettingDataCollection GetResourceAllocationSettings(VirtualSystemSettingData vmSettings)
|
|
{
|
|
// An ASSOCIATOR object provides the cross reference from the VirtualSystemSettingData and the
|
|
// ResourceAllocationSettingData, but generated wrappers do not expose a ASSOCIATOR OF query as a method.
|
|
// Instead, we use the System.Management to code the equivalant of
|
|
// string query = string.Format( "ASSOCIATORS OF {{{0}}} WHERE ResultClass = {1}", vmSettings.path, resultclassName);
|
|
//
|
|
var wmiObjQuery = new RelatedObjectQuery(vmSettings.Path.Path, ResourceAllocationSettingData.CreatedClassName);
|
|
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var wmiObjectSearch = new ManagementObjectSearcher(vmSettings.Scope, wmiObjQuery);
|
|
var wmiObjCollection = new ResourceAllocationSettingData.ResourceAllocationSettingDataCollection(wmiObjectSearch.Get());
|
|
|
|
if (wmiObjCollection != null)
|
|
{
|
|
return wmiObjCollection;
|
|
}
|
|
|
|
var errMsg = string.Format("No ResourceAllocationSettingData in VirtualSystemSettingData {0}", vmSettings.Path.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
public EthernetPortAllocationSettingData[] GetEthernetConnections(ComputerSystem vm)
|
|
{
|
|
// ComputerSystem -> VirtualSystemSettingData -> EthernetPortAllocationSettingData
|
|
VirtualSystemSettingData vmSettings = GetVmSettings(vm);
|
|
|
|
// An ASSOCIATOR object provides the cross reference from the VirtualSystemSettingData and the
|
|
// EthernetPortAllocationSettingData, but generated wrappers do not expose a ASSOCIATOR OF query as a method.
|
|
// Instead, we use the System.Management to code the equivalant of
|
|
// string query = string.Format( "ASSOCIATORS OF {{{0}}} WHERE ResultClass = {1}", vmSettings.path, resultclassName);
|
|
//
|
|
var wmiObjQuery = new RelatedObjectQuery(vmSettings.Path.Path, EthernetPortAllocationSettingData.CreatedClassName);
|
|
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var wmiObjectSearch = new ManagementObjectSearcher(vmSettings.Scope, wmiObjQuery);
|
|
var wmiObjCollection = new EthernetPortAllocationSettingData.EthernetPortAllocationSettingDataCollection(wmiObjectSearch.Get());
|
|
|
|
var result = new List<EthernetPortAllocationSettingData>(wmiObjCollection.Count);
|
|
foreach (EthernetPortAllocationSettingData item in wmiObjCollection)
|
|
{
|
|
result.Add(item);
|
|
}
|
|
return result.ToArray();
|
|
}
|
|
|
|
|
|
public EthernetSwitchPortVlanSettingData GetVlanSettings(EthernetPortAllocationSettingData ethernetConnection)
|
|
{
|
|
// An ASSOCIATOR object provides the cross reference from the VirtualSystemSettingData and the
|
|
// EthernetPortAllocationSettingData, but generated wrappers do not expose a ASSOCIATOR OF query as a method.
|
|
// Instead, we use the System.Management to code the equivalant of
|
|
// string query = string.Format( "ASSOCIATORS OF {{{0}}} WHERE ResultClass = {1}", vmSettings.path, resultclassName);
|
|
//
|
|
var wmiObjQuery = new RelatedObjectQuery(ethernetConnection.Path.Path, EthernetSwitchPortVlanSettingData.CreatedClassName);
|
|
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var wmiObjectSearch = new ManagementObjectSearcher(ethernetConnection.Scope, wmiObjQuery);
|
|
var wmiObjCollection = new EthernetSwitchPortVlanSettingData.EthernetSwitchPortVlanSettingDataCollection(wmiObjectSearch.Get());
|
|
|
|
if (wmiObjCollection.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Assert
|
|
if (wmiObjCollection.Count > 1)
|
|
{
|
|
var errMsg = string.Format("Internal error, morn one VLAN settings for a single ethernetConnection");
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
return wmiObjCollection.OfType<EthernetSwitchPortVlanSettingData>().First();
|
|
}
|
|
|
|
|
|
public SyntheticEthernetPortSettingData[] GetEthernetPortSettings(ComputerSystem vm)
|
|
{
|
|
// An ASSOCIATOR object provides the cross reference from the ComputerSettings and the
|
|
// SyntheticEthernetPortSettingData, via the VirtualSystemSettingData.
|
|
// However, generated wrappers do not expose a ASSOCIATOR OF query as a method.
|
|
// Instead, we use the System.Management to code the equivalant of
|
|
//
|
|
// string query = string.Format( "ASSOCIATORS OF {{{0}}} WHERE ResultClass = {1}", vm.path, resultclassName);
|
|
//
|
|
VirtualSystemSettingData vmSettings = GetVmSettings(vm);
|
|
|
|
var wmiObjQuery = new RelatedObjectQuery(vmSettings.Path.Path, SyntheticEthernetPortSettingData.CreatedClassName);
|
|
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var wmiObjectSearch = new ManagementObjectSearcher(vm.Scope, wmiObjQuery);
|
|
var wmiObjCollection = new SyntheticEthernetPortSettingData.SyntheticEthernetPortSettingDataCollection(wmiObjectSearch.Get());
|
|
|
|
List<SyntheticEthernetPortSettingData> results = new List<SyntheticEthernetPortSettingData>(wmiObjCollection.Count);
|
|
foreach (SyntheticEthernetPortSettingData item in wmiObjCollection)
|
|
{
|
|
results.Add(item);
|
|
}
|
|
|
|
return results.ToArray();
|
|
}
|
|
|
|
public string GetDefaultDataRoot()
|
|
{
|
|
string defaultRootPath = null;
|
|
VirtualSystemManagementServiceSettingData vs_mgmt_data = VirtualSystemManagementServiceSettingData.CreateInstance();
|
|
defaultRootPath = vs_mgmt_data.DefaultVirtualHardDiskPath;
|
|
if (defaultRootPath == null) {
|
|
defaultRootPath = Path.GetPathRoot(Environment.SystemDirectory) +
|
|
"\\Users\\Public\\Documents\\Hyper-V\\Virtual hard disks";
|
|
}
|
|
|
|
return defaultRootPath;
|
|
}
|
|
|
|
public VirtualSystemSettingData GetVmSettings(ComputerSystem vm)
|
|
{
|
|
// An ASSOCIATOR object provides the cross reference from the ComputerSettings and the
|
|
// VirtualSystemSettingData, but generated wrappers do not expose a ASSOCIATOR OF query as a method.
|
|
// Instead, we use the System.Management to code the equivalant of
|
|
// string query = string.Format( "ASSOCIATORS OF {{{0}}} WHERE ResultClass = {1}", vm.path, resultclassName);
|
|
//
|
|
var wmiObjQuery = new RelatedObjectQuery(vm.Path.Path, VirtualSystemSettingData.CreatedClassName);
|
|
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var wmiObjectSearch = new ManagementObjectSearcher(vm.Scope, wmiObjQuery);
|
|
var wmiObjCollection = new VirtualSystemSettingData.VirtualSystemSettingDataCollection(wmiObjectSearch.Get());
|
|
|
|
// When snapshots are taken into account, there can be multiple settings objects
|
|
// take the first one that isn't a snapshot
|
|
foreach (VirtualSystemSettingData wmiObj in wmiObjCollection)
|
|
{
|
|
if (wmiObj.VirtualSystemType == "Microsoft:Hyper-V:System:Realized" ||
|
|
wmiObj.VirtualSystemType == "Microsoft:Hyper-V:System:Planned")
|
|
{
|
|
return wmiObj;
|
|
}
|
|
}
|
|
|
|
var errMsg = string.Format("No VirtualSystemSettingData for VM {0}, path {1}", vm.ElementName, vm.Path.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
public KvpExchangeComponentSettingData GetKvpSettings(VirtualSystemSettingData vmSettings)
|
|
{
|
|
// An ASSOCIATOR object provides the cross reference from the VirtualSystemSettingData and the
|
|
// KvpExchangeComponentSettingData, but generated wrappers do not expose a ASSOCIATOR OF query as a method.
|
|
// Instead, we use the System.Management to code the equivalant of
|
|
// string query = string.Format( "ASSOCIATORS OF {{{0}}} WHERE ResultClass = {1}", vmSettings.path, resultclassName);
|
|
//
|
|
var wmiObjQuery = new RelatedObjectQuery(vmSettings.Path.Path, KvpExchangeComponentSettingData.CreatedClassName);
|
|
|
|
// NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain
|
|
// the virtualisation objects.
|
|
var wmiObjectSearch = new ManagementObjectSearcher(vmSettings.Scope, wmiObjQuery);
|
|
var wmiObjCollection = new KvpExchangeComponentSettingData.KvpExchangeComponentSettingDataCollection(wmiObjectSearch.Get());
|
|
|
|
foreach (KvpExchangeComponentSettingData wmiObj in wmiObjCollection)
|
|
{
|
|
return wmiObj;
|
|
}
|
|
|
|
var errMsg = string.Format("No KvpExchangeComponentSettingData in VirtualSystemSettingData {0}", vmSettings.Path.Path);
|
|
var ex = new WmiException(errMsg);
|
|
logger.Error(errMsg, ex);
|
|
throw ex;
|
|
}
|
|
|
|
public void GetSummaryInfo(Dictionary<string, VmStatsEntry> vmProcessorInfo, List<System.Management.ManagementPath> vmsToInspect)
|
|
{
|
|
// Process info available from WMI,
|
|
// See http://msdn.microsoft.com/en-us/library/hh850062(v=vs.85).aspx
|
|
uint[] requestedInfo = new uint[] { // TODO: correct?
|
|
0, // Name
|
|
1, // ElementName
|
|
4, // Number of processes
|
|
101 // ProcessorLoad
|
|
};
|
|
|
|
System.Management.ManagementBaseObject[] sysSummary;
|
|
var vmsvc = GetVirtualisationSystemManagementService();
|
|
System.Management.ManagementPath[] vmPaths = vmsToInspect.ToArray();
|
|
vmsvc.GetSummaryInformation(requestedInfo, vmPaths, out sysSummary);
|
|
|
|
foreach (var summary in sysSummary)
|
|
{
|
|
|
|
var summaryInfo = new SummaryInformation(summary);
|
|
|
|
logger.Debug("VM " + summaryInfo.Name + "(elementName " + summaryInfo.ElementName + ") has " +
|
|
summaryInfo.NumberOfProcessors + " CPUs, and load of " + summaryInfo.ProcessorLoad);
|
|
var vmInfo = new VmStatsEntry
|
|
{
|
|
cpuUtilization = summaryInfo.ProcessorLoad,
|
|
numCPUs = summaryInfo.NumberOfProcessors,
|
|
networkReadKBs = 1,
|
|
networkWriteKBs = 1,
|
|
entityType = "vm"
|
|
};
|
|
vmProcessorInfo.Add(summaryInfo.ElementName, vmInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class WmiException : Exception
|
|
{
|
|
public WmiException()
|
|
{
|
|
}
|
|
|
|
public WmiException(string message)
|
|
: base(message)
|
|
{
|
|
}
|
|
|
|
public WmiException(string message, Exception inner)
|
|
: base(message, inner)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Covers V2 API, see
|
|
/// http://msdn.microsoft.com/en-us/library/hh850031%28v=vs.85%29.aspx
|
|
/// </summary>
|
|
public static class ReturnCode
|
|
{
|
|
public const UInt32 Completed = 0;
|
|
public const UInt32 Started = 4096;
|
|
public const UInt32 Failed = 32768;
|
|
public const UInt32 AccessDenied = 32769;
|
|
public const UInt32 NotSupported = 32770;
|
|
public const UInt32 Unknown = 32771;
|
|
public const UInt32 Timeout = 32772;
|
|
public const UInt32 InvalidParameter = 32773;
|
|
public const UInt32 SystemInUse = 32774;
|
|
public const UInt32 InvalidState = 32775;
|
|
public const UInt32 IncorrectDataType = 32776;
|
|
public const UInt32 SystemNotAvailable = 32777;
|
|
public const UInt32 OutofMemory = 32778;
|
|
public static string ToString(UInt32 value)
|
|
{
|
|
string result = "Unknown return code";
|
|
switch (value)
|
|
{
|
|
case Completed: result = "Completed"; break;
|
|
case Started: result = "Started"; break;
|
|
case Failed: result = "Failed"; break;
|
|
case AccessDenied: result = "AccessDenied"; break;
|
|
case NotSupported: result = "NotSupported"; break;
|
|
case Unknown: result = "Unknown"; break;
|
|
case Timeout: result = "Timeout"; break;
|
|
case InvalidParameter: result = "InvalidParameter"; break;
|
|
case SystemInUse: result = "SystemInUse"; break;
|
|
case InvalidState: result = "InvalidState"; break;
|
|
case IncorrectDataType: result = "IncorrectDataType"; break;
|
|
case SystemNotAvailable: result = "SystemNotAvailable"; break;
|
|
case OutofMemory: result = "OutofMemory"; break;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Covers V2 API, see
|
|
/// http://msdn.microsoft.com/en-us/library/hh850031%28v=vs.85%29.aspx
|
|
/// </summary>
|
|
public static class JobState
|
|
{
|
|
public const UInt16 New = 2;
|
|
public const UInt16 Starting = 3;
|
|
public const UInt16 Running = 4;
|
|
public const UInt16 Suspended = 5;
|
|
public const UInt16 ShuttingDown = 6;
|
|
public const UInt16 Completed = 7;
|
|
public const UInt16 Terminated = 8;
|
|
public const UInt16 Killed = 9;
|
|
public const UInt16 Exception = 10;
|
|
public const UInt16 Service = 11;
|
|
public static string ToString(UInt16 value)
|
|
{
|
|
string result = "Unknown JobState code";
|
|
switch (value)
|
|
{
|
|
case New: result = "New"; break;
|
|
case Starting: result = "Starting"; break;
|
|
case Running: result = "Running"; break;
|
|
case Suspended: result = "Suspended"; break;
|
|
case ShuttingDown: result = "ShuttingDown"; break;
|
|
case Completed: result = "Completed"; break;
|
|
case Terminated: result = "Terminated"; break;
|
|
case Killed: result = "Killed"; break;
|
|
case Exception: result = "Exception"; break;
|
|
case Service: result = "Service"; break;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// V2 API (see http://msdn.microsoft.com/en-us/library/hh850279(v=vs.85).aspx)
|
|
/// has removed 'Paused' and 'Suspended' as compared to the
|
|
/// V1 API (see http://msdn.microsoft.com/en-us/library/cc723874%28v=vs.85%29.aspx)
|
|
/// However, Paused and Suspended appear on the VM state transition table
|
|
/// (see http://msdn.microsoft.com/en-us/library/hh850116(v=vs.85).aspx#methods)
|
|
/// </summary>
|
|
public class RequiredState
|
|
{
|
|
public const UInt16 Enabled = 2; // Turns the VM on.
|
|
public const UInt16 Disabled = 3; // Turns the VM off.
|
|
public const UInt16 ShutDown = 4;
|
|
public const UInt16 Offline = 6;
|
|
//public const UInt16 Test = 7;
|
|
public const UInt16 Defer = 8;
|
|
// public const UInt16 Quiesce = 9;
|
|
// public const UInt16 Reboot = 10; // A hard reset of the VM.
|
|
public const UInt16 Reset = 11; // For future use.
|
|
public const UInt16 Paused = 9; // Pauses the VM.
|
|
public const UInt16 Suspended = 32779; // Saves the state of the VM.
|
|
|
|
public static string ToString(UInt16 value)
|
|
{
|
|
string result = "Unknown RequiredState code";
|
|
switch (value)
|
|
{
|
|
case Enabled: result = "Enabled"; break;
|
|
case Disabled: result = "Disabled"; break;
|
|
case ShutDown: result = "ShutDown"; break;
|
|
case Offline: result = "Offline"; break;
|
|
case Defer: result = "Defer"; break;
|
|
case Reset: result = "Reset"; break;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// V2 API specifies the states below in its state transition graph at
|
|
/// http://msdn.microsoft.com/en-us/library/hh850116(v=vs.85).aspx#methods
|
|
/// However, the CIM standard has additional possibilities based on the description
|
|
/// of EnabledState.
|
|
/// The previous V1 API is described by
|
|
/// http://msdn.microsoft.com/en-us/library/cc136822%28v=vs.85%29.aspx
|
|
/// </summary>
|
|
public class EnabledState
|
|
{
|
|
/// <summary>
|
|
/// The state of the VM could not be determined.
|
|
/// </summary>
|
|
public const UInt16 Unknown = 0;
|
|
/// <summary>
|
|
/// The VM is running.
|
|
/// </summary>
|
|
public const UInt16 Enabled = 2;
|
|
/// <summary>
|
|
/// The VM is turned off.
|
|
/// </summary>
|
|
public const UInt16 Disabled = 3;
|
|
/// <summary>
|
|
/// The VM is paused.
|
|
/// </summary>
|
|
public const UInt16 Paused = 32768;
|
|
/// <summary>
|
|
/// The VM is in a saved state.
|
|
/// </summary>
|
|
public const UInt16 Suspended = 32769;
|
|
/// <summary>
|
|
/// The VM is starting. This is a transitional state between 3 (Disabled)
|
|
/// or 32769 (Suspended) and 2 (Enabled) initiated by a call to the
|
|
/// RequestStateChange method with a RequestedState parameter of 2 (Enabled).
|
|
/// </summary>
|
|
public const UInt16 Starting = 32770;
|
|
/// <summary>
|
|
/// Starting with Windows Server 2008 R2 this value is not supported.
|
|
/// If the VM is performing a snapshot operation, the element at index 1
|
|
/// of the OperationalStatus property array will contain 32768 (Creating Snapshot),
|
|
/// 32769 (Applying Snapshot), or 32770 (Deleting Snapshot).
|
|
/// </summary>
|
|
public const UInt16 Snapshotting = 32771;
|
|
/// <summary>
|
|
/// The VM is saving its state. This is a transitional state between 2 (Enabled)
|
|
/// and 32769 (Suspended) initiated by a call to the RequestStateChange method
|
|
/// with a RequestedState parameter of 32769 (Suspended).
|
|
/// </summary>
|
|
public const UInt16 Saving = 32773;
|
|
/// <summary>
|
|
/// The VM is turning off. This is a transitional state between 2 (Enabled)
|
|
/// and 3 (Disabled) initiated by a call to the RequestStateChange method
|
|
/// with a RequestedState parameter of 3 (Disabled) or a guest operating system
|
|
/// initiated power off.
|
|
/// </summary>
|
|
public const UInt16 Stopping = 32774;
|
|
/// <summary>
|
|
/// The VM is pausing. This is a transitional state between 2 (Enabled) and 32768 (Paused) initiated by a call to the RequestStateChange method with a RequestedState parameter of 32768 (Paused).
|
|
/// </summary>
|
|
public const UInt16 Pausing = 32776;
|
|
/// <summary>
|
|
/// The VM is resuming from a paused state. This is a transitional state between 32768 (Paused) and 2 (Enabled).
|
|
/// </summary>
|
|
public const UInt16 Resuming = 32777;
|
|
|
|
public static string ToString(UInt16 value)
|
|
{
|
|
string result = "Unknown";
|
|
switch (value)
|
|
{
|
|
case Enabled: result = "Enabled"; break;
|
|
case Disabled: result = "Disabled"; break;
|
|
case Paused: result = "Paused"; break;
|
|
case Suspended: result = "Suspended"; break;
|
|
case Starting: result = "Starting"; break;
|
|
case Snapshotting: result = "Snapshotting"; break; // NOT used
|
|
case Saving: result = "Saving"; break;
|
|
case Stopping: result = "Stopping"; break;
|
|
case Pausing: result = "Pausing"; break;
|
|
case Resuming: result = "Resuming"; break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static string ToCloudStackState(UInt16 value)
|
|
{
|
|
string result = "Unknown";
|
|
switch (value)
|
|
{
|
|
case Enabled: result = "Running"; break;
|
|
case Disabled: result = "Stopped"; break;
|
|
case Paused: result = "Unknown"; break;
|
|
case Suspended: result = "Unknown"; break;
|
|
case Starting: result = "Starting"; break;
|
|
case Snapshotting: result = "Unknown"; break; // NOT used
|
|
case Saving: result = "Saving"; break;
|
|
case Stopping: result = "Stopping"; break;
|
|
case Pausing: result = "Unknown"; break;
|
|
case Resuming: result = "Starting"; break;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|