mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-23 00:46:03 +01:00
f507b65126
When creating a new VM, we no longer use vm.provision to create VM's disks, but instead we create the disks ourselves using vdi.create; this way we can specify the initial and incremental allocations for the new disks. Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com>
747 lines
29 KiB
C#
747 lines
29 KiB
C#
/* Copyright (c) Citrix Systems Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms,
|
|
* with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using XenAdmin.Network;
|
|
using XenAPI;
|
|
using XenAdmin.Core;
|
|
using System.Xml;
|
|
|
|
|
|
namespace XenAdmin.Actions.VMActions
|
|
{
|
|
public enum InstallMethod
|
|
{
|
|
None,
|
|
CD,
|
|
Network
|
|
}
|
|
public class CreateVMAction : AsyncAction
|
|
{
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private readonly string NameLabel;
|
|
private readonly string NameDescription;
|
|
private readonly InstallMethod InsMethod;
|
|
private readonly string PvArgs;
|
|
private readonly VDI Cd;
|
|
private readonly string Url;
|
|
private readonly Host HomeServer;
|
|
private readonly long Vcpus;
|
|
private readonly long MemoryDynamicMin, MemoryDynamicMax, MemoryStaticMax;
|
|
private readonly List<DiskDescription> Disks;
|
|
private readonly List<VIF> Vifs;
|
|
private readonly bool StartAfter;
|
|
private readonly Host CopyBiosStringsFrom;
|
|
private readonly SR FullCopySR;
|
|
private readonly GPU_group GpuGroup;
|
|
private readonly VGPU_type VgpuType;
|
|
private readonly long CoresPerSocket;
|
|
private readonly string cloudConfigDriveTemplateText;
|
|
private SR firstSR = null;
|
|
|
|
private Action<VMStartAbstractAction, Failure> _startDiagnosisForm;
|
|
private Action<VM, bool> _warningDialogHAInvalidConfig;
|
|
|
|
private bool PointOfNoReturn;
|
|
|
|
private bool assignOrRemoveVgpu;
|
|
|
|
/// <summary>
|
|
/// These are the RBAC dependencies that you always need to create a VM. Check CreateVMAction constructor for runtime dependent dependencies.
|
|
/// </summary>
|
|
public static RbacMethodList StaticRBACDependencies = new RbacMethodList(
|
|
// provision VM
|
|
"vm.provision",
|
|
"vm.set_other_config",
|
|
// set VM Params
|
|
"vm.set_name_label",
|
|
"vm.set_name_description",
|
|
"vm.set_VCPUs_max",
|
|
"vm.set_VCPUs_at_startup",
|
|
// set VM Boot Params
|
|
"vm.set_HVM_boot_params",
|
|
"vm.set_PV_args",
|
|
"vm.set_other_config",
|
|
// Add CD Drive
|
|
"vbd.eject",
|
|
"vbd.insert",
|
|
// Create CD Drive
|
|
"vbd.create",
|
|
// Add disks
|
|
"vdi.destroy",
|
|
"vdi.create",
|
|
"vdi.set_sm_config",
|
|
"vbd.create",
|
|
"vbd.destroy",
|
|
"vdi.copy",
|
|
// Add networks
|
|
"vif.create",
|
|
"vm.set_platform"
|
|
);
|
|
|
|
public CreateVMAction(IXenConnection connection, VM template, Host copyBiosStringsFrom,
|
|
string name, string description, InstallMethod installMethod,
|
|
string pvArgs, VDI cd, string url, Host homeServer, long vcpus,
|
|
long memoryDynamicMin, long memoryDynamicMax, long memoryStaticMax,
|
|
List<DiskDescription> disks, SR fullCopySR, List<VIF> vifs, bool startAfter,
|
|
Action<VM, bool> warningDialogHAInvalidConfig,
|
|
Action<VMStartAbstractAction, Failure> startDiagnosisForm,
|
|
GPU_group gpuGroup, VGPU_type vgpuType, bool modifyVgpuSettings, long coresPerSocket, string cloudConfigDriveTemplateText)
|
|
: base(connection, string.Format(Messages.CREATE_VM, name),
|
|
string.Format(Messages.CREATE_VM_FROM_TEMPLATE, name, Helpers.GetName(template)))
|
|
{
|
|
Template = template;
|
|
CopyBiosStringsFrom = copyBiosStringsFrom;
|
|
FullCopySR = fullCopySR;
|
|
NameLabel = name;
|
|
NameDescription = description;
|
|
InsMethod = installMethod;
|
|
PvArgs = pvArgs;
|
|
Cd = cd;
|
|
Url = url;
|
|
HomeServer = homeServer;
|
|
Vcpus = vcpus;
|
|
MemoryDynamicMin = memoryDynamicMin;
|
|
MemoryDynamicMax = memoryDynamicMax;
|
|
MemoryStaticMax = memoryStaticMax;
|
|
Disks = disks;
|
|
Vifs = vifs;
|
|
StartAfter = startAfter;
|
|
_warningDialogHAInvalidConfig = warningDialogHAInvalidConfig;
|
|
_startDiagnosisForm = startDiagnosisForm;
|
|
GpuGroup = gpuGroup;
|
|
VgpuType = vgpuType;
|
|
CoresPerSocket = coresPerSocket;
|
|
this.cloudConfigDriveTemplateText = cloudConfigDriveTemplateText;
|
|
|
|
Pool pool_of_one = Helpers.GetPoolOfOne(Connection);
|
|
if (HomeServer != null || pool_of_one != null) // otherwise we have no where to put the action
|
|
AppliesTo.Add(HomeServer != null ? HomeServer.opaque_ref : pool_of_one.opaque_ref);
|
|
|
|
assignOrRemoveVgpu = GpuGroup != null && VgpuType != null || modifyVgpuSettings && Helpers.GpuCapability(Connection);
|
|
|
|
#region RBAC Dependencies
|
|
|
|
if (StartAfter)
|
|
ApiMethodsToRoleCheck.Add("vm.start");
|
|
if (HomeServerChanged())
|
|
ApiMethodsToRoleCheck.Add("vm.set_affinity");
|
|
if (Template.memory_dynamic_min != MemoryDynamicMin || Template.memory_dynamic_max != MemoryDynamicMax || Template.memory_static_max != MemoryStaticMax)
|
|
ApiMethodsToRoleCheck.Add("vm.set_memory_limits");
|
|
|
|
if (assignOrRemoveVgpu)
|
|
{
|
|
ApiMethodsToRoleCheck.Add("VGPU.destroy");
|
|
ApiMethodsToRoleCheck.Add("VGPU.create");
|
|
}
|
|
|
|
ApiMethodsToRoleCheck.AddRange(StaticRBACDependencies);
|
|
|
|
ApiMethodsToRoleCheck.AddRange(Role.CommonTaskApiList);
|
|
ApiMethodsToRoleCheck.AddRange(Role.CommonSessionApiList);
|
|
|
|
#endregion
|
|
}
|
|
|
|
protected override void Run()
|
|
{
|
|
if (FullCopySR != null)
|
|
{
|
|
// VM.copy is the best call to make if all target disks are on the same SR.
|
|
// however, if the target disks are on the same SR as the source disks, then the user is
|
|
// given the choice of a fast-clone (VM.clone) or a full-copy (VM.copy) on the storage page of the wizard. If the
|
|
// user chose a VM.clone, then FullCopySR will be null.
|
|
|
|
RelatedTask = VM.async_copy(Session, Template.opaque_ref, HiddenVmName, FullCopySR.opaque_ref);
|
|
}
|
|
else
|
|
{
|
|
// if the target disks are on mixed storage or the user chose to a do a fast-clone on the storage
|
|
// page then we end up here.
|
|
|
|
RelatedTask = VM.async_clone(Session, Template.opaque_ref, HiddenVmName);
|
|
}
|
|
|
|
Description = string.Format(Messages.CLONING_TEMPLATE, Helpers.GetName(Template));
|
|
PollToCompletion(0, 10);
|
|
|
|
VM = Connection.WaitForCache(new XenRef<VM>(Result));
|
|
|
|
CopyBiosStrings();
|
|
SetXenCenterProperties();
|
|
ProvisionVM();
|
|
SetVMParams();
|
|
SetVMBootParams();
|
|
AddCdDrive();
|
|
AddDisks();
|
|
AddNetworks();
|
|
XenAdminConfigManager.Provider.ShowObject(VM.opaque_ref);
|
|
PointOfNoReturn = true;
|
|
|
|
CloudCreateConfigDrive();
|
|
|
|
AssignVgpu();
|
|
|
|
if (StartAfter)
|
|
{
|
|
Description = Messages.STARTING_VM;
|
|
var startAction = new VMStartAction(VM, _warningDialogHAInvalidConfig, _startDiagnosisForm);
|
|
startAction.RunAsync();
|
|
}
|
|
|
|
Description = Messages.VM_SUCCESSFULLY_CREATED;
|
|
}
|
|
|
|
private void CloudCreateConfigDrive()
|
|
{
|
|
if (Template.CanHaveCloudConfigDrive && !string.IsNullOrEmpty(cloudConfigDriveTemplateText))
|
|
{
|
|
Description = Messages.CREATING_CLOUD_CONFIG_DRIVE;
|
|
|
|
var parameters = new Dictionary<string, string>();
|
|
parameters.Add("vmuuid", VM.uuid);
|
|
parameters.Add("sruuid", firstSR.uuid);
|
|
parameters.Add("configuration", cloudConfigDriveTemplateText.Replace("\r\n", "\n"));
|
|
|
|
var action = new ExecutePluginAction(Connection, HomeServer ?? Helpers.GetMaster(Connection),
|
|
"xscontainer",//plugin
|
|
"create_config_drive",//function
|
|
parameters,
|
|
true); //hidefromlogs
|
|
|
|
action.RunExternal(Connection.Session);
|
|
var result = action.Result.Replace("\n", Environment.NewLine);
|
|
}
|
|
}
|
|
|
|
private void AssignVgpu()
|
|
{
|
|
if (assignOrRemoveVgpu)
|
|
{
|
|
var action = new GpuAssignAction(VM, GpuGroup, VgpuType);
|
|
action.RunExternal(Session);
|
|
}
|
|
}
|
|
|
|
private void CopyBiosStrings()
|
|
{
|
|
if (CopyBiosStringsFrom != null && Template.DefaultTemplate)
|
|
{
|
|
VM.copy_bios_strings(Session, this.VM.opaque_ref, CopyBiosStringsFrom.opaque_ref);
|
|
}
|
|
}
|
|
|
|
private void SetXenCenterProperties()
|
|
{
|
|
XenAdminConfigManager.Provider.HideObject(VM.opaque_ref);
|
|
AppliesTo.Add(VM.opaque_ref);
|
|
}
|
|
|
|
private void SetVMParams()
|
|
{
|
|
Description = Messages.SETTING_VM_PROPERTIES;
|
|
XenAPI.VM.set_name_label(Session, VM.opaque_ref, NameLabel);
|
|
XenAPI.VM.set_name_description(Session, VM.opaque_ref, NameDescription);
|
|
ChangeVCPUSettingsAction vcpuAction = new ChangeVCPUSettingsAction(VM, Vcpus);
|
|
vcpuAction.RunExternal(Session);
|
|
|
|
// set cores-per-socket
|
|
Dictionary<string, string> platform = VM.platform == null ?
|
|
new Dictionary<string, string>() :
|
|
new Dictionary<string, string>(VM.platform);
|
|
platform["cores-per-socket"] = CoresPerSocket.ToString();
|
|
VM.set_platform(Session, VM.opaque_ref, platform);
|
|
|
|
// Check these values have changed before setting them, as they are RBAC protected
|
|
if (HomeServerChanged())
|
|
XenAPI.VM.set_affinity(Session, VM.opaque_ref, HomeServer != null ? HomeServer.opaque_ref : Helper.NullOpaqueRef);
|
|
|
|
if (Helpers.MidnightRideOrGreater(VM.Connection))
|
|
{
|
|
if (Template.memory_dynamic_min != MemoryDynamicMin || Template.memory_dynamic_max != MemoryDynamicMax || Template.memory_static_max != MemoryStaticMax)
|
|
XenAPI.VM.set_memory_limits(Session, VM.opaque_ref, Template.memory_static_min, MemoryStaticMax, MemoryDynamicMin, MemoryDynamicMax);
|
|
}
|
|
else
|
|
{
|
|
// For George and earlier hosts, we set them all the same.
|
|
// The order of operations doesn't matter, because George didn't enforce the ordering of values.
|
|
XenAPI.VM.set_memory_dynamic_min(Session, VM.opaque_ref, MemoryStaticMax);
|
|
XenAPI.VM.set_memory_dynamic_max(Session, VM.opaque_ref, MemoryStaticMax);
|
|
XenAPI.VM.set_memory_static_max(Session, VM.opaque_ref, MemoryStaticMax);
|
|
}
|
|
}
|
|
|
|
private bool HomeServerChanged()
|
|
{
|
|
if (HomeServer == null)
|
|
{
|
|
return Template.affinity.opaque_ref != Helper.NullOpaqueRef;
|
|
}
|
|
return HomeServer.opaque_ref != Template.affinity.opaque_ref;
|
|
}
|
|
|
|
private void SetVMBootParams()
|
|
{
|
|
if (Template.IsHVM && (Disks.Count == 0 || InsMethod == InstallMethod.Network)) // CA-46213
|
|
{
|
|
// boot from network
|
|
Dictionary<string, string> hvm_params = VM.HVM_boot_params;
|
|
hvm_params["order"] = GetBootOrderNetworkFirst();
|
|
XenAPI.VM.set_HVM_boot_params(Session, VM.opaque_ref, hvm_params);
|
|
}
|
|
else if (IsEli() && InsMethod == InstallMethod.Network)
|
|
{
|
|
Dictionary<string, string> other_config = VM.other_config;
|
|
string normal_url = IsRhel() ? NormalizeRepoUrlForRHEL(Url) : Url;
|
|
other_config["install-repository"] = normal_url;
|
|
XenAPI.VM.set_other_config(Session, VM.opaque_ref, other_config);
|
|
}
|
|
else if (IsEli() && InsMethod == InstallMethod.CD)
|
|
{
|
|
Dictionary<string, string> other_config = VM.other_config;
|
|
other_config["install-repository"] = "cdrom";
|
|
XenAPI.VM.set_other_config(Session, VM.opaque_ref, other_config);
|
|
}
|
|
|
|
if (!Template.IsHVM)
|
|
{
|
|
XenAPI.VM.set_PV_args(Session, VM.opaque_ref, PvArgs);
|
|
}
|
|
}
|
|
|
|
private bool IsEli()
|
|
{
|
|
return !Template.IsHVM && Template.PV_bootloader == "eliloader";
|
|
}
|
|
|
|
private bool IsRhel()
|
|
{
|
|
string distro = VM.InstallDistro;
|
|
return distro == "rhel41" || distro == "rhel44" || distro == "rhlike";
|
|
}
|
|
|
|
private string NormalizeRepoUrlForRHEL(string url)
|
|
{
|
|
Uri uri = new Uri(url);
|
|
return uri.Scheme == "nfs" ? string.Format("nfs:{0}:{1}", uri.Host, uri.PathAndQuery) : url;
|
|
}
|
|
|
|
private void ProvisionVM()
|
|
{
|
|
Description = Messages.PROVISIONING_VM;
|
|
RewriteProvisionXML();
|
|
RelatedTask = XenAPI.VM.async_provision(Session, VM.opaque_ref);
|
|
|
|
PollToCompletion(10, 60);
|
|
}
|
|
|
|
private void RewriteProvisionXML()
|
|
{
|
|
XmlNode xml = VM.ProvisionXml;
|
|
|
|
if (xml == null)
|
|
return;
|
|
|
|
// set the new vm's provision xml: remove "disks" entry, as we are going to explicitly create all the disks
|
|
Dictionary<string, string> other_config = VM.other_config;
|
|
other_config.Remove("disks");
|
|
XenAPI.VM.set_other_config(Session, VM.opaque_ref, other_config);
|
|
}
|
|
|
|
private void AddCdDrive()
|
|
{
|
|
if (Helpers.CustomWithNoDVD(Template))
|
|
return; // we have skipped the install media page because we are a cutom template with no cd drive - the user doesnt want a cd drive
|
|
|
|
Description = Messages.CREATE_CD_DRIVE;
|
|
VBD cd_drive = null;
|
|
foreach (VBD vbd in Connection.ResolveAll(VM.VBDs))
|
|
{
|
|
if (vbd.type != vbd_type.CD)
|
|
continue;
|
|
|
|
if ("0123".IndexOf(vbd.userdevice) < 0) // userdevice is not 0, 1, 2 or 3: these are the valid positions for CD drives
|
|
continue;
|
|
|
|
cd_drive = vbd;
|
|
break;
|
|
}
|
|
|
|
if (cd_drive == null)
|
|
{
|
|
cd_drive = CreateCdDrive();
|
|
}
|
|
|
|
if (!cd_drive.empty)
|
|
{
|
|
RelatedTask = VBD.async_eject(Session, cd_drive.opaque_ref);
|
|
PollToCompletion(65, 67);
|
|
}
|
|
|
|
if (InsMethod == InstallMethod.CD && Cd != null) // obviously dont insert the empty cd
|
|
{
|
|
RelatedTask = VBD.async_insert(Session, cd_drive.opaque_ref, Cd.opaque_ref);
|
|
PollToCompletion(67, 70);
|
|
}
|
|
}
|
|
|
|
private VBD CreateCdDrive()
|
|
{
|
|
List<string> devices = AllowedVBDs;
|
|
if (devices.Count == 0)
|
|
throw new Exception(Messages.NO_MORE_USERDEVICES);
|
|
VBD vbd = new VBD();
|
|
vbd.bootable = InsMethod == InstallMethod.CD;
|
|
vbd.empty = true;
|
|
vbd.unpluggable = true;
|
|
vbd.mode = vbd_mode.RO;
|
|
vbd.type = vbd_type.CD;
|
|
vbd.userdevice = devices.Contains("3") ? "3" : devices[0];
|
|
vbd.device = "";
|
|
vbd.VM = new XenRef<VM>(VM.opaque_ref);
|
|
vbd.VDI = null;
|
|
RelatedTask = VBD.async_create(Session, vbd);
|
|
PollToCompletion(60, 65);
|
|
|
|
return Connection.WaitForCache(new XenRef<VBD>(Result));
|
|
}
|
|
|
|
|
|
private void AddDisks()
|
|
{
|
|
Description = Messages.CREATING_DISKS;
|
|
List<VBD> vbds = Connection.ResolveAll(VM.VBDs);
|
|
|
|
bool firstDisk = true;
|
|
string suspendSr = null;
|
|
|
|
double progress = 70;
|
|
double step = 20.0 / (double)Disks.Count;
|
|
foreach (DiskDescription disk in Disks)
|
|
{
|
|
VBD vbd = GetDiskVBD(disk, vbds);
|
|
VDI vdi = null;
|
|
if (vbd != null)
|
|
{
|
|
vdi = Connection.Resolve<VDI>(vbd.VDI);
|
|
}
|
|
if (!DiskOk(disk, vbd))
|
|
{
|
|
if (vbd != null)
|
|
vdi = MoveDisk(disk, vbd, progress, step);
|
|
else
|
|
vdi = CreateDisk(disk, progress, step);
|
|
}
|
|
|
|
if (vdi == null)
|
|
continue;
|
|
|
|
if (vdi.name_description != disk.Disk.name_description)
|
|
VDI.set_name_description(Session, vdi.opaque_ref, disk.Disk.name_description);
|
|
if (vdi.name_label != disk.Disk.name_label)
|
|
VDI.set_name_label(Session, vdi.opaque_ref, disk.Disk.name_label);
|
|
|
|
if (firstDisk && Helpers.BostonOrGreater(Connection))
|
|
{
|
|
//use the first disk to set the VM.suspend_SR
|
|
SR vdiSR = Connection.Resolve(vdi.SR);
|
|
this.firstSR = vdiSR;
|
|
if(vdiSR != null && !vdiSR.HBALunPerVDI)
|
|
suspendSr = vdi.SR;
|
|
firstDisk = false;
|
|
}
|
|
|
|
progress += step;
|
|
}
|
|
if (Helpers.BostonOrGreater(Connection))
|
|
VM.set_suspend_SR(Session, VM.opaque_ref, suspendSr);
|
|
}
|
|
|
|
private VBD GetDiskVBD(DiskDescription disk, List<VBD> vbds)
|
|
{
|
|
foreach (VBD vbd in vbds)
|
|
{
|
|
if (disk.Device.userdevice == vbd.userdevice)
|
|
return vbd;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private bool DiskOk(DiskDescription disk, VBD vbd)
|
|
{
|
|
if (vbd == null)
|
|
return false;
|
|
|
|
VDI vdi = Connection.Resolve(vbd.VDI);
|
|
|
|
return vdi != null && disk.Disk.SR.opaque_ref == vdi.SR.opaque_ref;
|
|
}
|
|
|
|
private VDI MoveDisk(DiskDescription disk, VBD vbd, double progress, double step)
|
|
{
|
|
string old_vdi_ref = vbd.VDI.opaque_ref;
|
|
|
|
RelatedTask = XenAPI.VDI.async_copy(Session, vbd.VDI.opaque_ref, disk.Disk.SR.opaque_ref);
|
|
PollToCompletion(progress, progress + 0.25 * step);
|
|
AddVMHint(Connection.WaitForCache(new XenRef<VDI>(Result)));
|
|
|
|
|
|
VDI new_vdi = Connection.Resolve(new XenRef<VDI>(Result));
|
|
|
|
RelatedTask = XenAPI.VBD.async_destroy(Session, vbd.opaque_ref);
|
|
PollToCompletion(progress + 0.25 * step, progress + 0.5 * step);
|
|
|
|
RelatedTask = XenAPI.VDI.async_destroy(Session, old_vdi_ref);
|
|
PollToCompletion(progress + 0.5 * step, progress + 0.75 * step);
|
|
|
|
CreateVbd(disk, new_vdi, progress + 0.75 * step, progress + step, IsDeviceAtPositionZero(disk));
|
|
return new_vdi;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper: Check if the disk is at the zeroth position in the VBD list
|
|
/// </summary>
|
|
/// <param name="disk"></param>
|
|
/// <returns></returns>
|
|
private bool IsDeviceAtPositionZero(DiskDescription disk)
|
|
{
|
|
return disk.Device.userdevice == "0";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a VDI/disk.
|
|
/// If disk type is existing use the VDI in disk description
|
|
/// Otherwise create a new disk (provision it from the SR)
|
|
/// </summary>
|
|
/// <param name="disk"></param>
|
|
/// <param name="progress"></param>
|
|
/// <param name="step"></param>
|
|
/// <returns></returns>
|
|
private VDI CreateDisk(DiskDescription disk, double progress, double step)
|
|
{
|
|
VDI vdi;
|
|
bool bootable = false;
|
|
if(disk.Type == DiskDescription.DiskType.Existing)
|
|
vdi = disk.Disk;
|
|
else
|
|
{
|
|
vdi = CreateVdi(disk, progress, progress + 0.75 * step);
|
|
bootable = IsDeviceAtPositionZero(disk) && InsMethod != InstallMethod.CD;
|
|
}
|
|
|
|
AddVMHint(vdi);
|
|
CreateVbd(disk, vdi, progress + 0.75 * step, progress + step, bootable);
|
|
return vdi;
|
|
}
|
|
|
|
private void AddVMHint(VDI vdi)
|
|
{
|
|
Dictionary<string, string> sm_config = VDI.get_sm_config(Session, vdi.opaque_ref);
|
|
sm_config["vmhint"] = VM.opaque_ref;
|
|
VDI.set_sm_config(Session, vdi.opaque_ref, sm_config);
|
|
}
|
|
|
|
private VDI CreateVdi(DiskDescription disk, double progress1, double progress2)
|
|
{
|
|
VDI vdi = new VDI();
|
|
vdi.name_label = disk.Disk.name_label;
|
|
vdi.name_description = disk.Disk.name_description;
|
|
vdi.read_only = false;
|
|
vdi.sharable = false;
|
|
vdi.SR = disk.Disk.SR;
|
|
vdi.type = disk.Disk.type;
|
|
vdi.virtual_size = disk.Disk.virtual_size;
|
|
vdi.sm_config = disk.Disk.sm_config;
|
|
|
|
RelatedTask = XenAPI.VDI.async_create(Session, vdi);
|
|
PollToCompletion(progress1, progress2);
|
|
return Connection.WaitForCache(new XenRef<VDI>(Result));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a VBD
|
|
///
|
|
/// ** vbd.bootable **
|
|
/// 1. Windows ignores bootable flag
|
|
/// 2. Eliloader changes the device "0" to bootable when booting linux
|
|
/// </summary>
|
|
/// <param name="disk"></param>
|
|
/// <param name="vdi"></param>
|
|
/// <param name="progress1"></param>
|
|
/// <param name="progress2"></param>
|
|
/// <param name="bootable">Set VBD.bootable to this value - see comments above</param>
|
|
private void CreateVbd(DiskDescription disk, VDI vdi, double progress1, double progress2, bool bootable)
|
|
{
|
|
List<string> devices = AllowedVBDs;
|
|
if (devices.Count == 0)
|
|
throw new Exception(Messages.NO_MORE_USERDEVICES);
|
|
VBD vbd = new VBD();
|
|
vbd.IsOwner = true;
|
|
vbd.bootable = bootable;
|
|
vbd.empty = false;
|
|
vbd.unpluggable = true;
|
|
vbd.mode = vbd_mode.RW;
|
|
vbd.type = vbd_type.Disk;
|
|
vbd.userdevice = devices.Contains(disk.Device.userdevice) ? disk.Device.userdevice : devices[0];
|
|
vbd.device = "";
|
|
vbd.VM = new XenRef<VM>(VM.opaque_ref);
|
|
vbd.VDI = new XenRef<VDI>(vdi.opaque_ref);
|
|
RelatedTask = VBD.async_create(Session, vbd);
|
|
PollToCompletion(progress1, progress2);
|
|
Connection.WaitForCache(new XenRef<VBD>(Result));
|
|
}
|
|
|
|
private void AddNetworks()
|
|
{
|
|
// first of all we need to clear any vifs that we have cloned from the template
|
|
double progress = 90;
|
|
VIF vif;
|
|
List<VIF> existingTemplateVifs = Connection.ResolveAll(VM.VIFs);
|
|
double step = 5.0 / (double)existingTemplateVifs.Count;
|
|
for (int i = 0; i < existingTemplateVifs.Count; i++)
|
|
{
|
|
vif = existingTemplateVifs[i];
|
|
RelatedTask = XenAPI.VIF.async_destroy(Session, vif.opaque_ref);
|
|
|
|
PollToCompletion(progress, progress + step);
|
|
progress += step;
|
|
}
|
|
|
|
// then we add the ones the user has specified
|
|
step = 5.0 / (double)Vifs.Count;
|
|
for (int i = 0; i < Vifs.Count; i++)
|
|
{
|
|
vif = Vifs[i];
|
|
List<string> devices = AllowedVIFs;
|
|
VIF new_vif = new VIF();
|
|
|
|
if (devices.Count < 1)
|
|
{
|
|
// If we have assigned more VIFs than we have space for then don't try to create them
|
|
log.Warn("Tried to create more VIFs than the server allows. Ignoring remaining vifs");
|
|
return;
|
|
}
|
|
new_vif.device = devices.Contains(vif.device) ? vif.device : devices[0];
|
|
new_vif.MAC = vif.MAC;
|
|
new_vif.network = vif.network;
|
|
new_vif.VM = new XenRef<VM>(VM.opaque_ref);
|
|
new_vif.qos_algorithm_type = vif.qos_algorithm_type;
|
|
new_vif.qos_algorithm_params = vif.qos_algorithm_params;
|
|
RelatedTask = XenAPI.VIF.async_create(Session, new_vif);
|
|
|
|
PollToCompletion(progress, progress + step);
|
|
progress += step;
|
|
|
|
Connection.WaitForCache(new XenRef<VIF>(Result));
|
|
}
|
|
}
|
|
|
|
private string HiddenVmName
|
|
{
|
|
get
|
|
{
|
|
return string.Format("{0}{1}", Helpers.GuiTempObjectPrefix, NameLabel);
|
|
}
|
|
}
|
|
|
|
private List<string> AllowedVBDs
|
|
{
|
|
get
|
|
{
|
|
return new List<String>(XenAPI.VM.get_allowed_VBD_devices(Session, VM.opaque_ref));
|
|
}
|
|
}
|
|
|
|
private List<string> AllowedVIFs
|
|
{
|
|
get
|
|
{
|
|
return new List<String>(XenAPI.VM.get_allowed_VIF_devices(Session, VM.opaque_ref));
|
|
}
|
|
}
|
|
|
|
protected override void CleanOnError()
|
|
{
|
|
if (VM != null && !PointOfNoReturn && Connection.IsConnected)
|
|
{
|
|
try
|
|
{
|
|
VMDestroyAction.DestroyVM(Session, VM, true);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// if the clean up has failed for whatever reason we just log it and give up.
|
|
log.Error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private string GetBootOrderNetworkFirst()
|
|
{
|
|
// add "n" at the beginning of the order string
|
|
if (VM.HVM_boot_params.ContainsKey("order"))
|
|
{
|
|
string order = VM.HVM_boot_params["order"].ToLower();
|
|
int i = order.IndexOf("n");
|
|
switch (i)
|
|
{
|
|
case -1: return order.Insert(0, "n");
|
|
case 0: return order;
|
|
default: return order.Remove(i, 1).Insert(0, "n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return "ncd";
|
|
}
|
|
}
|
|
}
|
|
public class DiskDescription
|
|
{
|
|
public VDI Disk;
|
|
public VBD Device;
|
|
public DiskType Type;
|
|
public enum DiskType { New, Existing }
|
|
|
|
public DiskDescription(){}
|
|
|
|
public DiskDescription(VDI disk, VBD device)
|
|
{
|
|
Disk = disk;
|
|
Device = device;
|
|
Type = DiskType.New;
|
|
}
|
|
}
|
|
}
|