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

Switch default encapsulation to VXLAN, add annotations for autodetection #514

Merged
merged 6 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions src/k8s/pkg/k8sd/features/calico/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,10 @@ var (
calicoCtlImage = "ghcr.io/canonical/k8s-snap/calico/ctl"
// calicoCtlTag represents the tag to use for the calicoctl image.
calicoCtlTag = "v3.28.0"

// encapsulation represents the default encapsulation method to use for Calico.
encapsulation = "VXLAN"
berkayoz marked this conversation as resolved.
Show resolved Hide resolved

// apiServerEnabled determines if the Calico API server should be enabled.
apiServerEnabled = false
)
177 changes: 177 additions & 0 deletions src/k8s/pkg/k8sd/features/calico/internal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package calico

import (
"fmt"
"strings"

"github.com/canonical/k8s/pkg/k8sd/types"
)

const (
annotationAPIServerEnabled = "k8sd/v1alpha1/calico/apiserver-enabled"
annotationEncapsulationV4 = "k8sd/v1alpha1/calico/encapsulation-v4"
annotationEncapsulationV6 = "k8sd/v1alpha1/calico/encapsulation-v6"
annotationAutodetectionV4Firstfound = "k8sd/v1alpha1/calico/autodetection-v4/firstFound"
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
annotationAutodetectionV4Kubernetes = "k8sd/v1alpha1/calico/autodetection-v4/kubernetes"
annotationAutodetectionV4Interface = "k8sd/v1alpha1/calico/autodetection-v4/interface"
annotationAutodetectionV4SkipInterface = "k8sd/v1alpha1/calico/autodetection-v4/skipInterface"
annotationAutodetectionV4CanReach = "k8sd/v1alpha1/calico/autodetection-v4/canReach"
annotationAutodetectionV4Cidrs = "k8sd/v1alpha1/calico/autodetection-v4/cidrs"
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
annotationAutodetectionV6Firstfound = "k8sd/v1alpha1/calico/autodetection-v6/firstFound"
annotationAutodetectionV6Kubernetes = "k8sd/v1alpha1/calico/autodetection-v6/kubernetes"
annotationAutodetectionV6Interface = "k8sd/v1alpha1/calico/autodetection-v6/interface"
annotationAutodetectionV6SkipInterface = "k8sd/v1alpha1/calico/autodetection-v6/skipInterface"
annotationAutodetectionV6CanReach = "k8sd/v1alpha1/calico/autodetection-v6/canReach"
annotationAutodetectionV6Cidrs = "k8sd/v1alpha1/calico/autodetection-v6/cidrs"
)

type autodetection struct {
FirstFound bool `json:"firstFound,omitempty"`
Kubernetes string `json:"kubernetes,omitempty"`
InterfaceRegex string `json:"interface,omitempty"`
SkipInterfaceRegex string `json:"skipInterface,omitempty"`
CanReach string `json:"canReach,omitempty"`
CIDRs []string `json:"cidrs,omitempty"`
}

type config struct {
encapsulationV4 string
encapsulationV6 string
apiServerEnabled bool
autodetectionV4 *autodetection
autodetectionV6 *autodetection
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
}

func checkEncapsulation(v string) error {
switch v {
case "VXLAN", "IPIP", "IPIPCrossSubnet", "VXLANCrossSubnet", "None":
return nil
}
return fmt.Errorf("unsupported encapsulation type: %s", v)
}

func internalConfig(annotations types.Annotations) (config, error) {
config := config{
encapsulationV4: encapsulation,
encapsulationV6: encapsulation,
apiServerEnabled: apiServerEnabled,
}

if v, ok := annotations.Get(annotationAPIServerEnabled); ok {
config.apiServerEnabled = v == "true"
}

if v, ok := annotations.Get(annotationEncapsulationV4); ok {
if err := checkEncapsulation(v); err != nil {
return config, fmt.Errorf("invalid encapsulation-v4 annotation: %w", err)
}
config.encapsulationV4 = v
}

if v, ok := annotations.Get(annotationEncapsulationV6); ok {
if err := checkEncapsulation(v); err != nil {
return config, fmt.Errorf("invalid encapsulation-v6 annotation: %w", err)
}
config.encapsulationV6 = v
}

if v, ok := annotations.Get(annotationAutodetectionV4Firstfound); ok {
if config.autodetectionV4 != nil {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4Firstfound)
}
config.autodetectionV4 = &autodetection{
FirstFound: v == "true",
}
}
if v, ok := annotations.Get(annotationAutodetectionV4Kubernetes); ok {
if config.autodetectionV4 != nil {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4Kubernetes)
}
config.autodetectionV4 = &autodetection{
Kubernetes: v,
}
}
if v, ok := annotations.Get(annotationAutodetectionV4Interface); ok {
if config.autodetectionV4 != nil {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4Interface)
}
config.autodetectionV4 = &autodetection{
InterfaceRegex: v,
}
}
if v, ok := annotations.Get(annotationAutodetectionV4SkipInterface); ok {
if config.autodetectionV4 != nil {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4SkipInterface)
}
config.autodetectionV4 = &autodetection{
SkipInterfaceRegex: v,
}
}
if v, ok := annotations.Get(annotationAutodetectionV4CanReach); ok {
if config.autodetectionV4 != nil {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4CanReach)
}
config.autodetectionV4 = &autodetection{
CanReach: v,
}
}
if v, ok := annotations.Get(annotationAutodetectionV4Cidrs); ok {
if config.autodetectionV4 != nil {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4Cidrs)
}
config.autodetectionV4 = &autodetection{
CIDRs: strings.Split(v, ","),
}
}

if v, ok := annotations.Get(annotationAutodetectionV6Firstfound); ok {
if config.autodetectionV6 != nil {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6Firstfound)
}
config.autodetectionV6 = &autodetection{
FirstFound: v == "true",
}
}
if v, ok := annotations.Get(annotationAutodetectionV6Kubernetes); ok {
if config.autodetectionV6 != nil {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6Kubernetes)
}
config.autodetectionV6 = &autodetection{
Kubernetes: v,
}
}
if v, ok := annotations.Get(annotationAutodetectionV6Interface); ok {
if config.autodetectionV6 != nil {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6Interface)
}
config.autodetectionV6 = &autodetection{
InterfaceRegex: v,
}
}
if v, ok := annotations.Get(annotationAutodetectionV6SkipInterface); ok {
if config.autodetectionV6 != nil {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6SkipInterface)
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
}
config.autodetectionV6 = &autodetection{
SkipInterfaceRegex: v,
}
}
if v, ok := annotations.Get(annotationAutodetectionV6CanReach); ok {
if config.autodetectionV6 != nil {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6CanReach)
}
config.autodetectionV6 = &autodetection{
CanReach: v,
}
}
if v, ok := annotations.Get(annotationAutodetectionV6Cidrs); ok {
if config.autodetectionV6 != nil {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6Cidrs)
}
config.autodetectionV6 = &autodetection{
CIDRs: strings.Split(v, ","),
}
}

return config, nil
}
100 changes: 100 additions & 0 deletions src/k8s/pkg/k8sd/features/calico/internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package calico

import (
"testing"

. "github.com/onsi/gomega"
)

func TestInternalConfig(t *testing.T) {
for _, tc := range []struct {
name string
annotations map[string]string
expectedConfig config
expectError bool
}{
{
name: "Empty",
annotations: map[string]string{},
expectedConfig: config{
apiServerEnabled: false,
encapsulationV4: "VXLAN",
encapsulationV6: "VXLAN",
},
expectError: false,
},
{
name: "Valid",
annotations: map[string]string{
annotationAPIServerEnabled: "true",
annotationEncapsulationV4: "IPIP",
},
expectedConfig: config{
apiServerEnabled: true,
encapsulationV4: "IPIP",
encapsulationV6: "VXLAN",
},
expectError: false,
},
{
name: "InvalidEncapsulation",
annotations: map[string]string{
annotationEncapsulationV4: "Invalid",
},
expectError: true,
},
{
name: "InvalidAPIServerEnabled",
annotations: map[string]string{
annotationAPIServerEnabled: "invalid",
annotationEncapsulationV4: "VXLAN",
},
expectedConfig: config{
apiServerEnabled: false,
encapsulationV4: "VXLAN",
encapsulationV6: "VXLAN",
},
expectError: false,
},
{
name: "MultipleAutodetectionV4",
annotations: map[string]string{
annotationAutodetectionV4Firstfound: "true",
annotationAutodetectionV4Kubernetes: "true",
},
expectError: true,
},
{
name: "ValidAutodetectionCidrs",
annotations: map[string]string{
annotationAutodetectionV4Cidrs: "10.1.0.0/16,2001:0db8::/32",
},
expectedConfig: config{
apiServerEnabled: false,
encapsulationV4: "VXLAN",
encapsulationV6: "VXLAN",
autodetectionV4: &autodetection{
CIDRs: []string{"10.1.0.0/16", "2001:0db8::/32"},
},
autodetectionV6: nil,
},
expectError: false,
},
} {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)
annotations := make(map[string]string)
for k, v := range tc.annotations {
annotations[k] = v
}

parsed, err := internalConfig(annotations)
if tc.expectError {
g.Expect(err).To(HaveOccurred())
} else {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(parsed).To(Equal(tc.expectedConfig))
}
})
}
}
24 changes: 18 additions & 6 deletions src/k8s/pkg/k8sd/features/calico/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
// ApplyNetwork will deploy Calico when cfg.Enabled is true.
// ApplyNetwork will remove Calico when cfg.Enabled is false.
// ApplyNetwork returns an error if anything fails.
func ApplyNetwork(ctx context.Context, snap snap.Snap, cfg types.Network, _ types.Annotations) error {
func ApplyNetwork(ctx context.Context, snap snap.Snap, cfg types.Network, annotations types.Annotations) error {
m := snap.HelmClient()

if !cfg.GetEnabled() {
Expand All @@ -23,21 +23,28 @@ func ApplyNetwork(ctx context.Context, snap snap.Snap, cfg types.Network, _ type
return nil
}

config, err := internalConfig(annotations)
if err != nil {
return fmt.Errorf("failed to parse annotations: %w", err)
}

podIpPools := []map[string]any{}
ipv4PodCIDR, ipv6PodCIDR, err := utils.ParseCIDRs(cfg.GetPodCIDR())
if err != nil {
return fmt.Errorf("invalid pod cidr: %v", err)
}
if ipv4PodCIDR != "" {
podIpPools = append(podIpPools, map[string]any{
"name": "ipv4-ippool",
"cidr": ipv4PodCIDR,
"name": "ipv4-ippool",
"cidr": ipv4PodCIDR,
"encapsulation": config.encapsulationV4,
})
}
if ipv6PodCIDR != "" {
podIpPools = append(podIpPools, map[string]any{
"name": "ipv6-ippool",
"cidr": ipv6PodCIDR,
"name": "ipv6-ippool",
"cidr": ipv6PodCIDR,
"encapsulation": config.encapsulationV6,
})
}

Expand Down Expand Up @@ -65,10 +72,15 @@ func ApplyNetwork(ctx context.Context, snap snap.Snap, cfg types.Network, _ type
},
"installation": map[string]any{
"calicoNetwork": map[string]any{
"ipPools": podIpPools,
"ipPools": podIpPools,
"nodeAddressAutodetectionV4": config.autodetectionV4,
"nodeAddressAutodetectionV6": config.autodetectionV6,
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
},
"registry": imageRepo,
},
"apiServer": map[string]any{
"enabled": config.apiServerEnabled,
},
"serviceCIDRs": serviceCIDRs,
}

Expand Down
Loading