Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HyperV2012R2+] Fix Job Loss During Disk Manipulation by Transitioning to WMI-Based Implementation #221

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace SolidCP.EnterpriseServer.Code.Virtualization2012.Helpers
{
public static class JobHelper
{
public static bool TryJobCompleted(VirtualizationServer2012 vs, ConcreteJob job, bool resetProgressBarIndicatorAfterFinish = true)
public static bool TryJobCompleted(VirtualizationServer2012 vs, ConcreteJob job, bool resetProgressBarIndicatorAfterFinish = true, bool isPowerShellJob = false)
{
bool jobCompleted = false;
short timeout = 5;
Expand All @@ -20,14 +20,14 @@ public static bool TryJobCompleted(VirtualizationServer2012 vs, ConcreteJob job,
timeout--;
try
{
jobCompleted = JobCompleted(vs, job, resetProgressBarIndicatorAfterFinish);
jobCompleted = JobCompleted(vs, job, resetProgressBarIndicatorAfterFinish, isPowerShellJob);
}
catch (ThreadAbortException) //https://github.com/FuseCP/SolidCP/issues/103
{
//maybe there need to use Thread.ResetAbort(); ???

TaskManager.Write("VPS_CREATE_TRY_JOB_COMPLETE_ATTEMPTS_LEFT_AFTER_THREAD_ABORT", timeout.ToString());
job = vs.GetJob(job.Id); //get the last job state
job = GetLastJobUpdate(vs, job, isPowerShellJob); //get the last job state

jobCompleted = (job.JobState == ConcreteJobState.Completed); //is job completed?
}
Expand All @@ -41,7 +41,7 @@ public static bool TryJobCompleted(VirtualizationServer2012 vs, ConcreteJob job,
return jobCompleted;
}

public static bool JobCompleted(VirtualizationServer2012 vs, ConcreteJob job, bool resetProgressBarIndicatorAfterFinish = true)
public static bool JobCompleted(VirtualizationServer2012 vs, ConcreteJob job, bool resetProgressBarIndicatorAfterFinish = true, bool isPowerShellJob = false)
{
TaskManager.IndicatorMaximum = 100;
bool jobCompleted = true;
Expand All @@ -50,14 +50,14 @@ public static bool JobCompleted(VirtualizationServer2012 vs, ConcreteJob job, bo
{
timeout--;
Thread.Sleep(2000);
job = vs.GetJob(job.Id);
job = GetLastJobUpdate(vs, job, isPowerShellJob);
}

while (job.JobState == ConcreteJobState.Starting ||
job.JobState == ConcreteJobState.Running)
{
Thread.Sleep(3000);
job = vs.GetJob(job.Id);
job = GetLastJobUpdate(vs, job, isPowerShellJob);
TaskManager.IndicatorCurrent = job.PercentComplete;
}

Expand All @@ -70,9 +70,17 @@ public static bool JobCompleted(VirtualizationServer2012 vs, ConcreteJob job, bo
TaskManager.IndicatorCurrent = 0; // reset indicator
}

vs.ClearOldJobs();
if (isPowerShellJob)
{
vs.ClearOldPsJobs();
}

return jobCompleted;
}

public static ConcreteJob GetLastJobUpdate(VirtualizationServer2012 vs, ConcreteJob job, bool isPowerShellJob = false)
{
return isPowerShellJob ? vs.GetPsJob(job.Id) : vs.GetJob(job.Id);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ public interface IVirtualizationServer2012

// Jobs
ConcreteJob GetJob(string jobId);
ConcreteJob GetPsJob(string jobId);
List<ConcreteJob> GetAllJobs();
void ClearOldJobs();
void ClearOldPsJobs();
ChangeJobStateReturnCode ChangeJobState(string jobId, ConcreteJobRequestedState newState);

// Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ namespace SolidCP.Providers.Virtualization
{
public enum VirtualHardDiskFormat
{
VHD = 0,
VHDX = 1
Unknown = 0,
VHD = 2,
VHDX = 3
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace SolidCP.Providers.Virtualization
{
public static class HardDriveHelper
internal static class HardDriveHelper
{
public static VirtualHardDiskInfo[] Get(PowerShellManager powerShell, string vmname)
{
Expand Down Expand Up @@ -89,7 +89,7 @@ public static void GetVirtualHardDiskDetail(PowerShellManager powerShell, string
}
}

public static void Update(PowerShellManager powerShell, PowerShellManager powerShellwithJobs, VirtualMachine realVm, VirtualMachine vmSettings, string serverNameSettings)
public static void Update(PowerShellManager powerShell, Wmi wmi, VirtualMachine realVm, VirtualMachine vmSettings, string serverNameSettings)
{
if (realVm.Disks == null) //At this moment it isn't possible, but if somebody send vm data without vm.disks, we try to get it.
realVm.Disks = Get(powerShell, realVm.Name);
Expand Down Expand Up @@ -169,7 +169,7 @@ public static void Update(PowerShellManager powerShell, PowerShellManager powerS
if (add)
{
VirtualHardDiskInfo disk = GetParentVHD(realVm.Disks[0], powerShell);
CreateVirtualHardDisk(powerShellwithJobs, vmSettings.VirtualHardDrivePath[i], disk.DiskType, disk.BlockSizeBytes, (ulong)vmSettings.HddSize[i], serverNameSettings);
CreateVirtualHardDisk(wmi, vmSettings.VirtualHardDrivePath[i], disk.DiskType, disk.BlockSizeBytes, (ulong)vmSettings.HddSize[i], serverNameSettings);
Command cmd = new Command("Add-VMHardDiskDrive");
cmd.Parameters.Add("VMName", realVm.Name);
cmd.Parameters.Add("Path", vmSettings.VirtualHardDrivePath[i]);
Expand Down Expand Up @@ -228,20 +228,39 @@ private static VirtualHardDiskInfo GetParentVHD(VirtualHardDiskInfo disk, PowerS
return resDisk;
}

public static Collection<PSObject> CreateVirtualHardDisk(PowerShellManager powerShellwithJobs, string destinationPath, VirtualHardDiskType diskType, uint blockSizeBytes, UInt64 sizeGB, string serverNameSettings)
public static ManagementBaseObject CreateVirtualHardDisk(Wmi wmi, string destinationPath, VirtualHardDiskType diskType, uint blockSizeBytes, UInt64 sizeGB, string serverNameSettings)
{
string destFolder = Path.GetDirectoryName(destinationPath);
if (!DirectoryExists(destFolder, serverNameSettings)) CreateFolder(destFolder, serverNameSettings);

destinationPath = FileUtils.EvaluateSystemVariables(destinationPath);

Command cmd = new Command("New-VHD");
string fileExtension = Path.GetExtension(destinationPath);
VirtualHardDiskFormat format = fileExtension.Equals(".vhdx", StringComparison.InvariantCultureIgnoreCase) ? VirtualHardDiskFormat.VHDX : VirtualHardDiskFormat.VHD;

cmd.Parameters.Add("SizeBytes", sizeGB * Constants.Size1G);
cmd.Parameters.Add("Path", destinationPath);
cmd.Parameters.Add(diskType.ToString());
if (blockSizeBytes > 0) cmd.Parameters.Add("BlockSizeBytes", blockSizeBytes);
return powerShellwithJobs.TryExecuteAsJob(cmd, true);
ManagementObject imageService = wmi.GetWmiObject("msvm_ImageManagementService");
ManagementClass settingsClass = wmi.GetWmiClass("Msvm_VirtualHardDiskSettingData");

ManagementObject settingsInstance = settingsClass.CreateInstance();
settingsInstance["MaxInternalSize"] = sizeGB * Constants.Size1G;
settingsInstance["Path"] = destinationPath;
settingsInstance["Type"] = diskType;
settingsInstance["Format"] = format;
settingsInstance["BlockSize"] = (blockSizeBytes > 0) ? blockSizeBytes : 0;

ManagementBaseObject inParams = imageService.GetMethodParameters("CreateVirtualHardDisk");
inParams["VirtualDiskSettingData"] = settingsInstance.GetText(TextFormat.WmiDtd20);

return imageService.InvokeMethod("CreateVirtualHardDisk", inParams, null);


//Command cmd = new Command("New-VHD");

//cmd.Parameters.Add("SizeBytes", sizeGB * Constants.Size1G);
//cmd.Parameters.Add("Path", destinationPath);
//cmd.Parameters.Add(diskType.ToString());
//if (blockSizeBytes > 0) cmd.Parameters.Add("BlockSizeBytes", blockSizeBytes);
//return powerShellwithJobs.TryExecuteAsJob(cmd, true);
}

private static void CreateFolder(string path, string serverNameSettings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text;
using System.Threading.Tasks;

namespace SolidCP.Providers.Virtualization
{
public static class JobHelper
internal static class JobHelper
{
public static JobResult CreateSuccessResult(ReturnCode returnCode = ReturnCode.OK)
{
Expand All @@ -33,6 +34,18 @@ public static JobResult CreateUnsuccessResult(ReturnCode returnCode = ReturnCode
};
}

public static JobResult CreateJobResultFromWmiResults(Wmi wmi,ManagementBaseObject outParams)
{
JobResult result = new JobResult();

result.ReturnValue = (ReturnCode)Convert.ToInt32(outParams["ReturnValue"]);

ManagementObject objJob = wmi.GetWmiObjectByPath((string)outParams["Job"]);
result.Job = CreateFromWmiObject(objJob);

return result;
}

public static JobResult CreateResultFromPSResults(Collection<PSObject> objJob)
{
if (objJob == null || objJob.Count == 0)
Expand All @@ -53,13 +66,32 @@ public static JobResult CreateResultFromPSResults(Collection<PSObject> objJob)
return result;
}

public static ConcreteJob CreateFromWmiObject(ManagementObject objJob)
{
if (objJob == null || objJob.Properties.Count == 0)
return null;

ConcreteJob job = new ConcreteJob();
job.Id = (string)objJob["InstanceID"];
job.JobState = (ConcreteJobState)Convert.ToInt32(objJob["JobState"]);
job.Caption = (string)objJob["Caption"];
job.Description = (string)objJob["Description"];
job.StartTime = Wmi.ToDateTime((string)objJob["StartTime"]);
// TODO proper parsing of WMI time spans, e.g. 00000000000001.325247:000
job.ElapsedTime = DateTime.Now; //wmi.ToDateTime((string)objJob["ElapsedTime"]);
job.ErrorCode = Convert.ToInt32(objJob["ErrorCode"]);
job.ErrorDescription = (string)objJob["ErrorDescription"];
job.PercentComplete = Convert.ToInt32(objJob["PercentComplete"]);
return job;
}

public static ConcreteJob CreateFromPSObject(Collection<PSObject> objJob)
{
if (objJob == null || objJob.Count == 0)
return null;

ConcreteJob job = new ConcreteJob();
job.Id = objJob[0].GetProperty<int>("Id").ToString();
job.Id = objJob[0].GetProperty<Guid>("InstanceId").ToString();
job.JobState = objJob[0].GetEnum<ConcreteJobState>("JobStateInfo");
job.ChildJobs = GetChildJobs(objJob[0].GetProperty<List<Job>>("ChildJobs"));
job.Caption = objJob[0].GetProperty<string>("Name");
Expand Down
Loading