/// <summary> /// Installs from an XVA or OVA virtual machine template file on the local workstation to the /// remote XenServer host an XVA or OVA virtual machine template, optionally renaming it. /// </summary> /// <param name="path">The path to the template file on the local workstation.</param> /// <param name="name">The optional template name.</param> /// <param name="repositoryNameOrUuid"> /// Optionally specifies the target storage repository by name or UUID. /// This defaults to <b>Local storage</b>. /// </param> /// <param name="description"> /// Optionally specifies the template description. /// </param> /// <returns>The installed template.</returns> /// <exception cref="XenException">Thrown if the operation failed.</exception> public XenTemplate ImportVmTemplate(string path, string name = null, string repositoryNameOrUuid = "Local storage", string description = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(path), nameof(path)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(repositoryNameOrUuid), nameof(repositoryNameOrUuid)); var sr = client.Repository.GetTargetStorageRepository(repositoryNameOrUuid); var response = client.SafeInvoke("vm-import", $"filename={path}", $"sr-uuid={sr.Uuid}"); var uuid = response.OutputText.Trim(); var template = Find(uuid: uuid); if (!string.IsNullOrEmpty(name)) { template = Rename(template, name); } if (!string.IsNullOrEmpty(description)) { client.SafeInvoke("template-param-set", $"uuid={template.Uuid}", $"name-description={description}"); } return(template); }
/// <summary> /// <para> /// Creates a virtual machine from a template, optionally initializing its memory and /// disk size. /// </para> /// <note> /// This does not start the machine. /// </note> /// </summary> /// <param name="name">Name for the new virtual machine.</param> /// <param name="templateName">Identifies the template.</param> /// <param name="cores">Optionally specifies the number of CPU cores to be assigned. This defaults to <b>2</b>.</param> /// <param name="memoryBytes">Optionally specifies the memory assigned to the machine (overriding the template).</param> /// <param name="diskBytes">Optionally specifies the primary disk size (overriding the template).</param> /// <param name="snapshot">Optionally specifies that the virtual machine should snapshot the template. This defaults to <c>false</c>.</param> /// <param name="extraDisks"> /// Optionally specifies any additional virtual disks to be created and /// then attached to the new virtual machine. /// </param> /// <param name="primaryStorageRepository"> /// Optionally specifies the storage repository where the virtual machine's /// primary disk will be created. This defaults to <b>Local storage</b>. /// </param> /// <param name="extraStorageRespository"> /// Optionally specifies the storage repository where any extra disks for /// the virtual machine will be created. This defaults to <b>Local storage</b>. /// <note> /// The default value assumes that your XenServer pool is <b>NOT CONFIGURED FOR HA</b>. /// Auto start VMs are not recommended for HA pools due to potential conflicts. We're /// not sure what problems having autostart VMs in a HA pool cause. /// </note> /// </param> /// <returns>The new <see cref="XenVirtualMachine"/>.</returns> /// <exception cref="XenException">Thrown if the operation failed.</exception> /// <remarks> /// <note> /// <paramref name="snapshot"/> is ignored if the virtual machine template is not /// hosted by the same storage repository where the virtual machine is to be created. /// </note> /// </remarks> public XenVirtualMachine Create( string name, string templateName, int cores = 2, long memoryBytes = 0, long diskBytes = 0, bool snapshot = false, IEnumerable <XenVirtualDisk> extraDisks = null, string primaryStorageRepository = "Local storage", string extraStorageRespository = "Local storage") { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(templateName), nameof(templateName)); Covenant.Requires <ArgumentException>(cores > 0, nameof(cores)); Covenant.Requires <ArgumentException>(memoryBytes >= 0, nameof(memoryBytes)); Covenant.Requires <ArgumentException>(diskBytes >= 0, nameof(diskBytes)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(primaryStorageRepository), nameof(primaryStorageRepository)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(extraStorageRespository), nameof(extraStorageRespository)); if (client.Template.Find(templateName) == null) { throw new XenException($"Template [{templateName}] does not exist."); } // We need to determine whether the VM template is persisted to the same storage // repository as the desired target for the VM. If the storage repos are the same, // we won't pass the [sr-uuid] parameter so that XenServer will do a fast clone. // This is necessary because the xe command looks like it never does a fast clone // when [sr-uuid] is passed, even if the template and target VM storage repositories // are the same. // // https://github.com/nforgeio/neonKUBE/issues/326 // // Unfortunately, there doesn't appear to be a clean way to inspect a VM template // to list its disks and determine where they live. So we'll list the virtual disk // interfaces and look for the disk named like: // // <template-name> 0 // // where <template-name> identifies the template and "0" indicates the disk number. // // NOTE: This assumes that cluster VM templates have only a single disk, which // will probably always be the case since we only add disks after the VMs // are created. var primarySR = client.Repository.Find(name: primaryStorageRepository, mustExist: true); var vdiListResponse = client.InvokeItems("vdi-list"); var templateVdiName = $"{templateName} 0"; var templateSrUuid = (string)null; var srUuidArg = (string)null; foreach (var vdiProperties in vdiListResponse.Items) { if (vdiProperties["name-label"] == templateVdiName) { templateSrUuid = vdiProperties["sr-uuid"]; break; } } if (!snapshot || templateSrUuid != primarySR.Uuid) { srUuidArg = $"sr-uuid={primarySR.Uuid}"; } // Create the VM. var vmInstallResponse = client.SafeInvoke("vm-install", $"template={templateName}", $"new-name-label={name}", srUuidArg); var vmUuid = vmInstallResponse.OutputText.Trim(); // Configure the processors. client.SafeInvoke("vm-param-set", $"uuid={vmUuid}", $"platform:cores-per-socket=1"); client.SafeInvoke("vm-param-set", $"uuid={vmUuid}", $"VCPUs-max={cores}"); client.SafeInvoke("vm-param-set", $"uuid={vmUuid}", $"VCPUs-at-startup={cores}"); // Citrix says that VM autostart is not compatible with HA so we don't // want to enable autostart when HA is enabled. We'll assume that the // user will configure autostart manually via the HA settings. // // If the XenServer host is not HA enabled, we're going to configure // the VM to start automatically when the host machine boots. We're // going to list the XenServer pools to obtain the UUID and then inspect // its parameters to determine whether HA is enabled. We're going to // assume that any single XenServer host can only be a member of a // single pool. // // Note that the pool list will look like: // // uuid ( RO) : 55ab0faf-19e2-6a93-717d-441213705f60 // name-label ( RW): // name-description ( RW): // master ( RO): eae75dd5-6ae2-474f-a04d-a982d52821e7 // default-SR ( RW): 62166a25-0601-dc07-d1ce-e74625e71444 // // and the pool parameters will look like: // // uuid ( RO) : 55ab0faf-19e2-6a93-717d-441213705f60 // name-label ( RW): test // name-description ( RW): // master ( RO): eae75dd5-6ae2-474f-a04d-a982d52821e7 // default-SR ( RW): <not in database> // crash-dump-SR ( RW): <not in database> // suspend-image-SR ( RW): <not in database> // supported-sr-types ( RO): smb; lvm; iso; nfs; lvmofcoe; udev; hba; dummy; ext; lvmoiscsi; lvmohba; file; iscsi // other-config (MRW): auto_poweron: true; memory-ratio-hvm: 0.25; memory-ratio-pv: 0.25 // allowed-operations (SRO): cluster_create; ha_enable // current-operations (SRO): // ha-enabled ( RO): false // ha-configuration ( RO): // ha-statefiles ( RO): // ha-host-failures-to-tolerate ( RW): 0 // ha-plan-exists-for ( RO): 0 // ha-allow-overcommit ( RW): false // ha-overcommitted ( RO): false // blobs ( RO): // wlb-url ( RO): // wlb-username ( RO): // wlb-enabled ( RW): false // wlb-verify-cert ( RW): false // igmp-snooping-enabled ( RW): false // gui-config (MRW): // health-check-config (MRW): // restrictions ( RO): restrict_vswitch_controller: false; restrict_lab: false; restrict_stage: false; restrict_storagelink: false; restrict_storagelink_site_recovery: false; restrict_web_selfservice: false; restrict_web_selfservice_manager: false; restrict_hotfix_apply: false; restrict_export_resource_data: false; restrict_read_caching: false; restrict_cifs: false; restrict_health_check: false; restrict_xcm: false; restrict_vm_memory_introspection: false; restrict_batch_hotfix_apply: false; restrict_management_on_vlan: false; restrict_ws_proxy: false; restrict_vlan: false; restrict_qos: false; restrict_pool_attached_storage: false; restrict_netapp: false; restrict_equalogic: false; restrict_pooling: false; enable_xha: true; restrict_marathon: false; restrict_email_alerting: false; restrict_historical_performance: false; restrict_wlb: false; restrict_rbac: false; restrict_dmc: false; restrict_checkpoint: false; restrict_cpu_masking: false; restrict_connection: false; platform_filter: false; regular_nag_dialog: false; restrict_vmpr: false; restrict_vmss: false; restrict_intellicache: false; restrict_gpu: false; restrict_dr: false; restrict_vif_locking: false; restrict_storage_xen_motion: false; restrict_vgpu: false; restrict_integrated_gpu_passthrough: false; restrict_vss: false; restrict_guest_agent_auto_update: false; restrict_pci_device_for_auto_update: false; restrict_xen_motion: false; restrict_guest_ip_setting: false; restrict_ad: false; restrict_ssl_legacy_switch: false; restrict_nested_virt: false; restrict_live_patching: false; restrict_set_vcpus_number_live: false; restrict_pvs_proxy: false; restrict_igmp_snooping: false; restrict_rpu: false; restrict_pool_size: false; restrict_cbt: false; restrict_usb_passthrough: false; restrict_network_sriov: false; restrict_corosync: true; restrict_zstd_export: false // tags (SRW): // license-state ( RO): edition: xcp-ng; expiry: never // ha-cluster-stack ( RO): xhad // guest-agent-config (MRW): // cpu_info (MRO): features_hvm_host: 1fcbfbff-80b82221-2993fbff-00000403-00000000-00000000-00000000-00000000-00001000-9c000000-00000000-00000000-00000000-00000000-00000000; features_hvm: 1fcbfbff-80b82221-2993fbff-00000403-00000000-00000000-00000000-00000000-00001000-9c000000-00000000-00000000-00000000-00000000-00000000; features_pv_host: 1fc9cbf5-80b82201-2991cbf5-00000003-00000000-00000000-00000000-00000000-00001000-8c000000-00000000-00000000-00000000-00000000-00000000; features_pv: 1fc9cbf5-80b82201-2991cbf5-00000003-00000000-00000000-00000000-00000000-00001000-8c000000-00000000-00000000-00000000-00000000-00000000; socket_count: 2; cpu_count: 16; vendor: GenuineIntel // policy-no-vendor-device ( RW): false // live-patching-disabled ( RW): false var poolList = client.SafeInvokeItems("pool-list").Items.First(); var poolUuid = poolList["uuid"]; var pool = client.SafeInvokeItems("pool-param-list", $"uuid={poolUuid}").Items.First(); if (pool["ha-enabled"] == "false") { client.SafeInvoke("pool-param-set", $"uuid={poolUuid}", $"other-config:auto_poweron=true"); client.SafeInvoke("vm-param-set", $"uuid={vmUuid}", $"other-config:auto_poweron=true"); } // Configure memory. if (memoryBytes > 0) { client.SafeInvoke("vm-memory-limits-set", $"uuid={vmUuid}", $"dynamic-max={memoryBytes}", $"dynamic-min={memoryBytes}", $"static-max={memoryBytes}", $"static-min={memoryBytes}"); } // Configure the primary disk. if (diskBytes > 0) { var disks = client.SafeInvokeItems("vm-disk-list", $"vm={vmUuid}").Items; var vdi = disks.FirstOrDefault(properties => properties.ContainsKey("Disk 0 VDI")); if (vdi == null) { throw new XenException($"Cannot locate disk [0] for [{name}] virtual machine."); } var vdiUuid = vdi["uuid"]; // Expand the disk only if the requested size is greater than the current // [virtual-size] because XenServer cannot shrink disks. var virtualSize = long.Parse(vdi["virtual-size"]); if (diskBytes > virtualSize) { client.SafeInvoke("vdi-resize", $"uuid={vdiUuid}", $"disk-size={diskBytes}"); } // Rename the disk to "operating system". client.SafeInvoke("vdi-param-set", $"uuid={vdiUuid}", $"name-label={name}: OS disk", $"name-description=Operating system"); } // Configure any additional disks. if (extraDisks != null && extraDisks.Count() > 0) { var diskIndex = 1; // The boot disk has index=0 so we'll skip that. var extraSR = client.Repository.Find(name: extraStorageRespository, mustExist: true); foreach (var disk in extraDisks) { // Create the disk. client.SafeInvoke("vm-disk-add", $"uuid={vmUuid}", $"sr-uuid={extraSR.Uuid}", $"disk-size={disk.Size}", $"device={diskIndex}"); // Set the disk's name and description. var disks = client.SafeInvokeItems("vm-disk-list", $"vm={vmUuid}").Items; var vdi = disks.FirstOrDefault(properties => properties.ContainsKey("name-label") && properties["name-label"] == "Created by xe"); if (vdi == null) { throw new XenException($"Cannot locate the new node [{disk.Name}] disk."); } var vdiUuid = vdi["uuid"]; var diskName = disk.Name ?? "disk"; var diskDescription = disk.Description ?? string.Empty; client.SafeInvoke("vdi-param-set", $"uuid={vdiUuid}", $"name-label={diskName}", $"name-description={diskDescription}"); diskIndex++; } } return(client.Machine.Find(uuid: vmUuid)); }