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

Initial upgrade support #782

Merged
merged 7 commits into from
Jul 8, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
205 changes: 205 additions & 0 deletions e2e/test_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ package e2e

import (
"context"
"errors"
"fmt"
"time"

"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
Expand All @@ -35,6 +37,7 @@ import (
projectv1 "github.com/openshift/api/project/v1"
"github.com/spf13/cobra"
"io/ioutil"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -121,6 +124,16 @@ func integrationPodPhase(ns string, name string) func() v1.PodPhase {
}
}

func integrationPodImage(ns string, name string) func() string {
return func() string {
pod := integrationPod(ns, name)()
if pod == nil || len(pod.Spec.Containers) == 0 {
return ""
}
return pod.Spec.Containers[0].Image
}
}

func integrationPod(ns string, name string) func() *v1.Pod {
return func() *v1.Pod {
lst := v1.PodList{
Expand All @@ -145,6 +158,77 @@ func integrationPod(ns string, name string) func() *v1.Pod {
}
}

func integration(ns string, name string) func() *v1alpha1.Integration {
return func() *v1alpha1.Integration {
it := v1alpha1.NewIntegration(ns, name)
key := k8sclient.ObjectKey{
Namespace: ns,
Name: name,
}
if err := testClient.Get(testContext, key, &it); err != nil && !k8serrors.IsNotFound(err) {
panic(err)
} else if err != nil && k8serrors.IsNotFound(err) {
return nil
}
return &it
}
}

func integrationVersion(ns string, name string) func() string {
return func() string {
it := integration(ns, name)()
if it == nil {
return ""
}
return it.Status.Version
}
}

func setIntegrationVersion(ns string, name string, version string) error {
it := integration(ns, name)()
if it == nil {
return fmt.Errorf("no integration named %s found", name)
}
it.Status.Version = version
return testClient.Status().Update(testContext, it)
}

func kits(ns string) func() []v1alpha1.IntegrationKit {
return func() []v1alpha1.IntegrationKit {
lst := v1alpha1.NewIntegrationKitList()
opts := k8sclient.ListOptions{
Namespace: ns,
}
if err := testClient.List(testContext, &opts, &lst); err != nil {
panic(err)
}
return lst.Items
}
}

func kitsWithVersion(ns string, version string) func() int {
return func() int {
count := 0
for _, k := range kits(ns)() {
if k.Status.Version == version {
count++
}
}
return count
}
}

func setAllKitsVersion(ns string, version string) error {
for _, k := range kits(ns)() {
kit := k
kit.Status.Version = version
if err := testClient.Status().Update(testContext, &kit); err != nil {
return err
}
}
return nil
}

func operatorImage(ns string) func() string {
return func() string {
pod := operatorPod(ns)()
Expand All @@ -157,6 +241,16 @@ func operatorImage(ns string) func() string {
}
}

func operatorPodPhase(ns string) func() v1.PodPhase {
return func() v1.PodPhase {
pod := operatorPod(ns)()
if pod == nil {
return ""
}
return pod.Status.Phase
}
}

func configmap(ns string, name string) func() *v1.ConfigMap {
return func() *v1.ConfigMap {
cm := v1.ConfigMap{
Expand Down Expand Up @@ -200,6 +294,44 @@ func build(ns string, name string) func() *v1alpha1.Build {
}
}

func platform(ns string) func() *v1alpha1.IntegrationPlatform {
return func() *v1alpha1.IntegrationPlatform {
lst := v1alpha1.NewIntegrationPlatformList()
opts := k8sclient.ListOptions{
Namespace: ns,
}
if err := testClient.List(testContext, &opts, &lst); err != nil {
panic(err)
}
if len(lst.Items) == 0 {
return nil
}
if len(lst.Items) > 1 {
panic("multiple integration platforms found in namespace " + ns)
}
return &lst.Items[0]
}
}

func setPlatformVersion(ns string, version string) error {
p := platform(ns)()
if p == nil {
return errors.New("no platform found")
}
p.Status.Version = version
return testClient.Status().Update(testContext, p)
}

func platformVersion(ns string) func() string {
return func() string {
p := platform(ns)()
if p == nil {
return ""
}
return p.Status.Version
}
}

func operatorPod(ns string) func() *v1.Pod {
return func() *v1.Pod {
lst := v1.PodList{
Expand All @@ -224,10 +356,80 @@ func operatorPod(ns string) func() *v1.Pod {
}
}

func operatorTryPodForceKill(ns string) {
pod := operatorPod(ns)()
if pod != nil {
opts := func(options *k8sclient.DeleteOptions) {
zero := int64(0)
options.GracePeriodSeconds = &zero
}
if err := testClient.Delete(testContext, pod, opts); err != nil {
log.Error(err, "cannot forcefully kill the pod")
}
}
}

func scaleOperator(ns string, replicas int32) error {
lst := appsv1.DeploymentList{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: appsv1.SchemeGroupVersion.String(),
},
}
opts := k8sclient.ListOptions{
LabelSelector: labels.SelectorFromSet(labels.Set{
"camel.apache.org/component": "operator",
}),
Namespace: ns,
}
if err := testClient.List(testContext, &opts, &lst); err != nil {
return err
}
if len(lst.Items) == 0 {
return errors.New("camel k operator not found")
} else if len(lst.Items) > 1 {
return errors.New("too many camel k operators")
}

operatorDeployment := lst.Items[0]
operatorDeployment.Spec.Replicas = &replicas
err := testClient.Update(testContext, &operatorDeployment)
if err != nil {
return err
}

if replicas == 0 {
// speedup scale down by killing the pod
operatorTryPodForceKill(ns)
}
return nil
}

/*
Namespace testing functions
*/

func numPods(ns string) func() int {
return func() int {
lst := v1.PodList{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: v1.SchemeGroupVersion.String(),
},
}
opts := k8sclient.ListOptions{
Namespace: ns,
}
if err := testClient.List(testContext, &opts, &lst); err != nil && k8serrors.IsUnauthorized(err) {
return 0
} else if err != nil {
log.Error(err, "Error while listing the pods")
return 0
}
return len(lst.Items)
}
}

func withNewTestNamespace(doRun func(string)) {
ns := newTestNamespace()
defer deleteTestNamespace(ns)
Expand Down Expand Up @@ -258,6 +460,9 @@ func deleteTestNamespace(ns metav1.Object) {
log.Error(err, "cannot delete test namespace", "name", ns.GetName())
}
}

// Wait for all pods to be deleted
gomega.Eventually(numPods(ns.GetName()), 30 * time.Second).Should(gomega.Equal(0))
}

func newTestNamespace() metav1.Object {
Expand Down
96 changes: 96 additions & 0 deletions e2e/upgrade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// +build integration

// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration"

/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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

import (
"testing"
"time"

"github.com/apache/camel-k/pkg/util/defaults"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
)

func TestPlatformUpgrade(t *testing.T) {
withNewTestNamespace(func(ns string) {
RegisterTestingT(t)
Expect(kamel("install", "-n", ns).Execute()).Should(BeNil())
Eventually(platformVersion(ns)).Should(Equal(defaults.Version))

// Scale the operator down to zero
Expect(scaleOperator(ns, 0)).Should(BeNil())
Eventually(operatorPod(ns)).Should(BeNil())

// Change the version to an older one
Expect(setPlatformVersion(ns, "an.older.one")).Should(BeNil())
Eventually(platformVersion(ns)).Should(Equal("an.older.one"))

// Scale the operator up
Expect(scaleOperator(ns, 1)).Should(BeNil())
Eventually(operatorPod(ns)).ShouldNot(BeNil())

// Check the platform version change
Eventually(platformVersion(ns)).Should(Equal(defaults.Version))
})
}

func TestIntegrationUpgrade(t *testing.T) {
withNewTestNamespace(func(ns string) {
RegisterTestingT(t)
Expect(kamel("install", "-n", ns).Execute()).Should(BeNil())
Eventually(platformVersion(ns)).Should(Equal(defaults.Version))

// Run an integration
Expect(kamel("run", "-n", ns, "files/js.js").Execute()).Should(BeNil())
Eventually(integrationPodPhase(ns, "js"), 5*time.Minute).Should(Equal(v1.PodRunning))
initialImage := integrationPodImage(ns, "js")()

// Scale the operator down to zero
Expect(scaleOperator(ns, 0)).Should(BeNil())
Eventually(operatorPod(ns)).Should(BeNil())

// Change the version to an older one
Expect(setIntegrationVersion(ns, "js", "an.older.one")).Should(BeNil())
Expect(setAllKitsVersion(ns, "an.older.one")).Should(BeNil())
Eventually(integrationVersion(ns, "js")).Should(Equal("an.older.one"))
Eventually(kitsWithVersion(ns, "an.older.one")).Should(Equal(1))
Eventually(kitsWithVersion(ns, defaults.Version)).Should(Equal(0))

// Scale the operator up
Expect(scaleOperator(ns, 1)).Should(BeNil())
Eventually(operatorPod(ns)).ShouldNot(BeNil())
Eventually(operatorPodPhase(ns)).Should(Equal(v1.PodRunning))

// No auto-update expected
Consistently(integrationVersion(ns, "js"), 3*time.Second).Should(Equal("an.older.one"))

// Clear the integration status
Expect(kamel("rebuild", "js", "-n", ns).Execute()).Should(BeNil())

// Check the integration version change
Eventually(integrationVersion(ns, "js")).Should(Equal(defaults.Version))
Eventually(kitsWithVersion(ns, "an.older.one")).Should(Equal(1)) // old one is not recycled
Eventually(kitsWithVersion(ns, defaults.Version)).Should(Equal(1))
Eventually(integrationPodImage(ns, "js"), 5*time.Minute).ShouldNot(Equal(initialImage)) // rolling deployment triggered
Eventually(integrationPodPhase(ns, "js"), 5*time.Minute).Should(Equal(v1.PodRunning))
})
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,16 @@ require (
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0
go.uber.org/zap v1.9.1 // indirect
golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
golang.org/x/sys v0.0.0-20190312061237-fead79001313 // indirect
google.golang.org/appengine v1.5.0 // indirect
gopkg.in/yaml.v2 v2.2.2
k8s.io/api v0.0.0-20190222213804-5cb15d344471
k8s.io/apiextensions-apiserver v0.0.0-20190228180357-d002e88f6236 // indirect
k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628
k8s.io/client-go v0.0.0-20190228174230-b40b2a5939e4
k8s.io/klog v0.3.0 // indirect
k8s.io/klog v0.3.1 // indirect
k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22 // indirect
sigs.k8s.io/controller-runtime v0.1.10
sigs.k8s.io/testing_frameworks v0.1.1 // indirect
Expand Down
Loading