diff --git a/build-scripts/patches/moonray/0001-Moonray.patch b/build-scripts/patches/moonray/0001-Moonray.patch index 18fcdca80..8bceec131 100644 --- a/build-scripts/patches/moonray/0001-Moonray.patch +++ b/build-scripts/patches/moonray/0001-Moonray.patch @@ -1,6 +1,6 @@ -From 1a16f5952ebda874a37556cfdca410a8e7e00ae6 Mon Sep 17 00:00:00 2001 -From: Benjamin Schimke -Date: Wed, 5 Jun 2024 09:48:57 +0200 +From 4f26e12832ef73b5c76a59cf3209d4f1f5b0aa3b Mon Sep 17 00:00:00 2001 +From: k8s-bot +Date: Thu, 6 Jun 2024 10:34:52 +0200 Subject: [PATCH] Moonray --- @@ -19,7 +19,7 @@ index aeb1729..184f2ce 100644 import ( diff --git a/src/k8s/pkg/k8sd/features/implementation_moonray.go b/src/k8s/pkg/k8sd/features/implementation_moonray.go -index f8abf25..cdf39fa 100644 +index dece19d..5933221 100644 --- a/src/k8s/pkg/k8sd/features/implementation_moonray.go +++ b/src/k8s/pkg/k8sd/features/implementation_moonray.go @@ -1,5 +1,3 @@ diff --git a/k8s/manifests/charts/tigera-operator-v3.28.0.tgz b/k8s/manifests/charts/tigera-operator-v3.28.0.tgz new file mode 100644 index 000000000..070a7113e Binary files /dev/null and b/k8s/manifests/charts/tigera-operator-v3.28.0.tgz differ diff --git a/src/k8s/pkg/client/kubernetes/pods.go b/src/k8s/pkg/client/kubernetes/pods.go index 095b60155..9291ca281 100644 --- a/src/k8s/pkg/client/kubernetes/pods.go +++ b/src/k8s/pkg/client/kubernetes/pods.go @@ -32,3 +32,12 @@ func (c *Client) IsPodReady(ctx context.Context, name, namespace string, listOpt return false, nil } + +// ListPods lists all pods in a namespace. +func (c *Client) ListPods(ctx context.Context, namespace string, listOptions metav1.ListOptions) ([]corev1.Pod, error) { + pods, err := c.CoreV1().Pods(namespace).List(ctx, listOptions) + if err != nil { + return nil, fmt.Errorf("failed to list pods: %w", err) + } + return pods.Items, nil +} diff --git a/src/k8s/pkg/k8sd/features/calico/chart.go b/src/k8s/pkg/k8sd/features/calico/chart.go new file mode 100644 index 000000000..a645cd894 --- /dev/null +++ b/src/k8s/pkg/k8sd/features/calico/chart.go @@ -0,0 +1,33 @@ +package calico + +import ( + "path" + + "github.com/canonical/k8s/pkg/client/helm" +) + +var ( + // chartCalico represents manifests to deploy Calico. + chartCalico = helm.InstallableChart{ + Name: "ck-network", + Namespace: "tigera-operator", + ManifestPath: path.Join("charts", "tigera-operator-v3.28.0.tgz"), + } + + // tigeraOperatorRepo represents the repo to fetch the tigera-operator image for calico. + // Note: Tigera is the company behind Calico and the tigera-operator is the operator for Calico. + // TODO: use ROCKs instead of upstream + tigeraOperatorRegistry = "quay.io" + + // tigeraOperatorImage represents the image to fetch for calico. + tigeraOperatorImage = "tigera/operator" + + // tigeraOperatorVersion is the version to use for the tigera-operator image. + tigeraOperatorVersion = "v1.34.0" + + // calicoCtlImage represents the image to fetch for calicoctl. + // TODO: use ROCKs instead of upstream + calicoCtlImage = "docker.io/calico/ctl" + // calicoCtlTag represents the tag to use for the calicoctl image. + calicoCtlTag = "v3.28.0" +) diff --git a/src/k8s/pkg/k8sd/features/calico/network.go b/src/k8s/pkg/k8sd/features/calico/network.go new file mode 100644 index 000000000..45c9a1333 --- /dev/null +++ b/src/k8s/pkg/k8sd/features/calico/network.go @@ -0,0 +1,79 @@ +package calico + +import ( + "context" + "fmt" + + "github.com/canonical/k8s/pkg/client/helm" + "github.com/canonical/k8s/pkg/k8sd/types" + "github.com/canonical/k8s/pkg/snap" + "github.com/canonical/k8s/pkg/utils" +) + +// 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 { + m := snap.HelmClient() + + if !cfg.GetEnabled() { + if _, err := m.Apply(ctx, chartCalico, helm.StateDeleted, nil); err != nil { + return fmt.Errorf("failed to uninstall network: %w", err) + } + return nil + } + + 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, + }) + } + if ipv6PodCIDR != "" { + podIpPools = append(podIpPools, map[string]any{ + "name": "ipv6-ippool", + "cidr": ipv6PodCIDR, + }) + } + + serviceCIDRs := []string{} + ipv4ServiceCIDR, ipv6ServiceCIDR, err := utils.ParseCIDRs(cfg.GetPodCIDR()) + if err != nil { + return fmt.Errorf("invalid service cidr: %v", err) + } + if ipv4ServiceCIDR != "" { + serviceCIDRs = append(serviceCIDRs, ipv4ServiceCIDR) + } + if ipv6ServiceCIDR != "" { + serviceCIDRs = append(serviceCIDRs, ipv6ServiceCIDR) + } + + values := map[string]any{ + "tigeraOperator": map[string]any{ + "registry": tigeraOperatorRegistry, + "image": tigeraOperatorImage, + "version": tigeraOperatorVersion, + }, + "calicoctl": map[string]any{ + "image": calicoCtlImage, + "tag": calicoCtlTag, + }, + "installation": map[string]any{ + "calicoNetwork": map[string]any{ + "ipPools": podIpPools, + }, + }, + "serviceCIDRs": serviceCIDRs, + } + + if _, err := m.Apply(ctx, chartCalico, helm.StatePresent, values); err != nil { + return fmt.Errorf("failed to enable network: %w", err) + } + + return nil +} diff --git a/src/k8s/pkg/k8sd/features/calico/status.go b/src/k8s/pkg/k8sd/features/calico/status.go new file mode 100644 index 000000000..d82c965c3 --- /dev/null +++ b/src/k8s/pkg/k8sd/features/calico/status.go @@ -0,0 +1,49 @@ +package calico + +import ( + "context" + "fmt" + + "github.com/canonical/k8s/pkg/snap" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CheckNetwork checks the status of the Calico pods in the Kubernetes cluster. +// It verifies if all the Calico pods in the "tigera-operator" namespace are ready. +// If any pod is not ready, it returns false. Otherwise, it returns true. +func CheckNetwork(ctx context.Context, snap snap.Snap) (bool, error) { + client, err := snap.KubernetesClient("calico-system") + if err != nil { + return false, fmt.Errorf("failed to create kubernetes client: %w", err) + } + + operatorReady, err := client.IsPodReady(ctx, "kube-system", "tigera-operator", metav1.ListOptions{}) + if err != nil { + return false, fmt.Errorf("failed to get calico pods: %w", err) + } + if !operatorReady { + return false, nil + } + + calicoPods, err := client.ListPods(ctx, "calico-system", metav1.ListOptions{}) + if err != nil { + return false, fmt.Errorf("failed to get calico pods: %w", err) + } + calicoApiserverPods, err := client.ListPods(ctx, "calico-apiserver", metav1.ListOptions{}) + if err != nil { + return false, fmt.Errorf("failed to get calico-apiserver pods: %w", err) + } + + for _, pod := range append(calicoPods, calicoApiserverPods...) { + isReady, err := client.IsPodReady(ctx, pod.Name, "calico-system", metav1.ListOptions{}) + if err != nil { + return false, fmt.Errorf("failed to check if pod %q is ready: %w", pod.Name, err) + } + if !isReady { + return false, nil + } + } + + return true, nil +} diff --git a/src/k8s/pkg/k8sd/features/cilium/network.go b/src/k8s/pkg/k8sd/features/cilium/network.go index 68b310505..c51ff8b41 100644 --- a/src/k8s/pkg/k8sd/features/cilium/network.go +++ b/src/k8s/pkg/k8sd/features/cilium/network.go @@ -4,8 +4,6 @@ import ( "context" "fmt" "log" - "net" - "strings" "github.com/canonical/k8s/pkg/client/helm" "github.com/canonical/k8s/pkg/k8sd/types" @@ -29,25 +27,9 @@ func ApplyNetwork(ctx context.Context, snap snap.Snap, cfg types.Network, _ type return nil } - clusterCIDRs := strings.Split(cfg.GetPodCIDR(), ",") - if v := len(clusterCIDRs); v != 1 && v != 2 { - return fmt.Errorf("invalid kube-proxy --cluster-cidr value: %v", clusterCIDRs) - } - - var ( - ipv4CIDR string - ipv6CIDR string - ) - for _, cidr := range clusterCIDRs { - _, parsed, err := net.ParseCIDR(cidr) - switch { - case err != nil: - return fmt.Errorf("failed to parse cidr: %w", err) - case parsed.IP.To4() != nil: - ipv4CIDR = cidr - default: - ipv6CIDR = cidr - } + ipv4CIDR, ipv6CIDR, err := utils.ParseCIDRs(cfg.GetPodCIDR()) + if err != nil { + return fmt.Errorf("invalid kube-proxy --cluster-cidr value: %v", err) } values := map[string]any{ diff --git a/src/k8s/pkg/k8sd/features/implementation_moonray.go b/src/k8s/pkg/k8sd/features/implementation_moonray.go index f8abf250d..dece19d01 100644 --- a/src/k8s/pkg/k8sd/features/implementation_moonray.go +++ b/src/k8s/pkg/k8sd/features/implementation_moonray.go @@ -3,6 +3,7 @@ package features import ( + "github.com/canonical/k8s/pkg/k8sd/features/calico" "github.com/canonical/k8s/pkg/k8sd/features/cilium" "github.com/canonical/k8s/pkg/k8sd/features/coredns" "github.com/canonical/k8s/pkg/k8sd/features/localpv" @@ -13,7 +14,7 @@ import ( // TODO: Replace default by moonray. var Implementation Interface = &implementation{ applyDNS: coredns.ApplyDNS, - applyNetwork: cilium.ApplyNetwork, + applyNetwork: calico.ApplyNetwork, applyLoadBalancer: cilium.ApplyLoadBalancer, applyIngress: cilium.ApplyIngress, applyGateway: cilium.ApplyGateway, @@ -24,6 +25,6 @@ var Implementation Interface = &implementation{ // StatusChecks implements the Canonical Kubernetes moonray feature status checks. // TODO: Replace default by moonray. var StatusChecks StatusInterface = &statusChecks{ - checkNetwork: cilium.CheckNetwork, + checkNetwork: calico.CheckNetwork, checkDNS: coredns.CheckDNS, } diff --git a/src/k8s/pkg/utils/cidr.go b/src/k8s/pkg/utils/cidr.go index 1a2ae0919..124f75aea 100644 --- a/src/k8s/pkg/utils/cidr.go +++ b/src/k8s/pkg/utils/cidr.go @@ -99,5 +99,29 @@ func ParseAddressString(address string, port int64) (string, error) { } return util.CanonicalNetworkAddress(address, port), nil +} + +// ParseCIDRs parses the given CIDR string and returns the respective IPv4 and IPv6 CIDRs. +func ParseCIDRs(CIDRstring string) (string, string, error) { + clusterCIDRs := strings.Split(CIDRstring, ",") + if v := len(clusterCIDRs); v != 1 && v != 2 { + return "", "", fmt.Errorf("invalid CIDR list: %v", clusterCIDRs) + } + var ( + ipv4CIDR string + ipv6CIDR string + ) + for _, cidr := range clusterCIDRs { + _, parsed, err := net.ParseCIDR(cidr) + switch { + case err != nil: + return "", "", fmt.Errorf("failed to parse cidr: %w", err) + case parsed.IP.To4() != nil: + ipv4CIDR = cidr + default: + ipv6CIDR = cidr + } + } + return ipv4CIDR, ipv6CIDR, nil } diff --git a/src/k8s/pkg/utils/cidr_test.go b/src/k8s/pkg/utils/cidr_test.go index cfee353e3..3dfbe3941 100644 --- a/src/k8s/pkg/utils/cidr_test.go +++ b/src/k8s/pkg/utils/cidr_test.go @@ -112,3 +112,55 @@ func TestParseAddressString(t *testing.T) { }) } } + +func TestParseCIDRs(t *testing.T) { + RegisterTestingT(t) + + testCases := []struct { + input string + expectedIPv4 string + expectedIPv6 string + expectedErr bool + }{ + { + input: "192.168.1.0/24", + expectedIPv4: "192.168.1.0/24", + expectedIPv6: "", + }, + { + input: "2001:db8::/32", + expectedIPv4: "", + expectedIPv6: "2001:db8::/32", + }, + { + input: "192.168.1.0/24,2001:db8::/32", + expectedIPv4: "192.168.1.0/24", + expectedIPv6: "2001:db8::/32", + }, + { + input: "192.168.1.0/24,invalidCIDR", + expectedIPv4: "", + expectedIPv6: "", + expectedErr: true, + }, + { + input: "192.168.1.0/24,2001:db8::/32,10.0.0.0/8", + expectedIPv4: "", + expectedIPv6: "", + expectedErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.input, func(t *testing.T) { + ipv4CIDR, ipv6CIDR, err := utils.ParseCIDRs(tc.input) + if tc.expectedErr { + Expect(err).To(HaveOccurred()) + } else { + Expect(err).To(BeNil()) + Expect(ipv4CIDR).To(Equal(tc.expectedIPv4)) + Expect(ipv6CIDR).To(Equal(tc.expectedIPv6)) + } + }) + } +}