From 3c25c3d116a08ebfa8147683b95e2a18dc994f25 Mon Sep 17 00:00:00 2001 From: Andrew Gouin Date: Tue, 18 Jun 2024 13:43:46 -0600 Subject: [PATCH] Allow version upgrades for specific containers and initContainers (#425) * Allow version upgrades for specific containers and initContainers * improve test * lint * better check for changes * Use go 1.22 for CI * Fix manifests --- .github/workflows/go.yaml | 4 +- .github/workflows/manifests.yaml | 7 +- api/v1/cosmosfullnode_types.go | 8 ++ api/v1/zz_generated.deepcopy.go | 18 +++- .../cosmos.strange.love_cosmosfullnodes.yaml | 12 +++ internal/fullnode/build_pods.go | 23 +++++ internal/fullnode/pod_builder.go | 22 ++--- internal/fullnode/pod_builder_test.go | 89 +++++++++++++++++-- 8 files changed, 158 insertions(+), 25 deletions(-) diff --git a/.github/workflows/go.yaml b/.github/workflows/go.yaml index 8e7b3d5c..1776a067 100644 --- a/.github/workflows/go.yaml +++ b/.github/workflows/go.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '>=1.20.2' + go-version: '>=1.22' - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: @@ -29,6 +29,6 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '>=1.20.2' + go-version: '>=1.22' - name: unit tests run: make test diff --git a/.github/workflows/manifests.yaml b/.github/workflows/manifests.yaml index fd96a968..c263ee16 100644 --- a/.github/workflows/manifests.yaml +++ b/.github/workflows/manifests.yaml @@ -14,9 +14,8 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '>=1.20.2' + go-version: '>=1.22' - run: make generate manifests - - uses: CatChen/check-git-status-action@v1 - with: - fail-if-not-clean: true + - name: Ensure no changes + run: git diff --exit-code diff --git a/api/v1/cosmosfullnode_types.go b/api/v1/cosmosfullnode_types.go index a4c3f11d..0f2984c8 100644 --- a/api/v1/cosmosfullnode_types.go +++ b/api/v1/cosmosfullnode_types.go @@ -559,6 +559,14 @@ type ChainVersion struct { // The docker image for this version in "repository:tag" format. E.g. busybox:latest. Image string `json:"image"` + // Version overrides for initContainers of the fullnode/sentry pods. + // +optional + InitContainers map[string]string `json:"initContainers"` + + // Version overrides for containers of the fullnode/sentry pods. + // +optional + Containers map[string]string `json:"containers"` + // Determines if the node should forcefully halt at the upgrade height. // +optional SetHaltHeight bool `json:"setHaltHeight,omitempty"` diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index f709859c..d4f5a544 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -106,7 +106,9 @@ func (in *ChainSpec) DeepCopyInto(out *ChainSpec) { if in.Versions != nil { in, out := &in.Versions, &out.Versions *out = make([]ChainVersion, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.AdditionalInitArgs != nil { in, out := &in.AdditionalInitArgs, &out.AdditionalInitArgs @@ -133,6 +135,20 @@ func (in *ChainSpec) DeepCopy() *ChainSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ChainVersion) DeepCopyInto(out *ChainVersion) { *out = *in + if in.InitContainers != nil { + in, out := &in.InitContainers, &out.InitContainers + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChainVersion. diff --git a/config/crd/bases/cosmos.strange.love_cosmosfullnodes.yaml b/config/crd/bases/cosmos.strange.love_cosmosfullnodes.yaml index 457e90d2..528e81a7 100644 --- a/config/crd/bases/cosmos.strange.love_cosmosfullnodes.yaml +++ b/config/crd/bases/cosmos.strange.love_cosmosfullnodes.yaml @@ -352,6 +352,12 @@ spec: If not provided, the operator will not upgrade the chain, and will use the image specified in the pod spec. items: properties: + containers: + additionalProperties: + type: string + description: Version overrides for containers of the fullnode/sentry + pods. + type: object height: description: The block height when this version should be applied. @@ -361,6 +367,12 @@ spec: description: The docker image for this version in "repository:tag" format. E.g. busybox:latest. type: string + initContainers: + additionalProperties: + type: string + description: Version overrides for initContainers of the + fullnode/sentry pods. + type: object setHaltHeight: description: Determines if the node should forcefully halt at the upgrade height. diff --git a/internal/fullnode/build_pods.go b/internal/fullnode/build_pods.go index f86371c9..8b63aec3 100644 --- a/internal/fullnode/build_pods.go +++ b/internal/fullnode/build_pods.go @@ -38,6 +38,28 @@ func BuildPods(crd *cosmosv1.CosmosFullNode, cksums ConfigChecksums) ([]diff.Res return pods, nil } +func setChainContainerImages(pod *corev1.Pod, v *cosmosv1.ChainVersion) { + setChainContainerImage(pod, v.Image) + + for name, image := range v.InitContainers { + for i := range pod.Spec.InitContainers { + if pod.Spec.InitContainers[i].Name == name { + pod.Spec.InitContainers[i].Image = image + break + } + } + } + + for name, image := range v.Containers { + for i := range pod.Spec.Containers { + if pod.Spec.Containers[i].Name == name { + pod.Spec.Containers[i].Image = image + break + } + } + } +} + func setChainContainerImage(pod *corev1.Pod, image string) { for i := range pod.Spec.Containers { if pod.Spec.Containers[i].Name == mainContainer { @@ -45,6 +67,7 @@ func setChainContainerImage(pod *corev1.Pod, image string) { break } } + for i := range pod.Spec.InitContainers { if pod.Spec.InitContainers[i].Name == chainInitContainer { pod.Spec.InitContainers[i].Image = image diff --git a/internal/fullnode/pod_builder.go b/internal/fullnode/pod_builder.go index 1112e1d3..f2a08e6d 100644 --- a/internal/fullnode/pod_builder.go +++ b/internal/fullnode/pod_builder.go @@ -181,22 +181,28 @@ func podReadinessProbes(crd *cosmosv1.CosmosFullNode) []*corev1.Probe { func (b PodBuilder) Build() (*corev1.Pod, error) { pod := b.pod.DeepCopy() + if err := kube.ApplyStrategicMergePatch(pod, podPatch(b.crd)); err != nil { + return nil, err + } + if len(b.crd.Spec.ChainSpec.Versions) > 0 { instanceHeight := uint64(0) if height, ok := b.crd.Status.Height[pod.Name]; ok { instanceHeight = height } - var image string - for _, version := range b.crd.Spec.ChainSpec.Versions { - if instanceHeight < version.UpgradeHeight { + var vrs *cosmosv1.ChainVersion + for _, v := range b.crd.Spec.ChainSpec.Versions { + v := v + if instanceHeight < v.UpgradeHeight { break } - image = version.Image + vrs = &v } - if image != "" { - setChainContainerImage(pod, image) + if vrs != nil { + setChainContainerImages(pod, vrs) } } + if o, ok := b.crd.Spec.InstanceOverrides[pod.Name]; ok { if o.DisableStrategy != nil { return nil, nil @@ -206,10 +212,6 @@ func (b PodBuilder) Build() (*corev1.Pod, error) { } } - if err := kube.ApplyStrategicMergePatch(pod, podPatch(b.crd)); err != nil { - return nil, err - } - kube.NormalizeMetadata(&pod.ObjectMeta) return pod, nil } diff --git a/internal/fullnode/pod_builder_test.go b/internal/fullnode/pod_builder_test.go index e7f6f4b9..8ceb7cc4 100644 --- a/internal/fullnode/pod_builder_test.go +++ b/internal/fullnode/pod_builder_test.go @@ -606,27 +606,100 @@ gaiad start --home /home/operator/cosmos` } crd.Spec.ChainSpec.Versions = []cosmosv1.ChainVersion{ { - UpgradeHeight: 1, + UpgradeHeight: 0, Image: "image:v1.0.0", }, { UpgradeHeight: 100, Image: "image:v2.0.0", }, + { + UpgradeHeight: 300, + Image: "image:v3.0.0", + InitContainers: map[string]string{ + "chain-init": "chain-init:v3.0.0", + "new-init": "new-init:v3.0.0", + }, + Containers: map[string]string{ + "new-sidecar": "new-sidecar:v3.0.0", + }, + }, + { + UpgradeHeight: 400, + Image: "image:v4.0.0", + }, + } + + crd.Status.Height = map[string]uint64{ + "osmosis-0": 1, + "osmosis-1": 150, + "osmosis-2": 300, } builder := NewPodBuilder(&crd) - pod, err := builder.WithOrdinal(0).Build() + + pod0, err := builder.WithOrdinal(0).Build() require.NoError(t, err) - containers := lo.SliceToMap(pod.Spec.Containers, func(c corev1.Container) (string, corev1.Container) { return c.Name, c }) + containers := lo.SliceToMap(pod0.Spec.Containers, func(c corev1.Container) (string, corev1.Container) { return c.Name, c }) require.ElementsMatch(t, []string{"node", "new-sidecar", "healthcheck", "version-check-interval"}, lo.Keys(containers)) - }) - test.HasTypeLabel(t, func(crd cosmosv1.CosmosFullNode) []map[string]string { - builder := NewPodBuilder(&crd) - pod, _ := builder.WithOrdinal(5).Build() - return []map[string]string{pod.Labels} + initContainers := lo.SliceToMap(pod0.Spec.InitContainers, func(c corev1.Container) (string, corev1.Container) { return c.Name, c }) + require.ElementsMatch(t, []string{"chain-init", "new-init", "genesis-init", "addrbook-init", "config-merge", "version-check", "clean-init"}, lo.Keys(initContainers)) + + require.Equal(t, "osmosis-0", pod0.Name) + + require.Equal(t, "node", pod0.Spec.Containers[0].Name) + require.Equal(t, "image:v1.0.0", pod0.Spec.Containers[0].Image) + + require.Equal(t, "chain-init", pod0.Spec.InitContainers[1].Name) + require.Equal(t, "image:v1.0.0", pod0.Spec.InitContainers[1].Image) + + pod1, err := builder.WithOrdinal(1).Build() + require.NoError(t, err) + + require.Equal(t, "osmosis-1", pod1.Name) + + require.Equal(t, "node", pod1.Spec.Containers[0].Name) + require.Equal(t, "image:v2.0.0", pod1.Spec.Containers[0].Image) + + require.Equal(t, "chain-init", pod1.Spec.InitContainers[1].Name) + require.Equal(t, "image:v2.0.0", pod1.Spec.InitContainers[1].Image) + + pod2, err := builder.WithOrdinal(2).Build() + require.NoError(t, err) + + require.Equal(t, "osmosis-2", pod2.Name) + + require.Equal(t, "node", pod2.Spec.Containers[0].Name) + require.Equal(t, "image:v3.0.0", pod2.Spec.Containers[0].Image) + + require.Equal(t, "new-sidecar", pod2.Spec.Containers[1].Name) + require.Equal(t, "new-sidecar:v3.0.0", pod2.Spec.Containers[1].Image) + + require.Equal(t, "chain-init", pod2.Spec.InitContainers[1].Name) + require.Equal(t, "chain-init:v3.0.0", pod2.Spec.InitContainers[1].Image) + + require.Equal(t, "new-init", pod2.Spec.InitContainers[2].Name) + require.Equal(t, "new-init:v3.0.0", pod2.Spec.InitContainers[2].Image) + + crd.Status.Height["osmosis-2"] = 400 + pod2, err = builder.WithOrdinal(2).Build() + require.NoError(t, err) + + require.Equal(t, "osmosis-2", pod2.Name) + + require.Equal(t, "node", pod2.Spec.Containers[0].Name) + require.Equal(t, "image:v4.0.0", pod2.Spec.Containers[0].Image) + + require.Equal(t, "new-sidecar", pod2.Spec.Containers[1].Name) + require.Equal(t, "new-sidecar:latest", pod2.Spec.Containers[1].Image) + + require.Equal(t, "chain-init", pod2.Spec.InitContainers[1].Name) + require.Equal(t, "image:v4.0.0", pod2.Spec.InitContainers[1].Image) + + require.Equal(t, "new-init", pod2.Spec.InitContainers[2].Name) + require.Equal(t, "new-init:latest", pod2.Spec.InitContainers[2].Image) }) }