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 4 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"

// defaultEncapsulation represents the default defaultEncapsulation method to use for Calico.
defaultEncapsulation = "VXLAN"

// defaultAPIServerEnabled determines if the Calico API server should be enabled.
defaultAPIServerEnabled = false
)
11 changes: 7 additions & 4 deletions src/k8s/pkg/k8sd/features/calico/cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ func CleanupNetwork(ctx context.Context, snap snap.Snap) error {
return fmt.Errorf("failed to list network interfaces: %w", err)
}

// Compile the regular expression outside the loop
regex, err := regexp.Compile("^vxlan[-v6]*.calico|cali[a-f0-9]*|tunl[0-9]*$")
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("failed to compile regex pattern: %w", err)
}

// Find the interfaces created by Calico
for _, iface := range interfaces {
// Check if the interface name matches the regex pattern
// Adapted from MicroK8s' link removal hook:
// https://github.com/canonical/microk8s/blob/dff3627959d4774198000795a0a0afcaa003324b/microk8s-resources/default-hooks/remove.d/10-cni-link#L15
match, err := regexp.MatchString("^vxlan[-v6]*.calico|cali[a-f0-9]*|tunl[0-9]*$", iface.Name)
if err != nil {
return fmt.Errorf("failed to match regex pattern: %w", err)
}
match := regex.MatchString(iface.Name)
if match {
// Perform cleanup for Calico interface
if err := exec.CommandContext(ctx, "ip", "link", "delete", iface.Name).Run(); err != nil {
Expand Down
176 changes: 176 additions & 0 deletions src/k8s/pkg/k8sd/features/calico/internal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
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"
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"
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 config struct {
encapsulationV4 string
encapsulationV6 string
apiServerEnabled bool
autodetectionV4 map[string]any
autodetectionV6 map[string]any
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: defaultEncapsulation,
encapsulationV6: defaultEncapsulation,
apiServerEnabled: defaultAPIServerEnabled,
}

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
}

var autodetectionV4Key string
var autodetectionV4Value any

if v, ok := annotations.Get(annotationAutodetectionV4FirstFound); ok {
if autodetectionV4Key != "" {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4FirstFound)
}
autodetectionV4Key = "firstFound"
autodetectionV4Value = v == "true"
}
if v, ok := annotations.Get(annotationAutodetectionV4Kubernetes); ok {
if autodetectionV4Key != "" {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4Kubernetes)
}
autodetectionV4Key = "kubernetes"
autodetectionV4Value = v
}
if v, ok := annotations.Get(annotationAutodetectionV4Interface); ok {
if autodetectionV4Key != "" {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4Interface)
}
autodetectionV4Key = "interface"
autodetectionV4Value = v
}
if v, ok := annotations.Get(annotationAutodetectionV4SkipInterface); ok {
if autodetectionV4Key != "" {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4SkipInterface)
}
autodetectionV4Key = "skipInterface"
autodetectionV4Value = v
}
if v, ok := annotations.Get(annotationAutodetectionV4CanReach); ok {
if autodetectionV4Key != "" {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4CanReach)
}
autodetectionV4Key = "canReach"
autodetectionV4Value = v
}
if v, ok := annotations.Get(annotationAutodetectionV4CIDRs); ok {
if autodetectionV4Key != "" {
return config, fmt.Errorf("multiple autodetection-v4 annotations found: %s", annotationAutodetectionV4CIDRs)
}
autodetectionV4Key = "cidrs"
autodetectionV4Value = strings.Split(v, ",")
}

// If any annotation is set, pass the map to the config otherwise it's left nil
if autodetectionV4Key != "" {
config.autodetectionV4 = map[string]any{
autodetectionV4Key: autodetectionV4Value,
}
}

var autodetectionV6Key string
var autodetectionV6Value any

if v, ok := annotations.Get(annotationAutodetectionV6FirstFound); ok {
if autodetectionV6Key != "" {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6FirstFound)
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
}
autodetectionV6Key = "firstFound"
autodetectionV6Value = v == "true"
}
if v, ok := annotations.Get(annotationAutodetectionV6Kubernetes); ok {
if autodetectionV6Key != "" {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6Kubernetes)
}
autodetectionV6Key = "kubernetes"
autodetectionV6Value = v
}
if v, ok := annotations.Get(annotationAutodetectionV6Interface); ok {
if autodetectionV6Key != "" {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6Interface)
}
autodetectionV6Key = "interface"
autodetectionV6Value = v
}
if v, ok := annotations.Get(annotationAutodetectionV6SkipInterface); ok {
if autodetectionV6Key != "" {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6SkipInterface)
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
}
autodetectionV6Key = "skipInterface"
autodetectionV6Value = v
}
if v, ok := annotations.Get(annotationAutodetectionV6CanReach); ok {
if autodetectionV6Key != "" {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6CanReach)
}
autodetectionV6Key = "canReach"
autodetectionV6Value = v
}
if v, ok := annotations.Get(annotationAutodetectionV6CIDRs); ok {
if autodetectionV6Key != "" {
return config, fmt.Errorf("multiple autodetection-v6 annotations found: %s", annotationAutodetectionV6CIDRs)
}
autodetectionV6Key = "cidrs"
autodetectionV6Value = strings.Split(v, ",")
}

// If any annotation is set, pass the map to the config otherwise it's left nil
if autodetectionV6Key != "" {
config.autodetectionV6 = map[string]any{
autodetectionV6Key: autodetectionV6Value,
}
}

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: map[string]any{
"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))
}
})
}
}
28 changes: 23 additions & 5 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 @@ -69,9 +76,20 @@ func ApplyNetwork(ctx context.Context, snap snap.Snap, cfg types.Network, _ type
},
"registry": imageRepo,
},
"apiServer": map[string]any{
"enabled": config.apiServerEnabled,
},
"serviceCIDRs": serviceCIDRs,
}

if config.autodetectionV4 != nil {
values["installation"].(map[string]any)["calicoNetwork"].(map[string]any)["nodeAddressAutodetectionV4"] = config.autodetectionV4
berkayoz marked this conversation as resolved.
Show resolved Hide resolved
}

if config.autodetectionV6 != nil {
values["installation"].(map[string]any)["calicoNetwork"].(map[string]any)["nodeAddressAutodetectionV6"] = config.autodetectionV6
}

if _, err := m.Apply(ctx, chartCalico, helm.StatePresent, values); err != nil {
return fmt.Errorf("failed to enable network: %w", err)
}
Expand Down
Loading