public void Validate() { Provisioner = Provisioner ?? defaultProvisioner; DrivePrefix = DrivePrefix ?? defaultDrivePrefix; Setup = Setup ?? new SetupOptions(); Hosting = Hosting ?? new HostingOptions(); Vpn = Vpn ?? new VpnOptions(); HiveNode = HiveNode ?? new HiveNodeOptions(); Docker = Docker ?? new DockerOptions(); Image = Image ?? new ImageOptions(); Network = Network ?? new NetworkOptions(); Consul = Consul ?? new ConsulOptions(); Vault = Vault ?? new VaultOptions(); Log = Log ?? new LogOptions(); Dashboard = Dashboard ?? new DashboardOptions(); HiveFS = HiveFS ?? new HiveFSOptions(); Proxy = Proxy ?? new ProxyOptions(); HiveMQ = HiveMQ ?? new HiveMQOptions(); Setup.Validate(this); Network.Validate(this); Hosting.Validate(this); Vpn.Validate(this); HiveNode.Validate(this); Docker.Validate(this); Image.Validate(this); Consul.Validate(this); Vault.Validate(this); Log.Validate(this); Dashboard.Validate(this); HiveFS.Validate(this); Proxy.Validate(this); HiveMQ.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 HiveDefinitionException("At least one hive node must be defined."); } foreach (var node in NodeDefinitions.Values) { node.Validate(this); } if (Name == null) { throw new HiveDefinitionException($"The [{nameof(HiveDefinition)}.{nameof(Name)}] property is required."); } if (!IsValidName(Name)) { throw new HiveDefinitionException($"The [{nameof(HiveDefinition)}.{nameof(Name)}={Name}] property is not valid. Only letters, numbers, periods, dashes, and underscores are allowed."); } if (Datacenter == null) { throw new HiveDefinitionException($"The [{nameof(HiveDefinition)}.{nameof(Datacenter)}] property is required."); } if (!IsValidName(Datacenter)) { throw new HiveDefinitionException($"The [{nameof(HiveDefinition)}.{nameof(Datacenter)}={Datacenter}] property is not valid. Only letters, numbers, periods, dashes, and underscores are allowed."); } if (!string.IsNullOrEmpty(PackageProxy)) { var packageCacheUris = PackageProxy.Split(','); for (int i = 0; i < packageCacheUris.Length; i++) { packageCacheUris[i] = packageCacheUris[i].Trim(); if (!Uri.TryCreate(packageCacheUris[i], UriKind.Absolute, out var aptProxyUri)) { throw new HiveDefinitionException($"The [{nameof(HiveDefinition)}.{nameof(PackageProxy)}={PackageProxy}] includes [{packageCacheUris[i]}] which is not a valid URI."); } if (aptProxyUri.Scheme != "http") { throw new HiveDefinitionException($"The [{nameof(HiveDefinition)}.{nameof(PackageProxy)}={PackageProxy}] includes [{packageCacheUris[i]}] which does not have the [http] scheme."); } } } var managementNodeCount = Managers.Count(); if (managementNodeCount == 0) { throw new HiveDefinitionException("Hives must have at least one management node."); } else if (managementNodeCount > 5) { throw new HiveDefinitionException("Hives may not have more than [5] management nodes."); } else if (!NeonHelper.IsOdd(managementNodeCount)) { throw new HiveDefinitionException("Hives must have an odd number of management nodes: [1, 3, or 5]"); } // Ensure that each node has a valid unique or NULL IP address. NetworkCidr nodesSubnet = null; NetworkCidr vpnPoolSubnet = null; if (Network.NodesSubnet != null) { nodesSubnet = NetworkCidr.Parse(Network.NodesSubnet); } if (Vpn.Enabled) { vpnPoolSubnet = NetworkCidr.Parse(Network.VpnPoolSubnet); } 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 HiveDefinitionException($"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 HiveDefinitionException($"Node [name={node.Name}] has invalid private IP address [{node.PrivateAddress}]."); } if (vpnPoolSubnet != null && vpnPoolSubnet.Contains(address)) { throw new HiveDefinitionException($"Node [name={node.Name}] has private IP address [{node.PrivateAddress}] within the hosting [{nameof(Network.VpnPoolSubnet)}={Network.VpnPoolSubnet}]."); } if (nodesSubnet != null && !nodesSubnet.Contains(address)) { throw new HiveDefinitionException($"Node [name={node.Name}] has private IP address [{node.PrivateAddress}] that is not within the hosting [{nameof(Network.NodesSubnet)}={Network.NodesSubnet}]."); } } else if (!Hosting.IsCloudProvider) { throw new HiveDefinitionException($"Node [name={node.Name}] is not assigned a private IP address. This is required when deploying to a [{nameof(Environment)}={Environment}] hosting environment."); } } // Verify that we have nodes identified for persisting log data if logging is enabled. if (Log.Enabled) { if (Nodes.Where(n => n.Labels.LogEsData).Count() == 0) { throw new HiveDefinitionException($"At least one node must be configured to store log data by setting [{nameof(NodeDefinition.Labels)}.{nameof(NodeLabels.LogEsData)}=true] when hive logging is enabled."); } } }
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."); } } }