Beispiel #1
0
 /// <summary>
 /// Returns the maximum number of bytes to disk allocate to for this node when
 /// hosted on a hypervisor.
 /// </summary>
 /// <param name="hiveDefinition">The hive definition.</param>
 /// <returns>The size in bytes.</returns>
 public long GetVmDisk(HiveDefinition hiveDefinition)
 {
     if (!string.IsNullOrEmpty(VmDisk))
     {
         return(HiveDefinition.ValidateSize(VmDisk, this.GetType(), nameof(VmDisk)));
     }
     else
     {
         return(HiveDefinition.ValidateSize(hiveDefinition.Hosting.VmDisk, hiveDefinition.Hosting.GetType(), nameof(hiveDefinition.Hosting.VmDisk)));
     }
 }
Beispiel #2
0
        /// <summary>
        /// Returns the minimum number of bytes of memory allocate to for this node when
        /// hosted on a hypervisor.
        /// </summary>
        /// <param name="hiveDefinition">The hive definition.</param>
        /// <returns>The size in bytes.</returns>
        public long GetVmMinimumMemory(HiveDefinition hiveDefinition)
        {
            if (!string.IsNullOrEmpty(VmMinimumMemory))
            {
                return(HiveDefinition.ValidateSize(VmMinimumMemory, this.GetType(), nameof(VmMinimumMemory)));
            }
            else if (!string.IsNullOrEmpty(hiveDefinition.Hosting.VmMinimumMemory))
            {
                return(HiveDefinition.ValidateSize(hiveDefinition.Hosting.VmMinimumMemory, hiveDefinition.Hosting.GetType(), nameof(hiveDefinition.Hosting.VmMinimumMemory)));
            }
            else
            {
                // Return [VmMemory] otherwise.

                return(GetVmMemory(hiveDefinition));
            }
        }
Beispiel #3
0
        /// <summary>
        /// Returns the size in bytes of RAM to allocate to the MDS cache
        /// on this node integrated Ceph storage cluster is enabled and
        /// MDS is deployed to the node.
        /// </summary>
        /// <param name="hiveDefinition">The hive definition.</param>
        /// <returns>The size in bytes or zero if Ceph is not enabled.</returns>
        public long GetCephMDSCacheSize(HiveDefinition hiveDefinition)
        {
            if (!hiveDefinition.HiveFS.Enabled)
            {
                return(0);
            }

            if (Labels.CephMDSCacheSizeMB > 0)
            {
                return(Labels.CephMDSCacheSizeMB * NeonHelper.Mega);
            }
            else
            {
                Labels.CephMDSCacheSizeMB = (int)(HiveDefinition.ValidateSize(hiveDefinition.HiveFS.MDSCacheSize, hiveDefinition.Hosting.GetType(), nameof(hiveDefinition.HiveFS.MDSCacheSize)) / NeonHelper.Mega);

                return((long)Labels.CephMDSCacheSizeMB * NeonHelper.Mega);
            }
        }
Beispiel #4
0
        /// <summary>
        /// Validates the options and also ensures that all <c>null</c> properties are
        /// initialized to their default values.
        /// </summary>
        /// <param name="hiveDefinition">The hive definition.</param>
        /// <exception cref="HiveDefinitionException">Thrown if the definition is not valid.</exception>
        public void Validate(HiveDefinition hiveDefinition)
        {
            if (PublicCacheReplicas <= 0)
            {
                throw new HiveDefinitionException($"[{nameof(ProxyOptions)}.{nameof(PublicCacheReplicas)}={PublicCacheReplicas}] must be at least [1].");
            }

            if (HiveDefinition.ValidateSize(PublicCacheSize, this.GetType(), nameof(PublicCacheSize)) < 50 * NeonHelper.Mega)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(PublicCacheSize)}={PublicCacheSize}] cannot be less than [50MB].");
            }

            if (PrivateCacheReplicas <= 0)
            {
                throw new HiveDefinitionException($"[{nameof(ProxyOptions)}.{nameof(PrivateCacheReplicas)}={PrivateCacheReplicas}] must be at least [1].");
            }

            if (HiveDefinition.ValidateSize(PrivateCacheSize, this.GetType(), nameof(PrivateCacheSize)) < 50 * NeonHelper.Mega)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(PrivateCacheSize)}={PrivateCacheSize}] cannot be less than [50MB].");
            }
        }
Beispiel #5
0
        /// <summary>
        /// Validates the options and also ensures that all <c>null</c> properties are
        /// initialized to their default values.
        /// </summary>
        /// <param name="hiveDefinition">The hive definition.</param>
        /// <exception cref="HiveDefinitionException">Thrown if the definition is not valid.</exception>
        public void Validate(HiveDefinition hiveDefinition)
        {
            if (!Enabled)
            {
                return;
            }

            VolumePluginPackage = VolumePluginPackage ?? defaultVolumePluginPackage;

            if (string.IsNullOrEmpty(VolumePluginPackage) || !Uri.TryCreate(VolumePluginPackage, UriKind.Absolute, out var uri))
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(VolumePluginPackage)}={VolumePluginPackage}] must be set to a valid package URL.");
            }

            Release = Release ?? defaultRelease;
            Release = Release.ToLowerInvariant();

            if (!SupportedReleases.Contains(Release))
            {
                throw new HiveDefinitionException($"[{Release}] is not a supported Ceph release.");
            }

            // Examine the Ceph related labels for the hive nodes to verify that any
            // specified Ceph service assignments are reasonable.  We will also try to
            // automatically assign Ceph services to nodes when there are no explicit
            // assignments.

            // $hack(jeff.lill):
            //
            // It's not super clean to be doing this here but it's easy and I believe
            // I've already done this sort of thing elsewhere.

            var cephMONCount = hiveDefinition.Nodes.Count(n => n.Labels.CephMON);
            var cephOSDCount = hiveDefinition.Nodes.Count(n => n.Labels.CephOSD);
            var cephMDSCount = hiveDefinition.Nodes.Count(n => n.Labels.CephMDS);

            if (cephMONCount == 0)
            {
                // No Ceph monitor/manager nodes are explicitly assigned so we're going to
                // automatically place these on the hive managers.

                foreach (var node in hiveDefinition.Nodes.Where(n => n.IsManager))
                {
                    node.Labels.CephMON = true;
                }
            }

            if (cephOSDCount == 0)
            {
                // No Ceph OSD nodes are explicitly assigned.
                //
                // If the hive has at least three workers, we'll provision the
                // OSDs on all of the workers.
                //
                // If there are fewer than three workers, we'll provision ODSs on
                // all managers and all workers (AKA the Swarm nodes).

                if (hiveDefinition.Workers.Count() >= 3)
                {
                    foreach (var node in hiveDefinition.Workers)
                    {
                        node.Labels.CephOSD = true;
                    }
                }
                else
                {
                    foreach (var node in hiveDefinition.Swarm)
                    {
                        node.Labels.CephOSD = true;
                    }
                }
            }

            if (cephMONCount == 0)
            {
                // No Ceph MDS nodes are explicitly assigned so we're going to provision
                // these on the Ceph Monitor servers.

                foreach (var node in hiveDefinition.Nodes.Where(n => n.Labels.CephMON))
                {
                    node.Labels.CephMDS = true;
                }
            }

            // Recount the Ceph component instances to account for any the automatic
            // provisioning assignments that may have been performed above.

            cephMONCount = hiveDefinition.Nodes.Count(n => n.Labels.CephMON);
            cephOSDCount = hiveDefinition.Nodes.Count(n => n.Labels.CephOSD);
            cephMDSCount = hiveDefinition.Nodes.Count(n => n.Labels.CephMDS);

            // Validate the properties.

            if (string.IsNullOrWhiteSpace(Release))
            {
                Release = defaultRelease;
            }

            if (Release == string.Empty)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(Release)}={Release}] is not valid.  Please specify something like [{defaultRelease}].");
            }

            if (HiveDefinition.ValidateSize(OSDDriveSize, this.GetType(), nameof(OSDDriveSize)) < NeonHelper.Giga)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDDriveSize)}={OSDDriveSize}] cannot be less than [1GB].");
            }

            if (HiveDefinition.ValidateSize(OSDCacheSize, this.GetType(), nameof(OSDCacheSize)) < 64 * NeonHelper.Mega)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDCacheSize)}={OSDCacheSize}] cannot be less than [64MB].");
            }

            if (HiveDefinition.ValidateSize(OSDJournalSize, this.GetType(), nameof(OSDJournalSize)) < 64 * NeonHelper.Mega)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDJournalSize)}={OSDJournalSize}] cannot be less than [64MB].");
            }

            if (HiveDefinition.ValidateSize(OSDObjectSizeMax, this.GetType(), nameof(OSDObjectSizeMax)) < 64 * NeonHelper.Mega)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDObjectSizeMax)}={OSDObjectSizeMax}] cannot be less than [64MB].");
            }

            if (HiveDefinition.ValidateSize(MDSCacheSize, this.GetType(), nameof(MDSCacheSize)) < 64 * NeonHelper.Mega)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(MDSCacheSize)}={MDSCacheSize}] cannot be less than [64MB].");
            }

            if (cephMONCount == 0)
            {
                throw new HiveDefinitionException($"Ceph storage cluster requires at least one monitor node.");
            }

            if (cephOSDCount == 0)
            {
                throw new HiveDefinitionException($"Ceph storage cluster requires at least one OSD (data) node.");
            }

            if (cephMDSCount == 0)
            {
                throw new HiveDefinitionException($"Ceph storage cluster requires at least one MDS (metadata) node.");
            }

            if (OSDReplicaCount == 0)
            {
                // Set a reasonable default.

                OSDReplicaCount = Math.Min(3, cephOSDCount);
            }

            if (OSDReplicaCount < 0)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDReplicaCount)}={OSDReplicaCount}] cannot be less than zero.");
            }

            if (OSDReplicaCount > cephOSDCount)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDReplicaCount)}={OSDReplicaCount}] cannot be greater than the number of OSD nodes [{cephOSDCount}].");
            }

            if (OSDReplicaCountMin == 0)
            {
                // Set a reasonable default.

                if (OSDReplicaCount == 1)
                {
                    OSDReplicaCountMin = 1;
                }
                else
                {
                    OSDReplicaCountMin = OSDReplicaCount - 1;
                }
            }

            if (OSDReplicaCountMin < 0)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDReplicaCountMin)}={OSDReplicaCountMin}] cannot be less than zero.");
            }

            if (OSDReplicaCountMin > OSDReplicaCount)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDReplicaCountMin)}={OSDReplicaCountMin}] cannot be less than [{nameof(OSDReplicaCount)}={OSDReplicaCount}].");
            }

            if (OSDReplicaCountMin > cephOSDCount)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDReplicaCountMin)}={OSDReplicaCountMin}] cannot be greater than the number of OSD nodes [{cephOSDCount}].");
            }

            if (OSDPlacementGroups < 8)
            {
                throw new HiveDefinitionException($"[{nameof(HiveFSOptions)}.{nameof(OSDPlacementGroups)}={OSDPlacementGroups}] cannot be less than [8].");
            }
        }
Beispiel #6
0
        /// <inheritdoc/>
        public override bool Provision(bool force)
        {
            // $todo(jeff.lill):
            //
            // I'm not entirely sure that the [force] option makes sense for
            // production hives and especially when there are pet nodes.
            //
            // Perhaps it would make more sense to replace this with a
            // [neon hive remove] command.
            //
            //      https://github.com/jefflill/NeonForge/issues/156

            this.forceVmOverwrite = force;

            if (IsProvisionNOP)
            {
                // There's nothing to do here.

                return(true);
            }

            // Update the node labels with the actual capabilities of the
            // virtual machines being provisioned.

            foreach (var node in hive.Definition.Nodes)
            {
                if (string.IsNullOrEmpty(node.Labels.PhysicalMachine))
                {
                    node.Labels.PhysicalMachine = Environment.MachineName;
                }

                if (node.Labels.ComputeCores == 0)
                {
                    node.Labels.ComputeCores = hive.Definition.Hosting.VmProcessors;
                }

                if (node.Labels.ComputeRamMB == 0)
                {
                    node.Labels.ComputeRamMB = (int)(HiveDefinition.ValidateSize(hive.Definition.Hosting.VmMemory, typeof(HostingOptions), nameof(HostingOptions.VmMemory)) / NeonHelper.Mega);
                }

                if (node.Labels.StorageCapacityGB == 0)
                {
                    node.Labels.StorageCapacityGB = (int)(node.GetVmMemory(hive.Definition) / NeonHelper.Giga);
                }
            }

            // If a public address isn't explicitly specified, we'll assume that the
            // tool is running inside the network and we can access the private address.

            foreach (var node in hive.Definition.Nodes)
            {
                if (string.IsNullOrEmpty(node.PublicAddress))
                {
                    node.PublicAddress = node.PrivateAddress;
                }
            }

            // Perform the provisioning operations.

            controller = new SetupController <NodeDefinition>($"Provisioning [{hive.Definition.Name}] hive", hive.Nodes)
            {
                ShowStatus  = this.ShowStatus,
                MaxParallel = 1     // We're only going to provision one VM at a time on a local Hyper-V instance.
            };

            controller.AddGlobalStep("prepare hyper-v", () => PrepareHyperV());
            controller.AddStep("create virtual machines", (node, stepDelay) => ProvisionVM(node));
            controller.AddGlobalStep(string.Empty, () => Finish(), quiet: true);

            if (!controller.Run())
            {
                Console.Error.WriteLine("*** ERROR: One or more configuration steps failed.");
                return(false);
            }

            return(true);
        }
Beispiel #7
0
        public void Validate(HiveDefinition hiveDefinition)
        {
            Covenant.Requires <ArgumentNullException>(hiveDefinition != null);

            Labels     = Labels ?? new NodeLabels(this);
            HostGroups = HostGroups ?? new List <string>();

            if (Name == null)
            {
                throw new HiveDefinitionException($"The [{nameof(NodeDefinition)}.{nameof(Name)}] property is required.");
            }

            if (!HiveDefinition.IsValidName(Name))
            {
                throw new HiveDefinitionException($"The [{nameof(NodeDefinition)}.{nameof(Name)}={Name}] property is not valid.  Only letters, numbers, periods, dashes, and underscores are allowed.");
            }

            if (name == "localhost")
            {
                throw new HiveDefinitionException($"The [{nameof(NodeDefinition)}.{nameof(Name)}={Name}] property is not valid.  [localhost] is reserved.");
            }

            if (Name.StartsWith("neon-", StringComparison.InvariantCultureIgnoreCase))
            {
                throw new HiveDefinitionException($"The [{nameof(NodeDefinition)}.{nameof(Name)}={Name}] property is not valid because node names starting with [node-] are reserved.");
            }

            if (Name.Equals(HiveDefinition.VirtualSwarmManagerName, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new HiveDefinitionException($"The [{nameof(NodeDefinition)}.{nameof(Name)}={Name}] property is not valid.  [{HiveDefinition.VirtualSwarmManagerName}] is reserved for targeting Swarm related Ansible tasks.");
            }

            if (hiveDefinition.Hosting.IsOnPremiseProvider)
            {
                if (string.IsNullOrEmpty(PrivateAddress))
                {
                    throw new HiveDefinitionException($"Node [{Name}] requires [{nameof(PrivateAddress)}] when hosting in an on-premise facility.");
                }

                if (!IPAddress.TryParse(PrivateAddress, out var nodeAddress))
                {
                    throw new HiveDefinitionException($"Node [{Name}] has invalid IP address [{PrivateAddress}].");
                }
            }

            if (IsManager && hiveDefinition.Hosting.IsOnPremiseProvider && hiveDefinition.Vpn.Enabled)
            {
                if (!NetHelper.IsValidPort(VpnFrontendPort))
                {
                    throw new HiveDefinitionException($"Manager node [{Name}] has [{nameof(VpnFrontendPort)}={VpnFrontendPort}] which is not a valid network port.");
                }
            }

            Labels.Validate(hiveDefinition);

            foreach (var group in HostGroups)
            {
                if (string.IsNullOrWhiteSpace(group))
                {
                    throw new HiveDefinitionException($"Node [{Name}] assigns an empty group in [{nameof(HostGroups)}].");
                }
                else if (HiveHostGroups.BuiltIn.Contains(group))
                {
                    throw new HiveDefinitionException($"Node [{Name}] assigns the standard [{group}] in [{nameof(HostGroups)}].  Standard groups cannot be explicitly assigned since [neon-cli] handles them automatically.");
                }
                else if (!groupNameRegex.IsMatch(group))
                {
                    throw new HiveDefinitionException($"Node [{Name}] assigns the invalid group [{group}] in [{nameof(HostGroups)}].  Group names must start with a letter and then can be followed by zero or more letters, digits, dashes, and underscores.");
                }
            }

            if (Azure != null)
            {
                Azure.Validate(hiveDefinition, this.Name);
            }

            if (hiveDefinition.Hosting.IsRemoteHypervisorProvider)
            {
                if (string.IsNullOrEmpty(VmHost))
                {
                    throw new HiveDefinitionException($"Node [{Name}] does not specify a hypervisor [{nameof(NodeDefinition)}.{nameof(NodeDefinition.VmHost)}].");
                }
                else if (hiveDefinition.Hosting.VmHosts.FirstOrDefault(h => h.Name.Equals(VmHost, StringComparison.InvariantCultureIgnoreCase)) == null)
                {
                    throw new HiveDefinitionException($"Node [{Name}] references hypervisor [{VmHost}] which is defined in [{nameof(HostingOptions)}={nameof(HostingOptions.VmHosts)}].");
                }
            }

            if (VmMemory != null)
            {
                HiveDefinition.ValidateSize(VmMemory, this.GetType(), nameof(VmMemory));
            }

            if (VmMinimumMemory != null)
            {
                HiveDefinition.ValidateSize(VmMinimumMemory, this.GetType(), nameof(VmMinimumMemory));
            }

            if (VmDisk != null)
            {
                HiveDefinition.ValidateSize(VmDisk, this.GetType(), nameof(VmDisk));
            }
        }
Beispiel #8
0
        internal void ValidateHypervisor(HiveDefinition hiveDefinition, bool remoteHypervisors)
        {
            Covenant.Requires <ArgumentNullException>(hiveDefinition != null);

            if (VmProcessors <= 0)
            {
                throw new HiveDefinitionException($"[{nameof(LocalHyperVOptions)}.{nameof(VmProcessors)}={VmProcessors}] must be positive.");
            }

            VmMemory        = VmMemory ?? DefaultVmMemory;
            VmMinimumMemory = VmMinimumMemory ?? VmMemory;
            VmDisk          = VmDisk ?? DefaultVmMinimumMemory;
            VmHosts         = VmHosts ?? new List <VmHost>();

            HiveDefinition.ValidateSize(VmMemory, this.GetType(), nameof(VmMemory));
            HiveDefinition.ValidateSize(VmMinimumMemory, this.GetType(), nameof(VmMinimumMemory));
            HiveDefinition.ValidateSize(VmDisk, this.GetType(), nameof(VmDisk));

            // Verify that the hypervisor host machines have unique names and addresses.

            var hostNameSet    = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);
            var hostAddressSet = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);

            foreach (var vmHost in hiveDefinition.Hosting.VmHosts)
            {
                if (hostNameSet.Contains(vmHost.Name))
                {
                    throw new HiveDefinitionException($"One or more hypervisor hosts are assigned the [{vmHost.Name}] name.");
                }

                hostNameSet.Add(vmHost.Name);

                if (hostAddressSet.Contains(vmHost.Address))
                {
                    throw new HiveDefinitionException($"One or more hypervisor hosts are assigned the [{vmHost.Address}] address.");
                }

                hostAddressSet.Add(vmHost.Address);
            }

            // Ensure that some hypervisor hosts have been specified if we're deploying to remote
            // hypervisors and also that each node definition specifies a host hyoervisor.

            if (remoteHypervisors)
            {
                if (hiveDefinition.Hosting.VmHosts.Count == 0)
                {
                    throw new HiveDefinitionException($"At least one host XenServer must be specified in [{nameof(HostingOptions)}.{nameof(HostingOptions.VmHosts)}].");
                }

                foreach (var vmHost in VmHosts)
                {
                    vmHost.Validate(hiveDefinition);
                }

                foreach (var node in hiveDefinition.NodeDefinitions.Values)
                {
                    if (string.IsNullOrEmpty(node.VmHost))
                    {
                        throw new HiveDefinitionException($"Node [{node.Name}] does not specify a host hypervisor with [{nameof(NodeDefinition.VmHost)}].");
                    }

                    if (!hostNameSet.Contains(node.VmHost))
                    {
                        throw new HiveDefinitionException($"Node [{node.Name}] has [{nameof(VmHost)}={node.VmHost}] which specifies a hypervisor host that was not found in [{nameof(HostingOptions)}.{nameof(HostingOptions.VmHosts)}].");
                    }
                }
            }
        }
Beispiel #9
0
        /// <summary>
        /// Validates the options and also ensures that all <c>null</c> properties are
        /// initialized to their default values.
        /// </summary>
        /// <param name="hiveDefinition">The hive definition.</param>
        /// <exception cref="HiveDefinitionException">Thrown if the definition is not valid.</exception>
        public void Validate(HiveDefinition hiveDefinition)
        {
            RamLimit         = RamLimit ?? defaultRamLimit;
            RamHighWatermark = RamHighWatermark ?? defaultRamHighWatermark;
            SysadminPassword = SysadminPassword ?? defaultPassword;
            NeonPassword     = NeonPassword ?? defaultPassword;
            AppPassword      = AppPassword ?? defaultPassword;
            PartitionMode    = PartitionMode ?? defaultPartitionMode;
            PartitionMode    = PartitionMode.ToLowerInvariant();

            if (string.IsNullOrWhiteSpace(ErlangCookie))
            {
                ErlangCookie = NeonHelper.GetRandomPassword(20);
            }

            // RamHighWatermark: We're going to keep things simple and convert relative
            // percentage values to a number between [0..1] and we're going to convert
            // absolute bytes units into a simple number (without units).  This simplifies
            // the RabbitMQ Docker entrypoint script so all it needs to do is look for a
            // decimal point to identify a relative limit vs. an absolute limit.  The
            // script won't need to do percentage or unit conversions.

            if (RamHighWatermark.EndsWith("%"))
            {
                // RamHighWatermark is a relative percentage.

                var numberPart = RamHighWatermark.Substring(0, RamHighWatermark.Length - 1);

                if (!double.TryParse(numberPart, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var number) || number <= 0.0 || number >= 100.0)
                {
                    throw new HiveDefinitionException($"[{nameof(HiveMQOptions)}.{nameof(RamHighWatermark)}={RamHighWatermark}] is not within: 0% < {nameof(RamHighWatermark)} <= 100%");
                }

                RamHighWatermark = (number / 100).ToString("0.00#", CultureInfo.InvariantCulture);
            }
            else if (RamHighWatermark.Contains('.'))
            {
                // RamHighWatermark is a relative number between [0..1].

                if (!double.TryParse(RamHighWatermark, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var number) || number <= 0.0 || number >= 1.0)
                {
                    throw new HiveDefinitionException($"[{nameof(HiveMQOptions)}.{nameof(RamHighWatermark)}={RamHighWatermark}] is not within: 0.0 < {nameof(RamHighWatermark)} <= 1.0");
                }

                RamHighWatermark = number.ToString("0.00#", CultureInfo.InvariantCulture);
            }
            else
            {
                // RamHighWaterMark is absolute.

                var number = HiveDefinition.ValidateSize(RamHighWatermark, this.GetType(), nameof(RamHighWatermark));

                if (number <= 0)
                {
                    throw new HiveDefinitionException($"[{nameof(HiveMQOptions)}.{nameof(RamHighWatermark)}={RamHighWatermark}] must be greater than 0.");
                }

                RamHighWatermark = number.ToString();
            }

            var ramSize = HiveDefinition.ValidateSize(RamLimit, this.GetType(), nameof(RamLimit));

            if (Precompile && ramSize < 500 * NeonHelper.Mega)
            {
                throw new HiveDefinitionException($"[{nameof(HiveMQOptions)}.{nameof(RamLimit)}={RamLimit}] cannot be less than [500MB] when [{nameof(HiveMQOptions)}.{nameof(Precompile)}={Precompile}].");
            }
            else if (!Precompile && ramSize < 250 * NeonHelper.Mega)
            {
                throw new HiveDefinitionException($"[{nameof(HiveMQOptions)}.{nameof(RamLimit)}={RamLimit}] cannot be less than [250MB] when [{nameof(HiveMQOptions)}.{nameof(Precompile)}={Precompile}].");
            }

            // DiskFreeLimit: We're going to keep things simple and convert relative
            // percentage values to a number between [0..1] and we're going to convert
            // absolute bytes units into a simple number (without units).  This simplifies
            // the RabbitMQ Docker entrypoint script so all it needs to do is look for a
            // decimal point to identify a relative limit vs. an absolute limit.  The
            // script won't need to do percentage or unit conversions.

            if (string.IsNullOrWhiteSpace(DiskFreeLimit))
            {
                DiskFreeLimit = ((2 * ramSize) + (1 * NeonHelper.Giga)).ToString();
            }

            if (DiskFreeLimit.EndsWith("%"))
            {
                // DiskFreeLimit is a relative percentage.

                var numberPart = DiskFreeLimit.Substring(0, DiskFreeLimit.Length - 1);

                if (!double.TryParse(numberPart, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var number) || number <= 0.0 || number >= 100.0)
                {
                    throw new HiveDefinitionException($"[{nameof(HiveMQOptions)}.{nameof(DiskFreeLimit)}={DiskFreeLimit}] is not within: 0% < {nameof(DiskFreeLimit)} <= 100%");
                }

                DiskFreeLimit = (number / 100).ToString("0.00#", CultureInfo.InvariantCulture);
            }
            else if (DiskFreeLimit.Contains('.'))
            {
                // DiskFreeLimit is a relative number between [0..1].

                if (!double.TryParse(DiskFreeLimit, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var number) || number <= 0.0 || number >= 1.0)
                {
                    throw new HiveDefinitionException($"[{nameof(HiveMQOptions)}.{nameof(DiskFreeLimit)}={DiskFreeLimit}] is not within: 0.0 < {nameof(DiskFreeLimit)} <= 1.0");
                }

                DiskFreeLimit = number.ToString("0.00#", CultureInfo.InvariantCulture);
            }
            else
            {
                // DiskFreeLimit is absolute.

                var number = HiveDefinition.ValidateSize(DiskFreeLimit, this.GetType(), nameof(DiskFreeLimit));

                if (number < 1 * NeonHelper.Giga)
                {
                    throw new HiveDefinitionException($"[{nameof(HiveMQOptions)}.{nameof(DiskFreeLimit)}={DiskFreeLimit}] must be greater than [1GB].");
                }

                DiskFreeLimit = number.ToString();
            }

            switch (PartitionMode)
            {
            case "autoheal":
            case "pause_minority":
            case "pause_if_all_down":

                break;

            default:

                throw new HiveDefinitionException($"[{nameof(HiveMQOptions)}.{nameof(PartitionMode)}={PartitionMode}] is not valid.  Specify one of [autoheal], [pause_minority], or [pause_if_all_down].");
            }

            // We need to assign hive nodes to host the RabbitMQ instances.  We're going to do
            // this by examining and possibly setting the RabbitMQ and RabbitMQManager node labels.
            // If no hive nodes have this label set, then we'll set both of these for all manager
            // nodes.  Otherwise, we'll deploy to the nodes marked with [RabbitMQ=true].
            //
            // Note that we always need to deploy at least one node enabling the management
            // plugin.  If [RabbitMQManager] is not set for any nodes then we'll set it for
            // the first node (as sorted by name).
            //
            // Note also that [RabbitMQManager=true] implies [RabbitMQ=true].

            foreach (var node in hiveDefinition.Nodes.Where(n => n.Labels.HiveMQManager))
            {
                node.Labels.HiveMQ = true;
            }

            if (hiveDefinition.Nodes.Count(n => n.Labels.HiveMQ) == 0)
            {
                foreach (var manager in hiveDefinition.Managers)
                {
                    manager.Labels.HiveMQ        = true;
                    manager.Labels.HiveMQManager = true;
                }
            }
            else if (hiveDefinition.Nodes.Count(n => n.Labels.HiveMQManager) == 0)
            {
                var firstRabbitMQNode = hiveDefinition.Nodes.First(n => n.Labels.HiveMQ);

                firstRabbitMQNode.Labels.HiveMQManager = true;
            }
        }