/// <summary> /// Used for temporarily uploading an ISO disk to a XenServer such that it can be mounted /// to a VM, typically for one-time initialization purposes. neonKUBE uses this as a very /// simple poor man's alternative to <b>cloud-init</b> for initializing a VM on first boot. /// </summary> /// <param name="isoPath">Path to the source ISO file on the local workstation.</param> /// <param name="srName">Optionally specifies the storage repository name. <b>neon-UUID</b> with a generated UUID will be used by default.</param> /// <returns>A <see cref="XenTempIso"/> with information about the new storage repository and its contents.</returns> /// <remarks> /// <para> /// During cluster setup on virtualization platforms like XenServer and Hyper-V, neonKUBE need /// to configure new VMs with IP addresses, hostnames, etc. Traditionally, we've relied on /// being able to SSH into the VM to perform all of these actions, but this relied on being /// VM being able to obtain an IP address via DHCP and for setup to be able to discover the /// assigned address. /// </para> /// <para> /// The dependency on DHCP is somewhat problematic, because it's conceivable that this may /// not be available for more controlled environments. We looked into using Linux <b>cloud-init</b> /// for this, but that requires additional local infrastructure for non-cloud deployments and /// was also a bit more complex than what we had time for. /// </para> /// <para> /// Instead of <b>cloud-init</b>, we provisioned our XenServer and Hyper-V node templates /// with a <b>neon-init</b> service that runs before the network service to determine /// whether a DVD (ISO) is inserted into the VM and runs the <b>neon-init.sh</b> script /// there one time, if it exists. This script will initialize the node's IP address and /// could also be used for other configuration as well, like setting user credentials. /// </para> /// <note> /// In theory, we could have used the same technique for mounting a <b>cloud-init</b> data source /// via this ISO, but we decided not to go there, at least for now (we couldn't get that working). /// </note> /// <note> /// neonKUBE doesn't use this technique for true cloud deployments (AWS, Azure, Google,...) because /// we can configure VM networking directly via the cloud APIs. /// </note> /// <para> /// The XenServer requires the temporary ISO implementation to be a bit odd. We want these temporary /// ISOs to be created directly on the XenServer host machine so users won't have to configure any /// additional infrastructure as well as to simplify cluster setup. We'll be creating a local /// ISO storage repository from a folder on the host. Any files to be added to the repository /// must exist when the repository is created and it is not possible to add, modify, or remove /// files from a repository after its been created. /// </para> /// <note> /// XenServer hosts have only 4GB of free space at the root Linux level, so you must take care /// not to create large ISOs or to allow these to accumulate. /// </note> /// <para> /// This method uploads the ISO file <paramref name="isoPath"/> from the local workstation to /// the XenServer host, creating a new folder named with a UUID. Then a new storage repository /// will be created from this folder and a <see cref="XenTempIso"/> will be returned holding /// details about the new storage repository and its contents. The setup code will use this to /// insert the ISO into a VM. /// </para> /// <para> /// Once the setup code is done with the ISO, it will eject it from the VM and call /// <see cref="RemoveTempIso(XenTempIso)"/> to remove the storage repository. /// </para> /// </remarks> public XenTempIso CreateTempIso(string isoPath, string srName = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(isoPath), nameof(isoPath)); if (string.IsNullOrEmpty(srName)) { srName = "neon-" + Guid.NewGuid().ToString("d"); } var tempIso = new XenTempIso(); // Create the temporary SR subfolder and upload the ISO file. var srMountPath = "/var/run/sr-mount"; tempIso.SrPath = LinuxPath.Combine(srMountPath, Guid.NewGuid().ToString("d")); tempIso.IsoName = $"neon-dvd-{Guid.NewGuid().ToString("d")}.iso"; if (!sftpClient.PathExists(srMountPath)) { sftpClient.CreateDirectory(srMountPath); } if (!sftpClient.PathExists(tempIso.SrPath)) { sftpClient.CreateDirectory(tempIso.SrPath); sftpClient.ChangePermissions(tempIso.SrPath, Convert.ToInt16("751", 8)); } var xenIsoPath = LinuxPath.Combine(tempIso.SrPath, tempIso.IsoName); using (var isoInput = File.OpenRead(isoPath)) { sftpClient.UploadFile(isoInput, xenIsoPath); sftpClient.ChangePermissions(xenIsoPath, Convert.ToInt16("751", 8)); } // Create the new storage repository. This command returns the [sr-uuid]. var response = SafeInvoke("sr-create", $"name-label={tempIso.IsoName}", $"type=iso", $"device-config:location={tempIso.SrPath}", $"device-config:legacy_mode=true", $"content-type=iso"); tempIso.SrUuid = response.OutputText.Trim(); // XenServer created a PBD behind the scenes for the new SR. We're going // to need its UUID so we can completely remove the SR later. Note that // doesn't seem to appear immediately so, we'll retry a few times. var retry = new ExponentialRetryPolicy(typeof(InvalidOperationException), maxAttempts: 5, initialRetryInterval: TimeSpan.FromSeconds(0.5), maxRetryInterval: TimeSpan.FromSeconds(5)); retry.Invoke( () => { var result = SafeInvokeItems("pbd-list", $"sr-uuid={tempIso.SrUuid}"); tempIso.PdbUuid = result.Items.Single()["uuid"]; // Obtain the UUID for the ISO's VDI within the SR. result = SafeInvokeItems("vdi-list", $"sr-uuid={tempIso.SrUuid}"); tempIso.VdiUuid = result.Items.Single()["uuid"]; }); return(tempIso); }