public void Validate() { // Wire up the node label parents. foreach (var node in NodeDefinitions.Values) { if (node.Labels != null) { node.Labels.Node = node; } } // Validate the properties. Provisioner = Provisioner ?? defaultProvisioner; Kubernetes = Kubernetes ?? new KubernetesOptions(); Docker = Docker ?? new DockerOptions(); Ceph = Ceph ?? new CephOptions() { Enabled = false }; Mon = Mon ?? new MonOptions() { Enabled = false }; Prometheus = Prometheus ?? new PrometheusOptions() { Enabled = false }; DrivePrefix = DrivePrefix ?? defaultDrivePrefix; Setup = Setup ?? new SetupOptions(); Hosting = Hosting ?? new HostingOptions(); NodeOptions = NodeOptions ?? new NodeOptions(); Network = Network ?? new NetworkOptions(); Kubernetes.Validate(this); Docker.Validate(this); Ceph.Validate(this); Mon.Validate(this); Prometheus.Validate(this); Setup.Validate(this); Network.Validate(this); Hosting.Validate(this); NodeOptions.Validate(this); Network.Validate(this); new HostingManagerFactory().Validate(this); if (TimeSources == null || TimeSources.Length == 0 || TimeSources.Count(ts => string.IsNullOrWhiteSpace(ts)) > 0) { TimeSources = new string[] { "pool.ntp.org" }; } if (NodeDefinitions == null || NodeDefinitions.Count == 0) { throw new ClusterDefinitionException("At least one cluster node must be defined."); } foreach (var node in NodeDefinitions.Values) { node.Validate(this); } if (Name == null) { throw new ClusterDefinitionException($"The [{nameof(ClusterDefinition)}.{nameof(Name)}] property is required."); } if (!IsValidName(Name)) { throw new ClusterDefinitionException($"The [{nameof(ClusterDefinition)}.{nameof(Name)}={Name}] property is not valid. Only letters, numbers, periods, dashes, and underscores are allowed."); } if (Datacenter == null) { throw new ClusterDefinitionException($"The [{nameof(ClusterDefinition)}.{nameof(Datacenter)}] property is required."); } if (!IsValidName(Datacenter)) { throw new ClusterDefinitionException($"The [{nameof(ClusterDefinition)}.{nameof(Datacenter)}={Datacenter}] property is not valid. Only letters, numbers, periods, dashes, and underscores are allowed."); } var masterNodeCount = Masters.Count(); if (masterNodeCount == 0) { throw new ClusterDefinitionException("Clusters must have at least one master node."); } else if (masterNodeCount > 5) { throw new ClusterDefinitionException("Clusters may not have more than [5] master nodes."); } else if (!NeonHelper.IsOdd(masterNodeCount)) { throw new ClusterDefinitionException($"[{masterNodeCount}] master nodes is not allowed. Only an off number of master nodes is allowed: [1, 3, or 5]"); } if (!string.IsNullOrEmpty(PackageProxy)) { // Ensure that this is set to zero or more network endpoints // formatted like: // // HOSTNAME:PORT // ADDRESS:PORT foreach (var endpoint in PackageProxy.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) { var fields = endpoint.Split(':'); if (!IPAddress.TryParse(fields[0], out var address) && !NetHelper.IsValidHost(fields[0])) { throw new ClusterDefinitionException($"Invalid IP address or HOSTNAME [{fields[0]}] in [{nameof(ClusterDefinition)}.{nameof(PackageProxy)}={PackageProxy}]."); } if (!int.TryParse(fields[1], out var port) || !NetHelper.IsValidPort(port)) { throw new ClusterDefinitionException($"Invalid port [{fields[1]}] in [{nameof(ClusterDefinition)}.{nameof(PackageProxy)}={PackageProxy}]."); } } } // Ensure that each node has a valid unique or NULL IP address. NetworkCidr nodesSubnet = null; if (Network.NodeSubnet != null) { nodesSubnet = NetworkCidr.Parse(Network.NodeSubnet); } var addressToNode = new Dictionary <string, NodeDefinition>(); foreach (var node in SortedNodes) { if (node.PrivateAddress != null) { NodeDefinition conflictNode; if (addressToNode.TryGetValue(node.PrivateAddress, out conflictNode)) { throw new ClusterDefinitionException($"Node [name={node.Name}] has invalid private IP address [{node.PrivateAddress}] that conflicts with node [name={conflictNode.Name}]."); } } } foreach (var node in SortedNodes) { if (node.PrivateAddress != null) { if (!IPAddress.TryParse(node.PrivateAddress, out var address)) { throw new ClusterDefinitionException($"Node [name={node.Name}] has invalid private IP address [{node.PrivateAddress}]."); } if (nodesSubnet != null && !nodesSubnet.Contains(address)) { throw new ClusterDefinitionException($"Node [name={node.Name}] has private IP address [{node.PrivateAddress}] that is not within the hosting [{nameof(Network.NodeSubnet)}={Network.NodeSubnet}]."); } } else if (!Hosting.IsCloudProvider) { throw new ClusterDefinitionException($"Node [name={node.Name}] is not assigned a private IP address. This is required when deploying to a [{nameof(Environment)}={Environment}] hosting environment."); } } }