// 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 : IWmiCallsV2 { public static String CloudStackUserDataKey = "cloudstack-vm-userdata"; public static void Initialize() { // Trigger assembly load into curren appdomain } /// /// Returns ping status of the given ip /// private static ILog logger = LogManager.GetLogger(typeof(WmiCallsV2)); public static String PingHost(String ip) { return "Success"; } /// /// Returns ComputerSystem lacking any NICs and VOLUMEs /// 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; } /// /// Returns ComputerSystem lacking any NICs and VOLUMEs /// 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; } } 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; } /// /// 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 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 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 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 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; } } }