Ejemplo n.º 1
0
        /// <summary>
        /// Validates the node definition.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <param name="nodeName">The node name.</param>
        /// <exception cref="ArgumentException">Thrown if the definition is not valid.</exception>
        public void Validate(ClusterDefinition clusterDefinition, string nodeName)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition));

            var nodeDefinitionPrefix = $"{nameof(ClusterDefinition.NodeDefinitions)}";

            if (clusterDefinition.Hosting.IsHostedHypervisor)
            {
                if (string.IsNullOrEmpty(Host))
                {
                    throw new ClusterDefinitionException($"Node [{nodeName}] does not specify a hypervisor [{nodeDefinitionPrefix}.{nameof(NodeDefinition.Vm.Host)}].");
                }
                else if (clusterDefinition.Hosting.Vm.Hosts.FirstOrDefault(h => h.Name.Equals(Host, StringComparison.InvariantCultureIgnoreCase)) == null)
                {
                    throw new ClusterDefinitionException($"Node [{nodeName}] references hypervisor [{Host}] which is not defined in [{nameof(ClusterDefinition.Hosting)}.{nameof(ClusterDefinition.Hosting.Vm)}.{nameof(ClusterDefinition.Hosting.Vm.Hosts)}].");
                }
            }

            if (Memory != null)
            {
                ClusterDefinition.ValidateSize(Memory, this.GetType(), $"{nodeDefinitionPrefix}.{nameof(Memory)}");
            }

            if (OsDisk != null)
            {
                ClusterDefinition.ValidateSize(OsDisk, this.GetType(), $"{nodeDefinitionPrefix}.{nameof(OsDisk)}");
            }

            if (OpenEbsDisk != null)
            {
                ClusterDefinition.ValidateSize(OpenEbsDisk, this.GetType(), $"{nodeDefinitionPrefix}.{nameof(OpenEbsDisk)}");
            }
        }
Ejemplo n.º 2
0
 /// <summary>
 /// Returns the maximum number of bytes of memory allocate to for this node when
 /// hosted on a hypervisor.
 /// </summary>
 /// <param name="clusterDefinition">The cluster definition.</param>
 /// <returns>The size in bytes.</returns>
 public long GetMemory(ClusterDefinition clusterDefinition)
 {
     if (!string.IsNullOrEmpty(Memory))
     {
         return(ClusterDefinition.ValidateSize(Memory, this.GetType(), nameof(Memory)));
     }
     else
     {
         return(ClusterDefinition.ValidateSize(clusterDefinition.Hosting.Vm.Memory, clusterDefinition.Hosting.GetType(), nameof(clusterDefinition.Hosting.Vm.Memory)));
     }
 }
Ejemplo n.º 3
0
 /// <summary>
 /// Returns the size of the operating system disk to be created for this node when
 /// hosted on a hypervisor.
 /// </summary>
 /// <param name="clusterDefinition">The cluster definition.</param>
 /// <returns>The size in bytes.</returns>
 public long GetOsDisk(ClusterDefinition clusterDefinition)
 {
     if (!string.IsNullOrEmpty(OsDisk))
     {
         return(ClusterDefinition.ValidateSize(OsDisk, this.GetType(), nameof(OsDisk)));
     }
     else
     {
         return(ClusterDefinition.ValidateSize(clusterDefinition.Hosting.Vm.OsDisk, clusterDefinition.Hosting.GetType(), nameof(clusterDefinition.Hosting.Vm.OsDisk)));
     }
 }
Ejemplo n.º 4
0
        /// <summary>
        /// Returns the size of the OpenEBS cStor disk to be created for this node when
        /// hosted on a hypervisor.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <returns>The size in bytes.</returns>
        public long GetOpenEbsDisk(ClusterDefinition clusterDefinition)
        {
            const string minOpenEbsSize = "32 GiB";

            switch (clusterDefinition.Storage.OpenEbs.Engine)
            {
            case OpenEbsEngine.cStor:
            case OpenEbsEngine.Mayastor:

                if (!string.IsNullOrEmpty(OpenEbsDisk))
                {
                    return(ClusterDefinition.ValidateSize(OpenEbsDisk, this.GetType(), nameof(OpenEbsDisk), minimum: minOpenEbsSize));
                }
                else
                {
                    return(ClusterDefinition.ValidateSize(clusterDefinition.Hosting.Vm.OpenEbsDisk, clusterDefinition.Hosting.GetType(), nameof(clusterDefinition.Hosting.Vm.OpenEbsDisk), minimum: minOpenEbsSize));
                }

            default:

                return(0);
            }
        }
Ejemplo n.º 5
0
        internal void ValidateHypervisor(ClusterDefinition clusterDefinition, bool remoteHypervisors)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition));

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

            VmMemory = VmMemory ?? DefaultVmMemory;
            VmDisk   = VmDisk ?? DefaultVmDisk;
            VmHosts  = VmHosts ?? new List <HypervisorHost>();

            ClusterDefinition.ValidateSize(VmMemory, this.GetType(), nameof(VmMemory));
            ClusterDefinition.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 clusterDefinition.Hosting.VmHosts)
            {
                if (hostNameSet.Contains(vmHost.Name))
                {
                    throw new ClusterDefinitionException($"One or more hypervisor hosts are assigned the [{vmHost.Name}] name.");
                }

                hostNameSet.Add(vmHost.Name);

                if (hostAddressSet.Contains(vmHost.Address))
                {
                    throw new ClusterDefinitionException($"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 (clusterDefinition.Hosting.VmHosts.Count == 0)
                {
                    throw new ClusterDefinitionException($"At least one host XenServer must be specified in [{nameof(HostingOptions)}.{nameof(HostingOptions.VmHosts)}].");
                }

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

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

                    if (!hostNameSet.Contains(node.VmHost))
                    {
                        throw new ClusterDefinitionException($"Node [{node.Name}] has [{nameof(HypervisorHost)}={node.VmHost}] which specifies a hypervisor host that was not found in [{nameof(HostingOptions)}.{nameof(HostingOptions.VmHosts)}].");
                    }
                }
            }
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Validates the options.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <exception cref="ClusterDefinitionException">Thrown if the definition is not valid.</exception>
        internal void Validate(ClusterDefinition clusterDefinition)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition));

            var openEbsOptionsPrefix = $"{nameof(ClusterDefinition.Storage)}.{nameof(ClusterDefinition.Storage.OpenEbs)}";

            NfsSize = NfsSize ?? minNfsSize;

            ClusterDefinition.ValidateSize(NfsSize, typeof(OpenEbsOptions), nameof(NfsSize), minimum: minNfsSize);

            // Clusters require that at least one node has [OpenEbsStorage=true].  We'll set
            // this automatically when the user hasn't already done this.  All workers will have
            // this set to true when there are workers, otherwise we'll set this to true for all
            // control-plane nodes.

            if (!clusterDefinition.Nodes.Any(node => node.OpenEbsStorage))
            {
                if (clusterDefinition.Workers.Count() > 0)
                {
                    foreach (var worker in clusterDefinition.Workers)
                    {
                        worker.OpenEbsStorage = true;
                    }
                }
                else
                {
                    foreach (var controlNode in clusterDefinition.ControlNodes)
                    {
                        controlNode.OpenEbsStorage = true;
                    }
                }
            }

            switch (Engine)
            {
            case OpenEbsEngine.Default:

                if (clusterDefinition.Nodes.Count() == 1)
                {
                    Engine = OpenEbsEngine.HostPath;
                }
                else if (clusterDefinition.Nodes.Count(n => n.OpenEbsStorage) > 0)
                {
                    Engine = OpenEbsEngine.Jiva;
                }
                break;

            case OpenEbsEngine.HostPath:

                if (clusterDefinition.Nodes.Count() > 1)
                {
                    new ClusterDefinitionException($"[{openEbsOptionsPrefix}.{nameof(Engine)}={Engine}] storage engine is supported only for single-node clusters.");
                }
                break;

            case OpenEbsEngine.cStor:

                break;     // NOP

            case OpenEbsEngine.Jiva:

                break;     // NOP

            default:
            case OpenEbsEngine.Mayastor:

                throw new ClusterDefinitionException($"[{openEbsOptionsPrefix}.{nameof(Engine)}={Engine}] storage engine is not implemented.");
            }
        }
Ejemplo n.º 7
0
        /// <inheritdoc/>
        public override bool Provision(bool 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 cluster.Definition.Nodes)
            {
                if (string.IsNullOrEmpty(node.Labels.PhysicalMachine))
                {
                    node.Labels.PhysicalMachine = Environment.MachineName;
                }

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

                if (node.Labels.ComputeRam == 0)
                {
                    node.Labels.ComputeRam = (int)(ClusterDefinition.ValidateSize(cluster.Definition.Hosting.VmMemory, typeof(HostingOptions), nameof(HostingOptions.VmMemory)) / ByteUnits.MebiBytes);
                }

                if (string.IsNullOrEmpty(node.Labels.StorageSize))
                {
                    node.Labels.StorageSize = ByteUnits.ToGiString(node.GetVmMemory(cluster.Definition));
                }
            }

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

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

            // Perform the provisioning operations.

            controller = new SetupController <NodeDefinition>($"Provisioning [{cluster.Definition.Name}] cluster", cluster.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("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);
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Validates the options and also ensures that all <c>null</c> properties are
        /// initialized to their default values.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <exception cref="ClusterDefinitionException">Thrown if the definition is not valid.</exception>
        public void Validate(ClusterDefinition clusterDefinition)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition));

            var vmHostingOptionsPrefix = $"{nameof(ClusterDefinition.Hosting)}";

            // Validate the VM name prefix.

            if (!string.IsNullOrWhiteSpace(NamePrefix))
            {
                if (!ClusterDefinition.IsValidName(NamePrefix))
                {
                    throw new ClusterDefinitionException($"[{nameof(HostingOptions)}.{nameof(NamePrefix)}={NamePrefix}] must include only letters, digits, underscores, or periods.");
                }
            }

            if (Cores <= 0)
            {
                throw new ClusterDefinitionException($"[{nameof(HyperVHostingOptions)}.{nameof(Cores)}={Cores}] must be positive.");
            }

            Memory = Memory ?? DefaultMemory;
            OsDisk = OsDisk ?? DefaultOsDisk;
            Hosts  = Hosts ?? new List <HypervisorHost>();

            ClusterDefinition.ValidateSize(Memory, this.GetType(), $"{vmHostingOptionsPrefix}.{nameof(Memory)}");
            ClusterDefinition.ValidateSize(OsDisk, this.GetType(), $"{vmHostingOptionsPrefix}.{nameof(OsDisk)}");
            ClusterDefinition.ValidateSize(OpenEbsDisk, this.GetType(), $"{vmHostingOptionsPrefix}.{nameof(OpenEbsDisk)}");

            // 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 clusterDefinition.Hosting.Vm.Hosts)
            {
                if (hostNameSet.Contains(vmHost.Name))
                {
                    throw new ClusterDefinitionException($"One or more hypervisor hosts are assigned the [{vmHost.Name}] name.");
                }

                hostNameSet.Add(vmHost.Name);

                if (hostAddressSet.Contains(vmHost.Address))
                {
                    throw new ClusterDefinitionException($"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.

            if (clusterDefinition.Hosting.IsHostedHypervisor)
            {
                foreach (var vmHost in Hosts)
                {
                    vmHost.Validate(clusterDefinition);
                }
            }
        }
Ejemplo n.º 9
0
        public void Validate(ClusterDefinition clusterDefinition)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null);

            // Ensure that the labels are wired up to the parent node.

            if (Labels == null)
            {
                Labels = new NodeLabels(this);
            }
            else
            {
                Labels.Node = this;
            }

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

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

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

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

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

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

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

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

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

            if (VmDisk != null)
            {
                ClusterDefinition.ValidateSize(VmDisk, this.GetType(), nameof(VmDisk));
            }
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Validates the options and also ensures that all <c>null</c> properties are
        /// initialized to their default values.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <exception cref="ClusterDefinitionException">Thrown if the definition is not valid.</exception>
        public void Validate(ClusterDefinition clusterDefinition)
        {
            if (!Enabled)
            {
                return;
            }

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

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

            // Examine the Ceph related labels for the cluster 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.

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

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

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

            if (cephOSDCount == 0)
            {
                // No Ceph OSD nodes are explicitly assigned.
                //
                // If the cluster 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 (clusterDefinition.Workers.Count() >= 3)
                {
                    foreach (var node in clusterDefinition.Workers)
                    {
                        node.Labels.CephOSD = true;
                    }
                }
                else
                {
                    foreach (var node in clusterDefinition.Nodes)
                    {
                        node.Labels.CephOSD = true;
                    }
                }
            }

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

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

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

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

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

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

            // Validate the properties.

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

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

            if (ClusterDefinition.ValidateSize(OSDDriveSize, this.GetType(), nameof(OSDDriveSize)) < ByteUnits.GibiBytes)
            {
                throw new ClusterDefinitionException($"[{nameof(CephOptions)}.{nameof(OSDDriveSize)}={OSDDriveSize}] cannot be less than [1Gi].");
            }

            if (ClusterDefinition.ValidateSize(OSDCacheSize, this.GetType(), nameof(OSDCacheSize)) < 64 * ByteUnits.MebiBytes)
            {
                throw new ClusterDefinitionException($"[{nameof(CephOptions)}.{nameof(OSDCacheSize)}={OSDCacheSize}] cannot be less than [64Mi].");
            }

            if (ClusterDefinition.ValidateSize(OSDJournalSize, this.GetType(), nameof(OSDJournalSize)) < 64 * ByteUnits.MebiBytes)
            {
                throw new ClusterDefinitionException($"[{nameof(CephOptions)}.{nameof(OSDJournalSize)}={OSDJournalSize}] cannot be less than [64Mi].");
            }

            if (ClusterDefinition.ValidateSize(OSDObjectSizeMax, this.GetType(), nameof(OSDObjectSizeMax)) < 64 * ByteUnits.MebiBytes)
            {
                throw new ClusterDefinitionException($"[{nameof(CephOptions)}.{nameof(OSDObjectSizeMax)}={OSDObjectSizeMax}] cannot be less than [64Mi].");
            }

            if (ClusterDefinition.ValidateSize(MDSCacheSize, this.GetType(), nameof(MDSCacheSize)) < 64 * ByteUnits.MebiBytes)
            {
                throw new ClusterDefinitionException($"[{nameof(CephOptions)}.{nameof(MDSCacheSize)}={MDSCacheSize}] cannot be less than [64Mi].");
            }

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

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

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

            //if (cephMGRCount == 0)
            //{
            //    throw new ClusterDefinitionException($"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 ClusterDefinitionException($"[{nameof(CephOptions)}.{nameof(OSDReplicaCount)}={OSDReplicaCount}] cannot be less than zero.");
            }

            if (OSDReplicaCount > cephOSDCount)
            {
                throw new ClusterDefinitionException($"[{nameof(CephOptions)}.{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 ClusterDefinitionException($"[{nameof(CephOptions)}.{nameof(OSDReplicaCountMin)}={OSDReplicaCountMin}] cannot be less than zero.");
            }

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

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

            if (OSDPlacementGroups < 8)
            {
                throw new ClusterDefinitionException($"[{nameof(CephOptions)}.{nameof(OSDPlacementGroups)}={OSDPlacementGroups}] cannot be less than [8].");
            }
        }