/// <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)); Network ??= new AzureNetworkOptions(); Network.Validate(clusterDefinition); var azureHostingOptionsPrefix = $"{nameof(ClusterDefinition.Hosting)}.{nameof(ClusterDefinition.Hosting.Azure)}"; foreach (var ch in clusterDefinition.Name) { if (char.IsLetterOrDigit(ch) || ch == '-' || ch == '_') { continue; } throw new ClusterDefinitionException($"cluster name [{clusterDefinition.Name}] is not valid for Azure deployment. Only letters, digits, dashes, or underscores are allowed."); } if (string.IsNullOrEmpty(SubscriptionId)) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(SubscriptionId)}] cannot be empty."); } if (string.IsNullOrEmpty(TenantId)) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(TenantId)}] cannot be empty."); } if (string.IsNullOrEmpty(ClientId)) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(ClientId)}] cannot be empty."); } if (string.IsNullOrEmpty(ClientSecret)) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(ClientSecret)}] cannot be empty."); } if (string.IsNullOrEmpty(Region)) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(Region)}] cannot be empty."); } if (string.IsNullOrEmpty(DomainLabel)) { // We're going to generate a GUID and strip out the dashes. DomainLabel = "neon-" + Guid.NewGuid().ToString("d").Replace("-", string.Empty); } // Verify [ResourceGroup]. if (string.IsNullOrEmpty(ResourceGroup)) { ResourceGroup = clusterDefinition.Name; } if (ResourceGroup.Length > 64) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(ResourceGroup)}={ResourceGroup}] is longer than 64 characters."); } if (!char.IsLetter(ResourceGroup.First())) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(ResourceGroup)}={ResourceGroup}] does not begin with a letter."); } if (ResourceGroup.Last() == '_' || ResourceGroup.Last() == '-') { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(ResourceGroup)}={ResourceGroup}] ends with a dash or underscore."); } foreach (var ch in ResourceGroup) { if (!(char.IsLetterOrDigit(ch) || ch == '_' || ch == '-')) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(ResourceGroup)}={ResourceGroup}] includes characters other than letters, digits, dashes and underscores."); } } // Verify [Environment]. if (Environment != null) { Environment.Validate(clusterDefinition); } // Verify [DefaultVmSize] if (string.IsNullOrEmpty(DefaultVmSize)) { DefaultVmSize = defaultVmSize; } // Verify [DefaultDiskSize]. if (string.IsNullOrEmpty(DefaultDiskSize)) { DefaultDiskSize = defaultDiskSize; } if (!ByteUnits.TryParse(DefaultDiskSize, out var diskSize) || diskSize <= 0) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(DefaultDiskSize)}={DefaultDiskSize}] is not valid."); } // Verify [DefaultOpenEBSDiskSize]. if (string.IsNullOrEmpty(DefaultOpenEBSDiskSize)) { DefaultOpenEBSDiskSize = defaultOpenEBSDiskSize; } if (!ByteUnits.TryParse(DefaultOpenEBSDiskSize, out var openEbsDiskSize) || openEbsDiskSize <= 0) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(DefaultOpenEBSDiskSize)}={DefaultOpenEBSDiskSize}] is not valid."); } // Check Azure cluster limits. if (clusterDefinition.ControlNodes.Count() > KubeConst.MaxControlNodes) { throw new ClusterDefinitionException($"cluster control-plane count [{clusterDefinition.ControlNodes.Count()}] exceeds the [{KubeConst.MaxControlNodes}] limit for clusters."); } if (clusterDefinition.Nodes.Count() > AzureHelper.MaxClusterNodes) { throw new ClusterDefinitionException($"cluster node count [{clusterDefinition.Nodes.Count()}] exceeds the [{AzureHelper.MaxClusterNodes}] limit for clusters deployed to Azure."); } // Verify subnets if (!NetworkCidr.TryParse(VnetSubnet, out var vnetSubnet)) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(VnetSubnet)}={VnetSubnet}] is not a valid subnet."); } if (!NetworkCidr.TryParse(NodeSubnet, out var nodeSubnet)) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(NodeSubnet)}={NodeSubnet}] is not a valid subnet."); } if (!vnetSubnet.Contains(nodeSubnet)) { throw new ClusterDefinitionException($"[{azureHostingOptionsPrefix}.{nameof(NodeSubnet)}={NodeSubnet}] is contained within [{nameof(VnetSubnet)}={VnetSubnet}]."); } }
public void Validate(ClusterDefinition clusterDefinition) { Covenant.Requires <ArgumentNullException>(clusterDefinition != null); foreach (var ch in clusterDefinition.Name) { if (char.IsLetterOrDigit(ch) || ch == '-' || ch == '_') { continue; } throw new ClusterDefinitionException($"cluster name [{clusterDefinition.Name}] is not valid for Azure deployment. Only letters, digits, dashes, or underscores are allowed."); } if (string.IsNullOrEmpty(SubscriptionId)) { throw new ClusterDefinitionException($"Azure hosting [{nameof(SubscriptionId)}] property cannot be empty."); } if (string.IsNullOrEmpty(TenantId)) { throw new ClusterDefinitionException($"Azure hosting [{nameof(TenantId)}] property cannot be empty."); } if (string.IsNullOrEmpty(ApplicationId)) { throw new ClusterDefinitionException($"Azure hosting [{nameof(ApplicationId)}] property cannot be empty."); } if (string.IsNullOrEmpty(Password)) { throw new ClusterDefinitionException($"Azure hosting [{nameof(Password)}] property cannot be empty."); } if (string.IsNullOrEmpty(Region)) { throw new ClusterDefinitionException($"Azure hosting [{nameof(Region)}] property cannot be empty."); } if (string.IsNullOrEmpty(DomainLabel)) { throw new ClusterDefinitionException($"Azure hosting [{nameof(DomainLabel)}] property cannot be empty."); } // Verify [ResourceGroup]. if (string.IsNullOrEmpty(ResourceGroup)) { ResourceGroup = clusterDefinition.Name; } if (ResourceGroup.Length > 64) { throw new ClusterDefinitionException($"Azure hosting [{nameof(ResourceGroup)}] property cannot be longer than 64 characters."); } if (!char.IsLetter(ResourceGroup.First())) { throw new ClusterDefinitionException($"Azure hosting [{nameof(ResourceGroup)}] property must begin with a letter."); } if (ResourceGroup.Last() == '_' || ResourceGroup.Last() == '-') { throw new ClusterDefinitionException($"Azure hosting [{nameof(ResourceGroup)}] property must not end with a dash or underscore."); } foreach (var ch in ResourceGroup) { if (!(char.IsLetterOrDigit(ch) || ch == '_' || ch == '-')) { throw new ClusterDefinitionException($"Azure hosting [{nameof(ResourceGroup)}] property must include only letters, digits, dashes or underscores."); } } // Verify [Environment]. if (Environment != null) { Environment.Validate(clusterDefinition); } // Check Azure cluster limits. if (clusterDefinition.Masters.Count() > KubeConst.MaxMasters) { throw new ClusterDefinitionException($"cluster master count [{clusterDefinition.Masters.Count()}] exceeds the [{KubeConst.MaxMasters}] limit for clusters."); } if (clusterDefinition.Nodes.Count() > AzureHelper.MaxClusterNodes) { throw new ClusterDefinitionException($"cluster node count [{clusterDefinition.Nodes.Count()}] exceeds the [{AzureHelper.MaxClusterNodes}] limit for clusters deployed to Azure."); } }
/// <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)); Network ??= new AwsNetworkOptions(); Network.Validate(clusterDefinition); var awsHostionOptionsPrefix = $"{nameof(ClusterDefinition.Hosting)}.{nameof(ClusterDefinition.Hosting.Aws)}"; foreach (var ch in clusterDefinition.Name) { if (char.IsLetterOrDigit(ch) || ch == '-' || ch == '_') { continue; } throw new ClusterDefinitionException($"cluster name [{clusterDefinition.Name}] is not valid for AWS deployment. Only letters, digits, dashes, or underscores are allowed."); } if (string.IsNullOrEmpty(AccessKeyId)) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(AccessKeyId)}] is required."); } if (string.IsNullOrEmpty(SecretAccessKey)) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(SecretAccessKey)}] is required."); } if (string.IsNullOrEmpty(AvailabilityZone)) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(AvailabilityZone)}] is required."); } // Verify [ResourceGroup]. if (string.IsNullOrEmpty(ResourceGroup)) { ResourceGroup = clusterDefinition.Name; } if (ResourceGroup.Length > 64) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(ResourceGroup)}={ResourceGroup}] is longer than 64 characters."); } if (!char.IsLetter(ResourceGroup.First())) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(ResourceGroup)}={ResourceGroup}] does not begin with a letter."); } if (ResourceGroup.Last() == '_' || ResourceGroup.Last() == '-') { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(ResourceGroup)}={ResourceGroup}] ends with a dash or underscore."); } foreach (var ch in ResourceGroup) { if (!(char.IsLetterOrDigit(ch) || ch == '_' || ch == '-')) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(ResourceGroup)}={ResourceGroup}] includes characters other than letters, digits, dashes and underscores."); } } // Verify [ControlPlanePlacementPartitions] if (ControlPlanePlacementPartitions < 0) { ControlPlanePlacementPartitions = Math.Min(MaxPlacementPartitions, clusterDefinition.ControlNodes.Count()); } else { if (ControlPlanePlacementPartitions < 1 || MaxPlacementPartitions < ControlPlanePlacementPartitions) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(ControlPlanePlacementPartitions)}={ControlPlanePlacementPartitions}] cannot be in the range [1...{MaxPlacementPartitions}]"); } } // Verify [ControlPlanePlacementPartitions] if (WorkerPlacementPartitions < 1 || MaxPlacementPartitions < WorkerPlacementPartitions) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(WorkerPlacementPartitions)}={WorkerPlacementPartitions}] cannot be in the range [1...{MaxPlacementPartitions}]"); } // Verify [DefaultInstanceType] if (string.IsNullOrEmpty(DefaultInstanceType)) { DefaultInstanceType = defaultInstanceType; } // Verify [DefaultVolumeSize]. if (string.IsNullOrEmpty(DefaultVolumeSize)) { DefaultVolumeSize = defaultVolumeSize; } if (!ByteUnits.TryParse(DefaultVolumeSize, out var volumeSize) || volumeSize <= 0) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(DefaultVolumeSize)}={DefaultVolumeSize}] is not valid."); } // Verify [DefaultOpenEBSVolumeSize]. if (string.IsNullOrEmpty(DefaultOpenEBSVolumeSize)) { DefaultOpenEBSVolumeSize = defaultOpenEBSVolumeSize; } if (!ByteUnits.TryParse(DefaultOpenEBSVolumeSize, out var openEbsVolumeSize) || openEbsVolumeSize <= 0) { throw new ClusterDefinitionException($"[{awsHostionOptionsPrefix}.{nameof(DefaultOpenEBSVolumeSize)}={DefaultOpenEBSVolumeSize}] is not valid."); } // Check AWS cluster limits. if (clusterDefinition.ControlNodes.Count() > KubeConst.MaxControlNodes) { throw new ClusterDefinitionException($"cluster control-plane count [{awsHostionOptionsPrefix}.{clusterDefinition.ControlNodes.Count()}] exceeds the [{KubeConst.MaxControlNodes}] limit for clusters."); } if (clusterDefinition.Nodes.Count() > AwsHelper.MaxClusterNodes) { throw new ClusterDefinitionException($"cluster node count [{awsHostionOptionsPrefix}.{clusterDefinition.Nodes.Count()}] exceeds the [{AwsHelper.MaxClusterNodes}] limit for clusters deployed to AWS."); } //----------------------------------------------------------------- // Network subnets VpcSubnet = VpcSubnet ?? defaultVpcSubnet; NodeSubnet = NodeSubnet ?? defaultPrivateSubnet; PublicSubnet = PublicSubnet ?? defaultPublicSubnet; const int minAwsPrefix = 16; const int maxAwsPrefix = 28; // VpcSubnet if (!NetworkCidr.TryParse(VpcSubnet, out var vpcSubnet)) { throw new ClusterDefinitionException($"AWS hosting [{nameof(VpcSubnet)}={VpcSubnet}] is not a valid subnet."); } if (vpcSubnet.PrefixLength < minAwsPrefix) { throw new ClusterDefinitionException($"AWS hosting [{nameof(VpcSubnet)}={VpcSubnet}] is too large. The smallest CIDR prefix supported by AWS is [/{minAwsPrefix}]."); } if (vpcSubnet.PrefixLength > maxAwsPrefix) { throw new ClusterDefinitionException($"AWS hosting [{nameof(VpcSubnet)}={VpcSubnet}] is too large. The largest CIDR prefix supported by AWS is [/{maxAwsPrefix}]."); } // PrivateSubnet if (!NetworkCidr.TryParse(NodeSubnet, out var privateSubnet)) { throw new ClusterDefinitionException($"AWS hosting [{nameof(NodeSubnet)}={NodeSubnet}] is not a valid subnet."); } if (vpcSubnet.PrefixLength < minAwsPrefix) { throw new ClusterDefinitionException($"AWS hosting [{nameof(NodeSubnet)}={NodeSubnet}] is too large. The smallest CIDR prefix supported by AWS is [/{minAwsPrefix}]."); } if (vpcSubnet.PrefixLength > maxAwsPrefix) { throw new ClusterDefinitionException($"AWS hosting [{nameof(NodeSubnet)}={NodeSubnet}] is too large. The largest CIDR prefix supported by AWS is [/{maxAwsPrefix}]."); } // PublicSubnet if (!NetworkCidr.TryParse(PublicSubnet, out var publicSubnet)) { throw new ClusterDefinitionException($"AWS hosting [{nameof(PublicSubnet)}={PublicSubnet}] is not a valid subnet."); } // Ensure that the subnets fit together. if (!vpcSubnet.Contains(privateSubnet)) { throw new ClusterDefinitionException($"AWS hosting [{nameof(PublicSubnet)}={PublicSubnet}] is not contained within [{nameof(VpcSubnet)}={VpcSubnet}]."); } if (!vpcSubnet.Contains(publicSubnet)) { throw new ClusterDefinitionException($"AWS hosting [{nameof(NodeSubnet)}={NodeSubnet}] is not contained within [{nameof(VpcSubnet)}={VpcSubnet}]."); } if (privateSubnet.Overlaps(publicSubnet)) { throw new ClusterDefinitionException($"AWS hosting [{nameof(NodeSubnet)}={NodeSubnet}] and [{nameof(PublicSubnet)}={PublicSubnet}] cannot overlap."); } }