コード例 #1
0
        /// <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);
        }