2017-01-16 20:59:50 +01:00
/ * Copyright ( c ) Citrix Systems , Inc .
2013-06-24 13:41:48 +02:00
* 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 XenAdmin.Network ;
using XenAPI ;
using XenAdmin.Core ;
using System.Xml ;
namespace XenAdmin.Actions.VMActions
{
public enum InstallMethod
{
None ,
CD ,
Network
}
2018-08-20 12:24:20 +02:00
2018-08-23 08:41:35 +02:00
public enum BootMode { BIOS_BOOT , UEFI_BOOT , UEFI_SECURE_BOOT , NOT_AVAILABLE }
public static class EnumExt
{
public static string StringOf ( this BootMode x )
{
switch ( x )
{
case BootMode . BIOS_BOOT :
2019-02-11 19:38:27 +01:00
return Messages . BIOS_BOOT ;
2018-08-23 08:41:35 +02:00
case BootMode . UEFI_BOOT :
2019-02-11 19:38:27 +01:00
return Messages . UEFI_BOOT ;
2018-08-23 08:41:35 +02:00
case BootMode . UEFI_SECURE_BOOT :
2019-02-11 19:38:27 +01:00
return Messages . UEFI_SECURE_BOOT ;
2018-08-23 08:41:35 +02:00
default :
2019-02-11 19:38:27 +01:00
return Messages . UNAVAILABLE ;
2018-08-23 08:41:35 +02:00
}
}
}
2018-08-20 12:24:20 +02:00
2013-06-24 13:41:48 +02:00
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 ;
2018-08-23 08:41:35 +02:00
private readonly BootMode BootMode ;
2013-06-24 13:41:48 +02:00
private readonly Host HomeServer ;
2016-09-29 13:11:37 +02:00
private readonly long VcpusMax ;
private readonly long VcpusAtStartup ;
2013-06-24 13:41:48 +02:00
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 ;
2019-04-15 20:47:32 +02:00
private readonly List < VGPU > vGpus ;
2014-06-05 14:03:06 +02:00
private readonly long CoresPerSocket ;
2014-12-04 19:04:43 +01:00
private readonly string cloudConfigDriveTemplateText ;
private SR firstSR = null ;
2013-06-24 13:41:48 +02:00
private Action < VMStartAbstractAction , Failure > _startDiagnosisForm ;
private Action < VM , bool > _warningDialogHAInvalidConfig ;
private bool PointOfNoReturn ;
2014-06-30 10:44:19 +02:00
private bool assignOrRemoveVgpu ;
2013-06-24 13:41:48 +02:00
/// <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
2014-06-05 14:03:06 +02:00
"vif.create" ,
"vm.set_platform"
2013-06-24 13:41:48 +02:00
) ;
2013-09-20 12:24:46 +02:00
public CreateVMAction ( IXenConnection connection , VM template , Host copyBiosStringsFrom ,
string name , string description , InstallMethod installMethod ,
2018-08-23 08:41:35 +02:00
string pvArgs , VDI cd , string url , BootMode bootMode , Host homeServer , long vcpusMax , long vcpusAtStartup ,
long memoryDynamicMin , long memoryDynamicMax , long memoryStaticMax ,
2013-09-20 12:24:46 +02:00
List < DiskDescription > disks , SR fullCopySR , List < VIF > vifs , bool startAfter ,
Action < VM , bool > warningDialogHAInvalidConfig ,
Action < VMStartAbstractAction , Failure > startDiagnosisForm ,
2019-04-15 20:47:32 +02:00
List < VGPU > vGpus , bool modifyVgpuSettings , long coresPerSocket , string cloudConfigDriveTemplateText )
2013-09-20 12:24:46 +02:00
: base ( connection , string . Format ( Messages . CREATE_VM , name ) ,
string . Format ( Messages . CREATE_VM_FROM_TEMPLATE , name , Helpers . GetName ( template ) ) )
2013-06-24 13:41:48 +02:00
{
Template = template ;
CopyBiosStringsFrom = copyBiosStringsFrom ;
FullCopySR = fullCopySR ;
NameLabel = name ;
NameDescription = description ;
InsMethod = installMethod ;
PvArgs = pvArgs ;
Cd = cd ;
Url = url ;
2018-08-23 08:41:35 +02:00
BootMode = bootMode ;
2013-06-24 13:41:48 +02:00
HomeServer = homeServer ;
2016-09-29 13:11:37 +02:00
VcpusMax = vcpusMax ;
VcpusAtStartup = vcpusAtStartup ;
2013-06-24 13:41:48 +02:00
MemoryDynamicMin = memoryDynamicMin ;
MemoryDynamicMax = memoryDynamicMax ;
MemoryStaticMax = memoryStaticMax ;
Disks = disks ;
Vifs = vifs ;
StartAfter = startAfter ;
_warningDialogHAInvalidConfig = warningDialogHAInvalidConfig ;
_startDiagnosisForm = startDiagnosisForm ;
2019-04-15 20:47:32 +02:00
this . vGpus = vGpus ;
2014-06-05 14:03:06 +02:00
CoresPerSocket = coresPerSocket ;
2014-12-04 19:04:43 +01:00
this . cloudConfigDriveTemplateText = cloudConfigDriveTemplateText ;
2013-06-24 13:41:48 +02:00
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 ) ;
2019-04-15 20:47:32 +02:00
assignOrRemoveVgpu = vGpus ! = null & & vGpus . Count > 0 | | modifyVgpuSettings & & Helpers . GpuCapability ( Connection ) ;
2014-06-30 10:44:19 +02:00
2013-06-24 13:41:48 +02:00
#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" ) ;
2014-06-30 10:44:19 +02:00
if ( assignOrRemoveVgpu )
2013-09-20 12:24:46 +02:00
{
ApiMethodsToRoleCheck . Add ( "VGPU.destroy" ) ;
ApiMethodsToRoleCheck . Add ( "VGPU.create" ) ;
}
2013-06-24 13:41:48 +02:00
ApiMethodsToRoleCheck . AddRange ( StaticRBACDependencies ) ;
ApiMethodsToRoleCheck . AddRange ( Role . CommonTaskApiList ) ;
ApiMethodsToRoleCheck . AddRange ( Role . CommonSessionApiList ) ;
2013-09-20 12:24:46 +02:00
2013-06-24 13:41:48 +02:00
#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.
2021-10-21 18:12:12 +02:00
Description = string . Format ( Messages . COPYING_TEMPLATE , Helpers . GetName ( Template ) ) ;
2020-05-12 14:27:33 +02:00
RelatedTask = VM . async_copy ( Session , Template . opaque_ref , Helpers . MakeHiddenName ( NameLabel ) , FullCopySR . opaque_ref ) ;
2013-06-24 13:41:48 +02:00
}
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.
2021-10-21 18:12:12 +02:00
Description = string . Format ( Messages . CLONING_TEMPLATE , Helpers . GetName ( Template ) ) ;
2020-05-12 14:27:33 +02:00
RelatedTask = VM . async_clone ( Session , Template . opaque_ref , Helpers . MakeHiddenName ( NameLabel ) ) ;
2013-06-24 13:41:48 +02:00
}
PollToCompletion ( 0 , 10 ) ;
VM = Connection . WaitForCache ( new XenRef < VM > ( Result ) ) ;
2015-12-17 16:31:41 +01:00
ApplyRecommendationsForVendorDevice ( ) ;
2013-06-24 13:41:48 +02:00
CopyBiosStrings ( ) ;
SetXenCenterProperties ( ) ;
ProvisionVM ( ) ;
SetVMParams ( ) ;
SetVMBootParams ( ) ;
AddCdDrive ( ) ;
AddDisks ( ) ;
AddNetworks ( ) ;
XenAdminConfigManager . Provider . ShowObject ( VM . opaque_ref ) ;
[CA-233454] PVS tab doesn't show a new VM when it's created (#1314)
* [CA-233454] PVS tab doesn't show a new VM when it's created
Refined the rules for not adding a VM to the table, if it is a template (thus not_a_real_vm), and it has the __gui__ prefix (thus hidden), we still add it, but hide it.
When its name is changed (to remove the __gui__ prefix), we update its name and re-calculate whether it should be visible (in the case of a new VM this will be true once the __gui__ prefix is gone). Also resort the table if a node changes from hidden to visible, because it appears as an addition to the table.
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* [CA-233454] Update criteria for VM visibility to include is_a_real_vm
is_a_template is changed before the name_label removes the __gui__ prefix, so this works with no other changes to the vm property changed event.
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* [CA-233454] Update when the VMs are shown in the PVS list
New observable property IsBeingCreated for VMs, set to true when they're made a hidden object, and false when they're removed from hidden objects (both in CreateVMAction). In the PVS Page when this is set to false, we re-evaluate whether a VM can be shown. This means that new VMs show here at the same time they're added to the tree (only different is tree refresh time), instead of far earlier (and before their networks were added).
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* [CA-233454] Set IsbeingCreated in the CreateVMFastAction
* [CA-233454] Properly support the VM Fast Create action
Further changes to CreateVMFastAction, to ensure it works with PVS tab - use the __gui__ prefix when the VM is created and then change it back just before showing.
* [CA-233454] Small logic adjustments/tidying up
2016-11-30 13:24:02 +01:00
VM . IsBeingCreated = false ;
2013-06-24 13:41:48 +02:00
PointOfNoReturn = true ;
2021-08-06 01:30:16 +02:00
if ( Helpers . ContainerCapability ( Connection ) )
CloudCreateConfigDrive ( ) ;
2014-12-04 19:04:43 +01:00
2013-09-20 12:24:46 +02:00
AssignVgpu ( ) ;
2013-06-24 13:41:48 +02:00
if ( StartAfter )
{
Description = Messages . STARTING_VM ;
var startAction = new VMStartAction ( VM , _warningDialogHAInvalidConfig , _startDiagnosisForm ) ;
startAction . RunAsync ( ) ;
}
Description = Messages . VM_SUCCESSFULLY_CREATED ;
}
2015-12-17 16:31:41 +01:00
private void ApplyRecommendationsForVendorDevice ( )
{
2016-04-27 10:58:33 +02:00
var pool = Helpers . GetPoolOfOne ( Connection ) ;
bool poolPolicyNoVendorDevice = pool = = null | | pool . policy_no_vendor_device ;
2017-09-05 03:15:38 +02:00
bool hasVendorDeviceRecommendation = Template . HasVendorDeviceRecommendation ( ) ;
2016-04-27 10:58:33 +02:00
2017-09-05 03:15:38 +02:00
if ( hasVendorDeviceRecommendation & & ! poolPolicyNoVendorDevice & & ! Helpers . FeatureForbidden ( VM , Host . RestrictVendorDevice ) )
2015-12-17 16:31:41 +01:00
{
log . DebugFormat ( "Recommendation (has-vendor-device = true) has been found on the template ({0}) and the host is licensed, so applying it on VM ({1}) being created." , Template . opaque_ref , VM . opaque_ref ) ;
VM . set_has_vendor_device ( Connection . Session , VM . opaque_ref , true ) ;
}
else
{
log . DebugFormat ( "Recommendation (has-vendor-device = true) has not been applied on the VM ({0}) being created." , VM . opaque_ref ) ;
2017-09-05 03:15:38 +02:00
if ( ! hasVendorDeviceRecommendation )
2016-04-27 10:58:33 +02:00
log . DebugFormat ( "Recommendation (has-vendor-device) is not set or false on the template ({0})." , Template . opaque_ref ) ;
if ( poolPolicyNoVendorDevice )
log . DebugFormat ( "pool.policy_no_vendor_device returned {0}" , poolPolicyNoVendorDevice ) ;
2015-12-17 16:31:41 +01:00
if ( Helpers . FeatureForbidden ( VM , Host . RestrictVendorDevice ) )
2016-04-27 10:58:33 +02:00
log . DebugFormat ( "Helpers.FeatureForbidden(VM, Host.RestrictVendorDevice) returned {0}" , Helpers . FeatureForbidden ( VM , Host . RestrictVendorDevice ) ) ;
2015-12-17 16:31:41 +01:00
}
}
2014-12-04 19:04:43 +01:00
private void CloudCreateConfigDrive ( )
{
2017-09-03 04:33:29 +02:00
if ( Template . CanHaveCloudConfigDrive ( ) & & ! string . IsNullOrEmpty ( cloudConfigDriveTemplateText ) )
2014-12-04 19:04:43 +01:00
{
2015-02-19 15:48:52 +01:00
Description = Messages . CREATING_CLOUD_CONFIG_DRIVE ;
2014-12-04 19:04:43 +01:00
var parameters = new Dictionary < string , string > ( ) ;
parameters . Add ( "vmuuid" , VM . uuid ) ;
parameters . Add ( "sruuid" , firstSR . uuid ) ;
parameters . Add ( "configuration" , cloudConfigDriveTemplateText . Replace ( "\r\n" , "\n" ) ) ;
2021-08-31 12:31:16 +02:00
var action = new RunPluginAction ( Connection , HomeServer ? ? Helpers . GetCoordinator ( Connection ) ,
2014-12-04 19:04:43 +01:00
"xscontainer" , //plugin
"create_config_drive" , //function
parameters ,
true ) ; //hidefromlogs
action . RunExternal ( Connection . Session ) ;
var result = action . Result . Replace ( "\n" , Environment . NewLine ) ;
}
}
2013-09-20 12:24:46 +02:00
private void AssignVgpu ( )
{
2014-06-30 10:44:19 +02:00
if ( assignOrRemoveVgpu )
2013-09-20 12:24:46 +02:00
{
2019-06-05 04:20:49 +02:00
var newvGpus = new List < VGPU > ( ) ;
foreach ( var vGpu in vGpus )
{
newvGpus . Add ( new VGPU
{
GPU_group = new XenRef < GPU_group > ( vGpu . GPU_group . opaque_ref ) ,
type = new XenRef < VGPU_type > ( vGpu . type . opaque_ref ) ,
device = vGpu . device
} ) ;
}
var action = new GpuAssignAction ( VM , newvGpus ) ;
2013-09-20 12:24:46 +02:00
action . RunExternal ( Session ) ;
}
}
2013-06-24 13:41:48 +02:00
private void CopyBiosStrings ( )
{
2017-09-03 04:33:29 +02:00
if ( CopyBiosStringsFrom ! = null & & Template . DefaultTemplate ( ) )
2013-06-24 13:41:48 +02:00
{
VM . copy_bios_strings ( Session , this . VM . opaque_ref , CopyBiosStringsFrom . opaque_ref ) ;
}
}
private void SetXenCenterProperties ( )
{
[CA-233454] PVS tab doesn't show a new VM when it's created (#1314)
* [CA-233454] PVS tab doesn't show a new VM when it's created
Refined the rules for not adding a VM to the table, if it is a template (thus not_a_real_vm), and it has the __gui__ prefix (thus hidden), we still add it, but hide it.
When its name is changed (to remove the __gui__ prefix), we update its name and re-calculate whether it should be visible (in the case of a new VM this will be true once the __gui__ prefix is gone). Also resort the table if a node changes from hidden to visible, because it appears as an addition to the table.
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* [CA-233454] Update criteria for VM visibility to include is_a_real_vm
is_a_template is changed before the name_label removes the __gui__ prefix, so this works with no other changes to the vm property changed event.
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* [CA-233454] Update when the VMs are shown in the PVS list
New observable property IsBeingCreated for VMs, set to true when they're made a hidden object, and false when they're removed from hidden objects (both in CreateVMAction). In the PVS Page when this is set to false, we re-evaluate whether a VM can be shown. This means that new VMs show here at the same time they're added to the tree (only different is tree refresh time), instead of far earlier (and before their networks were added).
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* [CA-233454] Set IsbeingCreated in the CreateVMFastAction
* [CA-233454] Properly support the VM Fast Create action
Further changes to CreateVMFastAction, to ensure it works with PVS tab - use the __gui__ prefix when the VM is created and then change it back just before showing.
* [CA-233454] Small logic adjustments/tidying up
2016-11-30 13:24:02 +01:00
VM . IsBeingCreated = true ;
2013-06-24 13:41:48 +02:00
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 ) ;
2016-09-29 13:11:37 +02:00
ChangeVCPUSettingsAction vcpuAction = new ChangeVCPUSettingsAction ( VM , VcpusMax , VcpusAtStartup ) ;
2013-06-24 13:41:48 +02:00
vcpuAction . RunExternal ( Session ) ;
2014-06-05 14:03:06 +02:00
// 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 ) ;
2013-06-24 13:41:48 +02:00
// 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 ) ;
2015-10-26 17:01:55 +01:00
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 ) ;
2013-06-24 13:41:48 +02:00
}
private bool HomeServerChanged ( )
{
if ( HomeServer = = null )
{
return Template . affinity . opaque_ref ! = Helper . NullOpaqueRef ;
}
return HomeServer . opaque_ref ! = Template . affinity . opaque_ref ;
}
private void SetVMBootParams ( )
{
2017-09-03 04:33:29 +02:00
if ( Template . IsHVM ( ) & & ( Disks . Count = = 0 | | InsMethod = = InstallMethod . Network ) ) // CA-46213
2013-06-24 13:41:48 +02:00
{
// 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 ) ;
}
2017-09-03 04:33:29 +02:00
if ( ! Template . IsHVM ( ) )
2013-06-24 13:41:48 +02:00
{
XenAPI . VM . set_PV_args ( Session , VM . opaque_ref , PvArgs ) ;
}
2018-08-23 08:41:35 +02:00
else if ( BootMode ! = BootMode . NOT_AVAILABLE )
2018-08-20 12:24:20 +02:00
{
var hvm_params = VM . HVM_boot_params ;
2018-08-23 08:41:35 +02:00
hvm_params [ "firmware" ] = BootMode ! = BootMode . BIOS_BOOT ? "uefi" : "bios" ;
2018-08-20 12:24:20 +02:00
XenAPI . VM . set_HVM_boot_params ( Session , VM . opaque_ref , hvm_params ) ;
2018-08-23 08:41:35 +02:00
var platform = VM . platform ;
platform [ "secureboot" ] = BootMode = = BootMode . UEFI_SECURE_BOOT ? "true" : "false" ;
2018-08-20 12:24:20 +02:00
XenAPI . VM . set_platform ( Session , VM . opaque_ref , platform ) ;
}
2013-06-24 13:41:48 +02:00
}
private bool IsEli ( )
{
2017-09-03 04:33:29 +02:00
return ! Template . IsHVM ( ) & & Template . PV_bootloader = = "eliloader" ;
2013-06-24 13:41:48 +02:00
}
private bool IsRhel ( )
{
2017-09-03 04:33:29 +02:00
string distro = VM . InstallDistro ( ) ;
2013-06-24 13:41:48 +02:00
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 ( )
{
2017-09-05 03:15:38 +02:00
XmlNode xml = VM . ProvisionXml ( ) ;
2013-06-24 13:41:48 +02:00
if ( xml = = null )
return ;
2015-09-18 16:51:17 +02:00
// set the new vm's provision xml: remove "disks" entry, as we are going to explicitly create all the disks
2013-06-24 13:41:48 +02:00
Dictionary < string , string > other_config = VM . other_config ;
2015-09-18 16:51:17 +02:00
other_config . Remove ( "disks" ) ;
2013-06-24 13:41:48 +02:00
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 ) ;
2017-09-14 13:48:05 +02:00
VBD vbd = new VBD
{
bootable = InsMethod = = InstallMethod . CD ,
empty = true ,
unpluggable = true ,
mode = vbd_mode . RO ,
type = vbd_type . CD ,
userdevice = devices . Contains ( "3" ) ? "3" : devices [ 0 ] ,
VM = new XenRef < VM > ( VM . opaque_ref )
} ;
2013-06-24 13:41:48 +02:00
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 ) ;
2015-10-26 17:01:55 +01:00
if ( firstDisk )
2013-06-24 13:41:48 +02:00
{
//use the first disk to set the VM.suspend_SR
SR vdiSR = Connection . Resolve ( vdi . SR ) ;
2014-12-04 19:04:43 +01:00
this . firstSR = vdiSR ;
2017-09-03 04:33:29 +02:00
if ( vdiSR ! = null & & ! vdiSR . HBALunPerVDI ( ) )
2013-06-24 13:41:48 +02:00
suspendSr = vdi . SR ;
firstDisk = false ;
}
progress + = step ;
}
2015-10-26 17:01:55 +01:00
VM . set_suspend_SR ( Session , VM . opaque_ref , suspendSr ) ;
2013-06-24 13:41:48 +02:00
}
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 ) ;
2015-09-18 16:51:17 +02:00
bootable = IsDeviceAtPositionZero ( disk ) & & InsMethod ! = InstallMethod . CD ;
2013-06-24 13:41:48 +02:00
}
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 ;
2015-09-18 16:51:17 +02:00
vdi . sm_config = disk . Disk . sm_config ;
2013-06-24 13:41:48 +02:00
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 ( ) ;
2017-09-05 03:15:38 +02:00
vbd . SetIsOwner ( true ) ;
2013-06-24 13:41:48 +02:00
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 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 ;
}
}
}