You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by de...@apache.org on 2013/11/04 14:49:04 UTC
[35/85] [abbrv] A plugin for Hyper-V control is available for
CloudStack. The plugin implements basic VM control;
however, its architecture allows additional functionality to be easily added.
Incorporating the plugin in CloudStack will allow the commu
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs
new file mode 100644
index 0000000..7a0c2db
--- /dev/null
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs
@@ -0,0 +1,1467 @@
+// 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 CloudStack.Plugin.WmiWrappers.ROOT.VIRTUALIZATION;
+using log4net;
+using Microsoft.CSharp.RuntimeBinder;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Security.Cryptography;
+using System.Security.Principal;
+using System.Web.Http;
+
+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;
+ }
+
+ /// <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;
+ }
+
+ public static HypervResourceControllerConfig config = new HypervResourceControllerConfig();
+
+ private static ILog logger = LogManager.GetLogger(typeof(WmiCalls));
+
+ public static void Initialize()
+ {
+ }
+
+ // 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 + cmd.ToString());
+
+ object ansContent = new
+ {
+ result = true,
+ details = "success - NOP",
+ _reconnect = false
+ };
+
+ 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 + cmd.ToString());
+
+ string details = null;
+ bool result = false;
+
+ try
+ {
+ string vmName = (string)cmd.vmName;
+ string isoPath = "\\\\10.102.192.150\\SMB-Share\\202-2-305ed1f7-1be8-345e-86c3-a976f7f57f10.iso";
+ WmiCalls.AttachIso(vmName, isoPath);
+
+ result = true;
+ }
+ catch (Exception sysEx)
+ {
+ details = CloudStackTypes.AttachCommand + " failed due to " + sysEx.Message;
+ logger.Error(details, sysEx);
+ }
+
+ object ansContent = new
+ {
+ result = result,
+ details = details
+ };
+
+ 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 + cmd.ToString());
+
+ string details = null;
+ bool result = false;
+
+ try
+ {
+ string vmName = (string)cmd.vmName;
+ result = true;
+ }
+ catch (Exception sysEx)
+ {
+ details = CloudStackTypes.DettachCommand + " failed due to " + sysEx.Message;
+ logger.Error(details, sysEx);
+ }
+
+ object ansContent = new
+ {
+ result = result,
+ details = details
+ };
+
+ return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.DettachAnswer);
+ }
+ }
+
+ // 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 + cmd.ToString());
+
+ string details = null;
+ bool result = false;
+
+ try
+ {
+ // Assert
+ String errMsg = "No 'volume' details in " + CloudStackTypes.DestroyCommand + " " + 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);
+ }
+
+ // TODO: will we have to detach volume?
+ string vmName = (string)cmd.vmName;
+ if (!string.IsNullOrEmpty(vmName) && File.Exists(path))
+ {
+ var imgmgr = WmiCalls.GetImageManagementService();
+ var returncode = imgmgr.Unmount(path);
+ if (returncode != ReturnCode.Completed)
+ {
+ details = "Could not detach driver from vm " + vmName + " for drive " + path;
+ logger.Error(details);
+ }
+ }
+
+ 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
+ };
+
+ 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(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 + 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: how do you specify format as VHD or VHDX?
+ WmiCalls.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
+ };
+ 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 + cmd.ToString());
+ string details = null;
+ bool result = false;
+ long size = 0;
+ string newCopyFileName = null;
+
+ string poolLocalPath = cmd.localPath;
+ string poolUuid = cmd.poolUuid;
+ 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
+ };
+ 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
+ // TODO: create test
+ [HttpPost]
+ [ActionName(CloudStackTypes.CheckHealthCommand)]
+ public JContainer CheckHealthCommand([FromBody]dynamic cmd)
+ {
+ using (log4net.NDC.Push(Guid.NewGuid().ToString()))
+ {
+ logger.Info(CloudStackTypes.CheckHealthCommand + cmd.ToString());
+ object ansContent = new
+ {
+ result = true,
+ details = "resource is alive"
+ };
+ return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CheckHealthAnswer);
+ }
+ }
+
+ // 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 + cmd.ToString());
+ object ansContent = new
+ {
+ result = true,
+ details = "NOP, TODO: implement properly"
+ };
+ 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 + cmd.ToString());
+ string details = null;
+ bool result = false;
+ string vmName = cmd.vmName;
+ string state = null;
+
+ // TODO: Look up the VM, convert Hyper-V state to CloudStack version.
+ var sys = WmiCalls.GetComputerSystem(vmName);
+ if (sys == null)
+ {
+ details = CloudStackTypes.CheckVirtualMachineCommand + " requested unknown VM " + vmName;
+ logger.Error(details);
+ }
+ else
+ {
+ state = EnabledState.ToString(sys.EnabledState);
+ result = true;
+ }
+
+ object ansContent = new
+ {
+ result = result,
+ details = details,
+ state = state
+ };
+ 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 + cmd.ToString());
+ object ansContent = new
+ {
+ result = true,
+ details = "Current implementation does not delete local path corresponding to storage pool!"
+ };
+ 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 + cmd.ToString());
+ object ansContent = new
+ {
+ result = true,
+ details = "success - NOP"
+ };
+ 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 + cmd.ToString());
+ string details = null;
+ string localPath;
+ object ansContent;
+
+ bool result = ValidateStoragePoolCommand(cmd, out localPath, ref details);
+ if (!result)
+ {
+ ansContent = new
+ {
+ result = result,
+ details = details
+ };
+ return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer);
+ }
+
+ var tInfo = new Dictionary<string, string>();
+ long capacityBytes;
+ long availableBytes;
+ GetCapacityForLocalPath(localPath, out capacityBytes, out availableBytes);
+
+ String uuid = null;
+ var poolInfo = new
+ {
+ uuid = uuid,
+ host = cmd.pool.host,
+ localPath = cmd.pool.host,
+ hostPath = cmd.localPath,
+ poolType = cmd.pool.type,
+ capacityBytes = capacityBytes,
+ availableBytes = availableBytes
+ };
+
+ ansContent = new
+ {
+ result = result,
+ details = details,
+ templateInfo = tInfo,
+ poolInfo = poolInfo
+ };
+ return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.ModifyStoragePoolAnswer);
+ }
+ }
+
+ private bool ValidateStoragePoolCommand(dynamic cmd, out string localPath, ref string details)
+ {
+ dynamic pool = cmd.pool;
+ string poolTypeStr = pool.type;
+ StoragePoolType poolType;
+ localPath = cmd.localPath;
+ if (!Enum.TryParse<StoragePoolType>(poolTypeStr, out poolType) || poolType != StoragePoolType.Filesystem)
+ {
+ details = "Request to create / modify unsupported pool type: " + (poolTypeStr == null ? "NULL" : poolTypeStr) + "in cmd " + JsonConvert.SerializeObject(cmd);
+ logger.Error(details);
+ return false;
+ }
+ if (!Directory.Exists(localPath))
+ {
+ details = "Request to create / modify unsupported StoragePoolType.Filesystem with non-existent path:" + (localPath == null ? "NULL" : localPath) + "in cmd " + JsonConvert.SerializeObject(cmd);
+ logger.Error(details);
+ return false;
+ }
+ return true;
+ }
+
+
+ // 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 + cmd.ToString());
+ object ansContent = new
+ {
+ result = false,
+ details = "nothing to cleanup in our current implementation"
+ };
+ 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 + cmd.ToString());
+ object ansContent = new
+ {
+ result = true,
+ details = (string)null
+ };
+ 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 + cmd.ToString());
+ object ansContent = new
+ {
+ result = true,
+ details = (string)null
+ };
+ 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 + cmd.ToString()); // TODO: Security hole? VM data printed to log
+ string details = null;
+ bool result = false;
+
+ try
+ {
+ WmiCalls.DeployVirtualMachine(cmd);
+ 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
+ };
+ 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 + cmd.ToString());
+ string details = null;
+ bool result = false;
+
+ try
+ {
+ WmiCalls.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
+ };
+ return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.StopAnswer);
+ }
+ }
+
+ // 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 + cmd.ToString());
+
+ object ansContent = new
+ {
+ result = true,
+ details = "success - NOP for MaintainCommand",
+ _reconnect = false
+ };
+
+ 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 + cmd.ToString());
+
+ object ansContent = new
+ {
+ result = true,
+ details = "success - NOP for PingRoutingCommand",
+ _reconnect = false
+ };
+
+ 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 + cmd.ToString());
+
+ object ansContent = new
+ {
+ result = true,
+ details = "success - NOP for PingCommand",
+ _reconnect = false
+ };
+
+ return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.Answer);
+ }
+ }
+
+ // 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 + cmd.ToString());
+ bool result = false;
+ string details = null;
+ 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 = WmiCalls.GetComputerSystem(vmName);
+ if (sys == null)
+ {
+ logger.InfoFormat("GetVmStatsCommand requested unknown VM {0}", vmNames);
+ continue;
+ }
+ var sysInfo = WmiCalls.GetVmSettings(sys);
+ vmsToInspect.Add(sysInfo.Path);
+ }
+
+ // Process info available from WMI,
+ // See http://msdn.microsoft.com/en-us/library/cc160706%28v=vs.85%29.aspx
+ uint[] requestedInfo = new uint[] {
+ 0, // Name
+ 1, // ElementName
+ 4, // Number of processes
+ 101 // ProcessorLoad
+ };
+
+ System.Management.ManagementBaseObject[] sysSummary;
+ var vmsvc = WmiCalls.GetVirtualisationSystemManagementService();
+ System.Management.ManagementPath[] vmPaths = vmsToInspect.ToArray();
+ vmsvc.GetSummaryInformation(requestedInfo, vmPaths, out sysSummary);
+
+ foreach (var summary in sysSummary)
+ {
+ var summaryInfo = new CloudStack.Plugin.AgentShell.ROOT.VIRTUALIZATION.SummaryInformation(summary);
+
+ logger.Debug("VM " + summaryInfo.Name + "(elementName " + summaryInfo.ElementName + ") has " +
+ summaryInfo.NumberOfProcessors + " CPUs, and load of " + summaryInfo.ProcessorLoad);
+ var vmInfo = new VmStatsEntry
+ {
+ cpuUtilization = summaryInfo.ProcessorLoad,
+ numCPUs = summaryInfo.NumberOfProcessors,
+ networkReadKBs = 1,
+ networkWriteKBs = 1,
+ entityType = "vm"
+ };
+ vmProcessorInfo.Add(summaryInfo.ElementName, vmInfo);
+ }
+
+ // 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
+ {
+ vmInfos = vmProcessorInfo,
+ result = result,
+ details = details,
+ };
+ return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetVmStatsAnswer);
+ }
+ }
+
+ // POST api/HypervResource/StartupCommand
+ [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.
+
+ bool result = false;
+ string details = null;
+ object newData = null;
+
+ try
+ {
+ dynamic timeout = cmd.wait; // TODO: Useful?
+
+ TemplateObjectTO srcTemplateObjectTO = TemplateObjectTO.ParseJson(cmd.srcTO);
+ TemplateObjectTO destTemplateObjectTO = TemplateObjectTO.ParseJson(cmd.destTO);
+ VolumeObjectTO destVolumeObjectTO = VolumeObjectTO.ParseJson(cmd.destTO);
+
+ logger.Info(CloudStackTypes.CopyCommand + cmd.ToString());
+
+ // Already exists?
+ if (destTemplateObjectTO != null &&
+ File.Exists(destTemplateObjectTO.FullFileName) &&
+ !String.IsNullOrEmpty(destTemplateObjectTO.checksum))
+ {
+ // TODO: checksum fails us, because it is of the compressed image.
+ // ASK: should we store the compressed or uncompressed version or is the checksum not calculated correctly?
+ result = VerifyChecksum(destTemplateObjectTO.FullFileName, destTemplateObjectTO.checksum);
+ }
+
+ // Do we have to create a new one?
+ if (!result)
+ {
+ // 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)
+ {
+ string destFile = destTemplateObjectTO.FullFileName;
+
+ 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);
+ }
+
+ newData = cmd.destTO;
+ result = true;
+ }
+ else
+ {
+ details = "Data store combination not supported";
+ }
+ }
+ // Create volume from a template?
+ else if (srcTemplateObjectTO != null && destVolumeObjectTO != null)
+ {
+ if (destVolumeObjectTO.format == null)
+ {
+ destVolumeObjectTO.format = srcTemplateObjectTO.format;
+ }
+ string destFile = destVolumeObjectTO.FullFileName;
+ string srcFile = srcTemplateObjectTO.FullFileName;
+
+ if (!File.Exists(srcFile))
+ {
+ details = "Local template file missing from " + srcFile;
+ }
+ else
+ {
+ if (File.Exists(destFile))
+ {
+ logger.Info("Deleting existing file " + destFile);
+ File.Delete(destFile);
+ }
+
+ // TODO: thin provision instead of copying the full file.
+ File.Copy(srcFile, destFile);
+ newData = cmd.destTO;
+ 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 = cmd.destTO
+ };
+ return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.CopyCmdAnswer);
+ }
+ }
+
+ 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 + cmd.ToString());
+ bool result = false;
+ string details = null;
+ long capacity = 0;
+ long available = 0;
+ long used = 0;
+ try
+ {
+ string localPath = (string)cmd.localPath;
+ GetCapacityForLocalPath(localPath, out capacity, out available);
+ used = capacity - available;
+ result = true;
+ logger.Debug(CloudStackTypes.GetStorageStatsCommand + " set used bytes 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
+ };
+ 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 + 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;
+ WmiCalls.GetMemoryResources(out totalMemoryKBs, out freeMemoryKBs);
+ WmiCalls.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
+ };
+ return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetHostStatsAnswer);
+ }
+ }
+
+ // 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
+ strtRouteCmd.privateIpAddress = config.PrivateIpAddress;
+ strtRouteCmd.privateNetmask = config.PrivateNetmask;
+ strtRouteCmd.privateMacAddress = config.PrivateMacAddress;
+ strtRouteCmd.storageIpAddress = config.PrivateIpAddress;
+ strtRouteCmd.storageNetmask = config.PrivateNetmask;
+ strtRouteCmd.storageMacAddress = config.PrivateMacAddress;
+ strtRouteCmd.gatewayIpAddress = config.GatewayIpAddress;
+ strtRouteCmd.caps = "hvm";
+
+ // Detect CPUs, speed, memory
+ uint cores;
+ uint mhz;
+ WmiCalls.GetProcessorResources(out cores, out mhz);
+ strtRouteCmd.cpus = cores;
+ strtRouteCmd.speed = mhz;
+ ulong memoryKBs;
+ ulong freeMemoryKBs;
+ WmiCalls.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 localStoragePath = WmiCalls.GetDefaultVirtualDiskFolder();
+ if (localStoragePath != 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;
+ GetCapacityForLocalPath(localStoragePath, out capacity, out available);
+
+ logger.Debug(CloudStackTypes.StartupStorageCommand + " set available bytes to " + available);
+
+ string ipAddr = strtRouteCmd.privateIpAddress;
+ StoragePoolInfo pi = new StoragePoolInfo(
+ poolGuid.ToString(),
+ ipAddr,
+ localStoragePath,
+ localStoragePath,
+ 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
+ };
+ 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;
+ }
+ }
+
+ public static System.Net.NetworkInformation.NetworkInterface GetNicInfoFromIpAddress(string ipAddress, out string subnet)
+ {
+ System.Net.NetworkInformation.NetworkInterface[] nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
+ foreach (var nic in nics)
+ {
+ subnet = null;
+ // 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;
+ }
+ throw new ArgumentException("No NIC for ipAddress " + ipAddress);
+ }
+
+ 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;
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/Properties/AssemblyInfo.cs
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/Properties/AssemblyInfo.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..8ea504f
--- /dev/null
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("HypervResource")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HypervResource")]
+[assembly: AssemblyCopyright("Copyright © 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f1eb80c1-36fb-438c-a70d-0de5d5e295fb")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/Utils.cs
----------------------------------------------------------------------
diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/Utils.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/Utils.cs
new file mode 100644
index 0000000..c24a1ae
--- /dev/null
+++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/Utils.cs
@@ -0,0 +1,110 @@
+// 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 log4net;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HypervResource
+{
+ public class Utils
+ {
+ private static ILog s_logger = LogManager.GetLogger(typeof(Utils));
+
+ /// <summary>
+ /// Associate CloudStack object's content with a fully qualified type name.
+ /// </summary>
+ /// <param name="objType">Fully qualified type name, e.g. "org.apache.cloudstack.storage.to.TemplateObjectTO"</param>
+ /// <param name="objValue">Object's data, can be an anonymous object, e.g. </param>
+ /// <returns></returns>
+ public static JObject CreateCloudStackObject(string objType, object objValue)
+ {
+ JToken objContent = JToken.FromObject(objValue);
+ JProperty objTypeValuePairing = new JProperty(objType, objContent);
+
+ return new JObject(objTypeValuePairing);
+ }
+
+
+ /// <summary>
+ /// Copy file on network share to local volume.
+ /// </summary>
+ /// <remarks>
+ /// Access to the network share is acheived by logging into the domain corresponding to the user credentials provided.
+ /// Windows impersonation does not suffice, because impersonation is limited to domains with an established trust relationship.
+ /// We have had to import Win32 API calls to allow login. There are a number of examples online. We follow the
+ /// one at http://stackoverflow.com/a/2541569/939250 </remarks>
+ /// <param name="filePathRelativeToShare"></param>
+ /// <param name="cifsShareDetails"></param>
+ /// <param name="destFile"></param>
+ public static void DownloadCifsFileToLocalFile(string filePathRelativeToShare, NFSTO cifsShareDetails, string destFile)
+ {
+ try
+ {
+ IntPtr token = IntPtr.Zero;
+
+ bool isSuccess = LogonUser(cifsShareDetails.User, cifsShareDetails.Domain, cifsShareDetails.Password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, ref token);
+ using (WindowsImpersonationContext remoteIdentity = new WindowsIdentity(token).Impersonate())
+ {
+ String dest = Path.Combine(cifsShareDetails.UncPath, filePathRelativeToShare);
+ s_logger.Info(CloudStackTypes.CopyCommand + ": copy " + Path.Combine(cifsShareDetails.UncPath, filePathRelativeToShare) + " to " + destFile);
+
+ File.Copy(dest, destFile, true);
+ remoteIdentity.Undo();
+ }
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ string errMsg = "Invalid user or password for the share " + cifsShareDetails.UncPath;
+ s_logger.Error(errMsg);
+
+ throw new ArgumentException(errMsg, ex);
+ }
+ }
+
+ // from http://stackoverflow.com/a/2541569/939250
+ #region imports
+ [DllImport("advapi32.dll", SetLastError = true)]
+ private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ private static extern bool CloseHandle(IntPtr handle);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ public extern static bool DuplicateToken(IntPtr existingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr duplicateTokenHandle);
+ #endregion
+
+ #region logon consts
+ // logon types
+ const int LOGON32_LOGON_INTERACTIVE = 2;
+ const int LOGON32_LOGON_NETWORK = 3;
+ const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
+
+ // logon providers
+ const int LOGON32_PROVIDER_DEFAULT = 0;
+ const int LOGON32_PROVIDER_WINNT50 = 3;
+ const int LOGON32_PROVIDER_WINNT40 = 2;
+ const int LOGON32_PROVIDER_WINNT35 = 1;
+ #endregion
+ }
+}