Skip to content
Open
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
59 changes: 43 additions & 16 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,24 @@ const (
RulesManagementUnmanaged RulesManagementPolicy = "Unmanaged"
)

// StackType is a string enum type indicating the types of network addresses that are valid.
// +kubebuilder:validation:Enum=IPv4Only;DualStack
type StackType string

const (
// IPv4OnlyStackType indicates a stack type where only IPv4 addresses are valid.
IPv4OnlyStackType StackType = "IPv4Only"

// DualStackType indicates a stack type where both IPv4 and IPv6 addresses are valid.
DualStackType StackType = "DualStack"
)

const (
// DualStackAdditionalResourceSuffix is an identifier appended to the resource name to indicate
// that it is not for the ipv4 [default] stack type.
DualStackAdditionalResourceSuffix string = "ipv6"
)

// NetworkSpec encapsulates all things related to a GCP network.
type NetworkSpec struct {
// Name is the name of the network to be used.
Expand Down Expand Up @@ -191,6 +209,31 @@ type NetworkSpec struct {
// +kubebuilder:default:=64
// +optional
MinPortsPerVM int64 `json:"minPortsPerVm,omitempty"`

// InternalIpv6PrefixLength: The prefix length of the primary internal IPv6 range.
// +kubebuilder:validation:Minimum=0
// +kubebuilder:validation:Maximum=128
// +optional
InternalIpv6PrefixLength int `json:"internalIpv6PrefixLength,omitempty"`

// Ipv6Address: An IPv6 internal network address for this network interface.
// To use a static internal IP address, it must be unused and in the same
// region as the instance's zone. If not specified, Google Cloud will
// automatically assign an internal IPv6 address from the instance's subnetwork.
// +optional
Ipv6Address string `json:"ipv6Address,omitempty"`
Comment on lines +213 to +224

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are related, is it useful to have it as a combined CIDR string?


// StackType: The stackType for the subnets. If set to IPv4Only, new VMs in
// the subnet are assigned IPv4 addresses only. If set to DualStack, new VMs in
// the subnet can be assigned both IPv4 and IPv6 addresses. If not specified,
// IPv4Only is used. This field can be both set at resource creation time and
// updated using patch.
// GCP allows subnet stack types to be set independently, but, for simplicity,
// all subnets in the network will be created with the same stackType.
//
// +kubebuilder:default=IPv4Only
// +optional
StackType StackType `json:"stackType,omitempty"`
}

// LoadBalancerType defines the Load Balancer that should be created.
Expand Down Expand Up @@ -284,22 +327,6 @@ type SubnetSpec struct {
// +kubebuilder:default=PRIVATE_RFC_1918
// +optional
Purpose *string `json:"purpose,omitempty"`

// StackType: The stack type for the subnet. If set to IPV4_ONLY, new VMs in
// the subnet are assigned IPv4 addresses only. If set to IPV4_IPV6, new VMs in
// the subnet can be assigned both IPv4 and IPv6 addresses. If not specified,
// IPV4_ONLY is used. This field can be both set at resource creation time and
// updated using patch.
//
// Possible values:
// "IPV4_IPV6" - New VMs in this subnet can have both IPv4 and IPv6
// addresses.
// "IPV4_ONLY" - New VMs in this subnet will only be assigned IPv4 addresses.
// "IPV6_ONLY" - New VMs in this subnet will only be assigned IPv6 addresses.
// +kubebuilder:validation:Enum=IPV4_ONLY;IPV4_IPV6;IPV6_ONLY
// +kubebuilder:default=IPV4_ONLY
// +optional
StackType string `json:"stackType,omitempty"`
}

// String returns a string representation of the subnet.
Expand Down
2 changes: 2 additions & 0 deletions cloud/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ type ClusterGetter interface {
NetworkName() string
NetworkProject() string
IsSharedVpc() bool
StackType() infrav1.StackType
Ipv6Address() string
SkipFirewallRuleCreation() bool
Network() *infrav1.Network
AdditionalLabels() infrav1.Labels
Expand Down
116 changes: 91 additions & 25 deletions cloud/scope/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@ func (s *ClusterScope) IsSharedVpc() bool {
return s.NetworkProject() != s.Project()
}

// StackType returns the network stack type for the cluster.
func (s *ClusterScope) StackType() infrav1.StackType {
return s.GCPCluster.Spec.Network.StackType
}

// Ipv6Address returns the IPv6 address when one is provided in the spec and the cluster network is not IPv4 Only.
func (s *ClusterScope) Ipv6Address() string {
address := ""
if s.StackType() != infrav1.IPv4OnlyStackType {
address = s.GCPCluster.Spec.Network.Ipv6Address
}
return address
}

// Region returns the cluster region.
func (s *ClusterScope) Region() string {
return s.GCPCluster.Spec.Region
Expand Down Expand Up @@ -267,6 +281,12 @@ func (s *ClusterScope) NatRouterSpec() *compute.Router {
// SubnetSpecs returns google compute subnets spec.
func (s *ClusterScope) SubnetSpecs() []*compute.Subnetwork {
subnets := []*compute.Subnetwork{}

stackType := "IPV4_ONLY"
if s.StackType() == infrav1.DualStackType {
stackType = "IPV4_IPV6"
}

for _, subnetwork := range s.GCPCluster.Spec.Network.Subnets {
secondaryIPRanges := []*compute.SubnetworkSecondaryRange{}
for rangeName, secondaryCidrBlock := range subnetwork.SecondaryCidrBlocks {
Expand All @@ -283,7 +303,7 @@ func (s *ClusterScope) SubnetSpecs() []*compute.Subnetwork {
Network: s.NetworkLink(),
Purpose: ptr.Deref(subnetwork.Purpose, "PRIVATE_RFC_1918"),
Role: "ACTIVE",
StackType: subnetwork.StackType,
StackType: stackType,
})
}

Expand Down Expand Up @@ -342,13 +362,24 @@ func (s *ClusterScope) FirewallRulesSpec() []*compute.Firewall {

// ANCHOR: ClusterControlPlaneSpec

// AddressSpec returns google compute address spec.
func (s *ClusterScope) AddressSpec(lbname string) *compute.Address {
return &compute.Address{
// AddressSpecs returns google compute address specs.
func (s *ClusterScope) AddressSpecs(lbname string) []*compute.Address {
specs := []*compute.Address{{
Name: fmt.Sprintf("%s-%s", s.Name(), lbname),
AddressType: "EXTERNAL",
IpVersion: "IPV4",
}}

if s.StackType() == infrav1.DualStackType {
specs = append(specs, &compute.Address{
Name: fmt.Sprintf("%s-%s-%s", s.Name(), lbname, infrav1.DualStackAdditionalResourceSuffix),
AddressType: "EXTERNAL",
IpVersion: "IPV6",
Ipv6EndpointType: "NETLB",
})
}

return specs
}

// BackendServiceSpec returns google compute backend-service spec.
Expand All @@ -362,37 +393,72 @@ func (s *ClusterScope) BackendServiceSpec(lbname string) *compute.BackendService
}
}

// ForwardingRuleSpec returns google compute forwarding-rule spec.
func (s *ClusterScope) ForwardingRuleSpec(lbname string) *compute.ForwardingRule {
// ForwardingRuleSpecs returns a list of google compute forwarding-rule spec.
func (s *ClusterScope) ForwardingRuleSpecs(lbname string) []*compute.ForwardingRule {
port := int32(443)
if s.Cluster.Spec.ClusterNetwork.APIServerPort != 0 {
port = s.Cluster.Spec.ClusterNetwork.APIServerPort
}
portRange := fmt.Sprintf("%d-%d", port, port)
return &compute.ForwardingRule{
Name: fmt.Sprintf("%s-%s", s.Name(), lbname),
IPProtocol: "TCP",
LoadBalancingScheme: "EXTERNAL",
PortRange: portRange,
Labels: s.AdditionalLabels(),

forwardingRules := []*compute.ForwardingRule{
{
Name: fmt.Sprintf("%s-%s", s.Name(), lbname),
IPProtocol: "TCP",
LoadBalancingScheme: "EXTERNAL",
PortRange: portRange,
Labels: s.AdditionalLabels(),
},
}

if s.StackType() == infrav1.DualStackType {
forwardingRules = append(forwardingRules, &compute.ForwardingRule{
Name: fmt.Sprintf("%s-%s-%s", s.Name(), lbname, infrav1.DualStackAdditionalResourceSuffix),
IPProtocol: "TCP",
LoadBalancingScheme: "EXTERNAL",
PortRange: portRange,
Labels: s.AdditionalLabels(),
})
}

return forwardingRules
}

// HealthCheckSpec returns google compute health-check spec.
func (s *ClusterScope) HealthCheckSpec(lbname string) *compute.HealthCheck {
return &compute.HealthCheck{
Name: fmt.Sprintf("%s-%s", s.Name(), lbname),
Type: "HTTPS",
HttpsHealthCheck: &compute.HTTPSHealthCheck{
Port: 6443,
PortSpecification: "USE_FIXED_PORT",
RequestPath: "/readyz",
// HealthCheckSpecs returns a list of google compute health-check spec items.
func (s *ClusterScope) HealthCheckSpecs(lbname string) []*compute.HealthCheck {
specs := []*compute.HealthCheck{
{
Name: fmt.Sprintf("%s-%s", s.Name(), lbname),
Type: "HTTPS",
HttpsHealthCheck: &compute.HTTPSHealthCheck{
Port: 6443,
PortSpecification: "USE_FIXED_PORT",
RequestPath: "/readyz",
},
CheckIntervalSec: 10,
TimeoutSec: 5,
HealthyThreshold: 5,
UnhealthyThreshold: 3,
},
CheckIntervalSec: 10,
TimeoutSec: 5,
HealthyThreshold: 5,
UnhealthyThreshold: 3,
}

if s.StackType() == infrav1.DualStackType {
specs = append(specs, &compute.HealthCheck{
Name: fmt.Sprintf("%s-%s-%s", s.Name(), lbname, infrav1.DualStackAdditionalResourceSuffix),
Type: "HTTPS",
HttpsHealthCheck: &compute.HTTPSHealthCheck{
Port: 6443,
PortSpecification: "USE_FIXED_PORT",
RequestPath: "/readyz",
},
CheckIntervalSec: 10,
TimeoutSec: 5,
HealthyThreshold: 5,
UnhealthyThreshold: 3,
})
}

return specs
}

// InstanceGroupSpec returns google compute instance-group spec.
Expand Down
22 changes: 22 additions & 0 deletions cloud/scope/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,24 @@ func (m *MachineScope) InstanceNetworkInterfaceSpec() *compute.NetworkInterface
Name: "External NAT",
},
}

if m.ClusterGetter.StackType() == infrav1.DualStackType {
networkInterface.Ipv6AccessConfigs = []*compute.AccessConfig{
{
Type: "DIRECT_IPV6",
Name: "External IPv6",
},
}
}
}

if m.ClusterGetter.StackType() == infrav1.DualStackType {
accessType := "INTERNAL"
if m.GCPMachine.Spec.PublicIP != nil && *m.GCPMachine.Spec.PublicIP {
accessType = "EXTERNAL"
}
networkInterface.Ipv6AccessType = accessType
networkInterface.Ipv6Address = m.ClusterGetter.Ipv6Address()
}

if m.GCPMachine.Spec.Subnet != nil {
Expand Down Expand Up @@ -504,6 +522,10 @@ func (m *MachineScope) InstanceSpec(log logr.Logger) *compute.Instance {
instance.Scheduling.OnHostMaintenance = "TERMINATE"
}

if m.ClusterGetter.StackType() == infrav1.DualStackType {
instance.PrivateIpv6GoogleAccess = "INHERIT_FROM_SUBNETWORK"
}

return instance
}

Expand Down
22 changes: 21 additions & 1 deletion cloud/scope/managedcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,20 @@ func (s *ManagedClusterScope) IsSharedVpc() bool {
return s.NetworkProject() != s.Project()
}

// StackType returns the network stack type for the cluster.
func (s *ManagedClusterScope) StackType() infrav1.StackType {
return s.GCPManagedCluster.Spec.Network.StackType
}

// Ipv6Address returns the IPv6 address when one is provided in the spec and the cluster network is not IPv4 Only.
func (s *ManagedClusterScope) Ipv6Address() string {
address := ""
if s.StackType() != infrav1.IPv4OnlyStackType {
address = s.GCPManagedCluster.Spec.Network.Ipv6Address
}
return address
}

// NetworkLink returns the partial URL for the network.
func (s *ManagedClusterScope) NetworkLink() string {
return fmt.Sprintf("projects/%s/global/networks/%s", s.NetworkProject(), s.NetworkName())
Expand Down Expand Up @@ -253,6 +267,12 @@ func (s *ManagedClusterScope) NatRouterSpec() *compute.Router {
// SubnetSpecs returns google compute subnets spec.
func (s *ManagedClusterScope) SubnetSpecs() []*compute.Subnetwork {
subnets := []*compute.Subnetwork{}

stackType := "IPV4_ONLY"
if s.StackType() == infrav1.DualStackType {
stackType = "IPV4_IPV6"
}

for _, subnetwork := range s.GCPManagedCluster.Spec.Network.Subnets {
secondaryIPRanges := []*compute.SubnetworkSecondaryRange{}
for rangeName, secondaryCidrBlock := range subnetwork.SecondaryCidrBlocks {
Expand All @@ -269,7 +289,7 @@ func (s *ManagedClusterScope) SubnetSpecs() []*compute.Subnetwork {
Network: s.NetworkLink(),
Purpose: ptr.Deref(subnetwork.Purpose, "PRIVATE_RFC_1918"),
Role: "ACTIVE",
StackType: subnetwork.StackType,
StackType: stackType,
})
}

Expand Down
Loading