Beispiel #1
0
        /// <summary>
        /// Removes a hive certificate if it exists.
        /// </summary>
        /// <param name="name">The certificate name.</param>
        public void Remove(string name)
        {
            Covenant.Requires <ArgumentException>(HiveDefinition.IsValidName(name));

            hive.Vault.Client.DeleteAsync(HiveHelper.GetVaultCertificateKey(name)).Wait();
            hive.SignalTrafficManagerUpdate();
        }
Beispiel #2
0
        /// <summary>
        /// Adds or updates a hive certificate.
        /// </summary>
        /// <param name="name">The certificate name.</param>
        /// <param name="certificate">The certificate.</param>
        /// <exception cref="ArgumentException">Thrown if the certificate is not valid.</exception>
        /// <remarks>
        /// <note>
        /// The <paramref name="certificate"/> must be fully parsed (it's
        /// <see cref="TlsCertificate.Parse()"/> method must have been called at
        /// some point to load the <see cref="TlsCertificate.Hosts"/>,
        /// <see cref="TlsCertificate.ValidFrom"/> and <see cref="TlsCertificate.ValidUntil"/>
        /// properties).
        /// </note>
        /// </remarks>
        public void Set(string name, TlsCertificate certificate)
        {
            Covenant.Requires <ArgumentException>(HiveDefinition.IsValidName(name));
            Covenant.Requires <ArgumentNullException>(certificate != null);

            hive.Vault.Client.WriteJsonAsync(HiveHelper.GetVaultCertificateKey(name), certificate).Wait();
            hive.SignalTrafficManagerUpdate();
        }
Beispiel #3
0
 /// <summary>
 /// Returns the folder path where the VPN hive client folders will
 /// be located.
 /// </summary>
 /// <returns>The folder path.</returns>
 public static string GetVpnFolder()
 {
     if (HiveHelper.InToolContainer)
     {
         return("/dev/shm/vpn");
     }
     else
     {
         return(Path.Combine(HiveHelper.GetHiveUserFolder(), "vpn"));
     }
 }
Beispiel #4
0
        /// <summary>
        /// Constructs a hive proxy from a hive login.
        /// </summary>
        /// <param name="hiveLogin">The hive login.</param>
        /// <param name="nodeProxyCreator">
        /// The optional application supplied function that creates a node proxy
        /// given the node name, public address or FQDN, private address, and
        /// the node definition.
        /// </param>
        /// <param name="appendLog">Optionally have logs appended to an existing log file rather than creating a new one.</param>
        /// <param name="useBootstrap">
        /// Optionally specifies that the instance should use the HiveMQ client
        /// should directly eference to the HiveMQ cluster nodes for broadcasting
        /// proxy update messages rather than routing traffic through the <b>private</b>
        /// traffic manager.  This is used internally to resolve chicken-and-the-egg
        /// dilemmas for the traffic manager and proxy implementations that rely on
        /// HiveMQ messaging.
        /// </param>
        /// <param name="defaultRunOptions">
        /// Optionally specifies the <see cref="RunOptions"/> to be assigned to the
        /// <see cref="SshProxy{TMetadata}.DefaultRunOptions"/> property for the
        /// nodes managed by the hive proxy.  This defaults to <see cref="RunOptions.None"/>.
        /// </param>
        /// <remarks>
        /// The <paramref name="nodeProxyCreator"/> function will be called for each node in
        /// the hive definition giving the application the chance to create the management
        /// proxy using the node's SSH credentials and also to specify logging.  A default
        /// creator that doesn't initialize SSH credentials and logging is used if a <c>null</c>
        /// argument is passed.
        /// </remarks>
        public HiveProxy(
            HiveLogin hiveLogin,
            Func <string, string, IPAddress, bool, SshProxy <NodeDefinition> > nodeProxyCreator = null,
            bool appendLog               = false,
            bool useBootstrap            = false,
            RunOptions defaultRunOptions = RunOptions.None)

            : this(hiveLogin.Definition, nodeProxyCreator, appendLog : appendLog, useBootstrap : useBootstrap, defaultRunOptions : defaultRunOptions)
        {
            Covenant.Requires <ArgumentNullException>(hiveLogin != null);

            this.HiveLogin = hiveLogin;

            // This ensures that the local machine is initialized properly for the login.

            HiveHelper.InitLogin(hiveLogin);
        }
Beispiel #5
0
        /// <summary>
        /// Restarts the hive registry caches as required, using the
        /// upstream credentials passed.
        /// </summary>
        /// <param name="registry">The target registry hostname.</param>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        /// <returns><c>true</c> if the operation succeeded or was unnecessary.</returns>
        /// <remarks>
        /// <note>
        /// This method currently does nothing but return <c>true</c> if the
        /// registry specified is not the Docker public registry because the
        /// cache supports only the public registry or if the registry cache
        /// is not enabled for this hive.
        /// </note>
        /// </remarks>
        public bool RestartCache(string registry, string username, string password)
        {
            // Return immediately if this is a NOP.

            if (!HiveHelper.IsDockerPublicRegistry(registry) || !hive.Definition.Docker.RegistryCache)
            {
                return(true);
            }

            // We're not going to restart these in parallel so only one
            // manager cache will be down at any given time.  This should
            // result in no cache downtime for hives with multiple
            // managers.

            foreach (var manager in hive.Managers)
            {
                if (!manager.RestartRegistryCache(registry, username, password))
                {
                    return(false);
                }
            }

            return(true);
        }
Beispiel #6
0
        /// <summary>
        /// Retrieves a hive certificate.
        /// </summary>
        /// <param name="name">The certificate name.</param>
        /// <returns>The certificate if present or <c>null</c> if it doesn't exist.</returns>
        public TlsCertificate Get(string name)
        {
            Covenant.Requires <ArgumentException>(HiveDefinition.IsValidName(name));

            return(hive.Vault.Client.ReadJsonOrDefaultAsync <TlsCertificate>(HiveHelper.GetVaultCertificateKey(name)).Result);
        }
Beispiel #7
0
        /// <summary>
        /// Performs any required Hyper-V initialization before host nodes can be provisioned.
        /// </summary>
        private void PrepareHyperV()
        {
            // Determine where we're going to place the VM hard drive files and
            // ensure that the directory exists.

            if (!string.IsNullOrEmpty(hive.Definition.Hosting.VmDriveFolder))
            {
                vmDriveFolder = hive.Definition.Hosting.VmDriveFolder;
            }
            else
            {
                vmDriveFolder = HyperVClient.DefaultDriveFolder;
            }

            Directory.CreateDirectory(vmDriveFolder);

            // Download the zipped VHDX template if it's not already present or has
            // changed.  Note that we're going to name the file the same as the file
            // name from the URI and we're also going to persist the ETAG and file
            // length in file with the same name with a [.info] extension.
            //
            // Note that I'm not actually going check for ETAG changes to update
            // the download file.  The reason for this is that I want to avoid the
            // situation where the user has provisioned some nodes with one version
            // of the template and then goes on later to provision new nodes with
            // an updated template.  The [neon hive setup --remove-templates]
            // option is provided to delete any cached templates.
            //
            // This should only be an issue for people using the default "latest"
            // drive template.  Production hives should reference a specific
            // drive template.

            var driveTemplateUri  = new Uri(hive.Definition.Hosting.LocalHyperV.HostVhdxUri);
            var driveTemplateName = driveTemplateUri.Segments.Last();

            driveTemplatePath = Path.Combine(HiveHelper.GetVmTemplatesFolder(), driveTemplateName);

            var driveTemplateInfoPath  = Path.Combine(HiveHelper.GetVmTemplatesFolder(), driveTemplateName + ".info");
            var driveTemplateIsCurrent = true;
            var driveTemplateInfo      = (DriveTemplateInfo)null;

            if (!File.Exists(driveTemplatePath) || !File.Exists(driveTemplateInfoPath))
            {
                driveTemplateIsCurrent = false;
            }
            else
            {
                try
                {
                    driveTemplateInfo = NeonHelper.JsonDeserialize <DriveTemplateInfo>(File.ReadAllText(driveTemplateInfoPath));

                    if (new FileInfo(driveTemplatePath).Length != driveTemplateInfo.Length)
                    {
                        driveTemplateIsCurrent = false;
                    }
                }
                catch
                {
                    // The [*.info] file must be corrupt.

                    driveTemplateIsCurrent = false;
                }
            }

            if (!driveTemplateIsCurrent)
            {
                controller.SetOperationStatus($"Download Template VHDX: [{hive.Definition.Hosting.LocalHyperV.HostVhdxUri}]");

                Task.Run(
                    async() =>
                {
                    using (var client = new HttpClient())
                    {
                        // Download the file.

                        var response = await client.GetAsync(hive.Definition.Hosting.LocalHyperV.HostVhdxUri, HttpCompletionOption.ResponseHeadersRead);

                        response.EnsureSuccessStatusCode();

                        var contentLength = response.Content.Headers.ContentLength;

                        try
                        {
                            using (var fileStream = new FileStream(driveTemplatePath, FileMode.Create, FileAccess.ReadWrite))
                            {
                                using (var downloadStream = await response.Content.ReadAsStreamAsync())
                                {
                                    var buffer = new byte[64 * 1024];
                                    int cb;

                                    while (true)
                                    {
                                        cb = await downloadStream.ReadAsync(buffer, 0, buffer.Length);

                                        if (cb == 0)
                                        {
                                            break;
                                        }

                                        await fileStream.WriteAsync(buffer, 0, cb);

                                        if (contentLength.HasValue)
                                        {
                                            var percentComplete = (int)(((double)fileStream.Length / (double)contentLength) * 100.0);

                                            controller.SetOperationStatus($"Downloading VHDX: [{percentComplete}%] [{hive.Definition.Hosting.LocalHyperV.HostVhdxUri}]");
                                        }
                                        else
                                        {
                                            controller.SetOperationStatus($"Downloading VHDX: [{fileStream.Length} bytes] [{hive.Definition.Hosting.LocalHyperV.HostVhdxUri}]");
                                        }
                                    }
                                }
                            }
                        }
                        catch
                        {
                            // Ensure that the template and info files are deleted if there were any
                            // errors, to help avoid using a corrupted template.

                            if (File.Exists(driveTemplatePath))
                            {
                                File.Decrypt(driveTemplatePath);
                            }

                            if (File.Exists(driveTemplateInfoPath))
                            {
                                File.Decrypt(driveTemplateInfoPath);
                            }

                            throw;
                        }

                        // Generate the [*.info] file.

                        var templateInfo = new DriveTemplateInfo();

                        templateInfo.Length = new FileInfo(driveTemplatePath).Length;

                        if (response.Headers.TryGetValues("ETag", out var etags))
                        {
                            // Note that ETags look like they're surrounded by double
                            // quotes.  We're going to strip these out if present.

                            templateInfo.ETag = etags.SingleOrDefault().Replace("\"", string.Empty);
                        }

                        File.WriteAllText(driveTemplateInfoPath, NeonHelper.JsonSerialize(templateInfo, Formatting.Indented));
                    }
                }).Wait();

                controller.SetOperationStatus();
            }

            // Handle any necessary Hyper-V initialization.

            using (var hyperv = new HyperVClient())
            {
                // We're going to create the [neonHIVE] external switch if there
                // isn't already an external switch.

                controller.SetOperationStatus("Scanning network adapters");

                var switches       = hyperv.ListVMSwitches();
                var externalSwitch = switches.FirstOrDefault(s => s.Type == VirtualSwitchType.External);

                if (externalSwitch == null)
                {
                    hyperv.NewVMExternalSwitch(switchName = defaultSwitchName, IPAddress.Parse(hive.Definition.Network.Gateway));
                }
                else
                {
                    switchName = externalSwitch.Name;
                }

                // Ensure that the hive virtual machines exist and are stopped,
                // taking care to issue a warning if any machines already exist
                // and we're not doing [force] mode.

                controller.SetOperationStatus("Scanning virtual machines");

                var existingMachines = hyperv.ListVMs();
                var conflicts        = string.Empty;

                controller.SetOperationStatus("Stopping virtual machines");

                foreach (var machine in existingMachines)
                {
                    var nodeName    = ExtractNodeName(machine.Name);
                    var drivePath   = Path.Combine(vmDriveFolder, $"{machine.Name}.vhdx");
                    var isClusterVM = hive.FindNode(nodeName) != null;

                    if (isClusterVM)
                    {
                        if (forceVmOverwrite)
                        {
                            if (machine.State != VirtualMachineState.Off)
                            {
                                hive.GetNode(nodeName).Status = "stop virtual machine";
                                hyperv.StopVM(machine.Name);
                                hive.GetNode(nodeName).Status = string.Empty;
                            }

                            // The named machine already exists.  For force mode, we're going to stop and
                            // reuse the machine but replace the hard drive file as long as the file name
                            // matches what we're expecting for the machine.  We'll delete the VM if
                            // the names don't match and recreate it below.
                            //
                            // The reason for doing this is to avoid generating new MAC addresses
                            // every time we reprovision a VM.  This could help prevent the router/DHCP
                            // server from running out of IP addresses for the subnet.

                            var drives = hyperv.GetVMDrives(machine.Name);

                            if (drives.Count != 1 || !drives.First().Equals(drivePath, StringComparison.InvariantCultureIgnoreCase))
                            {
                                // Remove the machine and recreate it below.

                                hive.GetNode(nodeName).Status = "delete virtual machine";
                                hyperv.RemoveVM(machine.Name);
                                hive.GetNode(nodeName).Status = string.Empty;
                            }
                            else
                            {
                                continue;
                            }
                        }
                        else
                        {
                            // We're going to report errors when one or more machines already exist and
                            // [--force] was not specified.

                            if (conflicts.Length > 0)
                            {
                                conflicts += ", ";
                            }

                            conflicts += nodeName;
                        }
                    }
                }

                if (!string.IsNullOrEmpty(conflicts))
                {
                    throw new HyperVException($"[{conflicts}] virtual machine(s) already exist and cannot be automatically replaced unless you specify [--force].");
                }

                controller.SetOperationStatus();
            }
        }