Skip to content

Commit

Permalink
E2E test for v2 controller (#399)
Browse files Browse the repository at this point in the history
  • Loading branch information
alculquicondor authored Aug 14, 2021
1 parent b4b62cc commit d61992f
Show file tree
Hide file tree
Showing 11 changed files with 367 additions and 12 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,17 @@ jobs:
run: make mpi-operator.v1alpha1 mpi-operator.v1alpha2 mpi-operator.v1 mpi-operator.v2 kubectl-delivery
- name: Run tests
run: make test
e2e:
name: E2E
runs-on: ubuntu-latest
# Pull requests from the same repository won't trigger this checks as they were already triggered by the push
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
steps:
- name: Clone the code
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: '^1.15'
- name: Run tests
run: make RELEASE_VERSION=test CONTROLLER_VERSION=v2 test_e2e
36 changes: 32 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ LD_FLAGS_V2=" \
-X '${REPO_PATH}/v2/pkg/version.Built=${Date}' \
-X '${REPO_PATH}/v2/pkg/version.Version=${RELEASE_VERSION}'"
IMAGE_NAME?=kubeflow/mpi-operator
KUBEBUILDER_ASSETS := $(dir $(abspath $(firstword $(MAKEFILE_LIST))))bin/kubebuilder/bin
KUBEBUILDER_ASSETS_PATH := $(dir $(abspath $(firstword $(MAKEFILE_LIST))))bin/kubebuilder/bin
KIND_VERSION=v0.11.1
# This kubectl version supports -k for kustomization.
KUBECTL_VERSION=v1.21.4

build: all

Expand Down Expand Up @@ -51,13 +54,25 @@ fmt:
cd v2 && go fmt ./...

.PHONY: test
test: bin/kubebuilder
test:
go test -covermode atomic -coverprofile=profile.cov ./...
@make test_v2

.PHONY: test_v2
test_v2:
cd v2 && KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test -covermode atomic -coverprofile=profile.cov ./...
test_v2: export KUBEBUILDER_ASSETS = ${KUBEBUILDER_ASSETS_PATH}
test_v2: bin/kubebuilder
cd v2 && go test -covermode atomic -coverprofile=profile.cov ./cmd/... ./pkg/... ./test/integration/...

# Only works with CONTROLLER_VERSION=v2
.PHONY: test_e2e
test_e2e: export TEST_MPI_OPERATOR_IMAGE = ${IMAGE_NAME}:${RELEASE_VERSION}
test_e2e: bin/kubectl kind images test_images dev_manifest
cd v2 && go test ./test/e2e/...

.PHONY: dev_manifest
dev_manifest:
# Use `~` instead of `/` because image name might contain `/`.
sed -e "s~%IMAGE_NAME%~${IMAGE_NAME}~g" -e "s~%IMAGE_TAG%~${RELEASE_VERSION}~g" manifests/overlays/dev/kustomization.yaml.template > manifests/overlays/dev/kustomization.yaml

.PHONY: generate
generate:
Expand All @@ -82,6 +97,10 @@ images:
@echo "version: ${RELEASE_VERSION}"
${IMG_BUILDER} build --build-arg version=${CONTROLLER_VERSION} -t ${IMAGE_NAME}:${RELEASE_VERSION} .

.PHONY: test_images
test_images:
${IMG_BUILDER} build -t kubeflow/mpi-pi:openmpi examples/pi

.PHONY: tidy
tidy:
go mod tidy
Expand All @@ -100,7 +119,16 @@ bin/kubebuilder:
tar -C bin/kubebuilder --strip-components=1 -zvxf envtest-bins.tar.gz
rm envtest-bins.tar.gz

bin/kubectl:
mkdir -p bin
curl -L -o bin/kubectl https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl
chmod +x bin/kubectl

.PHONY: lint
lint: bin/golangci-lint ## Run golangci-lint linter
$(GOLANGCI_LINT) run --new-from-rev=origin/master
cd v2 && ../$(GOLANGCI_LINT) run --new-from-rev=origin/master

.PHONY: kind
kind:
go install sigs.k8s.io/kind@${KIND_VERSION}
4 changes: 2 additions & 2 deletions examples/pi/pi-intel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ spec:
template:
spec:
containers:
- image: docker.io/kubeflow/mpi-pi:intel
- image: kubeflow/mpi-pi:intel
imagePullPolicy: Always
name: mpi-launcher
securityContext:
Expand All @@ -33,7 +33,7 @@ spec:
template:
spec:
containers:
- image: docker.io/kubeflow/mpi-pi:intel
- image: kubeflow/mpi-pi:intel
imagePullPolicy: Always
name: mpi-worker
securityContext:
Expand Down
2 changes: 1 addition & 1 deletion examples/pi/pi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ int main(int argc, char *argv[]) {
std::uniform_real_distribution<double> distribution(-1.0, 1.0);
double x, y;
long long worker_count = 0;
int worker_tests = 100000000;
int worker_tests = 10000000;
for (int i = 0; i < worker_tests; i++) {
x = distribution(generator);
y = distribution(generator);
Expand Down
4 changes: 2 additions & 2 deletions examples/pi/pi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ spec:
template:
spec:
containers:
- image: docker.io/kubeflow/mpi-pi
- image: kubeflow/mpi-pi
name: mpi-launcher
securityContext:
runAsUser: 1000
Expand All @@ -33,7 +33,7 @@ spec:
template:
spec:
containers:
- image: docker.io/kubeflow/mpi-pi
- image: kubeflow/mpi-pi
name: mpi-worker
securityContext:
runAsUser: 1000
Expand Down
1 change: 0 additions & 1 deletion manifests/base/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,5 @@ spec:
- --lock-namespace
- $(lock-namespace)
image: mpioperator/mpi-operator:latest
imagePullPolicy: Always
name: mpi-operator
serviceAccountName: mpi-operator
1 change: 1 addition & 0 deletions manifests/overlays/dev/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ commonLabels:
app.kubernetes.io/component: mpijob
images:
- name: mpioperator/mpi-operator
newName: dev-registry/mpi-operator
newTag: latest
newName: %IMAGE_NAME%
newTag: %IMAGE_TAG%
configMapGenerator:
- name: mpi-operator-config
envs:
Expand Down
2 changes: 2 additions & 0 deletions v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ require (
github.com/go-openapi/spec v0.19.3
github.com/google/go-cmp v0.5.6
github.com/kubeflow/common v0.3.4
github.com/onsi/ginkgo v1.14.1
github.com/onsi/gomega v1.10.2
github.com/prometheus/client_golang v1.7.1
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
k8s.io/api v0.19.9
Expand Down
157 changes: 157 additions & 0 deletions v2/test/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright 2021 The Kubeflow Authors.
//
// 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

import (
"context"
"fmt"
"os"
"os/exec"
"path"
"testing"
"time"

clientset "github.com/kubeflow/mpi-operator/v2/pkg/client/clientset/versioned"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
controllerruntime "sigs.k8s.io/controller-runtime"
)

const (
envUseExistingCluster = "USE_EXISTING_CLUSTER"
envTestMPIOperatorImage = "TEST_MPI_OPERATOR_IMAGE"
envTestKindImage = "TEST_KIND_IMAGE"

defaultMPIOperatorImage = "kubeflow/mpi-operator:local"
defaultKindImage = "kindest/node:v1.21.2"
openMPIImage = "kubeflow/mpi-pi:openmpi"
rootPath = "../../.."
kubectlPath = rootPath + "/bin/kubectl"
operatorManifestsPath = rootPath + "/manifests/overlays/dev"

mpiOperator = "mpi-operator"

waitInterval = 500 * time.Millisecond
foreverTimeout = 100 * time.Second
)

var (
useExistingCluster bool
kindPath string
mpiOperatorImage string
kindImage string

k8sClient kubernetes.Interface
mpiClient clientset.Interface
)

func init() {
useExistingCluster = getEnvDefault(envUseExistingCluster, "false") == "true"
mpiOperatorImage = getEnvDefault(envTestMPIOperatorImage, defaultMPIOperatorImage)
kindImage = getEnvDefault(envTestKindImage, defaultKindImage)
kindPath = "kind"
if goPath := os.Getenv("GOPATH"); goPath != "" {
kindPath = path.Join(goPath, "bin", "kind")
}
}

func TestE2E(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "E2e Suite")
}

var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
if !useExistingCluster {
ginkgo.By("Creating a local cluster")
err := bootstrapKindCluster()
gomega.Expect(err).ToNot(gomega.HaveOccurred())
}
ginkgo.By("Obtaining clients")
restConfig, err := controllerruntime.GetConfig()
gomega.Expect(err).ToNot(gomega.HaveOccurred())

k8sClient, err = kubernetes.NewForConfig(restConfig)
gomega.Expect(err).ToNot(gomega.HaveOccurred())

mpiClient, err = clientset.NewForConfig(restConfig)
gomega.Expect(err).ToNot(gomega.HaveOccurred())

ginkgo.By("Installing operator")
err = installOperator()
gomega.Expect(err).ToNot(gomega.HaveOccurred())

return nil
}, func([]byte) {})

var _ = ginkgo.SynchronizedAfterSuite(func() {
if !useExistingCluster {
ginkgo.By("Deleting local cluster")
err := runCommand(kindPath, "delete", "cluster")
gomega.Expect(err).ToNot(gomega.HaveOccurred())
} else {
ginkgo.By("Uninstalling operator")
err := runCommand(kubectlPath, "delete", "-k", operatorManifestsPath)
gomega.Expect(err).ToNot(gomega.HaveOccurred())
}
}, func() {})

func getEnvDefault(key, defaultVal string) string {
v, ok := os.LookupEnv(key)
if ok {
return v
}
return defaultVal
}

func bootstrapKindCluster() error {
err := runCommand(kindPath, "create", "cluster", "--image", kindImage)
if err != nil {
return fmt.Errorf("creating kind cluster: %w", err)
}
err = runCommand(kindPath, "load", "docker-image", mpiOperatorImage, openMPIImage)
if err != nil {
return fmt.Errorf("loading container images: %w", err)
}
return nil
}

func installOperator() error {
err := runCommand(kubectlPath, "apply", "-k", operatorManifestsPath)
if err != nil {
return fmt.Errorf("applying operator YAMLs: %w", err)
}
ctx := context.Background()
return wait.Poll(waitInterval, foreverTimeout, func() (bool, error) {
deployment, err := k8sClient.AppsV1().Deployments(mpiOperator).Get(ctx, mpiOperator, metav1.GetOptions{})
if errors.IsNotFound(err) {
return false, nil
}
if err != nil {
return false, err
}
return deployment.Status.AvailableReplicas != 0, nil
})
}

func runCommand(name string, args ...string) error {
cmd := exec.Command(name, args...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
return cmd.Run()
}
Loading

0 comments on commit d61992f

Please sign in to comment.