Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial support for configuring IPv6 with AWS #11442

Merged
merged 3 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cloudmock/aws/mockec2/subnets.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ func (m *MockEC2) CreateSubnetWithId(request *ec2.CreateSubnetInput, id string)
AvailabilityZone: request.AvailabilityZone,
}

if request.Ipv6CidrBlock != nil {
subnet.Ipv6CidrBlockAssociationSet = []*ec2.SubnetIpv6CidrBlockAssociation{
{
AssociationId: aws.String("subnet-cidr-assoc-ipv6-" + id),
Ipv6CidrBlock: request.Ipv6CidrBlock,
Ipv6CidrBlockState: &ec2.SubnetCidrBlockState{
State: aws.String(ec2.SubnetCidrBlockStateCodeAssociated),
},
},
}
}

if m.subnets == nil {
m.subnets = make(map[string]*subnetInfo)
}
Expand Down
33 changes: 24 additions & 9 deletions cloudmock/aws/mockec2/vpcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,18 +243,33 @@ func (m *MockEC2) AssociateVpcCidrBlock(request *ec2.AssociateVpcCidrBlockInput)
if !ok {
return nil, fmt.Errorf("VPC %q not found", id)
}
association := &ec2.VpcCidrBlockAssociation{
CidrBlock: request.CidrBlock,
AssociationId: aws.String(fmt.Sprintf("%v-%v", id, len(vpc.main.CidrBlockAssociationSet))),
CidrBlockState: &ec2.VpcCidrBlockState{
State: aws.String(ec2.VpcCidrBlockStateCodeAssociated),
},
var ipv4association *ec2.VpcCidrBlockAssociation
var ipv6association *ec2.VpcIpv6CidrBlockAssociation
if aws.BoolValue(request.AmazonProvidedIpv6CidrBlock) {
ipv6association = &ec2.VpcIpv6CidrBlockAssociation{
Ipv6Pool: aws.String("Amazon"),
Ipv6CidrBlock: aws.String("2001:db8::/56"),
AssociationId: aws.String(fmt.Sprintf("%v-%v", id, len(vpc.main.Ipv6CidrBlockAssociationSet))),
Ipv6CidrBlockState: &ec2.VpcCidrBlockState{
State: aws.String(ec2.VpcCidrBlockStateCodeAssociated),
},
}
vpc.main.Ipv6CidrBlockAssociationSet = append(vpc.main.Ipv6CidrBlockAssociationSet, ipv6association)
} else {
ipv4association = &ec2.VpcCidrBlockAssociation{
CidrBlock: request.CidrBlock,
AssociationId: aws.String(fmt.Sprintf("%v-%v", id, len(vpc.main.CidrBlockAssociationSet))),
CidrBlockState: &ec2.VpcCidrBlockState{
State: aws.String(ec2.VpcCidrBlockStateCodeAssociated),
},
}
vpc.main.CidrBlockAssociationSet = append(vpc.main.CidrBlockAssociationSet, ipv4association)
}
vpc.main.CidrBlockAssociationSet = append(vpc.main.CidrBlockAssociationSet, association)

return &ec2.AssociateVpcCidrBlockOutput{
CidrBlockAssociation: association,
VpcId: request.VpcId,
CidrBlockAssociation: ipv4association,
Ipv6CidrBlockAssociation: ipv6association,
VpcId: request.VpcId,
}, nil
}

Expand Down
8 changes: 5 additions & 3 deletions cmd/kops/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func (i *integrationTest) withNTH() *integrationTest {
// TestMinimal runs the test on a minimum configuration, similar to kops create cluster minimal.example.com --zones us-west-1a
func TestMinimal(t *testing.T) {
newIntegrationTest("minimal.example.com", "minimal").runTestTerraformAWS(t)
newIntegrationTest("minimal.example.com", "minimal").runTestCloudformation(t)
}

// TestMinimal runs the test on a minimum gossip configuration
Expand Down Expand Up @@ -190,9 +191,10 @@ func TestExternalPolicies(t *testing.T) {
newIntegrationTest("externalpolicies.example.com", "externalpolicies").runTestTerraformAWS(t)
}

// TestMinimalCloudformation runs the test on a minimum configuration, similar to kops create cluster minimal.example.com --zones us-west-1a
func TestMinimalCloudformation(t *testing.T) {
newIntegrationTest("minimal.example.com", "minimal-cloudformation").runTestCloudformation(t)
// TestMinimalIPv6 runs the test on a minimum IPv6 configuration, similar to kops create cluster minimal.example.com --zones us-west-1a
func TestMinimalIPv6(t *testing.T) {
newIntegrationTest("minimal-ipv6.example.com", "minimal-ipv6").runTestTerraformAWS(t)
newIntegrationTest("minimal-ipv6.example.com", "minimal-ipv6").runTestCloudformation(t)
}

// TestMinimalEtcd runs the test on a minimum configuration using custom etcd config, similar to kops create cluster minimal.example.com --zones us-west-1a
Expand Down
12 changes: 10 additions & 2 deletions cmd/kops/lifecycle_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ func (o *LifecycleTestOptions) AddDefaults() {
o.SrcDir = "../../tests/integration/update_cluster/" + o.SrcDir
}

// TestLifecycleMinimal runs the test on a minimum configuration, similar to kops create cluster minimal.example.com --zones us-west-1a
func TestLifecycleMinimal(t *testing.T) {
// TestLifecycleMinimalAWS runs the test on a minimum configuration, similar to kops create cluster minimal.example.com --zones us-west-1a
func TestLifecycleMinimalAWS(t *testing.T) {
runLifecycleTestAWS(&LifecycleTestOptions{
t: t,
SrcDir: "minimal",
Expand Down Expand Up @@ -111,6 +111,14 @@ func TestLifecyclePrivateKopeio(t *testing.T) {
})
}

// TestLifecycleIPv6 runs the test on a IPv6 topology
func TestLifecycleIPv6(t *testing.T) {
runLifecycleTestAWS(&LifecycleTestOptions{
t: t,
SrcDir: "minimal-ipv6",
})
}

// TestLifecycleSharedVPC runs the test on a shared VPC
func TestLifecycleSharedVPC(t *testing.T) {
runLifecycleTestAWS(&LifecycleTestOptions{
Expand Down
5 changes: 5 additions & 0 deletions k8s/crds/kops.k8s.io_clusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4103,6 +4103,7 @@ spec:
items:
properties:
cidr:
description: CIDR is the IPv4 CIDR block assigned to the subnet.
type: string
egress:
description: Egress defines the method of traffic egress for
Expand All @@ -4112,6 +4113,10 @@ spec:
description: ProviderID is the cloud provider id for the objects
associated with the zone (the subnet on AWS)
type: string
ipv6CIDR:
description: IPv6CIDR is the IPv6 CIDR block assigned to the
subnet.
type: string
name:
type: string
publicIP:
Expand Down
4 changes: 3 additions & 1 deletion pkg/apis/kops/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,10 @@ const (
type ClusterSubnetSpec struct {
// Name is the name of the subnet
Name string `json:"name,omitempty"`
// CIDR is the network cidr of the subnet
// CIDR is the IPv4 CIDR block assigned to the subnet.
CIDR string `json:"cidr,omitempty"`
// IPv6CIDR is the IPv6 CIDR block assigned to the subnet.
IPv6CIDR string `json:"ipv6CIDR,omitempty"`
// Zone is the zone the subnet is in, set for subnets that are zonally scoped
Zone string `json:"zone,omitempty"`
// Region is the region the subnet is in, set for subnets that are regionally scoped
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/kops/v1alpha2/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,10 @@ type ClusterSubnetSpec struct {
// Region is the region the subnet is in, set for subnets that are regionally scoped
Region string `json:"region,omitempty"`

// CIDR is the IPv4 CIDR block assigned to the subnet.
CIDR string `json:"cidr,omitempty"`
// IPv6CIDR is the IPv6 CIDR block assigned to the subnet.
IPv6CIDR string `json:"ipv6CIDR,omitempty"`

// ProviderID is the cloud provider id for the objects associated with the zone (the subnet on AWS)
ProviderID string `json:"id,omitempty"`
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/kops/v1alpha2/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/apis/kops/validation/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 32 additions & 3 deletions pkg/apis/kops/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"k8s.io/kops/pkg/model/components"
"k8s.io/kops/pkg/model/iam"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/utils"
)

func newValidateCluster(cluster *kops.Cluster) field.ErrorList {
Expand Down Expand Up @@ -78,7 +79,7 @@ func newValidateCluster(cluster *kops.Cluster) field.ErrorList {
func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

allErrs = append(allErrs, validateSubnets(spec.Subnets, fieldPath.Child("subnets"))...)
allErrs = append(allErrs, validateSubnets(spec, fieldPath.Child("subnets"))...)

// SSHAccess
for i, cidr := range spec.SSHAccess {
Expand Down Expand Up @@ -312,7 +313,11 @@ func validateCIDR(cidr string, fieldPath *field.Path) field.ErrorList {
if !strings.Contains(cidr, "/") {
ip := net.ParseIP(cidr)
if ip != nil {
detail += fmt.Sprintf(" (did you mean \"%s/32\")", cidr)
if ip.To4() != nil && !strings.Contains(cidr, ":") {
detail += fmt.Sprintf(" (did you mean \"%s/32\")", cidr)
} else {
detail += fmt.Sprintf(" (did you mean \"%s/64\")", cidr)
}
}
}
allErrs = append(allErrs, field.Invalid(fieldPath, cidr, detail))
Expand All @@ -324,6 +329,16 @@ func validateCIDR(cidr string, fieldPath *field.Path) field.ErrorList {
return allErrs
}

func validateIPv6CIDR(cidr string, fieldPath *field.Path) field.ErrorList {
allErrs := validateCIDR(cidr, fieldPath)

if !utils.IsIPv6CIDR(cidr) {
allErrs = append(allErrs, field.Invalid(fieldPath, cidr, "Network is not an IPv6 CIDR"))
}

return allErrs
}

func validateTopology(topology *kops.TopologySpec, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

Expand Down Expand Up @@ -360,9 +375,11 @@ func validateTopology(topology *kops.TopologySpec, fieldPath *field.Path) field.
return allErrs
}

func validateSubnets(subnets []kops.ClusterSubnetSpec, fieldPath *field.Path) field.ErrorList {
func validateSubnets(cluster *kops.ClusterSpec, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

subnets := cluster.Subnets

// cannot be empty
if len(subnets) == 0 {
allErrs = append(allErrs, field.Required(fieldPath, ""))
Expand Down Expand Up @@ -395,6 +412,14 @@ func validateSubnets(subnets []kops.ClusterSubnetSpec, fieldPath *field.Path) fi
}
}

if kops.CloudProviderID(cluster.CloudProvider) != kops.CloudProviderAWS {
for i := range subnets {
if subnets[i].IPv6CIDR != "" {
allErrs = append(allErrs, field.Forbidden(fieldPath.Child("ipv6CIDR"), "ipv6CIDR can only be specified for AWS"))
}
}
}

return allErrs
}

Expand All @@ -410,6 +435,10 @@ func validateSubnet(subnet *kops.ClusterSubnetSpec, fieldPath *field.Path) field
if subnet.CIDR != "" {
allErrs = append(allErrs, validateCIDR(subnet.CIDR, fieldPath.Child("cidr"))...)
}
// IPv6CIDR
if subnet.IPv6CIDR != "" {
hakman marked this conversation as resolved.
Show resolved Hide resolved
allErrs = append(allErrs, validateIPv6CIDR(subnet.IPv6CIDR, fieldPath.Child("ipv6CIDR"))...)
}

if subnet.Egress != "" {
egressType := strings.Split(subnet.Egress, "-")[0]
Expand Down
35 changes: 34 additions & 1 deletion pkg/apis/kops/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,42 @@ func TestValidateSubnets(t *testing.T) {
},
ExpectedErrors: []string{"Invalid value::subnets[0].cidr"},
},
{
Input: []kops.ClusterSubnetSpec{
{Name: "a", IPv6CIDR: "2001:db8::/56"},
},
},
{
Input: []kops.ClusterSubnetSpec{
{Name: "a", IPv6CIDR: "10.0.0.0/8"},
},
ExpectedErrors: []string{"Invalid value::subnets[0].ipv6CIDR"},
},
hakman marked this conversation as resolved.
Show resolved Hide resolved
{
Input: []kops.ClusterSubnetSpec{
{Name: "a", IPv6CIDR: "::ffff:10.128.0.0"},
},
ExpectedErrors: []string{"Invalid value::subnets[0].ipv6CIDR"},
},
{
Input: []kops.ClusterSubnetSpec{
{Name: "a", IPv6CIDR: "::ffff:10.128.0.0/8"},
},
ExpectedErrors: []string{"Invalid value::subnets[0].ipv6CIDR"},
},
{
Input: []kops.ClusterSubnetSpec{
{Name: "a", CIDR: "::ffff:10.128.0.0/8"},
},
ExpectedErrors: []string{"Invalid value::subnets[0].cidr"},
},
}
for _, g := range grid {
errs := validateSubnets(g.Input, field.NewPath("subnets"))
cluster := &kops.ClusterSpec{
CloudProvider: "aws",
Subnets: g.Input,
}
errs := validateSubnets(cluster, field.NewPath("subnets"))

testErrors(t, g.Input, errs, g.ExpectedErrors)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/model/awsmodel/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading