Return-Path: X-Original-To: apmail-cloudstack-commits-archive@www.apache.org Delivered-To: apmail-cloudstack-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B5B1910BBE for ; Mon, 4 Nov 2013 13:57:35 +0000 (UTC) Received: (qmail 72115 invoked by uid 500); 4 Nov 2013 13:52:08 -0000 Delivered-To: apmail-cloudstack-commits-archive@cloudstack.apache.org Received: (qmail 71211 invoked by uid 500); 4 Nov 2013 13:50:41 -0000 Mailing-List: contact commits-help@cloudstack.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cloudstack.apache.org Delivered-To: mailing list commits@cloudstack.apache.org Received: (qmail 69110 invoked by uid 99); 4 Nov 2013 13:48:33 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 04 Nov 2013 13:48:33 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id E93273A5E8; Mon, 4 Nov 2013 13:48:31 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: devdeep@apache.org To: commits@cloudstack.apache.org Date: Mon, 04 Nov 2013 13:49:03 -0000 Message-Id: <48da733cfa4348c59c889d1da1668e39@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [34/85] [abbrv] A plugin for Hyper-V control is available for CloudStack. The plugin implements basic VM control; however, its architecture allows additional functionality to be easily added. Incorporating the plugin in CloudStack will allow the commu http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCalls.cs ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCalls.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCalls.cs new file mode 100644 index 0000000..9b00c82 --- /dev/null +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCalls.cs @@ -0,0 +1,1328 @@ +// 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; +using log4net; +using System.Globalization; +using System.Management; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using CloudStack.Plugin.WmiWrappers.ROOT.CIMV2; +using System.IO; + +namespace HypervResource +{ + public class WmiCalls + { + public static void Initialize() + { + // Trigger assembly load into curren appdomain + } + + private static ILog logger = LogManager.GetLogger(typeof(WmiCalls)); + + /// + /// Returns ComputerSystem lacking any NICs and VOLUMEs + /// + public static 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; + } + + /// + /// Create a (synthetic) nic, and attach it to the vm + /// + /// + /// + /// + /// + public static SyntheticEthernetPortSettingData CreateNICforVm(ComputerSystem vm, string mac, string vlan) + { + logger.DebugFormat("Creating nic for VM {0} (GUID {1})", vm.ElementName, vm.Name); + + // Obtain controller for Hyper-V networking subsystem + VirtualSwitchManagementService 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().First(); + + var newSynthNICSettings = new SyntheticEthernetPortSettingData((ManagementBaseObject)defaultSynthNICSettings.LateBoundObject.Clone()); + + // Get the virtual switch + VirtualSwitch vSwitch = GetExternalVirtSwitch(); + + // Crate switch port for new VM + ManagementPath newSwitchPath = CreateSwitchPortForVm(vm, vmNetMgmtSvc, vSwitch); + + // Add required VLAND support + if (vlan != null) + { + SetPortVlan(vlan, vmNetMgmtSvc, newSwitchPath); + } + + logger.DebugFormat("Created switch port {0} on switch {1}", newSwitchPath.Path, vSwitch.Path.Path); + + // Assign configuration to new NIC + string normalisedMAC = string.Join("", (mac.Split(new char[] { ':' }))); + newSynthNICSettings.LateBoundObject["Connection"] = new string[] { newSwitchPath.Path }; + 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_HARDDISK_DRIVE = "Microsoft Synthetic Disk Drive"; + public const string IDE_ISO_DRIVE = "Microsoft Synthetic DVD Drive"; + + public const string IDE_ISO_DISK = "Microsoft Virtual CD/DVD Disk"; // For IDE_ISO_DRIVE + public const string IDE_HARDDISK_DISK = "Microsoft Virtual Hard Disk"; // For IDE_HARDDISK_DRIVE + + /// + /// Create new VM. By default we start it. + /// + public static ComputerSystem DeployVirtualMachine(dynamic jsonObj) + { + 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; + + // 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 + { + // TODO: revise exception type + 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 = WmiCalls.CreateVM(vmName, memSize, vcpus); + + foreach (var diskDrive in diskDrives) + { + string vhdFile = null; + string diskName = null; + VolumeObjectTO volInfo = VolumeObjectTO.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); + } + + string driveType = diskDrive.type; + + string ideCtrllr = "0"; + string driveResourceType = null; + switch (driveType) { + case "ROOT": + ideCtrllr = "0"; + driveResourceType = IDE_HARDDISK_DRIVE; + break; + case "ISO": + ideCtrllr = "1"; + driveResourceType = IDE_ISO_DRIVE; + 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 ); + AddDiskDriveToVm(newVm, vhdFile, ideCtrllr, driveResourceType); + } + + // add nics + foreach (var nic in nicInfo) + { + string mac = nic.mac; + string vlan = null; + string isolationUri = nic.isolationUri; + if (isolationUri != null && isolationUri.StartsWith("vlan://") && !isolationUri.Equals("vlan://untagged")) + { + 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; + } + } + CreateNICforVm(newVm, mac, vlan); + } + + logger.DebugFormat("Starting VM {0}", vmName); + SetState(newVm, RequiredState.Enabled); + logger.InfoFormat("Started VM {0}", vmName); + return newVm; + } + + /// + /// Create a disk and attach it to the vm + /// + /// + /// + /// IDE_HARDDISK_DRIVE or IDE_ISO_DRIVE + public static ManagementPath AddDiskDriveToVm(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 IDE_HARDDISK_DRIVE: + diskResourceSubType = IDE_HARDDISK_DISK; + break; + case IDE_ISO_DRIVE: + diskResourceSubType = IDE_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 = AttachNewDriveToVm(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; + } + + private static ManagementPath AttachNewDriveToVm(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["Address"] = "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.Address); + 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]; + } + + /// + /// Attach iso to the vm + /// + /// + /// + private static void AttachIsoToVm(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 ctrller = GetDvdDriveSettings(vmSettings); + + // A description of the drive is created by modifying a clone of the default ResourceAllocationSettingData for that drive type + string defaultDiskQuery = String.Format("ResourceSubType LIKE \"{0}\" AND InstanceID LIKE \"%Default\"", IDE_ISO_DISK); + var newDiskSettings = CloneResourceAllocationSetting(defaultDiskQuery); + + // Set IDE controller and address on the controller for the new drive + newDiskSettings.LateBoundObject["Parent"] = ctrller.Path.ToString(); + newDiskSettings.LateBoundObject["Connection"] = new string[] { isoPath }; + newDiskSettings.CommitObject(); + + // Add the new vhd object as a virtual hard disk to the vm. + string[] newDiskResource = new string[] { newDiskSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20) }; + ManagementPath[] newDiskPaths = AddVirtualResource(newDiskResource, vm); + // assert + if (newDiskPaths.Length != 1) + { + var errMsg = string.Format( + "Failed to add disk image to VM {0} (GUID {1}): number of resource created {2}", + vm.ElementName, + vm.Name, + newDiskPaths.Length); + 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, + isoPath); + } + + private static void InsertDiskImage(ComputerSystem vm, string vhdfile, 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 = CloneResourceAllocationSetting(defaultDiskQuery); + + // Set disk drive and VHD file on disk for new disk + newDiskSettings.LateBoundObject["Parent"] = drivePath.Path; + newDiskSettings.LateBoundObject["Connection"] = new string[] { vhdfile }; + newDiskSettings.CommitObject(); + + // Add the new vhd object as a virtual hard disk to the vm. + string[] newDiskResource = new string[] { newDiskSettings.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20) }; + ManagementPath[] newDiskPaths = AddVirtualResource(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, + vhdfile); + } + + 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().First(); + return new ResourceAllocationSettingData((ManagementBaseObject)defaultDiskDriveSettings.LateBoundObject.Clone()); + } + + public static 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 + { + AttachIsoToVm(vm, iso); + } + } + + public static void DestroyVm(dynamic jsonObj) + { + string vmToDestroy = jsonObj.vmName; + DestroyVm(vmToDestroy); + } + + /// + /// Remove all VMs and all SwitchPorts with the displayName. VHD gets deleted elsewhere. + /// + /// + public static 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 + 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.DestroyVirtualSystem(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); + } + + public static 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; + // TimeSpan is a value type; default ctor is equivalent to 0. + var ret_val = vm.RequestStateChange(requiredState, new TimeSpan(), out jobPath); + + // If the Job is done asynchronously + if (ret_val == ReturnCode.Started) + { + JobCompleted(jobPath); + } + else if (ret_val == 32775) + { + 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 + public static bool DeleteSwitchPort(string elementName) + { + var virtSwitchMgmtSvc = GetVirtualSwitchManagementService(); + // Get NIC path + var condition = string.Format("ElementName=\"{0}\"", elementName); + var switchPortCollection = SwitchPort.GetInstances(virtSwitchMgmtSvc.Scope, condition); + if (switchPortCollection.Count == 0) + { + return true; + } + + foreach (SwitchPort port in switchPortCollection) + { + // Destroy + var ret_val = virtSwitchMgmtSvc.DeleteSwitchPort(port.Path); + + if (ret_val != ReturnCode.Completed) + { + return false; + } + } + + return true; + } + + // Add new + private static ManagementPath[] AddVirtualResource(string[] resourceSettings, ComputerSystem vm ) + { + var virtSysMgmtSvc = GetVirtualisationSystemManagementService(); + + ManagementPath jobPath; + ManagementPath[] resourcePaths; + var ret_val = virtSysMgmtSvc.AddVirtualSystemResources( + resourceSettings, + vm.Path, + 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 static ManagementPath CreateSwitchPortForVm(ComputerSystem vm, VirtualSwitchManagementService vmNetMgmtSvc, VirtualSwitch vSwitch) + { + ManagementPath newSwitchPath = null; + var ret_val = vmNetMgmtSvc.CreateSwitchPort( + vm.ElementName, + Guid.NewGuid().ToString(), + "", + vSwitch.Path, + out newSwitchPath); + // Job is always done synchronously + if (ret_val != ReturnCode.Completed) + { + var errMsg = string.Format( + "Failed to create switch for NIC on VM {0} (GUID {1}), error code {2}", + vm.ElementName, + vm.Name, + ret_val); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + return newSwitchPath; + } + + // add vlan support by setting AccessVLAN on VLANEndpointSettingData for port + private static void SetPortVlan(string vlan, VirtualSwitchManagementService vmNetMgmtSvc, ManagementPath newSwitchPath) + { + logger.DebugFormat("Setting VLAN to {0}", vlan); + + VLANEndpointSettingData vlanEndpointSettings = GetVlanEndpointSettings(vmNetMgmtSvc, newSwitchPath); + vlanEndpointSettings.LateBoundObject["AccessVLAN"] = vlan; + vlanEndpointSettings.CommitObject(); + } + + public static VLANEndpointSettingData GetVlanEndpointSettings(VirtualSwitchManagementService vmNetMgmtSvc, ManagementPath newSwitchPath) + { + // Get Msvm_VLANEndpoint through associated with new Port + var vlanEndpointQuery = new RelatedObjectQuery(newSwitchPath.Path, VLANEndpoint.CreatedClassName); + var vlanEndpointSearch = new ManagementObjectSearcher(vmNetMgmtSvc.Scope, vlanEndpointQuery); + var vlanEndpointCollection = new VLANEndpoint.VLANEndpointCollection(vlanEndpointSearch.Get()); + + // assert + if (vlanEndpointCollection.Count != 1) + { + var errMsg = string.Format("No VLANs for vSwitch on Hyper-V server for switch {0}", newSwitchPath.Path); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + + VLANEndpoint vlanEndpoint = vlanEndpointCollection.OfType().First(); + + // Get Msvm_VLANEndpointSettingData assocaited with Msvm_VLANEndpoint + var vlanEndpointSettingsQuery = new RelatedObjectQuery(vlanEndpoint.Path.Path, VLANEndpointSettingData.CreatedClassName); + var vlanEndpointSettingsSearch = new ManagementObjectSearcher(vmNetMgmtSvc.Scope, vlanEndpointSettingsQuery); + var vlanEndpointSettingsCollection = new VLANEndpointSettingData.VLANEndpointSettingDataCollection(vlanEndpointSettingsSearch.Get()); + + // assert + if (vlanEndpointSettingsCollection.Count != 1) + { + var errMsg = string.Format("Internal error, VLAN for vSwitch not setup propertly Hyper-V"); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + + VLANEndpointSettingData vlanEndpointSettings = vlanEndpointSettingsCollection.OfType().First(); + return vlanEndpointSettings; + } + + /// + /// External VSwitch has an external NIC, and we assume there is only one external NIC + /// + /// + /// + /// Throws if there is no vswitch + public static VirtualSwitch GetExternalVirtSwitch() + { + // Work back from the first *bound* external NIC we find. + var externNICs = ExternalEthernetPort.GetInstances("IsBound = TRUE"); + + if (externNICs.Count == 0 ) + { + var errMsg = "No ExternalEthernetPort available to Hyper-V"; + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + + ExternalEthernetPort externNIC = externNICs.OfType().First(); + + // 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, SwitchLANEndpoint.CreatedClassName); + var endpointSearch = new ManagementObjectSearcher(externNIC.Scope, endpointQuery); + var endpointCollection = new SwitchLANEndpoint.SwitchLANEndpointCollection(endpointSearch.Get()); + + // assert + if (endpointCollection.Count < 1 ) + { + var errMsg = string.Format("No SwitchLANEndpoint for external NIC {0} on Hyper-V server", externNIC.Name); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + + SwitchLANEndpoint endPoint = endpointCollection.OfType().First(); + var switchPortQuery = new RelatedObjectQuery(endPoint.Path.Path, SwitchPort.CreatedClassName); + var switchPortSearch = new ManagementObjectSearcher(externNIC.Scope, switchPortQuery); + var switchPortCollection = new SwitchPort.SwitchPortCollection(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; + } + + SwitchPort switchPort = switchPortCollection.OfType().First(); + var vSwitchQuery = new RelatedObjectQuery(switchPort.Path.Path, VirtualSwitch.CreatedClassName); + var vSwitchSearch = new ManagementObjectSearcher(externNIC.Scope, vSwitchQuery); + var vSwitchCollection = new VirtualSwitch.VirtualSwitchCollection(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; + } + + VirtualSwitch vSwitch = vSwitchCollection.OfType().First(); + + return vSwitch; + } + + + private static void ModifyVmResources(VirtualSystemManagementService vmMgmtSvc, ComputerSystem vm, string[] resourceSettings) + { + // Resource settings are changed through the management service + System.Management.ManagementPath jobPath; + + var ret_val = vmMgmtSvc.ModifyVirtualSystemResources(vm.Path, + resourceSettings, + 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. + + VirtualSystemGlobalSettingData vs_gs_data = VirtualSystemGlobalSettingData.CreateInstance(); + vs_gs_data.LateBoundObject["ElementName"] = name; + + System.Management.ManagementPath jobPath; + System.Management.ManagementPath defined_sys; + var ret_val = vmMgmtSvc.DefineVirtualSystem( + new string[0], + null, + vs_gs_data.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20), + out defined_sys, + 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 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 static VirtualSwitchManagementService 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 = VirtualSwitchManagementService.GetInstances(); + foreach (VirtualSwitchManagementService 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; + } + + public static void CreateDynamicVirtualHardDisk(ulong MaxInternalSize, string Path) + { + // Resource settings are changed through the management service + System.Management.ManagementPath jobPath; + var imgMgr = GetImageManagementService(); + var ret_val = imgMgr.CreateDynamicVirtualHardDisk(MaxInternalSize, 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 CreateDynamicVirtualHardDisk size {0}, path {1} to {2}", + MaxInternalSize, + Path, + ReturnCode.ToString(ret_val)); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + } + + public static 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 static 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; + } + + /// + /// Similar to http://msdn.microsoft.com/en-us/library/hh850031%28v=vs.85%29.aspx + /// + /// + /// + 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); + } + + public static void GetProcessorResources(out uint cores, out uint mhz) + { + // Processor processors + cores = 0; + mhz = 0; + Processor.ProcessorCollection procCol = Processor.GetInstances(); + foreach (Processor procInfo in procCol) + { + cores += procInfo.NumberOfCores; + mhz = procInfo.MaxClockSpeed; + } + } + + public static 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 static void GetMemoryResources(out ulong physicalRamKBs, out ulong freeMemoryKBs) + { + OperatingSystem0 os = new OperatingSystem0(); + physicalRamKBs = os.TotalVisibleMemorySize; + freeMemoryKBs = os.FreePhysicalMemory; + } + + public static string GetDefaultVirtualDiskFolder() + { + VirtualSystemManagementServiceSettingData.VirtualSystemManagementServiceSettingDataCollection coll = VirtualSystemManagementServiceSettingData.GetInstances(); + string defaultVirtualHardDiskPath = null; + foreach (VirtualSystemManagementServiceSettingData settings in coll) + { + return 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 static 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 static List GetVmElementNames() + { + List result = new List(); + 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 static 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 static 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 static ResourceAllocationSettingData GetDvdDriveSettings(VirtualSystemSettingData vmSettings) + { + var wmiObjCollection = GetResourceAllocationSettings(vmSettings); + + foreach (ResourceAllocationSettingData wmiObj in wmiObjCollection) + { + 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 static ResourceAllocationSettingData GetIDEControllerSettings(VirtualSystemSettingData vmSettings, string cntrllerAddr) + { + var wmiObjCollection = GetResourceAllocationSettings(vmSettings); + + foreach (ResourceAllocationSettingData wmiObj in wmiObjCollection) + { + if (wmiObj.ResourceSubType == "Microsoft Emulated 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; + } + + /// + /// 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 + /// + /// + /// + public static 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 static SwitchPort[] GetSwitchPorts(ComputerSystem vm) + { + var virtSwitchMgmtSvc = GetVirtualSwitchManagementService(); + // Get NIC path + var condition = string.Format("ElementName=\"{0}\"", vm.ElementName); + var switchPortCollection = SwitchPort.GetInstances(virtSwitchMgmtSvc.Scope, condition); + + List result = new List(switchPortCollection.Count); + foreach (SwitchPort item in switchPortCollection) + { + result.Add(item); + } + return result.ToArray(); + } + + + /// + /// Deprecated + /// + /// + /// + public static SwitchPort GetSwitchPort(SyntheticEthernetPort nic) + { + // An ASSOCIATOR object provides the cross reference between WMI objects, + // 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}", wmiObject.path, resultclassName); + // + var wmiObjQuery = new RelatedObjectQuery(nic.Path.Path, VmLANEndpoint.CreatedClassName); + + // NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain + // the virtualisation objects. + var wmiObjectSearch = new ManagementObjectSearcher(nic.Scope, wmiObjQuery); + var wmiObjCollection = new VmLANEndpoint.VmLANEndpointCollection(wmiObjectSearch.Get()); + + // assert + if (wmiObjCollection.Count < 1) + { + var errMsg = string.Format("No VmLANEndpoint for external NIC {0} on Hyper-V server", nic.Name); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + + VmLANEndpoint vmEndPoint = wmiObjCollection.OfType().First(); + var switchPortQuery = new RelatedObjectQuery(vmEndPoint.Path.Path, SwitchPort.CreatedClassName); + var switchPortSearch = new ManagementObjectSearcher(nic.Scope, switchPortQuery); + var switchPortCollection = new SwitchPort.SwitchPortCollection(switchPortSearch.Get()); + + // assert + if (switchPortCollection.Count < 1) + { + var errMsg = string.Format("No SwitchPort for external NIC {0} on Hyper-V server", nic.Name); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + + SwitchPort switchPort = wmiObjCollection.OfType().First(); + + return switchPort; + } + + public static SyntheticEthernetPortSettingData[] GetEthernetPorts(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 results = new List(wmiObjCollection.Count); + foreach (SyntheticEthernetPortSettingData item in wmiObjCollection) + { + results.Add(item); + } + + return results.ToArray(); + } + + public static 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.SettingType == 3) + { + 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; + } + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs new file mode 100755 index 0000000..7557320 --- /dev/null +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs @@ -0,0 +1,500 @@ +// 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; + +namespace HypervResource +{ + public class WmiCallsV2 + { + public static String CloudStackUserDataKey = "cloudstack-vm-userdata"; + + public static void Initialize() + { + // Trigger assembly load into curren appdomain + } + + private static ILog logger = LogManager.GetLogger(typeof(WmiCallsV2)); + + /// + /// Returns ComputerSystem lacking any NICs and VOLUMEs + /// + public static 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; + } + + /// + /// Returns ComputerSystem lacking any NICs and VOLUMEs + /// + public static 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; + } + } + + public static 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; + } + + /// + /// Similar to http://msdn.microsoft.com/en-us/library/hh850031%28v=vs.85%29.aspx + /// + /// + /// + 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); + } + + public static 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 static List GetVmElementNames() + { + List result = new List(); + 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 static 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 static 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 class WmiException : Exception + { + public WmiException() + { + } + + public WmiException(string message) + : base(message) + { + } + + public WmiException(string message, Exception inner) + : base(message, inner) + { + } + } + + /// + /// Covers V2 API, see + /// http://msdn.microsoft.com/en-us/library/hh850031%28v=vs.85%29.aspx + /// + 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; + } + } + + /// + /// Covers V2 API, see + /// http://msdn.microsoft.com/en-us/library/hh850031%28v=vs.85%29.aspx + /// + 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; + } + } + + /// + /// 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) + /// + 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 = 32768; // Pauses the VM. + public const UInt16 Suspended = 32769; // 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 Test: result = "Test"; break; + case Defer: result = "Defer"; break; + case Quiesce: result = "Quiesce"; break; + case Reboot: result = "Reboot"; break; + case Reset: result = "Reset"; break; + } + return result; + } + } + + /// + /// 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 + /// + public class EnabledState + { + /// + /// The state of the VM could not be determined. + /// + public const UInt16 Unknown = 0; + /// + /// The VM is running. + /// + public const UInt16 Enabled = 2; + /// + /// The VM is turned off. + /// + public const UInt16 Disabled = 3; + /// + /// The VM is paused. + /// + public const UInt16 Paused = 32768; + /// + /// The VM is in a saved state. + /// + public const UInt16 Suspended = 32769; + /// + /// 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). + /// + public const UInt16 Starting = 32770; + /// + /// 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). + /// + public const UInt16 Snapshotting = 32771; + /// + /// 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). + /// + public const UInt16 Saving = 32773; + /// + /// 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. + /// + public const UInt16 Stopping = 32774; + /// + /// 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). + /// + public const UInt16 Pausing = 32776; + /// + /// The VM is resuming from a paused state. This is a transitional state between 32768 (Paused) and 2 (Enabled). + /// + 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; + } + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/packages.config ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/packages.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/packages.config new file mode 100644 index 0000000..b0f2ace --- /dev/null +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file