/// <summary> /// Downloads and installs an XVA or OVA virtual machine template, optionally renaming it. /// </summary> /// <param name="uri">The HTTP or FTP URI for the template file.</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> /// <returns>The installed template.</returns> /// <exception cref="XenException">Thrown if the operation failed.</exception> public XenTemplate Install(string uri, string name = null, string repositoryNameOrUuid = "Local storage") { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(uri), nameof(uri)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(repositoryNameOrUuid), nameof(repositoryNameOrUuid)); if (!Uri.TryCreate(uri, UriKind.Absolute, out var uriParsed)) { throw new ArgumentException($"[{uri}] is not a valid URI", nameof(uri)); } var sr = client.Repository.GetTargetStorageRepository(repositoryNameOrUuid); if (uriParsed.Scheme != "http" && uriParsed.Scheme != "ftp") { throw new ArgumentException($"[{uri}] uses an unsupported scheme. Only [http/ftp] are allowed.", nameof(uri)); } var response = client.SafeInvoke("vm-import", $"url={uri}", $"sr-uuid={sr.Uuid}"); var uuid = response.OutputText.Trim(); var template = Find(uuid: uuid); if (!string.IsNullOrEmpty(name)) { template = Rename(template, name); } 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="processors">Optionally specifies the number of processors to assign. 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 disk assigned to the machine (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="extraDrives"> /// Optionally specifies any additional virtual drives to be created and /// then attached to the new virtual machine (e.g. for Ceph OSD). /// </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 drives for /// the virtual machine will be created. This defaults to <b>Local storage</b>. /// </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 processors = 2, long memoryBytes = 0, long diskBytes = 0, bool snapshot = false, IEnumerable <XenVirtualDrive> extraDrives = null, string primaryStorageRepository = "Local storage", string extraStorageRespository = "Local storage") { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(templateName)); Covenant.Requires <ArgumentException>(processors > 0); Covenant.Requires <ArgumentException>(memoryBytes >= 0); Covenant.Requires <ArgumentException>(diskBytes >= 0); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(primaryStorageRepository)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(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/jefflill/NeonForge/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 neonHIVE 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.Results) { 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 uuid = vmInstallResponse.OutputText.Trim(); // Configure processors client.SafeInvoke("vm-param-set", $"uuid={uuid}", $"VCPUs-at-startup={processors}", $"VCPUs-max={processors}"); // Configure memory. if (memoryBytes > 0) { client.SafeInvoke("vm-memory-limits-set", $"uuid={uuid}", $"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", $"uuid={uuid}").Results; var vdi = disks.FirstOrDefault(items => items.ContainsKey("Disk 0 VDI")); if (vdi == null) { throw new XenException($"Cannot locate disk for [{name}] virtual machine."); } var vdiUuid = vdi["uuid"]; client.SafeInvoke("vdi-resize", $"uuid={vdiUuid}", $"disk-size={diskBytes}"); } // Configure any additional disks. if (extraDrives != null && extraDrives.Count() > 0) { var driveIndex = 1; // The boot device has index=0 var extraSR = client.Repository.Find(name: extraStorageRespository, mustExist: true); foreach (var drive in extraDrives) { client.SafeInvoke("vm-disk-add", $"uuid={uuid}", $"sr-uuid={extraSR.Uuid}", $"disk-size={drive.Size}", $"device={driveIndex}"); driveIndex++; } } return(client.Machine.Find(uuid: uuid)); }