// Licensed to the Apache Software Foundation (ASF) under one | |
// or more contributor license agreements. See the NOTICE file | |
// distributed with this work for additional information | |
// regarding copyright ownership. The ASF licenses this file | |
// to you under the Apache License, Version 2.0 (the | |
// "License"); you may not use this file except in compliance | |
// with the License. You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, | |
// software distributed under the License is distributed on an | |
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
// KIND, either express or implied. See the License for the | |
// specific language governing permissions and limitations | |
// under the License. | |
using System; | |
using CloudStack.Plugin.WmiWrappers.ROOT.VIRTUALIZATION.V2; | |
using System.Management; | |
using Newtonsoft.Json.Linq; | |
using Newtonsoft.Json; | |
using System.IO; | |
using log4net; | |
using HypervResource; | |
using CloudStack.Plugin.AgentShell; | |
using System.Collections.Generic; | |
using NSubstitute; | |
using System.Web.Http; | |
using Xunit; | |
namespace ServerResource.Tests | |
{ | |
public class HypervResourceController1Test | |
{ | |
protected static string testCifsUrl = AgentSettings.Default.testCifsUrl; | |
protected static string testCifsPath = AgentSettings.Default.testCifsPath; | |
protected static String testPrimaryDataStoreHost = HypervResourceController.config.StorageIpAddress; | |
protected static String testS3TemplateName = AgentSettings.Default.testS3TemplateName; | |
protected static String testCifsTemplateName = AgentSettings.Default.testS3TemplateName; | |
protected static String testSystemVMTemplateName = AgentSettings.Default.testSystemVMTemplateName; | |
protected static String testSystemVMTemplateNameNoExt = AgentSettings.Default.testSystemVMTemplateNameNoExt; | |
protected static String testLocalStoreUUID = "5fe2bad3-d785-394e-9949-89786b8a63d2"; | |
protected static String testLocalStorePath = Path.Combine(AgentSettings.Default.hyperv_plugin_root, "var", "test", "storagepool"); | |
protected static String testSecondaryStoreLocalPath = Path.Combine(AgentSettings.Default.hyperv_plugin_root, "var", "test", "secondary"); | |
// TODO: differentiate between NFS and HTTP template URLs. | |
protected static String testSampleTemplateUUID = "TestCopiedLocalTemplate.vhdx"; | |
protected static String testSampleTemplateURL = testSampleTemplateUUID; | |
// test volumes are both a minimal size vhdx. Changing the extension to .vhd makes on corrupt. | |
protected static String testSampleVolumeWorkingUUID = "TestVolumeLegit.vhdx"; | |
protected static String testSampleVolumeCorruptUUID = "TestVolumeCorrupt.vhd"; | |
protected static String testSampleVolumeTempUUID = "TestVolumeTemp.vhdx"; | |
protected static String testSampleVolumeTempUUIDNoExt = "TestVolumeTemp"; | |
protected static String testSampleVolumeWorkingURIJSON; | |
protected static String testSampleVolumeCorruptURIJSON; | |
protected static String testSampleVolumeTempURIJSON; | |
protected static String testSampleTemplateURLJSON; | |
protected static String testLocalStorePathJSON; | |
protected static IWmiCallsV2 wmiCallsV2; | |
private static ILog s_logger = LogManager.GetLogger(typeof(HypervResourceController1Test)); | |
/// <summary> | |
/// Test WmiCalls to which incoming HTTP POST requests are dispatched. | |
/// | |
/// TODO: revise beyond first approximation | |
/// First approximation is a quick port of the existing Java tests for Hyper-V server resource. | |
/// A second approximation would use the AgentShell settings files directly. | |
/// A third approximation would look to invoke ServerResource methods via an HTTP request | |
/// </summary> | |
public HypervResourceController1Test() | |
{ | |
wmiCallsV2 = Substitute.For<IWmiCallsV2>(); | |
//AgentService.ConfigServerResource(); | |
HypervResourceController.config.PrivateMacAddress = AgentSettings.Default.private_mac_address; | |
HypervResourceController.config.PrivateNetmask = AgentSettings.Default.private_ip_netmask; | |
HypervResourceController.config.StorageIpAddress = HypervResourceController.config.PrivateIpAddress; | |
HypervResourceController.config.StorageMacAddress = HypervResourceController.config.PrivateMacAddress; | |
HypervResourceController.config.StorageNetmask = HypervResourceController.config.PrivateNetmask; | |
// Used to create existing StoragePool in preparation for the ModifyStoragePool | |
testLocalStoreUUID = AgentSettings.Default.local_storage_uuid.ToString(); | |
// Make sure secondary store is available. | |
string fullPath = Path.GetFullPath(testSecondaryStoreLocalPath); | |
s_logger.Info("Test secondary storage in " + fullPath); | |
DirectoryInfo testSecondarStoreDir = new DirectoryInfo(fullPath); | |
if (!testSecondarStoreDir.Exists) | |
{ | |
try | |
{ | |
testSecondarStoreDir.Create(); | |
} | |
catch (System.IO.IOException ex) | |
{ | |
throw new NotImplementedException("Need to be able to create the folder " + testSecondarStoreDir.FullName + " failed due to " + ex.Message); | |
} | |
} | |
// Convert to secondary storage string to canonical path | |
testSecondaryStoreLocalPath = testSecondarStoreDir.FullName; | |
AgentSettings.Default.local_secondary_storage_path = testSecondaryStoreLocalPath; | |
// Make sure local primary storage is available | |
DirectoryInfo testPoolDir = new DirectoryInfo(testLocalStorePath); | |
//Assert.True(testPoolDir.Exists, "To simulate local file system Storage Pool, you need folder at " + testPoolDir.FullName); | |
// Convert to local primary storage string to canonical path | |
testLocalStorePath = testPoolDir.FullName; | |
AgentSettings.Default.local_storage_path = testLocalStorePath; | |
// Clean up old test files in local storage folder | |
FileInfo testVolWorks = new FileInfo(Path.Combine(testLocalStorePath, testSampleVolumeWorkingUUID)); | |
// Assert.True(testVolWorks.Exists, "Create a working virtual disk at " + testVolWorks.FullName); | |
testSampleTemplateURLJSON = JsonConvert.SerializeObject(testSampleTemplateUUID); | |
s_logger.Info("Created " + testSampleTemplateURLJSON + " in local storage."); | |
// Capture other JSON encoded paths | |
testSampleVolumeWorkingURIJSON = Newtonsoft.Json.JsonConvert.SerializeObject(testVolWorks.FullName); | |
testLocalStorePathJSON = JsonConvert.SerializeObject(testLocalStorePath); | |
// TODO: may need to initialise the server resource in future. | |
// s_hypervresource.initialize(); | |
// Verify sample template is in place storage pool | |
s_logger.Info("setUp complete, sample StoragePool at " + testLocalStorePathJSON | |
+ " sample template at " + testSampleTemplateURLJSON); | |
} | |
private String CreateTestDiskImageFromExistingImage(FileInfo srcFile, | |
String dstPath, | |
String dstFileName) | |
{ | |
var newFullname = Path.Combine(dstPath, dstFileName); | |
var newFileInfo = new FileInfo(newFullname); | |
if (!newFileInfo.Exists) | |
{ | |
newFileInfo = srcFile.CopyTo(newFullname); | |
} | |
newFileInfo.Refresh(); | |
Assert.True(newFileInfo.Exists, "Attempted to create " + newFullname + " from " + newFileInfo.FullName); | |
return JsonConvert.SerializeObject(newFileInfo.FullName); | |
} | |
[Fact] | |
public void TestCreateCommand() | |
{ | |
DirectoryInfo localStorePath = new DirectoryInfo(testLocalStorePath); | |
if (!localStorePath.Exists) | |
{ | |
try | |
{ | |
localStorePath.Create(); | |
} | |
catch (System.IO.IOException ex) | |
{ | |
throw new NotImplementedException("Need to be able to create the folder " + localStorePath.FullName + " failed due to " + ex.Message); | |
} | |
} | |
FileInfo sampleTemplateFile = new FileInfo(Path.Combine(testLocalStorePath, testSampleTemplateUUID)); | |
if (!sampleTemplateFile.Exists) | |
{ | |
//Create a file to write to. | |
using (StreamWriter sw = sampleTemplateFile.CreateText()) | |
{ | |
sw.WriteLine("This is fake template file for test"); | |
} | |
} | |
var counter = 0; | |
wmiCallsV2.When(x => x.CreateDynamicVirtualHardDisk(Arg.Any<ulong>(), Arg.Any<String>())).Do(x => counter++); | |
// TODO: Need sample to update the test. | |
// Arrange | |
String createCmd = "{\"volId\":10,\"pool\":{\"id\":201,\"uuid\":\"" + testLocalStoreUUID + "\",\"host\":\"" + HypervResourceController.config.StorageIpAddress + "\"" + | |
",\"path\":" + testLocalStorePathJSON + ",\"port\":0,\"type\":\"Filesystem\"},\"diskCharacteristics\":{\"size\":0," + | |
"\"tags\":[],\"type\":\"ROOT\",\"name\":\"ROOT-9\",\"useLocalStorage\":true,\"recreatable\":true,\"diskOfferingId\":11," + | |
"\"volumeId\":10,\"hyperType\":\"Hyperv\"},\"templateUrl\":" + testSampleTemplateURLJSON + ",\"contextMap\":{},\"wait\":0}"; | |
dynamic jsonCreateCmd = JsonConvert.DeserializeObject(createCmd); | |
HypervResourceController rsrcServer = new HypervResourceController(); | |
HypervResourceController.wmiCallsV2 = wmiCallsV2; | |
Assert.True(Directory.Exists(testLocalStorePath), testLocalStorePath + " does not exist "); | |
string filePath = Path.Combine(testLocalStorePath, (string)JsonConvert.DeserializeObject(testSampleTemplateURLJSON)); | |
Assert.True(File.Exists(filePath), "The template we make volumes from is missing from path " + filePath); | |
int fileCount = Directory.GetFiles(testLocalStorePath).Length; | |
s_logger.Debug(" test local store has " + fileCount + "files"); | |
// Act | |
// Test requires there to be a template at the tempalteUrl, which is its location in the local file system. | |
dynamic jsonResult = rsrcServer.CreateCommand(jsonCreateCmd); | |
s_logger.Debug("CreateDynamicVirtualHardDisk method is called " + counter + " times"); | |
//Assert.Equal(counter, 1); | |
JObject ansAsProperty2 = jsonResult[0]; | |
dynamic ans = ansAsProperty2.GetValue(CloudStackTypes.CreateAnswer); | |
Assert.NotNull(ans); | |
Assert.True((bool)ans.result, "Failed to CreateCommand due to " + (string)ans.result); | |
Assert.Equal(Directory.GetFiles(testLocalStorePath).Length, fileCount + 1); | |
FileInfo newFile = new FileInfo((string)ans.volume.path); | |
Assert.True(newFile.Length > 0, "The new file should have a size greater than zero"); | |
newFile.Delete(); | |
sampleTemplateFile.Delete(); | |
} | |
[Fact] | |
public void TestDestroyCommand() | |
{ | |
testSampleVolumeTempURIJSON = "\"storagepool\""; | |
// Arrange | |
String destroyCmd = //"{\"volume\":" + getSampleVolumeObjectTO() + "}"; | |
"{\"volume\":{\"name\":\"" + testSampleVolumeTempUUIDNoExt | |
+ "\",\"storagePoolType\":\"Filesystem\"," | |
+ "\"mountPoint\":" | |
+ testLocalStorePathJSON | |
+ ",\"path\":" + testSampleVolumeTempURIJSON | |
+ ",\"storagePoolUuid\":\"" + testLocalStoreUUID | |
+ "\"," | |
+ "\"type\":\"ROOT\",\"id\":9,\"size\":0}}"; | |
ImageManagementService imgmgr = new ImageManagementService(); | |
wmiCallsV2.GetImageManagementService().Returns(imgmgr); | |
HypervResourceController rsrcServer = new HypervResourceController(); | |
HypervResourceController.wmiCallsV2 = wmiCallsV2; | |
dynamic jsonDestroyCmd = JsonConvert.DeserializeObject(destroyCmd); | |
// Act | |
dynamic destroyAns = rsrcServer.DestroyCommand(jsonDestroyCmd); | |
// Assert | |
JObject ansAsProperty2 = destroyAns[0]; | |
dynamic ans = ansAsProperty2.GetValue(CloudStackTypes.Answer); | |
String path = jsonDestroyCmd.volume.path; | |
Assert.True((bool)ans.result, "DestroyCommand did not succeed " + ans.details); | |
Assert.True(!File.Exists(path), "Failed to delete file " + path); | |
} | |
[Fact] | |
public void TestStartCommand() | |
{ | |
ComputerSystem system = new ComputerSystem(); | |
wmiCallsV2.DeployVirtualMachine(Arg.Any<Object>(), Arg.Any<string>()).Returns(system); | |
// Arrange | |
HypervResourceController rsrcServer = new HypervResourceController(); | |
HypervResourceController.wmiCallsV2 = wmiCallsV2; | |
String sample = getSampleStartCommand(); | |
dynamic jsonStartCmd = JsonConvert.DeserializeObject(sample); | |
// Act | |
dynamic startAns = rsrcServer.StartCommand(jsonStartCmd); | |
// Assert | |
Assert.NotNull(startAns[0][CloudStackTypes.StartAnswer]); | |
Assert.True((bool)startAns[0][CloudStackTypes.StartAnswer].result, "StartCommand did not succeed " + startAns[0][CloudStackTypes.StartAnswer].details); | |
Assert.Null((string)startAns[0][CloudStackTypes.StartAnswer].details); | |
} | |
[Fact] | |
public void TestStopCommand() | |
{ | |
//string vmName = "Test VM"; | |
var counter = 0; | |
wmiCallsV2.When(x => x.DestroyVm(Arg.Any<Object>())).Do(x => counter++); | |
// Arrange | |
HypervResourceController rsrcServer = new HypervResourceController(); | |
HypervResourceController.wmiCallsV2 = wmiCallsV2; | |
String sampleStop = "{\"isProxy\":false,\"vmName\":\"i-2-17-VM\",\"contextMap\":{},\"checkBeforeCleanup\":false,\"wait\":0}"; | |
dynamic jsonStopCmd = JsonConvert.DeserializeObject(sampleStop); | |
// Act | |
dynamic stopAns = rsrcServer.StopCommand(jsonStopCmd); | |
// Assert VM is gone! | |
Assert.NotNull(stopAns[0][CloudStackTypes.StopAnswer]); | |
Assert.True((bool)stopAns[0][CloudStackTypes.StopAnswer].result, "StopCommand did not succeed " + stopAns[0][CloudStackTypes.StopAnswer].details); | |
Assert.Null((string)stopAns[0][CloudStackTypes.StopAnswer].details); | |
Assert.Equal<int>(counter, 1); | |
} | |
public static String getSamplePrimaryDataStoreInfo() | |
{ | |
String samplePrimaryDataStoreInfo = | |
"{\"org.apache.cloudstack.storage.to.PrimaryDataStoreTO\":" + | |
"{\"uuid\":\"" + testLocalStoreUUID + "\"," + | |
"\"id\":201," + | |
"\"host\":\"" + testPrimaryDataStoreHost + "\"," + | |
"\"type\":\"Filesystem\"," + // Not used in PrimaryDataStoreTO | |
"\"poolType\":\"Filesystem\"," + // Not used in PrimaryDataStoreTO | |
"\"path\":" + testLocalStorePathJSON + "," + | |
"\"port\":0}" + | |
"}"; | |
return samplePrimaryDataStoreInfo; | |
} | |
public static String getSampleVolumeObjectTO() | |
{ | |
String sampleVolumeObjectTO = | |
"{\"org.apache.cloudstack.storage.to.VolumeObjectTO\":" + | |
"{\"uuid\":\"19ae8e67-cb2c-4ab4-901e-e0b864272b59\"," + | |
"\"volumeType\":\"ROOT\"," + | |
"\"format\":\"VHDX\"," + | |
"\"dataStore\":" + getSamplePrimaryDataStoreInfo() + "," + | |
"\"name\":\"" + testSampleVolumeTempUUIDNoExt + "\"," + | |
"\"size\":52428800," + | |
"\"volumeId\":10," + | |
// "\"vmName\":\"i-3-5-VM\"," + // TODO: do we have to fill in the vmName? | |
"\"accountId\":3,\"id\":10}" + | |
"}"; // end of destTO | |
return sampleVolumeObjectTO; | |
} | |
public static String getSampleStartCommand() | |
{ | |
String sample = "{\"vm\":{\"id\":17,\"name\":\"i-2-17-VM\",\"type\":\"User\",\"cpus\":1,\"speed\":500," + | |
"\"minRam\":536870912,\"maxRam\":536870912,\"arch\":\"x86_64\"," + | |
"\"os\":\"CentOS 6.0 (64-bit)\",\"bootArgs\":\"\",\"rebootOnCrash\":false," + | |
"\"enableHA\":false,\"limitCpuUse\":false,\"vncPassword\":\"31f82f29aff646eb\"," + | |
"\"params\":{},\"uuid\":\"8b030b6a-0243-440a-8cc5-45d08815ca11\"" + | |
",\"disks\":[" + | |
"{\"data\":" + getSampleVolumeObjectTO() + ",\"diskSeq\":0,\"type\":\"ROOT\"}," + | |
"{\"diskSeq\":1,\"type\":\"ISO\"}" + | |
"]," + | |
"\"nics\":[" + | |
"{\"deviceId\":0,\"networkRateMbps\":100,\"defaultNic\":true,\"uuid\":\"99cb4813-23af-428c-a87a-2d1899be4f4b\"," + | |
"\"ip\":\"10.1.1.67\",\"netmask\":\"255.255.255.0\",\"gateway\":\"10.1.1.1\"," + | |
"\"mac\":\"02:00:51:2c:00:0e\",\"dns1\":\"4.4.4.4\",\"broadcastType\":\"Vlan\",\"type\":\"Guest\"," + | |
"\"broadcastUri\":\"vlan://261\",\"isolationUri\":\"vlan://261\",\"isSecurityGroupEnabled\":false}" + | |
"]},\"contextMap\":{},\"wait\":0}"; | |
return sample; | |
} | |
} | |
} |