| // 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 Amazon; |
| using Amazon.S3; |
| using Amazon.S3.Model; |
| using log4net; |
| using Microsoft.CSharp.RuntimeBinder; |
| using Newtonsoft.Json; |
| using Newtonsoft.Json.Linq; |
| using System; |
| using System.Collections; |
| using System.Collections.Specialized; |
| using System.Collections.Generic; |
| using System.Configuration; |
| using System.IO; |
| using System.Linq; |
| using System.Net; |
| using System.Net.Http; |
| using System.Text; |
| using System.Security.Cryptography; |
| using System.Security.Principal; |
| using System.Web.Http; |
| using CloudStack.Plugin.WmiWrappers.ROOT.VIRTUALIZATION.V2; |
| |
| namespace HypervResource |
| { |
| |
| public struct HypervResourceControllerConfig |
| { |
| private string privateIpAddress; |
| private static ILog logger = LogManager.GetLogger(typeof(HypervResourceControllerConfig)); |
| |
| public string PrivateIpAddress |
| { |
| get |
| { |
| return privateIpAddress; |
| } |
| set |
| { |
| ValidateIpAddress(value); |
| privateIpAddress = value; |
| System.Net.NetworkInformation.NetworkInterface nic = HypervResourceController.GetNicInfoFromIpAddress(privateIpAddress, out PrivateNetmask); |
| PrivateMacAddress = nic.GetPhysicalAddress().ToString(); |
| } |
| } |
| |
| private static void ValidateIpAddress(string value) |
| { |
| // Convert to IP address |
| IPAddress ipAddress; |
| if (!IPAddress.TryParse(value, out ipAddress)) |
| { |
| String errMsg = "Invalid PrivateIpAddress: " + value; |
| logger.Error(errMsg); |
| throw new ArgumentException(errMsg); |
| } |
| } |
| public string GatewayIpAddress; |
| public string PrivateMacAddress; |
| public string PrivateNetmask; |
| public string StorageNetmask; |
| public string StorageMacAddress; |
| public string StorageIpAddress; |
| public long RootDeviceReservedSpaceBytes; |
| public string RootDeviceName; |
| public ulong ParentPartitionMinMemoryMb; |
| public string LocalSecondaryStoragePath; |
| |
| private string getPrimaryKey(string id) |
| { |
| return "primary_storage_" + id; |
| } |
| |
| public string getPrimaryStorage(string id) |
| { |
| NameValueCollection settings = ConfigurationManager.AppSettings; |
| return settings.Get(getPrimaryKey(id)); |
| } |
| |
| public void setPrimaryStorage(string id, string path) |
| { |
| Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); |
| KeyValueConfigurationCollection settings = config.AppSettings.Settings; |
| string key = getPrimaryKey(id); |
| if (settings[key] != null) |
| { |
| settings.Remove(key); |
| } |
| settings.Add(key, path); |
| config.Save(ConfigurationSaveMode.Modified); |
| ConfigurationManager.RefreshSection("appSettings"); |
| } |
| |
| public List<string> getAllPrimaryStorages() |
| { |
| List<string> poolPaths = new List<string>(); |
| Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); |
| KeyValueConfigurationCollection settings = config.AppSettings.Settings; |
| foreach (string key in settings.AllKeys) |
| { |
| if (key.Contains("primary_storage_")) |
| { |
| poolPaths.Add(settings[key].Value); |
| } |
| } |
| return poolPaths; |
| } |
| } |
| |
| /// <summary> |
| /// Supports one HTTP GET and multiple HTTP POST URIs |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// POST takes dynamic to allow it to receive JSON without concern for what is the underlying object. |
| /// E.g. http://stackoverflow.com/questions/14071715/passing-dynamic-json-object-to-web-api-newtonsoft-example |
| /// and http://stackoverflow.com/questions/3142495/deserialize-json-into-c-sharp-dynamic-object |
| /// Use ActionName attribute to allow multiple POST URLs, one for each supported command |
| /// E.g. http://stackoverflow.com/a/12703423/939250 |
| /// Strictly speaking, this goes against the purpose of an ApiController, which is to provide one GET/POST/PUT/DELETE, etc. |
| /// However, it reduces the amount of code by removing the need for a switch according to the incoming command type. |
| /// http://weblogs.asp.net/fredriknormen/archive/2012/06/11/asp-net-web-api-exception-handling.aspx |
| /// </para> |
| /// <para> |
| /// Exceptions handled on command by command basis rather than globally to allow details of the command |
| /// to be reflected in the response. Default error handling is in the catch for Exception, but |
| /// other exception types may be caught where the feedback would be different. |
| /// NB: global alternatives discussed at |
| /// http://weblogs.asp.net/fredriknormen/archive/2012/06/11/asp-net-web-api-exception-handling.aspx |
| /// </para> |
| /// </remarks> |
| public class HypervResourceController : ApiController |
| { |
| public static void Configure(HypervResourceControllerConfig config) |
| { |
| HypervResourceController.config = config; |
| wmiCallsV2 = new WmiCallsV2(); |
| } |
| |
| public static HypervResourceControllerConfig config = new HypervResourceControllerConfig(); |
| |
| private static ILog logger = LogManager.GetLogger(typeof(HypervResourceController)); |
| Dictionary<String, String> contextMap = new Dictionary<String, String>(); |
| |
| public static void Initialize() |
| { |
| } |
| |
| public static IWmiCallsV2 wmiCallsV2 { get; set;} |
| |
| // GET api/HypervResource |
| public string Get() |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| return "HypervResource controller running, use POST to send JSON encoded RPCs"; ; |
| } |
| } |
| |
| /// <summary> |
| /// NOP - placeholder for future setup, e.g. delete existing VMs or Network ports |
| /// POST api/HypervResource/SetupCommand |
| /// </summary> |
| /// <param name="cmd"></param> |
| /// <returns></returns> |
| /// TODO: produce test |
| [HttpPost] |
| [ActionName(CloudStackTypes.SetupCommand)] |
| public JContainer SetupCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.SetupCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| |
| try |
| { |
| result = true; |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.SetupCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = "success - NOP", |
| _reconnect = false, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.SetupAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/AttachCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.AttachCommand)] |
| public JContainer AttachCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.AttachCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| |
| try |
| { |
| string vmName = (string)cmd.vmName; |
| DiskTO disk = DiskTO.ParseJson(cmd.disk); |
| |
| if (disk.type.Equals("ISO")) |
| { |
| TemplateObjectTO dataStore = disk.templateObjectTO; |
| NFSTO share = dataStore.nfsDataStoreTO; |
| string diskPath = Utils.NormalizePath(Path.Combine(share.UncPath, dataStore.path)); |
| wmiCallsV2.AttachIso(vmName, diskPath); |
| result = true; |
| } |
| else if (disk.type.Equals("DATADISK")) |
| { |
| VolumeObjectTO volume = disk.volumeObjectTO; |
| string diskPath = Utils.NormalizePath(volume.FullFileName); |
| wmiCallsV2.AttachDisk(vmName, diskPath, disk.diskSequence); |
| result = true; |
| } |
| else |
| { |
| details = "Invalid disk type to be attached to vm " + vmName; |
| } |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.AttachCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| disk = cmd.disk, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.AttachAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/DetachCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.DettachCommand)] |
| public JContainer DetachCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.DettachCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| |
| try |
| { |
| string vmName = (string)cmd.vmName; |
| DiskTO disk = DiskTO.ParseJson(cmd.disk); |
| |
| if (disk.type.Equals("ISO")) |
| { |
| TemplateObjectTO dataStore = disk.templateObjectTO; |
| NFSTO share = dataStore.nfsDataStoreTO; |
| string diskPath = Utils.NormalizePath(Path.Combine(share.UncPath, dataStore.path)); |
| wmiCallsV2.DetachDisk(vmName, diskPath); |
| result = true; |
| } |
| else if (disk.type.Equals("DATADISK")) |
| { |
| VolumeObjectTO volume = disk.volumeObjectTO; |
| string diskPath = Utils.NormalizePath(volume.FullFileName); |
| wmiCallsV2.DetachDisk(vmName, diskPath); |
| result = true; |
| } |
| else |
| { |
| details = "Invalid disk type to be dettached from vm " + vmName; |
| } |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.DettachCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.DettachAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/RebootCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.RebootCommand)] |
| public JContainer RebootCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.RebootCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| |
| try |
| { |
| string vmName = (string)cmd.vmName; |
| var sys = wmiCallsV2.GetComputerSystem(vmName); |
| if (sys == null) |
| { |
| details = CloudStackTypes.RebootCommand + " requested unknown VM " + vmName; |
| logger.Error(details); |
| } |
| else |
| { |
| wmiCallsV2.SetState(sys, RequiredState.Reset); |
| result = true; |
| } |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.RebootCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.RebootAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/DestroyCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.DestroyCommand)] |
| public JContainer DestroyCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.DestroyCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| |
| try |
| { |
| // Assert |
| String errMsg = "No 'volume' details in " + CloudStackTypes.DestroyCommand + " " + Utils.CleanString(cmd.ToString()); |
| if (cmd.volume == null) |
| { |
| logger.Error(errMsg); |
| throw new ArgumentException(errMsg); |
| } |
| |
| // Assert |
| errMsg = "No valid path in DestroyCommand in " + CloudStackTypes.DestroyCommand + " " + (String)cmd.ToString(); |
| if (cmd.volume.path == null) |
| { |
| logger.Error(errMsg); |
| throw new ArgumentException(errMsg); |
| } |
| |
| String path = (string)cmd.volume.path; |
| if (!File.Exists(path)) |
| { |
| logger.Info(CloudStackTypes.DestroyCommand + ", but volume at pass already deleted " + path); |
| } |
| |
| string vmName = (string)cmd.vmName; |
| if (!string.IsNullOrEmpty(vmName) && File.Exists(path)) |
| { |
| // Make sure that this resource is removed from the VM |
| wmiCallsV2.DetachDisk(vmName, path); |
| } |
| |
| File.Delete(path); |
| result = true; |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.DestroyCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer); |
| } |
| } |
| |
| // POST api/HypervResource/DeleteCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.DeleteCommand)] |
| public JContainer DeleteCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.DestroyCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| |
| try |
| { |
| // Assert |
| String errMsg = "No 'volume' details in " + CloudStackTypes.DestroyCommand + " " + Utils.CleanString(cmd.ToString()); |
| VolumeObjectTO destVolumeObjectTO = VolumeObjectTO.ParseJson(cmd.data); |
| |
| if (destVolumeObjectTO.name == null) |
| { |
| logger.Error(errMsg); |
| throw new ArgumentException(errMsg); |
| } |
| |
| String path = destVolumeObjectTO.FullFileName; |
| if (!File.Exists(path)) |
| { |
| logger.Info(CloudStackTypes.DestroyCommand + ", but volume at pass already deleted " + path); |
| } |
| |
| string vmName = (string)cmd.vmName; |
| if (!string.IsNullOrEmpty(vmName) && File.Exists(path)) |
| { |
| // Make sure that this resource is removed from the VM |
| wmiCallsV2.DetachDisk(vmName, path); |
| } |
| |
| File.Delete(path); |
| result = true; |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.DestroyCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer); |
| } |
| } |
| |
| |
| private static JArray ReturnCloudStackTypedJArray(object ansContent, string ansType) |
| { |
| JObject ansObj = Utils.CreateCloudStackObject(ansType, ansContent); |
| JArray answer = new JArray(ansObj); |
| logger.Info(Utils.CleanString(ansObj.ToString())); |
| return answer; |
| } |
| |
| // POST api/HypervResource/CreateCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.CreateCommand)] |
| public JContainer CreateCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.CreateCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| VolumeInfo volume = new VolumeInfo(); |
| |
| try |
| { |
| string diskType = cmd.diskCharacteristics.type; |
| ulong disksize = cmd.diskCharacteristics.size; |
| string templateUri = cmd.templateUrl; |
| |
| // assert: valid storagepool? |
| string poolTypeStr = cmd.pool.type; |
| string poolLocalPath = cmd.pool.path; |
| string poolUuid = cmd.pool.uuid; |
| string newVolPath = null; |
| long volId = cmd.volId; |
| string newVolName = null; |
| |
| if (ValidStoragePool(poolTypeStr, poolLocalPath, poolUuid, ref details)) |
| { |
| // No template URI? Its a blank disk. |
| if (string.IsNullOrEmpty(templateUri)) |
| { |
| // assert |
| VolumeType volType; |
| if (!Enum.TryParse<VolumeType>(diskType, out volType) && volType != VolumeType.DATADISK) |
| { |
| details = "Cannot create volumes of type " + (string.IsNullOrEmpty(diskType) ? "NULL" : diskType); |
| } |
| else |
| { |
| newVolName = cmd.diskCharacteristics.name; |
| newVolPath = Path.Combine(poolLocalPath, newVolName, diskType.ToLower()); |
| // TODO: make volume format and block size configurable |
| wmiCallsV2.CreateDynamicVirtualHardDisk(disksize, newVolPath); |
| if (File.Exists(newVolPath)) |
| { |
| result = true; |
| } |
| else |
| { |
| details = "Failed to create DATADISK with name " + newVolName; |
| } |
| } |
| } |
| else |
| { |
| // TODO: Does this always work, or do I need to download template at times? |
| if (templateUri.Contains("/") || templateUri.Contains("\\")) |
| { |
| details = "Problem with templateURL " + templateUri + |
| " the URL should be volume UUID in primary storage created by previous PrimaryStorageDownloadCommand"; |
| logger.Error(details); |
| } |
| else |
| { |
| logger.Debug("Template's name in primary store should be " + templateUri); |
| // HypervPhysicalDisk BaseVol = primaryPool.getPhysicalDisk(tmplturl); |
| FileInfo srcFileInfo = new FileInfo(templateUri); |
| newVolName = Guid.NewGuid() + srcFileInfo.Extension; |
| newVolPath = Path.Combine(poolLocalPath, newVolName); |
| logger.Debug("New volume will be at " + newVolPath); |
| string oldVolPath = Path.Combine(poolLocalPath, templateUri); |
| File.Copy(oldVolPath, newVolPath); |
| if (File.Exists(newVolPath)) |
| { |
| result = true; |
| } |
| else |
| { |
| details = "Failed to create DATADISK with name " + newVolName; |
| } |
| } |
| volume = new VolumeInfo( |
| volId, diskType, |
| poolTypeStr, poolUuid, newVolName, |
| newVolPath, newVolPath, (long)disksize, null); |
| } |
| } |
| } |
| catch (Exception sysEx) |
| { |
| // TODO: consider this as model for error processing in all commands |
| details = CloudStackTypes.CreateCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| volume = volume, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CreateAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/PrimaryStorageDownloadCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.PrimaryStorageDownloadCommand)] |
| public JContainer PrimaryStorageDownloadCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.PrimaryStorageDownloadCommand + Utils.CleanString(cmd.ToString())); |
| string details = null; |
| bool result = false; |
| long size = 0; |
| string newCopyFileName = null; |
| |
| string poolLocalPath = cmd.localPath; |
| if (!Directory.Exists(poolLocalPath)) |
| { |
| details = "None existent local path " + poolLocalPath; |
| } |
| else |
| { |
| // Compose name for downloaded file. |
| string sourceUrl = cmd.url; |
| if (sourceUrl.ToLower().EndsWith(".vhd")) |
| { |
| newCopyFileName = Guid.NewGuid() + ".vhd"; |
| } |
| if (sourceUrl.ToLower().EndsWith(".vhdx")) |
| { |
| newCopyFileName = Guid.NewGuid() + ".vhdx"; |
| } |
| |
| // assert |
| if (newCopyFileName == null) |
| { |
| details = CloudStackTypes.PrimaryStorageDownloadCommand + " Invalid file extension for hypervisor type in source URL " + sourceUrl; |
| logger.Error(details); |
| } |
| else |
| { |
| try |
| { |
| FileInfo newFile; |
| if (CopyURI(sourceUrl, newCopyFileName, poolLocalPath, out newFile, ref details)) |
| { |
| size = newFile.Length; |
| result = true; |
| } |
| } |
| catch (System.Exception ex) |
| { |
| details = CloudStackTypes.PrimaryStorageDownloadCommand + " Cannot download source URL " + sourceUrl + " due to " + ex.Message; |
| logger.Error(details, ex); |
| } |
| } |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| templateSize = size, |
| installPath = newCopyFileName, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.PrimaryStorageDownloadAnswer); |
| } |
| } |
| |
| private static bool ValidStoragePool(string poolTypeStr, string poolLocalPath, string poolUuid, ref string details) |
| { |
| StoragePoolType poolType; |
| if (!Enum.TryParse<StoragePoolType>(poolTypeStr, out poolType) || poolType != StoragePoolType.Filesystem) |
| { |
| details = "Primary storage pool " + poolUuid + " type " + poolType + " local path " + poolLocalPath + " has invalid StoragePoolType"; |
| logger.Error(details); |
| return false; |
| } |
| else if (!Directory.Exists(poolLocalPath)) |
| { |
| details = "Primary storage pool " + poolUuid + " type " + poolType + " local path " + poolLocalPath + " has invalid local path"; |
| logger.Error(details); |
| return false; |
| } |
| return true; |
| } |
| |
| /// <summary> |
| /// Exceptions to watch out for: |
| /// Exceptions related to URI creation |
| /// System.SystemException |
| /// +-System.ArgumentNullException |
| /// +-System.FormatException |
| /// +-System.UriFormatException |
| /// |
| /// Exceptions related to NFS URIs |
| /// System.SystemException |
| /// +-System.NotSupportedException |
| /// +-System.ArgumentException |
| /// +-System.ArgumentNullException |
| /// +-System.Security.SecurityException; |
| /// +-System.UnauthorizedAccessException |
| /// +-System.IO.IOException |
| /// +-System.IO.PathTooLongException |
| /// |
| /// Exceptions related to HTTP URIs |
| /// System.SystemException |
| /// +-System.InvalidOperationException |
| /// +-System.Net.WebException |
| /// +-System.NotSupportedException |
| /// +-System.ArgumentNullException |
| /// </summary> |
| /// <param name="sourceUri"></param> |
| /// <param name="newCopyFileName"></param> |
| /// <param name="poolLocalPath"></param> |
| /// <returns></returns> |
| private bool CopyURI(string sourceUri, string newCopyFileName, string poolLocalPath, out FileInfo newFile, ref string details) |
| { |
| Uri source = new Uri(sourceUri); |
| String destFilePath = Path.Combine(poolLocalPath, newCopyFileName); |
| string[] pathSegments = source.Segments; |
| String templateUUIDandExtension = pathSegments[pathSegments.Length - 1]; |
| newFile = new FileInfo(destFilePath); |
| |
| // NFS URI assumed to already be mounted locally. Mount location given by settings. |
| if (source.Scheme.ToLower().Equals("nfs")) |
| { |
| String srcDiskPath = Path.Combine(HypervResourceController.config.LocalSecondaryStoragePath, templateUUIDandExtension); |
| String taskMsg = "Copy NFS url in " + sourceUri + " at " + srcDiskPath + " to pool " + poolLocalPath; |
| logger.Debug(taskMsg); |
| File.Copy(srcDiskPath, destFilePath); |
| } |
| else if (source.Scheme.ToLower().Equals("http") || source.Scheme.ToLower().Equals("https")) |
| { |
| System.Net.WebClient webclient = new WebClient(); |
| webclient.DownloadFile(source, destFilePath); |
| } |
| else |
| { |
| details = "Unsupported URI scheme " + source.Scheme.ToLower() + " in source URI " + sourceUri; |
| logger.Error(details); |
| return false; |
| } |
| |
| if (!File.Exists(destFilePath)) |
| { |
| details = "Filed to copy " + sourceUri + " to primary pool destination " + destFilePath; |
| logger.Error(details); |
| return false; |
| } |
| return true; |
| } |
| |
| // POST api/HypervResource/CheckHealthCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.CheckHealthCommand)] |
| public JContainer CheckHealthCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.CheckHealthCommand + Utils.CleanString(cmd.ToString())); |
| object ansContent = new |
| { |
| result = true, |
| details = "resource is alive", |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CheckHealthAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/CheckOnHostCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.CheckOnHostCommand)] |
| public JContainer CheckOnHostCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.CheckOnHostCommand + Utils.CleanString(cmd.ToString())); |
| string details = "host is not alive"; |
| bool result = true; |
| try |
| { |
| foreach (string poolPath in config.getAllPrimaryStorages()) |
| { |
| if (IsHostAlive(poolPath, (string)cmd.host.privateNetwork.ip)) |
| { |
| result = false; |
| details = "host is alive"; |
| break; |
| } |
| } |
| } |
| catch (Exception e) |
| { |
| logger.Error("Error Occurred in " + CloudStackTypes.CheckOnHostCommand + " : " + e.Message); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CheckOnHostAnswer); |
| } |
| } |
| |
| private bool IsHostAlive(string poolPath, string privateIp) |
| { |
| bool hostAlive = false; |
| try |
| { |
| string hbFile = Path.Combine(poolPath, "hb-" + privateIp); |
| FileInfo file = new FileInfo(hbFile); |
| using (StreamReader sr = file.OpenText()) |
| { |
| string epoch = sr.ReadLine(); |
| string[] dateTime = epoch.Split('@'); |
| string[] date = dateTime[0].Split('-'); |
| string[] time = dateTime[1].Split(':'); |
| DateTime epochTime = new DateTime(Convert.ToInt32(date[0]), Convert.ToInt32(date[1]), Convert.ToInt32(date[2]), Convert.ToInt32(time[0]), |
| Convert.ToInt32(time[1]), Convert.ToInt32(time[2]), DateTimeKind.Utc); |
| DateTime currentTime = DateTime.UtcNow; |
| DateTime ThreeMinuteLaterEpoch = epochTime.AddMinutes(3); |
| if (currentTime.CompareTo(ThreeMinuteLaterEpoch) < 0) |
| { |
| hostAlive = true; |
| } |
| sr.Close(); |
| } |
| } |
| catch (Exception e) |
| { |
| logger.Info("Exception occurred in verifying host " + e.Message); |
| } |
| |
| return hostAlive; |
| } |
| |
| // POST api/HypervResource/CheckSshCommand |
| // TODO: create test |
| [HttpPost] |
| [ActionName(CloudStackTypes.CheckSshCommand)] |
| public JContainer CheckSshCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.CheckSshCommand + Utils.CleanString(cmd.ToString())); |
| object ansContent = new |
| { |
| result = true, |
| details = "NOP, TODO: implement properly", |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CheckSshAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/CheckVirtualMachineCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.CheckVirtualMachineCommand)] |
| public JContainer CheckVirtualMachineCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.CheckVirtualMachineCommand + Utils.CleanString(cmd.ToString())); |
| string details = null; |
| bool result = false; |
| string vmName = cmd.vmName; |
| string powerState = null; |
| |
| // TODO: Look up the VM, convert Hyper-V state to CloudStack version. |
| var sys = wmiCallsV2.GetComputerSystem(vmName); |
| if (sys == null) |
| { |
| details = CloudStackTypes.CheckVirtualMachineCommand + " requested unknown VM " + vmName; |
| logger.Error(details); |
| } |
| else |
| { |
| powerState = EnabledState.ToCloudStackPowerState(sys.EnabledState); |
| result = true; |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| powerstate = powerState, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CheckVirtualMachineAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/DeleteStoragePoolCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.DeleteStoragePoolCommand)] |
| public JContainer DeleteStoragePoolCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.DeleteStoragePoolCommand + Utils.CleanString(cmd.ToString())); |
| object ansContent = new |
| { |
| result = true, |
| details = "Current implementation does not delete local path corresponding to storage pool!", |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer); |
| } |
| } |
| |
| /// <summary> |
| /// NOP - legacy command - |
| /// POST api/HypervResource/CreateStoragePoolCommand |
| /// </summary> |
| /// <param name="cmd"></param> |
| /// <returns></returns> |
| [HttpPost] |
| [ActionName(CloudStackTypes.CreateStoragePoolCommand)] |
| public JContainer CreateStoragePoolCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.CreateStoragePoolCommand + Utils.CleanString(cmd.ToString())); |
| object ansContent = new |
| { |
| result = true, |
| details = "success - NOP", |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer); |
| } |
| } |
| |
| // POST api/HypervResource/ModifyStoragePoolCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.ModifyStoragePoolCommand)] |
| public JContainer ModifyStoragePoolCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.ModifyStoragePoolCommand + Utils.CleanString(cmd.ToString())); |
| string details = null; |
| string localPath; |
| StoragePoolType poolType; |
| long capacityBytes = 0; |
| long availableBytes = 0; |
| string hostPath = null; |
| bool result = false; |
| var tInfo = new Dictionary<string, string>(); |
| object ansContent; |
| |
| try |
| { |
| result = ValidateStoragePoolCommand(cmd, out localPath, out poolType, ref details); |
| if (!result) |
| { |
| ansContent = new |
| { |
| result = result, |
| details = details, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer); |
| } |
| |
| if (poolType == StoragePoolType.Filesystem) |
| { |
| hostPath = localPath; |
| GetCapacityForPath(hostPath, out capacityBytes, out availableBytes); |
| } |
| else if (poolType == StoragePoolType.NetworkFilesystem || |
| poolType == StoragePoolType.SMB) |
| { |
| NFSTO share = new NFSTO(); |
| String uriStr = "cifs://" + (string)cmd.pool.host + (string)cmd.pool.path; |
| share.uri = new Uri(uriStr); |
| hostPath = Utils.NormalizePath(share.UncPath); |
| |
| // Check access to share. |
| Utils.GetShareDetails(hostPath, out capacityBytes, out availableBytes); |
| config.setPrimaryStorage((string)cmd.pool.uuid, hostPath); |
| } |
| else |
| { |
| result = false; |
| } |
| } |
| catch |
| { |
| result = false; |
| details = String.Format("Failed to add storage pool {0}, please verify your pool details", (string)cmd.pool.uuid); |
| logger.Error(details); |
| } |
| |
| String uuid = null; |
| var poolInfo = new |
| { |
| uuid = uuid, |
| host = cmd.pool.host, |
| hostPath = cmd.pool.path, |
| localPath = hostPath, |
| poolType = cmd.pool.type, |
| capacityBytes = capacityBytes, |
| availableBytes = availableBytes |
| }; |
| |
| |
| ansContent = new |
| { |
| result = result, |
| details = details, |
| localPath = hostPath, |
| templateInfo = tInfo, |
| poolInfo = poolInfo, |
| contextMap = contextMap |
| }; |
| |
| if (result) |
| { |
| try |
| { |
| if ((bool)cmd.add) |
| { |
| logger.Info("Adding HeartBeat Task to task scheduler for pool " + (string)cmd.pool.uuid); |
| Utils.AddHeartBeatTask((string)cmd.pool.uuid, hostPath, config.PrivateIpAddress); |
| } |
| else |
| { |
| logger.Info("Deleting HeartBeat Task from task scheduler for pool " + (string)cmd.pool.uuid); |
| Utils.RemoveHeartBeatTask(cmd.pool.uuid); |
| } |
| } |
| catch (Exception e) |
| { |
| logger.Error("Error occurred in adding/delete HeartBeat Task to/from Task Scheduler : " + e.Message); |
| } |
| } |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.ModifyStoragePoolAnswer); |
| } |
| } |
| |
| private bool ValidateStoragePoolCommand(dynamic cmd, out string localPath, out StoragePoolType poolType, ref string details) |
| { |
| dynamic pool = cmd.pool; |
| string poolTypeStr = pool.type; |
| localPath = cmd.localPath; |
| if (!Enum.TryParse<StoragePoolType>(poolTypeStr, out poolType)) |
| { |
| details = "Request to create / modify unsupported pool type: " + (poolTypeStr == null ? "NULL" : poolTypeStr) + "in cmd " + JsonConvert.SerializeObject(cmd); |
| logger.Error(details); |
| return false; |
| } |
| |
| if (poolType != StoragePoolType.Filesystem && |
| poolType != StoragePoolType.NetworkFilesystem && |
| poolType != StoragePoolType.SMB) |
| { |
| details = "Request to create / modify unsupported pool type: " + (poolTypeStr == null ? "NULL" : poolTypeStr) + "in cmd " + JsonConvert.SerializeObject(cmd); |
| logger.Error(details); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // POST api/HypervResource/PlugNicCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.PlugNicCommand)] |
| public JContainer PlugNicCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.PlugNicCommand + Utils.CleanString(cmd.ToString())); |
| object ansContent = new |
| { |
| result = true, |
| details = "Hot Nic plug not supported, change any empty virtual network adapter network settings", |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.PlugNicAnswer); |
| } |
| } |
| |
| |
| // POST api/HypervResource/CleanupNetworkRulesCmd |
| [HttpPost] |
| [ActionName(CloudStackTypes.CleanupNetworkRulesCmd)] |
| public JContainer CleanupNetworkRulesCmd([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.CleanupNetworkRulesCmd + Utils.CleanString(cmd.ToString())); |
| object ansContent = new |
| { |
| result = false, |
| details = "nothing to cleanup in our current implementation", |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer); |
| } |
| } |
| |
| // POST api/HypervResource/CheckNetworkCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.CheckNetworkCommand)] |
| public JContainer CheckNetworkCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.CheckNetworkCommand + Utils.CleanString(cmd.ToString())); |
| object ansContent = new |
| { |
| result = true, |
| details = (string)null, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CheckNetworkAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/ReadyCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.ReadyCommand)] |
| public JContainer ReadyCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.ReadyCommand + Utils.CleanString(cmd.ToString())); |
| object ansContent = new |
| { |
| result = true, |
| details = (string)null, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.ReadyAnswer); |
| } |
| |
| } |
| |
| // POST api/HypervResource/StartCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.StartCommand)] |
| public JContainer StartCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.StartCommand + Utils.CleanString(cmd.ToString())); |
| string details = null; |
| bool result = false; |
| |
| try |
| { |
| string systemVmIsoPath = null; |
| String uriStr = (String)cmd.secondaryStorage; |
| if (!String.IsNullOrEmpty(uriStr)) |
| { |
| NFSTO share = new NFSTO(); |
| share.uri = new Uri(uriStr); |
| string defaultDataPath = wmiCallsV2.GetDefaultDataRoot(); |
| string secondaryPath = Utils.NormalizePath(Path.Combine(share.UncPath, "systemvm")); |
| string[] choices = Directory.GetFiles(secondaryPath, "systemvm*.iso"); |
| if (choices.Length != 1) |
| { |
| String errMsg = "Couldn't locate the systemvm iso on " + secondaryPath; |
| logger.Error(errMsg); |
| } |
| else |
| { |
| systemVmIsoPath = choices[0]; |
| } |
| } |
| wmiCallsV2.DeployVirtualMachine(cmd, systemVmIsoPath); |
| result = true; |
| } |
| catch (Exception wmiEx) |
| { |
| details = CloudStackTypes.StartCommand + " fail on exception" + wmiEx.Message; |
| logger.Error(details, wmiEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| vm = cmd.vm, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.StartAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/StopCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.StopCommand)] |
| public JContainer StopCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.StopCommand + Utils.CleanString(cmd.ToString())); |
| string details = null; |
| bool result = false; |
| bool checkBeforeCleanup = cmd.checkBeforeCleanup; |
| String vmName = cmd.vmName; |
| |
| if (checkBeforeCleanup == true) |
| { |
| ComputerSystem vm = wmiCallsV2.GetComputerSystem(vmName); |
| if (vm == null || vm.EnabledState == 2) |
| { |
| // VM is not available or vm is not in running state |
| return ReturnCloudStackTypedJArray(new { result = false, details = "VM is not available or vm is not running on host, bailing out", vm = vmName, contextMap = contextMap }, CloudStackTypes.StopAnswer); |
| } |
| } |
| try |
| { |
| wmiCallsV2.DestroyVm(cmd); |
| result = true; |
| } |
| catch (Exception wmiEx) |
| { |
| details = CloudStackTypes.StopCommand + " fail on exception" + wmiEx.Message; |
| logger.Error(details, wmiEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| vm = cmd.vm, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.StopAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/CreateObjectCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.CreateObjectCommand)] |
| public JContainer CreateObjectCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.CreateObjectCommand + Utils.CleanString(cmd.ToString())); |
| |
| bool result = false; |
| string details = null; |
| object newData = null; |
| try |
| { |
| VolumeObjectTO volume = VolumeObjectTO.ParseJson(cmd.data); |
| PrimaryDataStoreTO primary = volume.primaryDataStore; |
| ulong volumeSize = volume.size; |
| string volumeName = volume.uuid + ".vhdx"; |
| string volumePath = null; |
| |
| if (primary.isLocal) |
| { |
| volumePath = Path.Combine(primary.Path, volumeName); |
| } |
| else |
| { |
| volumePath = @"\\" + primary.uri.Host + primary.uri.LocalPath + @"\" + volumeName; |
| volumePath = Utils.NormalizePath(volumePath); |
| } |
| volume.path = volume.uuid; |
| wmiCallsV2.CreateDynamicVirtualHardDisk(volumeSize, volumePath); |
| if (File.Exists(volumePath)) |
| { |
| result = true; |
| JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.VolumeObjectTO, volume); |
| newData = ansObj; |
| } |
| else |
| { |
| details = "Failed to create disk with name " + volumePath; |
| } |
| } |
| catch (Exception ex) |
| { |
| // Test by providing wrong key |
| details = CloudStackTypes.CreateObjectCommand + " failed on exception, " + ex.Message; |
| logger.Error(details, ex); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| data = newData, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CreateObjectAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/MaintainCommand |
| // TODO: should this be a NOP? |
| [HttpPost] |
| [ActionName(CloudStackTypes.MaintainCommand)] |
| public JContainer MaintainCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.MaintainCommand + Utils.CleanString(cmd.ToString())); |
| |
| object ansContent = new |
| { |
| result = true, |
| willMigrate = true, |
| details = "success - NOP for MaintainCommand", |
| _reconnect = false, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.MaintainAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/PingRoutingCommand |
| // TODO: should this be a NOP? |
| [HttpPost] |
| [ActionName(CloudStackTypes.PingRoutingCommand)] |
| public JContainer PingRoutingCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.PingRoutingCommand + Utils.CleanString(cmd.ToString())); |
| |
| object ansContent = new |
| { |
| result = true, |
| details = "success - NOP for PingRoutingCommand", |
| _reconnect = false, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer); |
| } |
| } |
| |
| // POST api/HypervResource/PingCommand |
| // TODO: should this be a NOP? |
| [HttpPost] |
| [ActionName(CloudStackTypes.PingCommand)] |
| public JContainer PingCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.PingCommand + Utils.CleanString(cmd.ToString())); |
| |
| object ansContent = new |
| { |
| result = true, |
| details = "success - NOP for PingCommand", |
| _reconnect = false, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer); |
| } |
| } |
| |
| // POST api/HypervResource/ModifyVmVnicVlanCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.ModifyVmNicConfigCommand)] |
| public JContainer ModifyVmNicConfigCommand([FromBody]dynamic cmd) |
| { |
| |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.ModifyVmNicConfigCommand + Utils.CleanString(cmd.ToString())); |
| bool result = false; |
| String vmName = cmd.vmName; |
| String vlan = cmd.vlan; |
| string macAddress = cmd.macAddress; |
| uint pos = cmd.index; |
| bool enable = cmd.enable; |
| string switchLableName = cmd.switchLableName; |
| if (macAddress != null) |
| { |
| wmiCallsV2.ModifyVmVLan(vmName, vlan, macAddress); |
| result = true; |
| } |
| else if (pos >= 1) |
| { |
| wmiCallsV2.ModifyVmVLan(vmName, vlan, pos, enable, switchLableName); |
| result = true; |
| } |
| |
| object ansContent = new |
| { |
| vmName = vmName, |
| result = result, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.ModifyVmNicConfigAnswer); |
| } |
| |
| } |
| |
| // POST api/HypervResource/GetVmConfigCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.GetVmConfigCommand)] |
| public JContainer GetVmConfigCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.GetVmConfigCommand + Utils.CleanString(cmd.ToString())); |
| bool result = false; |
| String vmName = cmd.vmName; |
| ComputerSystem vm = wmiCallsV2.GetComputerSystem(vmName); |
| List<NicDetails> nicDetails = new List<NicDetails>(); |
| var nicSettingsViaVm = wmiCallsV2.GetEthernetPortSettings(vm); |
| NicDetails nic = null; |
| int index = 0; |
| int[] nicStates = new int[8]; |
| int[] nicVlan = new int[8]; |
| int vlanid = 1; |
| |
| var ethernetConnections = wmiCallsV2.GetEthernetConnections(vm); |
| foreach (EthernetPortAllocationSettingData item in ethernetConnections) |
| { |
| EthernetSwitchPortVlanSettingData vlanSettings = wmiCallsV2.GetVlanSettings(item); |
| if (vlanSettings == null) |
| { |
| vlanid = -1; |
| } |
| else |
| { |
| vlanid = vlanSettings.AccessVlanId; |
| } |
| nicStates[index] = (Int32)(item.EnabledState); |
| nicVlan[index] = vlanid; |
| index++; |
| } |
| |
| index = 0; |
| foreach (SyntheticEthernetPortSettingData item in nicSettingsViaVm) |
| { |
| nic = new NicDetails(item.Address, nicVlan[index], nicStates[index]); |
| index++; |
| nicDetails.Add(nic); |
| } |
| |
| |
| result = true; |
| |
| object ansContent = new |
| { |
| vmName = vmName, |
| nics = nicDetails, |
| result = result, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetVmConfigAnswer); |
| } |
| } |
| |
| |
| |
| // POST api/HypervResource/GetVmStatsCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.GetVmStatsCommand)] |
| public JContainer GetVmStatsCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.GetVmStatsCommand + Utils.CleanString(cmd.ToString())); |
| bool result = false; |
| JArray vmNamesJson = cmd.vmNames; |
| string[] vmNames = vmNamesJson.ToObject<string[]>(); |
| Dictionary<string, VmStatsEntry> vmProcessorInfo = new Dictionary<string, VmStatsEntry>(vmNames.Length); |
| |
| var vmsToInspect = new List<System.Management.ManagementPath>(); |
| foreach (var vmName in vmNames) |
| { |
| var sys = wmiCallsV2.GetComputerSystem(vmName); |
| if (sys == null) |
| { |
| logger.InfoFormat("GetVmStatsCommand requested unknown VM {0}", vmNames); |
| continue; |
| } |
| var sysInfo = wmiCallsV2.GetVmSettings(sys); |
| vmsToInspect.Add(sysInfo.Path); |
| } |
| |
| wmiCallsV2.GetSummaryInfo(vmProcessorInfo, vmsToInspect); |
| |
| // TODO: Network usage comes from Performance Counter API; however it is only available in kb/s, and not in total terms. |
| // Curious about these? Use perfmon to inspect them, e.g. http://msdn.microsoft.com/en-us/library/xhcx5a20%28v=vs.100%29.aspx |
| // Recent post on these counter at http://blogs.technet.com/b/cedward/archive/2011/07/19/hyper-v-networking-optimizations-part-6-of-6-monitoring-hyper-v-network-consumption.aspx |
| result = true; |
| |
| object ansContent = new |
| { |
| vmStatsMap = vmProcessorInfo, |
| result = result, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetVmStatsAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/CopyCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.CopyCommand)] |
| public JContainer CopyCommand(dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| // Log command *after* we've removed security details from the command. |
| logger.Info(CloudStackTypes.CopyCommand + Utils.CleanString(cmd.ToString())); |
| |
| bool result = false; |
| string details = null; |
| object newData = null; |
| TemplateObjectTO destTemplateObjectTO = null; |
| VolumeObjectTO destVolumeObjectTO = null; |
| VolumeObjectTO srcVolumeObjectTO = null; |
| TemplateObjectTO srcTemplateObjectTO = null; |
| |
| try |
| { |
| srcTemplateObjectTO = TemplateObjectTO.ParseJson(cmd.srcTO); |
| destTemplateObjectTO = TemplateObjectTO.ParseJson(cmd.destTO); |
| srcVolumeObjectTO = VolumeObjectTO.ParseJson(cmd.srcTO); |
| destVolumeObjectTO = VolumeObjectTO.ParseJson(cmd.destTO); |
| |
| string destFile = null; |
| if (destTemplateObjectTO != null) |
| { |
| if (destTemplateObjectTO.path == null) |
| { |
| destTemplateObjectTO.path = System.Guid.NewGuid().ToString(); |
| } |
| if (destTemplateObjectTO.primaryDataStore != null) |
| { |
| destFile = destTemplateObjectTO.FullFileName; |
| } |
| else if (destTemplateObjectTO.nfsDataStoreTO != null) |
| { |
| destFile = destTemplateObjectTO.FullFileName; |
| } |
| } |
| |
| // Create local copy of a template? |
| if (srcTemplateObjectTO != null && destTemplateObjectTO != null) |
| { |
| // S3 download to primary storage? |
| // NFS provider download to primary storage? |
| if ((srcTemplateObjectTO.s3DataStoreTO != null || srcTemplateObjectTO.nfsDataStoreTO != null) && destTemplateObjectTO.primaryDataStore != null) |
| { |
| if (File.Exists(destFile)) |
| { |
| logger.Info("Deleting existing file " + destFile); |
| File.Delete(destFile); |
| } |
| |
| if (srcTemplateObjectTO.s3DataStoreTO != null) |
| { |
| // Download from S3 to destination data storage |
| DownloadS3ObjectToFile(srcTemplateObjectTO.path, srcTemplateObjectTO.s3DataStoreTO, destFile); |
| } |
| else if (srcTemplateObjectTO.nfsDataStoreTO != null) |
| { |
| // Download from S3 to destination data storage |
| Utils.DownloadCifsFileToLocalFile(srcTemplateObjectTO.path, srcTemplateObjectTO.nfsDataStoreTO, destFile); |
| } |
| |
| // Uncompress, as required |
| if (srcTemplateObjectTO.path.EndsWith(".bz2")) |
| { |
| String uncompressedFile = destFile + ".tmp"; |
| String compressedFile = destFile; |
| using (var uncompressedOutStrm = new FileStream(uncompressedFile, FileMode.CreateNew, FileAccess.Write)) |
| { |
| using (var compressedInStrm = new FileStream(destFile, FileMode.Open, FileAccess.Read)) |
| { |
| using (var bz2UncompressorStrm = new Ionic.BZip2.BZip2InputStream(compressedInStrm, true) /* outer 'using' statement will close FileStream*/ ) |
| { |
| int count = 0; |
| int bufsize = 1024 * 1024; |
| byte[] buf = new byte[bufsize]; |
| |
| // EOF returns -1, see http://dotnetzip.codeplex.com/workitem/16069 |
| while (0 < (count = bz2UncompressorStrm.Read(buf, 0, bufsize))) |
| { |
| uncompressedOutStrm.Write(buf, 0, count); |
| } |
| } |
| } |
| } |
| File.Delete(compressedFile); |
| File.Move(uncompressedFile, compressedFile); |
| if (File.Exists(uncompressedFile)) |
| { |
| String errMsg = "Extra file left around called " + uncompressedFile + " when creating " + destFile; |
| logger.Error(errMsg); |
| throw new IOException(errMsg); |
| } |
| } |
| |
| // assert |
| if (!File.Exists(destFile)) |
| { |
| String errMsg = "Failed to create " + destFile + " , because the file is missing"; |
| logger.Error(errMsg); |
| throw new IOException(errMsg); |
| } |
| |
| FileInfo destFileInfo = new FileInfo(destFile); |
| destTemplateObjectTO.size = destFileInfo.Length.ToString(); |
| JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.TemplateObjectTO, destTemplateObjectTO); |
| newData = ansObj; |
| result = true; |
| } |
| else |
| { |
| details = "Data store combination not supported"; |
| } |
| } |
| // Create volume from a template? |
| else if (srcTemplateObjectTO != null && destVolumeObjectTO != null) |
| { |
| // VolumeObjectTO guesses file extension based on existing files |
| // this can be wrong if the previous file had a different file type |
| var guessedDestFile = destVolumeObjectTO.FullFileName; |
| if (File.Exists(guessedDestFile)) |
| { |
| logger.Info("Deleting existing file " + guessedDestFile); |
| File.Delete(guessedDestFile); |
| } |
| |
| destVolumeObjectTO.format = srcTemplateObjectTO.format; |
| destFile = destVolumeObjectTO.FullFileName; |
| if (File.Exists(destFile)) |
| { |
| logger.Info("Deleting existing file " + destFile); |
| File.Delete(destFile); |
| } |
| |
| string srcFile = srcTemplateObjectTO.FullFileName; |
| if (!File.Exists(srcFile)) |
| { |
| details = "Local template file missing from " + srcFile; |
| } |
| else |
| { |
| // TODO: thin provision instead of copying the full file. |
| File.Copy(srcFile, destFile); |
| destVolumeObjectTO.path = destVolumeObjectTO.uuid; |
| JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.VolumeObjectTO, destVolumeObjectTO); |
| newData = ansObj; |
| result = true; |
| } |
| } |
| else if (srcVolumeObjectTO != null && destVolumeObjectTO != null) |
| { |
| var guessedDestFile = destVolumeObjectTO.FullFileName; |
| if (File.Exists(guessedDestFile)) |
| { |
| logger.Info("Deleting existing file " + guessedDestFile); |
| File.Delete(guessedDestFile); |
| } |
| |
| destVolumeObjectTO.format = srcVolumeObjectTO.format; |
| destFile = destVolumeObjectTO.FullFileName; |
| if (File.Exists(destFile)) |
| { |
| logger.Info("Deleting existing file " + destFile); |
| File.Delete(destFile); |
| } |
| |
| string srcFile = srcVolumeObjectTO.FullFileName; |
| if (!File.Exists(srcFile)) |
| { |
| details = "Local template file missing from " + srcFile; |
| } |
| else |
| { |
| // Create the directory before copying the files. CreateDirectory |
| // doesn't do anything if the directory is already present. |
| Directory.CreateDirectory(Path.GetDirectoryName(destFile)); |
| File.Copy(srcFile, destFile); |
| |
| if (srcVolumeObjectTO.nfsDataStore != null && srcVolumeObjectTO.primaryDataStore == null) |
| { |
| logger.Info("Copied volume from secondary data store to primary. Path: " + destVolumeObjectTO.path); |
| } |
| else if (srcVolumeObjectTO.primaryDataStore != null && srcVolumeObjectTO.nfsDataStore == null) |
| { |
| destVolumeObjectTO.path = destVolumeObjectTO.path + "/" + destVolumeObjectTO.uuid; |
| if (destVolumeObjectTO.format != null) |
| { |
| destVolumeObjectTO.path += "." + destVolumeObjectTO.format.ToLower(); |
| } |
| } |
| else |
| { |
| logger.Error("Destination volume path wasn't set. Unsupported source volume data store."); |
| } |
| |
| // Create volumeto object deserialize and send it |
| JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.VolumeObjectTO, destVolumeObjectTO); |
| newData = ansObj; |
| result = true; |
| } |
| } |
| else if (srcVolumeObjectTO != null && destTemplateObjectTO != null) |
| { |
| var guessedDestFile = destTemplateObjectTO.FullFileName; |
| if (File.Exists(guessedDestFile)) |
| { |
| logger.Info("Deleting existing file " + guessedDestFile); |
| File.Delete(guessedDestFile); |
| } |
| |
| destTemplateObjectTO.format = srcVolumeObjectTO.format; |
| destFile = destTemplateObjectTO.FullFileName; |
| if (File.Exists(destFile)) |
| { |
| logger.Info("Deleting existing file " + destFile); |
| File.Delete(destFile); |
| } |
| |
| string srcFile = srcVolumeObjectTO.FullFileName; |
| if (!File.Exists(srcFile)) |
| { |
| details = "Local template file missing from " + srcFile; |
| } |
| else |
| { |
| // Create the directory before copying the files. CreateDirectory |
| // doesn't do anything if the directory is already present. |
| Directory.CreateDirectory(Path.GetDirectoryName(destFile)); |
| File.Copy(srcFile, destFile); |
| |
| FileInfo destFileInfo = new FileInfo(destFile); |
| // Write the template.properties file |
| PostCreateTemplate(Path.GetDirectoryName(destFile), destTemplateObjectTO.id, destTemplateObjectTO.name, |
| destFileInfo.Length.ToString(), srcVolumeObjectTO.size.ToString(), destTemplateObjectTO.format); |
| |
| TemplateObjectTO destTemplateObject = new TemplateObjectTO(); |
| destTemplateObject.size = srcVolumeObjectTO.size.ToString(); |
| destTemplateObject.format = srcVolumeObjectTO.format; |
| destTemplateObject.path = destTemplateObjectTO.path + "/" + destTemplateObjectTO.uuid; |
| if (destTemplateObject.format != null) |
| { |
| destTemplateObject.path += "." + destTemplateObject.format.ToLower(); |
| } |
| destTemplateObject.nfsDataStoreTO = destTemplateObjectTO.nfsDataStoreTO; |
| destTemplateObject.checksum = destTemplateObjectTO.checksum; |
| JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.TemplateObjectTO, destTemplateObject); |
| newData = ansObj; |
| result = true; |
| } |
| } |
| else |
| { |
| details = "Data store combination not supported"; |
| } |
| } |
| catch (Exception ex) |
| { |
| // Test by providing wrong key |
| details = CloudStackTypes.CopyCommand + " failed on exception, " + ex.Message; |
| logger.Error(details, ex); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| newData = newData, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CopyCmdAnswer); |
| } |
| } |
| |
| private static void PostCreateTemplate(string path, string templateId, string templateUuid, string physicalSize, string virtualSize, string format) |
| { |
| string templatePropFile = Path.Combine(path, "template.properties"); |
| using (StreamWriter sw = new StreamWriter(File.Open(templatePropFile, FileMode.Create), Encoding.GetEncoding("iso-8859-1"))) |
| { |
| if (format != null) |
| { |
| format = format.ToLower(); |
| } |
| |
| sw.NewLine = "\n"; |
| sw.WriteLine("id=" + templateId); |
| sw.WriteLine("filename=" + templateUuid + "." + format); |
| sw.WriteLine(format + ".filename=" + templateUuid + "." + format); |
| sw.WriteLine("uniquename=" + templateUuid); |
| sw.WriteLine(format + "=true"); |
| sw.WriteLine("virtualsize=" + virtualSize); |
| sw.WriteLine(format + ".virtualsize=" + virtualSize); |
| sw.WriteLine("size=" + physicalSize); |
| sw.WriteLine(format + ".size=" + physicalSize); |
| sw.WriteLine("public=false"); |
| } |
| } |
| |
| private static bool VerifyChecksum(string destFile, string checksum) |
| { |
| string localChecksum = BitConverter.ToString(CalcFileChecksum(destFile)).Replace("-", "").ToLower(); |
| logger.Debug("Checksum of " + destFile + " is " + checksum); |
| if (checksum.Equals(localChecksum)) |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| /// <summary> |
| /// Match implmentation of DownloadManagerImpl.computeCheckSum |
| /// </summary> |
| /// <param name="destFile"></param> |
| /// <returns></returns> |
| private static byte[] CalcFileChecksum(string destFile) |
| { |
| // TODO: Add unit test to verify that checksum algorithm has not changed. |
| using (MD5 md5 = MD5.Create()) |
| { |
| using (FileStream stream = File.OpenRead(destFile)) |
| { |
| return md5.ComputeHash(stream); |
| } |
| } |
| } |
| |
| private static void DownloadS3ObjectToFile(string srcObjectKey, S3TO srcS3TO, string destFile) |
| { |
| AmazonS3Config S3Config = new AmazonS3Config |
| { |
| ServiceURL = srcS3TO.endpoint, |
| CommunicationProtocol = Amazon.S3.Model.Protocol.HTTP |
| }; |
| |
| if (srcS3TO.httpsFlag) |
| { |
| S3Config.CommunicationProtocol = Protocol.HTTPS; |
| } |
| |
| try |
| { |
| using (AmazonS3 client = Amazon.AWSClientFactory.CreateAmazonS3Client(srcS3TO.accessKey, srcS3TO.secretKey, S3Config)) |
| { |
| GetObjectRequest getObjectRequest = new GetObjectRequest().WithBucketName(srcS3TO.bucketName).WithKey(srcObjectKey); |
| |
| using (S3Response getObjectResponse = client.GetObject(getObjectRequest)) |
| { |
| using (Stream s = getObjectResponse.ResponseStream) |
| { |
| using (FileStream fs = new FileStream(destFile, FileMode.Create, FileAccess.Write)) |
| { |
| byte[] data = new byte[524288]; |
| int bytesRead = 0; |
| do |
| { |
| bytesRead = s.Read(data, 0, data.Length); |
| fs.Write(data, 0, bytesRead); |
| } |
| while (bytesRead > 0); |
| fs.Flush(); |
| } |
| } |
| } |
| } |
| } |
| catch (Exception ex) |
| { |
| string errMsg = "Download from S3 url" + srcS3TO.endpoint + " said: " + ex.Message; |
| logger.Error(errMsg, ex); |
| throw new Exception(errMsg, ex); |
| } |
| } |
| |
| // POST api/HypervResource/GetStorageStatsCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.GetStorageStatsCommand)] |
| public JContainer GetStorageStatsCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.GetStorageStatsCommand + Utils.CleanString(cmd.ToString())); |
| bool result = false; |
| string details = null; |
| long capacity = 0; |
| long available = 0; |
| long used = 0; |
| try |
| { |
| StoragePoolType poolType; |
| string hostPath = null; |
| if (!Enum.TryParse<StoragePoolType>((string)cmd.pooltype, out poolType)) |
| { |
| details = "Request to get unsupported pool type: " + ((string)cmd.pooltype == null ? "NULL" : (string)cmd.pooltype) + "in cmd " + |
| JsonConvert.SerializeObject(cmd); |
| logger.Error(details); |
| } |
| else if (poolType == StoragePoolType.Filesystem) |
| { |
| hostPath = (string)cmd.localPath; |
| GetCapacityForPath(hostPath, out capacity, out available); |
| used = capacity - available; |
| result = true; |
| } |
| else if (poolType == StoragePoolType.NetworkFilesystem || poolType == StoragePoolType.SMB) |
| { |
| string sharePath = config.getPrimaryStorage((string)cmd.id); |
| if (sharePath != null) |
| { |
| hostPath = sharePath; |
| Utils.GetShareDetails(sharePath, out capacity, out available); |
| used = capacity - available; |
| result = true; |
| } |
| } |
| else |
| { |
| result = false; |
| } |
| |
| if (result) |
| { |
| logger.Debug(CloudStackTypes.GetStorageStatsCommand + " set used bytes for " + hostPath + " to " + used); |
| } |
| } |
| catch (Exception ex) |
| { |
| details = CloudStackTypes.GetStorageStatsCommand + " failed on exception" + ex.Message; |
| logger.Error(details, ex); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| capacity = capacity, |
| used = used, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetStorageStatsAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/GetHostStatsCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.GetHostStatsCommand)] |
| public JContainer GetHostStatsCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.GetHostStatsCommand + Utils.CleanString(cmd.ToString())); |
| bool result = false; |
| string details = null; |
| object hostStats = null; |
| |
| var entityType = "host"; |
| ulong totalMemoryKBs; |
| ulong freeMemoryKBs; |
| double networkReadKBs; |
| double networkWriteKBs; |
| double cpuUtilization; |
| |
| try |
| { |
| long hostId = (long)cmd.hostId; |
| wmiCallsV2.GetMemoryResources(out totalMemoryKBs, out freeMemoryKBs); |
| wmiCallsV2.GetProcessorUsageInfo(out cpuUtilization); |
| |
| // TODO: can we assume that the host has only one adaptor? |
| string tmp; |
| var privateNic = GetNicInfoFromIpAddress(config.PrivateIpAddress, out tmp); |
| var nicStats = privateNic.GetIPv4Statistics(); //TODO: add IPV6 support, currentl |
| networkReadKBs = nicStats.BytesReceived; |
| networkWriteKBs = nicStats.BytesSent; |
| |
| // Generate GetHostStatsAnswer |
| hostStats = new |
| { |
| hostId = hostId, |
| entityType = entityType, |
| cpuUtilization = cpuUtilization, |
| networkReadKBs = networkReadKBs, |
| networkWriteKBs = networkWriteKBs, |
| totalMemoryKBs = (double)totalMemoryKBs, |
| freeMemoryKBs = (double)freeMemoryKBs |
| }; |
| result = true; |
| } |
| catch (Exception ex) |
| { |
| details = CloudStackTypes.GetHostStatsCommand + " failed on exception" + ex.Message; |
| logger.Error(details, ex); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| hostStats = hostStats, |
| details = details, |
| contextMap = contextMap |
| }; |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetHostStatsAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/PrepareForMigrationCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.PrepareForMigrationCommand)] |
| public JContainer PrepareForMigrationCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.PrepareForMigrationCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = true; |
| |
| try |
| { |
| details = "NOP - success"; |
| } |
| catch (Exception sysEx) |
| { |
| result = false; |
| details = CloudStackTypes.PrepareForMigrationCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.PrepareForMigrationAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/MigrateCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.MigrateCommand)] |
| public JContainer MigrateCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.MigrateCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| |
| try |
| { |
| string vm = (string)cmd.vmName; |
| string destination = (string)cmd.destIp; |
| wmiCallsV2.MigrateVm(vm, destination); |
| result = true; |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.MigrateCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.MigrateAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/MigrateVolumeCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.MigrateVolumeCommand)] |
| public JContainer MigrateVolumeCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.MigrateVolumeCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| |
| try |
| { |
| string vm = (string)cmd.attachedVmName; |
| string volume = (string)cmd.volumePath; |
| wmiCallsV2.MigrateVolume(vm, volume, GetStoragePoolPath(cmd.pool)); |
| result = true; |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.MigrateVolumeCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| volumePath = (string)cmd.volumePath, |
| details = details, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.MigrateVolumeAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/MigrateWithStorageCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.MigrateWithStorageCommand)] |
| public JContainer MigrateWithStorageCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.MigrateWithStorageCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| List<dynamic> volumeTos = new List<dynamic>(); |
| |
| try |
| { |
| string vm = (string)cmd.vm.name; |
| string destination = (string)cmd.tgtHost; |
| var volumeToPoolList = cmd.volumeToFilerAsList; |
| var volumeToPool = new Dictionary<string, string>(); |
| foreach (var item in volumeToPoolList) |
| { |
| volumeTos.Add(item.t); |
| string poolPath = GetStoragePoolPath(item.u); |
| volumeToPool.Add((string)item.t.path, poolPath); |
| } |
| |
| wmiCallsV2.MigrateVmWithVolume(vm, destination, volumeToPool); |
| result = true; |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.MigrateWithStorageCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| volumeTos = JArray.FromObject(volumeTos), |
| details = details, |
| contextMap = contextMap |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.MigrateWithStorageAnswer); |
| } |
| } |
| |
| private string GetStoragePoolPath(dynamic pool) |
| { |
| string poolTypeStr = pool.type; |
| StoragePoolType poolType; |
| if (!Enum.TryParse<StoragePoolType>(poolTypeStr, out poolType)) |
| { |
| throw new ArgumentException("Invalid pool type " + poolTypeStr); |
| } |
| else if (poolType == StoragePoolType.SMB) |
| { |
| NFSTO share = new NFSTO(); |
| String uriStr = "cifs://" + (string)pool.host + (string)pool.path; |
| share.uri = new Uri(uriStr); |
| return Utils.NormalizePath(share.UncPath); |
| } |
| else if (poolType == StoragePoolType.Filesystem) |
| { |
| return pool.path; |
| } |
| |
| throw new ArgumentException("Couldn't parse path for pool type " + poolTypeStr); |
| } |
| |
| // POST api/HypervResource/StartupCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.StartupCommand)] |
| public JContainer StartupCommand([FromBody]dynamic cmdArray) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(cmdArray.ToString()); |
| // Log agent configuration |
| logger.Info("Agent StartupRoutingCommand received " + cmdArray.ToString()); |
| dynamic strtRouteCmd = cmdArray[0][CloudStackTypes.StartupRoutingCommand]; |
| |
| // Insert networking details |
| string privateIpAddress = strtRouteCmd.privateIpAddress; |
| config.PrivateIpAddress = privateIpAddress; |
| string subnet; |
| System.Net.NetworkInformation.NetworkInterface privateNic = GetNicInfoFromIpAddress(privateIpAddress, out subnet); |
| strtRouteCmd.privateIpAddress = privateIpAddress; |
| strtRouteCmd.privateNetmask = subnet; |
| strtRouteCmd.privateMacAddress = privateNic.GetPhysicalAddress().ToString(); |
| string storageip = strtRouteCmd.storageIpAddress; |
| System.Net.NetworkInformation.NetworkInterface storageNic = GetNicInfoFromIpAddress(storageip, out subnet); |
| |
| strtRouteCmd.storageIpAddress = storageip; |
| strtRouteCmd.storageNetmask = subnet; |
| strtRouteCmd.storageMacAddress = storageNic.GetPhysicalAddress().ToString(); |
| strtRouteCmd.gatewayIpAddress = storageNic.GetPhysicalAddress().ToString(); |
| strtRouteCmd.hypervisorVersion = System.Environment.OSVersion.Version.Major.ToString() + "." + |
| System.Environment.OSVersion.Version.Minor.ToString(); |
| strtRouteCmd.caps = "hvm"; |
| |
| dynamic details = strtRouteCmd.hostDetails; |
| if (details != null) |
| { |
| string productVersion = System.Environment.OSVersion.Version.Major.ToString() + "." + |
| System.Environment.OSVersion.Version.Minor.ToString(); |
| details.Add("product_version", productVersion); |
| details.Add("rdp.server.port", 2179); |
| } |
| |
| // Detect CPUs, speed, memory |
| uint cores; |
| uint mhz; |
| uint sockets; |
| wmiCallsV2.GetProcessorResources(out sockets, out cores, out mhz); |
| strtRouteCmd.cpus = cores; |
| strtRouteCmd.speed = mhz; |
| strtRouteCmd.cpuSockets = sockets; |
| ulong memoryKBs; |
| ulong freeMemoryKBs; |
| wmiCallsV2.GetMemoryResources(out memoryKBs, out freeMemoryKBs); |
| strtRouteCmd.memory = memoryKBs * 1024; // Convert to bytes |
| |
| // Need 2 Gig for DOM0, see http://technet.microsoft.com/en-us/magazine/hh750394.aspx |
| strtRouteCmd.dom0MinMemory = config.ParentPartitionMinMemoryMb * 1024 * 1024; // Convert to bytes |
| |
| // Insert storage pool details. |
| // |
| // Read the localStoragePath for virtual disks from the Hyper-V configuration |
| // See http://blogs.msdn.com/b/virtual_pc_guy/archive/2010/05/06/managing-the-default-virtual-machine-location-with-hyper-v.aspx |
| // for discussion of Hyper-V file locations paths. |
| string virtualDiskFolderPath = wmiCallsV2.GetDefaultVirtualDiskFolder(); |
| if (virtualDiskFolderPath != null) |
| { |
| // GUID arbitrary. Host agents deals with storage pool in terms of localStoragePath. |
| // We use HOST guid. |
| string poolGuid = strtRouteCmd.guid; |
| |
| if (poolGuid == null) |
| { |
| poolGuid = Guid.NewGuid().ToString(); |
| logger.InfoFormat("Setting Startup StoragePool GUID to " + poolGuid); |
| } |
| else |
| { |
| logger.InfoFormat("Setting Startup StoragePool GUID same as HOST, i.e. " + poolGuid); |
| } |
| |
| long capacity; |
| long available; |
| GetCapacityForPath(virtualDiskFolderPath, out capacity, out available); |
| logger.Debug(CloudStackTypes.StartupStorageCommand + " set available bytes to " + available); |
| |
| string ipAddr = strtRouteCmd.privateIpAddress; |
| var vmStates = wmiCallsV2.GetVmSync(config.PrivateIpAddress); |
| strtRouteCmd.vms = Utils.CreateCloudStackMapObject(vmStates); |
| |
| StoragePoolInfo pi = new StoragePoolInfo( |
| poolGuid.ToString(), |
| ipAddr, |
| virtualDiskFolderPath, |
| virtualDiskFolderPath, |
| StoragePoolType.Filesystem.ToString(), |
| capacity, |
| available); |
| |
| // Build StartupStorageCommand using an anonymous type |
| // See http://stackoverflow.com/a/6029228/939250 |
| object ansContent = new |
| { |
| poolInfo = pi, |
| guid = pi.uuid, |
| dataCenter = strtRouteCmd.dataCenter, |
| resourceType = StorageResourceType.STORAGE_POOL.ToString(), // TODO: check encoding |
| contextMap = contextMap |
| }; |
| JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.StartupStorageCommand, ansContent); |
| cmdArray.Add(ansObj); |
| } |
| |
| // Convert result to array for type correctness? |
| logger.Info(CloudStackTypes.StartupCommand + " result is " + cmdArray.ToString()); |
| return cmdArray; |
| } |
| } |
| |
| // POST api/HypervResource/GetVncPortCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.GetVncPortCommand)] |
| public JContainer GetVncPortCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.GetVncPortCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| bool result = false; |
| string address = null; |
| int port = -9; |
| |
| try |
| { |
| string vmName = (string)cmd.name; |
| var sys = wmiCallsV2.GetComputerSystem(vmName); |
| address = "instanceId=" + sys.Name ; |
| result = true; |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.GetVncPortAnswer + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| object ansContent = new |
| { |
| result = result, |
| details = details, |
| address = address, |
| port = port |
| }; |
| |
| return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetVncPortAnswer); |
| } |
| } |
| |
| // POST api/HypervResource/HostVmStateReportCommand |
| [HttpPost] |
| [ActionName(CloudStackTypes.HostVmStateReportCommand)] |
| public JContainer HostVmStateReportCommand([FromBody]dynamic cmd) |
| { |
| using (log4net.NDC.Push(Guid.NewGuid().ToString())) |
| { |
| logger.Info(CloudStackTypes.HostVmStateReportCommand + Utils.CleanString(cmd.ToString())); |
| |
| string details = null; |
| List<Dictionary<string, string>> hostVmStateReport = new List<Dictionary<string, string>>(); |
| |
| try |
| { |
| var vmCollection = wmiCallsV2.GetComputerSystemCollection(); |
| foreach (ComputerSystem vm in vmCollection) |
| { |
| if (EnabledState.ToCloudStackPowerState(vm.EnabledState).Equals("PowerOn")) |
| { |
| var dict = new Dictionary<string, string>(); |
| dict.Add(vm.ElementName, EnabledState.ToCloudStackPowerState(vm.EnabledState)); |
| hostVmStateReport.Add(dict); |
| } |
| if (EnabledState.ToCloudStackPowerState(vm.EnabledState).Equals("PowerOff")) |
| { |
| string note = wmiCallsV2.GetVmNote((wmiCallsV2.GetVmSettings(vm)).Path); |
| if (note != null && note.Contains("CloudStack")) |
| { |
| if (!note.Contains("creating")) |
| { |
| try |
| { |
| wmiCallsV2.DestroyVm(vm.ElementName); |
| } |
| catch (Exception ex) |
| { |
| logger.Error("Failed to delete stopped VMs due to " + ex.Message); |
| } |
| } |
| else |
| { |
| var dict = new Dictionary<string, string>(); |
| dict.Add(vm.ElementName, "PowerOn"); |
| hostVmStateReport.Add(dict); |
| } |
| } |
| } |
| } |
| } |
| catch (Exception sysEx) |
| { |
| details = CloudStackTypes.HostVmStateReportCommand + " failed due to " + sysEx.Message; |
| logger.Error(details, sysEx); |
| } |
| |
| var answer = JArray.FromObject(hostVmStateReport); |
| logger.Info(String.Format("{0}: {1}",CloudStackTypes.HostVmStateReportCommand, answer.ToString())); |
| |
| return answer; |
| } |
| } |
| |
| public static System.Net.NetworkInformation.NetworkInterface GetNicInfoFromIpAddress(string ipAddress, out string subnet) |
| { |
| System.Net.NetworkInformation.NetworkInterface[] nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); |
| System.Net.NetworkInformation.NetworkInterface defaultnic = null; |
| foreach (var nic in nics) |
| { |
| subnet = null; |
| defaultnic = nic; |
| // TODO: use to remove NETMASK and MAC from the config file, and to validate the IPAddress. |
| var nicProps = nic.GetIPProperties(); |
| bool found = false; |
| foreach (var addr in nicProps.UnicastAddresses) |
| { |
| if (addr.Address.Equals(IPAddress.Parse(ipAddress))) |
| { |
| subnet = addr.IPv4Mask.ToString(); |
| found = true; |
| } |
| } |
| if (!found) |
| { |
| continue; |
| } |
| return nic; |
| } |
| var defaultSubnet = defaultnic.GetIPProperties().UnicastAddresses[0]; |
| subnet = defaultSubnet.IPv4Mask.ToString(); |
| return defaultnic; |
| } |
| |
| public static void GetCapacityForLocalPath(string localStoragePath, out long capacityBytes, out long availableBytes) |
| { |
| // NB: DriveInfo does not work for remote folders (http://stackoverflow.com/q/1799984/939250) |
| // DriveInfo requires a driver letter... |
| string fullPath = Path.GetFullPath(localStoragePath); |
| System.IO.DriveInfo poolInfo = new System.IO.DriveInfo(fullPath); |
| capacityBytes = poolInfo.TotalSize; |
| availableBytes = poolInfo.AvailableFreeSpace; |
| |
| // Don't allow all of the Root Device to be used for virtual disks |
| if (poolInfo.RootDirectory.Name.ToLower().Equals(config.RootDeviceName)) |
| { |
| availableBytes -= config.RootDeviceReservedSpaceBytes; |
| availableBytes = availableBytes > 0 ? availableBytes : 0; |
| capacityBytes -= config.RootDeviceReservedSpaceBytes; |
| capacityBytes = capacityBytes > 0 ? capacityBytes : 0; |
| } |
| } |
| |
| public static void GetCapacityForPath(String hostPath, out long capacityBytes, out long availableBytes) |
| { |
| if (hostPath.Substring(0, 2) == "\\\\") |
| { |
| Utils.GetShareDetails(hostPath, out capacityBytes, out availableBytes); |
| } |
| else |
| { |
| GetCapacityForLocalPath(hostPath, out capacityBytes, out availableBytes); |
| } |
| } |
| } |
| } |