elemental-operator/tests/e2e/upgrades_test.go

311 lines
9.2 KiB
Go

/*
Copyright © 2022 - 2025 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e_test
import (
"fmt"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"
kubectl "github.com/rancher-sandbox/ele-testhelpers/kubectl"
elementalv1 "github.com/rancher/elemental-operator/api/v1beta1"
fleetv1 "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
upgradev1 "github.com/rancher/system-upgrade-controller/pkg/apis/upgrade.cattle.io/v1"
"github.com/rancher/elemental-operator/tests/catalog"
)
// We are using the RT channel not to overlap with the default one
const channelImage = `"registry.suse.com/rancher/elemental-teal-rt-channel:1.3.4"`
func getPlan(s string) (up *upgradev1.Plan, err error) {
up = &upgradev1.Plan{}
err = kubectl.GetObject(s, cattleSystemNamespace, "plan", up)
return
}
func waitTestChannelPopulate(k *kubectl.Kubectl, mr *elementalv1.ManagedOSVersionChannel, name string, image, version string) {
EventuallyWithOffset(1, func() error {
return k.ApplyJSON("", name, mr)
}, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred())
EventuallyWithOffset(1, func() string {
r, err := kubectl.GetData(fleetNamespace, "ManagedOSVersion", version, `jsonpath={.spec.metadata.upgradeImage}`)
if err != nil {
fmt.Println(err)
}
return string(r)
}, 6*time.Minute, 2*time.Second).Should(
Equal(image),
)
}
func upgradePod(k *kubectl.Kubectl) string {
pods, err := k.GetPodNames(cattleSystemNamespace, "upgrade.cattle.io/controller=system-upgrade-controller")
ExpectWithOffset(1, err).ToNot(HaveOccurred())
podName := ""
for _, p := range pods {
if strings.Contains(p, "apply-os-upgrader-update-osversion") {
podName = p
}
}
return podName
}
func checkUpgradePod(k *kubectl.Kubectl, env, image, command, args, mm types.GomegaMatcher) {
By("checking upgrade pod")
// Wait for the upgrade pod to appear
k.EventuallyPodMatch(
cattleSystemNamespace,
"upgrade.cattle.io/controller=system-upgrade-controller",
3*time.Minute, 2*time.Second,
ContainElement(ContainSubstring("apply-os-upgrader-update-osversion-on-operator-e2e")),
)
podName := upgradePod(k)
pod := &corev1.Pod{}
err := kubectl.GetObject(podName, cattleSystemNamespace, "pods", pod)
ExpectWithOffset(1, err).ToNot(HaveOccurred())
container := pod.Spec.Containers[0]
envs := []string{}
for _, e := range container.Env {
envs = append(envs, fmt.Sprintf("%s=%s", e.Name, e.Value))
}
mounts := []string{}
for _, e := range container.VolumeMounts {
mounts = append(mounts, fmt.Sprintf("%s=%s", e.Name, e.MountPath))
}
ExpectWithOffset(1, container.Name).To(Equal("upgrade"))
ExpectWithOffset(1, envs).To(env)
ExpectWithOffset(1, container.Image).To(image)
ExpectWithOffset(1, container.Command).To(command)
ExpectWithOffset(1, container.Args).To(args)
ExpectWithOffset(1, mounts).To(mm)
}
var _ = Describe("ManagedOSImage Upgrade e2e tests", Ordered, func() {
k := kubectl.New()
Context("Using ManagedOSVersion reference", func() {
osImage := "update-osversion"
osVersion := "osversion"
AfterEach(func() {
k.Delete("managedosimage", "-n", fleetNamespace, osImage)
k.Delete("managedosversion", "-n", fleetNamespace, osVersion)
// Plans do not successfully apply, so nodes stays in cordoned state.
// uncordon them so tests will keep running
nodeName, err := kubectl.Run("get", "nodes", "-o", "jsonpath='{.items[0].metadata.name}'")
Expect(err).ToNot(HaveOccurred())
kubectl.Run("uncordon", nodeName)
k.Delete("jobs", "--all", "--wait", "-n", fleetNamespace)
k.Delete("plans", "--all", "--wait", "-n", fleetNamespace)
k.Delete("bundles", "--all", "--wait", "-n", fleetNamespace)
k.Delete("managedosversionchannel", "--all", "--wait", "-n", fleetNamespace)
// delete dangling upgrade pods
pods, err := k.GetPodNames(cattleSystemNamespace, "upgrade.cattle.io/controller=system-upgrade-controller")
if err != nil {
fmt.Println(err)
}
fmt.Println(pods)
applyPods := []string{}
for _, p := range pods {
if !strings.HasPrefix(p, "system-upgrade-controller") {
applyPods = append(applyPods, p)
}
if strings.Contains(p, "apply-os-upgrader") {
By("deleting " + p)
k.Delete("pod", "-n", cattleSystemNamespace, "--wait", "--force", p)
err = k.WaitForPodDelete(cattleSystemNamespace, p)
Expect(err).ToNot(HaveOccurred())
}
}
})
createsCorrectPlan := func(meta map[string]runtime.RawExtension, c *upgradev1.ContainerSpec, m types.GomegaMatcher) {
By("creating a new ManagedOSVersion")
ov := catalog.NewManagedOSVersion(
fleetNamespace, osVersion, "v1.0", "0.0.0",
meta,
c,
)
EventuallyWithOffset(1, func() error {
return k.ApplyJSON("", osVersion, ov)
}, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred())
By("creating a new ManagedOSImage")
ui := catalog.NewManagedOSImage(
fleetNamespace,
osImage,
[]fleetv1.BundleTarget{},
"",
osVersion,
)
EventuallyWithOffset(1, func() error {
return k.ApplyJSON("", osImage, ui)
}, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred())
By("fetching bundle content")
EventuallyWithOffset(1, func() string {
r, err := kubectl.GetData(fleetNamespace, "bundle", "mos-update-osversion", `jsonpath={.spec.resources[*].content}`)
if err != nil {
fmt.Println(err)
}
return string(r)
}, 1*time.Minute, 2*time.Second).Should(
m,
)
}
It("tries to apply a plan", func() {
By("creating a new ManagedOSImage referencing a ManagedOSVersion")
createsCorrectPlan(map[string]runtime.RawExtension{
"upgradeImage": {Raw: []byte(`"registry.com/repository/image:v1.0"`)}, "robin": {Raw: []byte(`"batman"`)},
}, nil,
And(
ContainSubstring(`"version":"v1.0"`),
ContainSubstring(`"image":"registry.com/repository/image"`),
ContainSubstring(`"command":["/usr/sbin/suc-upgrade"]`),
ContainSubstring(`"name":"METADATA_ROBIN","value":"batman"`),
),
)
By("checking plan version")
Eventually(func() string {
up, err := getPlan("os-upgrader-update-osversion")
if err == nil {
return up.Spec.Version
}
fmt.Println(err)
return ""
}, 3*time.Minute, 2*time.Second).Should(Equal("v1.0"))
plan, err := getPlan("os-upgrader-update-osversion")
Expect(err).ToNot(HaveOccurred())
Expect(plan.Spec.Upgrade.Image).To(Equal("registry.com/repository/image"))
checkUpgradePod(k,
And(
ContainElement("METADATA_ROBIN=batman"),
ContainElement("METADATA_UPGRADEIMAGE=registry.com/repository/image:v1.0"),
),
Equal("registry.com/repository/image:v1.0"),
Equal([]string{"/usr/sbin/suc-upgrade"}),
BeNil(),
And(
ContainElement("host-root=/host"),
),
)
})
It("applies an os-upgrade from a channel", func() {
mr := catalog.NewManagedOSVersionChannel(
fleetNamespace,
"testchannel3",
"custom",
"1m",
map[string]runtime.RawExtension{
"image": {Raw: []byte(channelImage)},
},
nil,
)
defer k.Delete("managedosversionchannel", "-n", fleetNamespace, "testchannel3")
waitTestChannelPopulate(k, mr, "testchannel3", "registry.suse.com/rancher/elemental-teal-rt/5.4:1.2.2", "v1.2.2-rt")
err := k.ApplyJSON("", osImage, catalog.NewManagedOSImage(
fleetNamespace,
osImage,
[]fleetv1.BundleTarget{},
"",
"v1.2.2-rt",
))
Expect(err).ToNot(HaveOccurred())
checkUpgradePod(k,
And(
ContainElement("METADATA_UPGRADEIMAGE=registry.suse.com/rancher/elemental-teal-rt/5.4:1.2.2"),
),
Equal("registry.suse.com/rancher/elemental-teal-rt/5.4:1.2.2"),
Equal([]string{"/usr/sbin/suc-upgrade"}),
BeNil(),
And(
ContainElement("host-root=/host"),
),
)
})
It("overwrites default container spec from a channel", func() {
mr := catalog.NewManagedOSVersionChannel(
fleetNamespace,
"testchannel4",
"custom",
"1m",
map[string]runtime.RawExtension{
"image": {Raw: []byte(channelImage)},
},
&upgradev1.ContainerSpec{
Image: "Foobarz",
Command: []string{"foo"},
Args: []string{"baz"},
},
)
defer k.Delete("managedosversionchannel", "-n", fleetNamespace, "testchannel4")
waitTestChannelPopulate(k, mr, "testchannel4", "registry.suse.com/rancher/elemental-teal-rt/5.4:1.2.2", "v1.2.2-rt")
err := k.ApplyJSON("", osImage, catalog.NewManagedOSImage(
fleetNamespace,
osImage,
[]fleetv1.BundleTarget{},
"",
"v1.2.2-rt",
))
Expect(err).ToNot(HaveOccurred())
checkUpgradePod(k,
And(
ContainElement("METADATA_UPGRADEIMAGE=registry.suse.com/rancher/elemental-teal-rt/5.4:1.2.2"),
),
Equal("Foobarz"),
Equal([]string{"foo"}),
Equal([]string{"baz"}),
And(
ContainElement("host-root=/host"),
),
)
})
})
})