mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			2476 lines
		
	
	
		
			104 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			2476 lines
		
	
	
		
			104 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| // Licensed to the Apache Software Foundation (ASF) under one
 | |
| // or more contributor license agreements.  See the NOTICE file
 | |
| // distributed with this work for additional information
 | |
| // regarding copyright ownership.  The ASF licenses this file
 | |
| // to you under the Apache License, Version 2.0 (the
 | |
| // "License"); you may not use this file except in compliance
 | |
| // with the License.  You may obtain a copy of the License at
 | |
| //
 | |
| //   http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing,
 | |
| // software distributed under the License is distributed on an
 | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 | |
| // KIND, either express or implied.  See the License for the
 | |
| // specific language governing permissions and limitations
 | |
| // under the License.
 | |
| 
 | |
| using 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 valide 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);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |