/// <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)}"); } }
/// <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))); } }
/// <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))); } }
/// <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); } }
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)}]."); } } } }
/// <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."); } }
/// <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); }
/// <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); } } }
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)); } }
/// <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]."); } }