Refactor e2e tests to new ginkgo framework

This commit is contained in:
Tomasz Sęk 2021-01-26 10:47:09 +01:00
parent b6bf47b949
commit 524d0b861f
No known key found for this signature in database
GPG Key ID: DC356D23F6A644D0
33 changed files with 523 additions and 984 deletions

View File

@ -89,47 +89,10 @@ test: ## Runs the go tests
@RUNNING_TESTS=1 go test -tags "$(BUILDTAGS) cgo" $(PACKAGES_FOR_UNIT_TESTS)
.PHONY: e2e
CURRENT_DIRECTORY := $(shell pwd)
e2e: container-runtime-build ## Runs e2e tests, you can use EXTRA_ARGS
e2e: deepcopy-gen ## Runs e2e tests, you can use EXTRA_ARGS
@echo "+ $@"
@echo "Docker image: $(DOCKER_REGISTRY):$(GITCOMMIT)"
ifeq ($(KUBERNETES_PROVIDER),minikube)
kubectl config use-context $(KUBECTL_CONTEXT)
endif
ifeq ($(KUBERNETES_PROVIDER),crc)
oc project $(CRC_OC_PROJECT)
endif
cp deploy/service_account.yaml deploy/namespace-init.yaml
cat deploy/role.yaml >> deploy/namespace-init.yaml
cat deploy/role_binding.yaml >> deploy/namespace-init.yaml
cat deploy/operator.yaml >> deploy/namespace-init.yaml
ifeq ($(OSFLAG), LINUX)
ifeq ($(IMAGE_PULL_MODE), remote)
sed -i 's|\(image:\).*|\1 $(DOCKER_ORGANIZATION)/$(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml
sed -i 's|\(imagePullPolicy\): IfNotPresent|\1: Always|g' deploy/namespace-init.yaml
else
sed -i 's|\(image:\).*|\1 $(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml
endif
ifeq ($(KUBERNETES_PROVIDER),minikube)
sed -i 's|\(imagePullPolicy\): IfNotPresent|\1: Never|g' deploy/namespace-init.yaml
endif
endif
ifeq ($(OSFLAG), OSX)
ifeq ($(IMAGE_PULL_MODE), remote)
sed -i '' 's|\(image:\).*|\1 $(DOCKER_ORGANIZATION)/$(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml
sed -i '' 's|\(imagePullPolicy\): IfNotPresent|\1: Always|g' deploy/namespace-init.yaml
else
sed -i '' 's|\(image:\).*|\1 $(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml
endif
ifeq ($(KUBERNETES_PROVIDER),minikube)
sed -i '' 's|\(imagePullPolicy\): IfNotPresent|\1: Never|g' deploy/namespace-init.yaml
endif
endif
RUNNING_TESTS=1 go test -parallel=1 "./test/e2e/" -tags "$(BUILDTAGS) cgo" -v -timeout 60m -run "$(E2E_TEST_SELECTOR)" \
-root=$(CURRENT_DIRECTORY) -kubeconfig=$(HOME)/.kube/config -globalMan deploy/crds/jenkins_$(API_VERSION)_jenkins_crd.yaml \
-namespacedMan deploy/namespace-init.yaml $(TEST_ARGS)
$(TEST_ARGS)
.PHONY: vet
vet: ## Verifies `go vet` passes
@ -420,7 +383,6 @@ generate-docs: ## Re-generate docs directory from the website directory
hugo -s website -d ../docs
##################### FROM OPERATOR SDK ########################
#TODO rename
# Install CRDs into a cluster
install-crds: manifests kustomize
$(KUSTOMIZE) build config/crd | kubectl apply -f -
@ -474,9 +436,8 @@ bundle: manifests kustomize
bundle-build:
docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .
#FIXME temporary target for running tests (test used above for go test)
ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
testing: generate fmt vet manifests
# Download kubebuilder
kubebuilder:
mkdir -p ${ENVTEST_ASSETS_DIR}
test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh
source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out
source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR);

View File

@ -16,7 +16,7 @@ limitations under the License.
// Package v1alpha2 contains API Schema definitions for the jenkins.io v1alpha2 API group
// +kubebuilder:object:generate=true
// +groupName=jenkins.io.jenkins.io
// +groupName=jenkins.io
package v1alpha2
import (
@ -26,7 +26,7 @@ import (
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "jenkins.io.jenkins.io", Version: "v1alpha2"}
GroupVersion = schema.GroupVersion{Group: "jenkins.io", Version: "v1alpha2"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

View File

@ -674,7 +674,3 @@ type GroovyScripts struct {
type ConfigurationAsCode struct {
Customization `json:",inline"`
}
func init() {
SchemeBuilder.Register(&Jenkins{}, &JenkinsList{})
}

View File

@ -47,7 +47,3 @@ type JenkinsImageList struct {
metav1.ListMeta `json:"metadata,omitempty"`
Items []JenkinsImage `json:"items"`
}
func init() {
SchemeBuilder.Register(&JenkinsImage{}, &JenkinsImageList{})
}

View File

@ -6,9 +6,9 @@ metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.1
creationTimestamp: null
name: jenkins.jenkins.io.jenkins.io
name: jenkins.jenkins.io
spec:
group: jenkins.io.jenkins.io
group: jenkins.io
names:
kind: Jenkins
listKind: JenkinsList
@ -38,7 +38,7 @@ spec:
properties:
backup:
description: 'Backup defines configuration of Jenkins backup More
info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore'
info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/'
properties:
action:
description: Action defines action which performs backup in backup
@ -157,11 +157,11 @@ spec:
type: object
basePlugins:
description: 'BasePlugins contains plugins required by operator
Defaults to : - name: kubernetes version: 1.15.7 - name: workflow-job
version: "2.39" - name: workflow-aggregator version: "2.6" -
name: git version: 3.10.0 - name: job-dsl version: "1.74" -
name: configuration-as-code version: "1.19" - name: kubernetes-credentials-provider
version: 0.12.1'
Defaults to : - name: kubernetes version: "1.28.6" - name: workflow-job
version: "2.40" - name: workflow-aggregator version: "2.6" -
name: git version: "4.5.0" - name: job-dsl version: "1.77" -
name: configuration-as-code version: "1.46" - name: kubernetes-credentials-provider
version: "0.15"'
items:
description: Plugin defines Jenkins plugin.
properties:
@ -1184,7 +1184,7 @@ spec:
support fsGroup based ownership(and permissions). It will
have no effect on ephemeral volume types such as: secret,
configmaps and emptydir. Valid values are "OnRootMismatch"
and "Always". If not specified defaults to "Always".'
and "Always". If not specified, "Always" is used.'
type: string
runAsGroup:
description: The GID to run the entrypoint of the container
@ -1817,22 +1817,16 @@ spec:
dataSource:
description: 'This field can be used to specify
either: * An existing VolumeSnapshot object
(snapshot.storage.k8s.io/VolumeSnapshot -
Beta) * An existing PVC (PersistentVolumeClaim)
* An existing custom resource/object that
implements data population (Alpha) In order
to use VolumeSnapshot object types, the appropriate
feature gate must be enabled (VolumeSnapshotDataSource
or AnyVolumeDataSource) If the provisioner
or an external controller can support the
specified data source, it will create a new
volume based on the contents of the specified
data source. If the specified data source
is not supported, the volume will not be created
and the failure will be reported as an event.
In the future, we plan to support more data
source types and the behavior of the provisioner
may change.'
(snapshot.storage.k8s.io/VolumeSnapshot) *
An existing PVC (PersistentVolumeClaim) *
An existing custom resource that implements
data population (Alpha) In order to use custom
resource types that implement data population,
the AnyVolumeDataSource feature gate must
be enabled. If the provisioner or an external
controller can support the specified data
source, it will create a new volume based
on the contents of the specified data source.'
properties:
apiGroup:
description: APIGroup is the group for the
@ -2560,8 +2554,6 @@ spec:
type: object
type: object
type: array
required:
- sources
type: object
quobyte:
description: Quobyte represents a Quobyte mount on the host
@ -3030,7 +3022,7 @@ spec:
type: array
restore:
description: 'Backup defines configuration of Jenkins backup restore
More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore'
More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/'
properties:
action:
description: Action defines action which performs restore backup
@ -3112,10 +3104,10 @@ spec:
type: array
seedJobs:
description: 'SeedJobs defines list of Jenkins Seed Job configurations
More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines'
More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration#configure-seed-jobs-and-pipelines'
items:
description: 'SeedJob defines configuration for seed job More info:
https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines.'
https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration/#configure-seed-jobs-and-pipelines.'
properties:
additionalClasspath:
description: AdditionalClasspath is setting for Job DSL API
@ -3209,7 +3201,7 @@ spec:
will restrict traffic through the cloud-provider load-balancer
will be restricted to the specified client IPs. This field will
be ignored if the cloud-provider does not support the feature."
More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/'
More info: https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/#restricting-cloud-metadata-api-access'
items:
type: string
type: array
@ -3290,7 +3282,7 @@ spec:
will restrict traffic through the cloud-provider load-balancer
will be restricted to the specified client IPs. This field will
be ignored if the cloud-provider does not support the feature."
More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/'
More info: https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/#restricting-cloud-metadata-api-access'
items:
type: string
type: array

View File

@ -6,9 +6,9 @@ metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.1
creationTimestamp: null
name: jenkinsimages.jenkins.io.jenkins.io
name: jenkinsimages.jenkins.io
spec:
group: jenkins.io.jenkins.io
group: jenkins.io
names:
kind: JenkinsImage
listKind: JenkinsImageList

View File

@ -2,7 +2,7 @@
# since it depends on service name and namespace that are out of this kustomize package.
# It should be run by config/default
resources:
- bases/jenkins.io.jenkins.io_jenkins.yaml
- bases/jenkins.io_jenkins.yaml
# +kubebuilder:scaffold:crdkustomizeresource
patchesStrategicMerge:

View File

@ -4,4 +4,4 @@ kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: jenkins.jenkins.io.jenkins.io
name: jenkins.jenkins.io

View File

@ -2,7 +2,7 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: jenkins.jenkins.io.jenkins.io
name: jenkins.jenkins.io
spec:
conversion:
strategy: Webhook

View File

@ -5,7 +5,7 @@ metadata:
name: jenkins-editor-role
rules:
- apiGroups:
- jenkins.io.jenkins.io
- jenkins.io
resources:
- jenkins
verbs:
@ -17,7 +17,7 @@ rules:
- update
- watch
- apiGroups:
- jenkins.io.jenkins.io
- jenkins.io
resources:
- jenkins/status
verbs:

View File

@ -5,7 +5,7 @@ metadata:
name: jenkins-viewer-role
rules:
- apiGroups:
- jenkins.io.jenkins.io
- jenkins.io
resources:
- jenkins
verbs:
@ -13,7 +13,7 @@ rules:
- list
- watch
- apiGroups:
- jenkins.io.jenkins.io
- jenkins.io
resources:
- jenkins/status
verbs:

View File

@ -1,4 +1,4 @@
apiVersion: jenkins.io.jenkins.io/v1alpha2
apiVersion: jenkins.io/v1alpha2
kind: Jenkins
metadata:
name: jenkins-example

View File

@ -1,170 +0,0 @@
package controllers
import (
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources"
"github.com/jenkinsci/kubernetes-operator/pkg/constants"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
const (
userConfigurationConfigMapName = "user-config"
userConfigurationSecretName = "user-secret"
)
type seedJobConfig struct {
v1alpha2.SeedJob
JobNames []string `json:"jobNames,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
PrivateKey string `json:"privateKey,omitempty"`
}
var (
jenkinsCRName = "jenkins-example"
namespace = "default"
priorityClassName = ""
mySeedJob = seedJobConfig{
SeedJob: v1alpha2.SeedJob{
ID: "jenkins-operator",
CredentialID: "jenkins-operator",
JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType,
Targets: "cicd/jobs/*.jenkins",
Description: "Jenkins Operator repository",
RepositoryBranch: "master",
RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git",
PollSCM: "1 1 1 1 1",
UnstableOnDeprecation: true,
BuildPeriodically: "1 1 1 1 1",
FailOnMissingPlugin: true,
IgnoreMissingFiles: true,
//AdditionalClasspath: can fail with the seed job agent
GitHubPushTrigger: true,
},
}
groovyScripts = v1alpha2.GroovyScripts{
Customization: v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{
{
Name: userConfigurationConfigMapName,
},
},
Secret: v1alpha2.SecretRef{
Name: userConfigurationSecretName,
},
},
}
casc = v1alpha2.ConfigurationAsCode{
Customization: v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{
{
Name: userConfigurationConfigMapName,
},
},
Secret: v1alpha2.SecretRef{
Name: userConfigurationSecretName,
},
},
}
)
func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins {
var seedJobs []v1alpha2.SeedJob
if seedJob != nil {
seedJobs = append(seedJobs, *seedJob...)
}
jenkins := &v1alpha2.Jenkins{
TypeMeta: v1alpha2.JenkinsTypeMeta(),
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha2.JenkinsSpec{
GroovyScripts: groovyScripts,
ConfigurationAsCode: casc,
Master: v1alpha2.JenkinsMaster{
Annotations: map[string]string{"test": "label"},
Containers: []v1alpha2.Container{
{
Name: resources.JenkinsMasterContainerName,
Env: []corev1.EnvVar{
{
Name: "TEST_ENV",
Value: "test_env_value",
},
},
ReadinessProbe: &corev1.Probe{
Handler: corev1.Handler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/login",
Port: intstr.FromString("http"),
Scheme: corev1.URISchemeHTTP,
},
},
InitialDelaySeconds: int32(80),
TimeoutSeconds: int32(4),
FailureThreshold: int32(10),
},
LivenessProbe: &corev1.Probe{
Handler: corev1.Handler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/login",
Port: intstr.FromString("http"),
Scheme: corev1.URISchemeHTTP,
},
},
InitialDelaySeconds: int32(80),
TimeoutSeconds: int32(4),
FailureThreshold: int32(10),
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "plugins-cache",
MountPath: "/usr/share/jenkins/ref/plugins",
},
},
},
{
Name: "envoyproxy",
Image: "envoyproxy/envoy-alpine:v1.14.1",
},
},
Plugins: []v1alpha2.Plugin{
{Name: "audit-trail", Version: "3.7"},
{Name: "simple-theme-plugin", Version: "0.6"},
{Name: "github", Version: "1.32.0"},
{Name: "devoptics", Version: "1.1905", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1905/devoptics.hpi"},
},
PriorityClassName: priorityClassName,
NodeSelector: map[string]string{"kubernetes.io/os": "linux"},
Volumes: []corev1.Volume{
{
Name: "plugins-cache",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
},
},
SeedJobs: seedJobs,
Service: v1alpha2.Service{
Type: corev1.ServiceTypeNodePort,
Port: constants.DefaultHTTPPortInt32,
},
},
}
jenkins.Spec.Roles = []rbacv1.RoleRef{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: resources.GetResourceName(jenkins),
},
}
return jenkins
}

View File

@ -1,40 +0,0 @@
/*
Copyright 2021.
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 controllers
import (
"context"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var _ = Describe("Jenkins controller", func() {
Describe("deploying Jenkins CR into a cluster", func() {
Context("when deploying CR to cluster", func() {
It("create Jenkins instance", func() {
ctx := context.Background()
jenkins := createJenkinsCR(jenkinsCRName, namespace, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName)
Expect(k8sClient.Create(ctx, jenkins)).Should(Succeed())
})
})
})
})

1
go.mod
View File

@ -22,6 +22,7 @@ require (
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
k8s.io/api v0.20.2
k8s.io/apimachinery v0.20.2
k8s.io/cli-runtime v0.20.2
k8s.io/client-go v0.20.2
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
sigs.k8s.io/controller-runtime v0.7.0

View File

@ -15,6 +15,8 @@ func NewSimpleProbe(uri string, port string, scheme corev1.URIScheme, initialDel
},
},
InitialDelaySeconds: initialDelaySeconds,
SuccessThreshold: int32(1),
PeriodSeconds: int32(1),
}
}

View File

@ -3,10 +3,8 @@ package e2e
import (
"context"
"fmt"
"testing"
"time"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources"
@ -15,9 +13,8 @@ import (
"github.com/jenkinsci/kubernetes-operator/pkg/plugins"
"github.com/bndr/gojenkins"
framework "github.com/operator-framework/operator-sdk/pkg/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -26,86 +23,8 @@ import (
const e2e = "e2e"
func TestConfiguration(t *testing.T) {
t.Parallel()
namespace, ctx := setupTest(t)
defer showLogsAndCleanup(t, ctx)
jenkinsCRName := e2e
numberOfExecutors := 6
numberOfExecutorsEnvName := "NUMBER_OF_EXECUTORS"
systemMessage := "Configuration as Code integration works!!!"
systemMessageEnvName := "SYSTEM_MESSAGE"
priorityClassName := ""
mySeedJob := seedJobConfig{
SeedJob: v1alpha2.SeedJob{
ID: "jenkins-operator",
CredentialID: "jenkins-operator",
JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType,
Targets: "cicd/jobs/*.jenkins",
Description: "Jenkins Operator repository",
RepositoryBranch: "master",
RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git",
PollSCM: "1 1 1 1 1",
UnstableOnDeprecation: true,
BuildPeriodically: "1 1 1 1 1",
FailOnMissingPlugin: true,
IgnoreMissingFiles: true,
//AdditionalClasspath: can fail with the seed job agent
GitHubPushTrigger: true,
},
}
groovyScripts := v1alpha2.GroovyScripts{
Customization: v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{
{
Name: userConfigurationConfigMapName,
},
},
Secret: v1alpha2.SecretRef{
Name: userConfigurationSecretName,
},
},
}
casc := v1alpha2.ConfigurationAsCode{
Customization: v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{
{
Name: userConfigurationConfigMapName,
},
},
Secret: v1alpha2.SecretRef{
Name: userConfigurationSecretName,
},
},
}
stringData := make(map[string]string)
stringData[systemMessageEnvName] = systemMessage
stringData[numberOfExecutorsEnvName] = fmt.Sprintf("%d", numberOfExecutors)
// base
createUserConfigurationSecret(t, namespace, stringData)
createUserConfigurationConfigMap(t, namespace, numberOfExecutorsEnvName, fmt.Sprintf("${%s}", systemMessageEnvName))
jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName)
createDefaultLimitsForContainersInNamespace(t, namespace)
createKubernetesCredentialsProviderSecret(t, namespace, mySeedJob)
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
verifyJenkinsMasterPodAttributes(t, jenkins)
verifyServices(t, jenkins)
jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(t, jenkins, namespace)
defer cleanUpFunc()
verifyPlugins(t, jenkinsClient, jenkins)
// user
waitForJenkinsUserConfigurationToComplete(t, jenkins)
verifyUserConfiguration(t, jenkinsClient, numberOfExecutors, systemMessage)
verifyJenkinsSeedJobs(t, jenkinsClient, []seedJobConfig{mySeedJob})
}
func TestPlugins(t *testing.T) {
// FIXME
/*func TestPlugins(t *testing.T) {
t.Parallel()
namespace, ctx := setupTest(t)
// Deletes test namespace
@ -145,28 +64,11 @@ func TestPlugins(t *testing.T) {
build, err := job.GetLastBuild()
require.NoError(t, err)
assert.True(t, build.IsGood())
}
}*/
func TestPriorityClass(t *testing.T) {
if skipTestPriorityClass {
t.Skip()
}
t.Parallel()
namespace, ctx := setupTest(t)
defer showLogsAndCleanup(t, ctx)
func createUserConfigurationSecret(namespace string, stringData map[string]string) {
By("creating user configuration secret")
jenkinsCRName := "k8s-ete-priority-class-existing"
// One of the existing priority classes
priorityClassName := "system-cluster-critical"
jenkins := createJenkinsCR(t, jenkinsCRName, namespace, nil, v1alpha2.GroovyScripts{}, v1alpha2.ConfigurationAsCode{}, priorityClassName)
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
verifyJenkinsMasterPodAttributes(t, jenkins)
_, cleanUpFunc := verifyJenkinsAPIConnection(t, jenkins, namespace)
defer cleanUpFunc()
}
func createUserConfigurationSecret(t *testing.T, namespace string, stringData map[string]string) {
userConfiguration := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: userConfigurationSecretName,
@ -175,13 +77,13 @@ func createUserConfigurationSecret(t *testing.T, namespace string, stringData ma
StringData: stringData,
}
t.Logf("User configuration secret %+v", *userConfiguration)
if err := framework.Global.Client.Create(context.TODO(), userConfiguration, nil); err != nil {
t.Fatal(err)
}
_, _ = fmt.Fprintf(GinkgoWriter, "User configuration secret %+v\n", *userConfiguration)
Expect(k8sClient.Create(context.TODO(), userConfiguration)).Should(Succeed())
}
func createUserConfigurationConfigMap(t *testing.T, namespace string, numberOfExecutorsSecretKeyName string, systemMessage string) {
func createUserConfigurationConfigMap(namespace string, numberOfExecutorsSecretKeyName string, systemMessage string) {
By("creating user configuration config map")
userConfiguration := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: userConfigurationConfigMapName,
@ -203,13 +105,13 @@ unclassified:
},
}
t.Logf("User configuration %+v", *userConfiguration)
if err := framework.Global.Client.Create(context.TODO(), userConfiguration, nil); err != nil {
t.Fatal(err)
}
_, _ = fmt.Fprintf(GinkgoWriter, "User configuration %+v\n", *userConfiguration)
Expect(k8sClient.Create(context.TODO(), userConfiguration)).Should(Succeed())
}
func createDefaultLimitsForContainersInNamespace(t *testing.T, namespace string) {
func createDefaultLimitsForContainersInNamespace(namespace string) {
By("creating default limits for containers in namespace")
limitRange := &corev1.LimitRange{
ObjectMeta: metav1.ObjectMeta{
Name: e2e,
@ -232,31 +134,34 @@ func createDefaultLimitsForContainersInNamespace(t *testing.T, namespace string)
},
}
t.Logf("LimitRange %+v", *limitRange)
if err := framework.Global.Client.Create(context.TODO(), limitRange, nil); err != nil {
t.Fatal(err)
}
_, _ = fmt.Fprintf(GinkgoWriter, "LimitRange %+v\n", *limitRange)
Expect(k8sClient.Create(context.TODO(), limitRange)).Should(Succeed())
}
func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha2.Jenkins) {
jenkinsPod := getJenkinsMasterPod(t, jenkins)
jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name)
func verifyJenkinsMasterPodAttributes(jenkins *v1alpha2.Jenkins) {
By("creating Jenkins master pod properly")
assertMapContainsElementsFromAnotherMap(t, jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations)
assert.Equal(t, jenkins.Spec.Master.NodeSelector, jenkinsPod.Spec.NodeSelector)
jenkinsPod := getJenkinsMasterPod(jenkins)
jenkins = getJenkins(jenkins.Namespace, jenkins.Name)
assert.Equal(t, resources.JenkinsMasterContainerName, jenkinsPod.Spec.Containers[0].Name)
assert.Equal(t, len(jenkins.Spec.Master.Containers), len(jenkinsPod.Spec.Containers))
assertMapContainsElementsFromAnotherMap(jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations)
Expect(jenkinsPod.Spec.NodeSelector).Should(Equal(jenkins.Spec.Master.NodeSelector))
assert.Equal(t, jenkins.Spec.Master.SecurityContext, jenkinsPod.Spec.SecurityContext)
assert.Equal(t, jenkins.Spec.Master.Containers[0].Command, jenkinsPod.Spec.Containers[0].Command)
Expect(jenkinsPod.Spec.Containers[0].Name).Should(Equal(resources.JenkinsMasterContainerName))
Expect(jenkinsPod.Spec.Containers).Should(HaveLen(len(jenkins.Spec.Master.Containers)))
assert.Equal(t, resources.GetJenkinsMasterPodLabels(*jenkins), jenkinsPod.Labels)
assert.Equal(t, jenkins.Spec.Master.PriorityClassName, jenkinsPod.Spec.PriorityClassName)
if jenkins.Spec.Master.SecurityContext == nil {
jenkins.Spec.Master.SecurityContext = &corev1.PodSecurityContext{}
}
Expect(jenkinsPod.Spec.SecurityContext).Should(Equal(jenkins.Spec.Master.SecurityContext))
Expect(jenkinsPod.Spec.Containers[0].Command).Should(Equal(jenkins.Spec.Master.Containers[0].Command))
Expect(jenkinsPod.Labels).Should(Equal(resources.GetJenkinsMasterPodLabels(*jenkins)))
Expect(jenkinsPod.Spec.PriorityClassName).Should(Equal(jenkins.Spec.Master.PriorityClassName))
for _, actualContainer := range jenkinsPod.Spec.Containers {
if actualContainer.Name == resources.JenkinsMasterContainerName {
verifyContainer(t, resources.NewJenkinsMasterContainer(jenkins), actualContainer)
verifyContainer(resources.NewJenkinsMasterContainer(jenkins), actualContainer)
continue
}
@ -269,11 +174,11 @@ func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha2.Jenkins) {
}
if expectedContainer == nil {
t.Errorf("Container '%+v' not found in pod", actualContainer)
Fail(fmt.Sprintf("Container '%+v' not found in pod", actualContainer))
continue
}
verifyContainer(t, *expectedContainer, actualContainer)
verifyContainer(*expectedContainer, actualContainer)
}
for _, expectedVolume := range jenkins.Spec.Master.Volumes {
@ -281,58 +186,54 @@ func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha2.Jenkins) {
for _, actualVolume := range jenkinsPod.Spec.Volumes {
if expectedVolume.Name == actualVolume.Name {
volumeFound = true
assert.Equal(t, expectedVolume, actualVolume)
Expect(actualVolume).Should(Equal(expectedVolume))
}
}
if !volumeFound {
t.Errorf("Missing volume '+%v', actaul volumes '%+v'", expectedVolume, jenkinsPod.Spec.Volumes)
Fail(fmt.Sprintf("Missing volume '+%v', actaul volumes '%+v'", expectedVolume, jenkinsPod.Spec.Volumes))
}
}
t.Log("Jenkins pod attributes are valid")
}
func verifyContainer(t *testing.T, expected corev1.Container, actual corev1.Container) {
assert.Equal(t, expected.Args, actual.Args, expected.Name, expected.Name)
assert.Equal(t, expected.Command, actual.Command, expected.Name)
assert.ElementsMatch(t, expected.Env, actual.Env, expected.Name)
assert.Equal(t, expected.EnvFrom, actual.EnvFrom, expected.Name)
assert.Equal(t, expected.Image, actual.Image, expected.Name)
assert.Equal(t, expected.ImagePullPolicy, actual.ImagePullPolicy, expected.Name)
assert.Equal(t, expected.Lifecycle, actual.Lifecycle, expected.Name)
assert.Equal(t, expected.LivenessProbe, actual.LivenessProbe, expected.Name)
assert.Equal(t, expected.Ports, actual.Ports, expected.Name)
assert.Equal(t, expected.ReadinessProbe, actual.ReadinessProbe, expected.Name)
assert.Equal(t, expected.Resources, actual.Resources, expected.Name)
assert.Equal(t, expected.SecurityContext, actual.SecurityContext, expected.Name)
assert.Equal(t, expected.WorkingDir, actual.WorkingDir, expected.Name)
func verifyContainer(expected corev1.Container, actual corev1.Container) {
Expect(actual.Args).Should(Equal(expected.Args), expected.Name)
Expect(actual.Command).Should(Equal(expected.Command), expected.Name)
Expect(actual.Env).Should(ConsistOf(expected.Env), expected.Name)
Expect(actual.EnvFrom).Should(Equal(expected.EnvFrom), expected.Name)
Expect(actual.Image).Should(Equal(expected.Image), expected.Name)
Expect(actual.ImagePullPolicy).Should(Equal(expected.ImagePullPolicy), expected.Name)
Expect(actual.Lifecycle).Should(Equal(expected.Lifecycle), expected.Name)
Expect(actual.LivenessProbe).Should(Equal(expected.LivenessProbe), expected.Name)
Expect(actual.Ports).Should(Equal(expected.Ports), expected.Name)
Expect(actual.ReadinessProbe).Should(Equal(expected.ReadinessProbe), expected.Name)
Expect(actual.Resources).Should(Equal(expected.Resources), expected.Name)
Expect(actual.SecurityContext).Should(Equal(expected.SecurityContext), expected.Name)
Expect(actual.WorkingDir).Should(Equal(expected.WorkingDir), expected.Name)
if !base.CompareContainerVolumeMounts(expected, actual) {
t.Errorf("Volume mounts are different in container '%s': expected '%+v', actual '%+v'",
expected.Name, expected.VolumeMounts, actual.VolumeMounts)
Fail(fmt.Sprintf("Volume mounts are different in container '%s': expected '%+v', actual '%+v'",
expected.Name, expected.VolumeMounts, expected.VolumeMounts))
}
}
func verifyPlugins(t *testing.T, jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha2.Jenkins) {
func verifyPlugins(jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha2.Jenkins) {
By("installing plugins in Jenkins instance")
installedPlugins, err := jenkinsClient.GetPlugins(1)
if err != nil {
t.Fatal(err)
}
Expect(err).NotTo(HaveOccurred())
for _, basePlugin := range plugins.BasePlugins() {
if found, ok := isPluginValid(installedPlugins, basePlugin); !ok {
t.Fatalf("Invalid plugin '%s', actual '%+v'", basePlugin, found)
Fail(fmt.Sprintf("Invalid plugin '%s', actual '%+v'", basePlugin, found))
}
}
for _, userPlugin := range jenkins.Spec.Master.Plugins {
plugin := plugins.Plugin{Name: userPlugin.Name, Version: userPlugin.Version}
if found, ok := isPluginValid(installedPlugins, plugin); !ok {
t.Fatalf("Invalid plugin '%s', actual '%+v'", plugin, found)
Fail(fmt.Sprintf("Invalid plugin '%s', actual '%+v'", plugin, found))
}
}
t.Log("All plugins have been installed")
}
func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (*gojenkins.Plugin, bool) {
@ -348,13 +249,15 @@ func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (*
return p, requiredPlugin.Version == p.Version
}
func verifyUserConfiguration(t *testing.T, jenkinsClient jenkinsclient.Jenkins, amountOfExecutors int, systemMessage string) {
func verifyUserConfiguration(jenkinsClient jenkinsclient.Jenkins, amountOfExecutors int, systemMessage string) {
By("configuring Jenkins by groovy scripts")
checkConfigurationViaGroovyScript := fmt.Sprintf(`
if (!new Integer(%d).equals(Jenkins.instance.numExecutors)) {
throw new Exception("Configuration via groovy scripts failed")
}`, amountOfExecutors)
logs, err := jenkinsClient.ExecuteScript(checkConfigurationViaGroovyScript)
assert.NoError(t, err, logs)
Expect(err).NotTo(HaveOccurred(), logs)
checkSecretLoaderViaGroovyScript := fmt.Sprintf(`
if (!new Integer(%d).equals(new Integer(secrets['NUMBER_OF_EXECUTORS']))) {
@ -363,30 +266,33 @@ if (!new Integer(%d).equals(new Integer(secrets['NUMBER_OF_EXECUTORS']))) {
loader := groovy.AddSecretsLoaderToGroovyScript("/var/jenkins/groovy-scripts-secrets")
logs, err = jenkinsClient.ExecuteScript(loader(checkSecretLoaderViaGroovyScript))
assert.NoError(t, err, logs)
Expect(err).NotTo(HaveOccurred(), logs)
By("configuring Jenkins by CasC")
checkConfigurationAsCode := fmt.Sprintf(`
if (!"%s".equals(Jenkins.instance.systemMessage)) {
throw new Exception("Configuration as code failed")
}`, systemMessage)
logs, err = jenkinsClient.ExecuteScript(checkConfigurationAsCode)
assert.NoError(t, err, logs)
Expect(err).NotTo(HaveOccurred(), logs)
}
func verifyServices(t *testing.T, jenkins *v1alpha2.Jenkins) {
jenkinsHTTPService := getJenkinsService(t, jenkins, "http")
jenkinsSlaveService := getJenkinsService(t, jenkins, "slave")
assert.Equal(t, intstr.IntOrString{IntVal: constants.DefaultHTTPPortInt32, Type: intstr.Int}, jenkinsHTTPService.Spec.Ports[0].TargetPort)
assert.Equal(t, intstr.IntOrString{IntVal: constants.DefaultSlavePortInt32, Type: intstr.Int}, jenkinsSlaveService.Spec.Ports[0].TargetPort)
func verifyServices(jenkins *v1alpha2.Jenkins) {
By("creating Jenkins services properly")
jenkinsHTTPService := getJenkinsService(jenkins, "http")
jenkinsSlaveService := getJenkinsService(jenkins, "slave")
Expect(jenkinsHTTPService.Spec.Ports[0].TargetPort).Should(Equal(intstr.IntOrString{IntVal: constants.DefaultHTTPPortInt32, Type: intstr.Int}))
Expect(jenkinsSlaveService.Spec.Ports[0].TargetPort).Should(Equal(intstr.IntOrString{IntVal: constants.DefaultSlavePortInt32, Type: intstr.Int}))
}
func assertMapContainsElementsFromAnotherMap(t *testing.T, expected map[string]string, actual map[string]string) {
func assertMapContainsElementsFromAnotherMap(expected map[string]string, actual map[string]string) {
for key, expectedValue := range expected {
actualValue, keyExists := actual[key]
if !keyExists {
assert.Failf(t, "key '%s' doesn't exist in map '%+v'", key, actual)
Fail(fmt.Sprintf("key '%s' doesn't exist in map '%+v'", key, actual))
continue
}
assert.Equal(t, expectedValue, actualValue, expected, actual)
Expect(actualValue).Should(Equal(expectedValue), key, expected, actual)
}
}

View File

@ -2,6 +2,8 @@
package e2e
// TODO
/*
import (
"fmt"
"os/exec"
@ -66,3 +68,4 @@ func TestDeployHelmChart(t *testing.T) {
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
waitForJenkinsUserConfigurationToComplete(t, jenkins)
}
/*

View File

@ -0,0 +1,141 @@
package e2e
import (
"fmt"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
. "github.com/onsi/ginkgo"
corev1 "k8s.io/api/core/v1"
// +kubebuilder:scaffold:imports
)
var _ = Describe("Jenkins controller configuration", func() {
const (
jenkinsCRName = e2e
numberOfExecutors = 6
numberOfExecutorsEnvName = "NUMBER_OF_EXECUTORS"
systemMessage = "Configuration as Code integration works!!!"
systemMessageEnvName = "SYSTEM_MESSAGE"
priorityClassName = ""
)
var (
namespace *corev1.Namespace
jenkins *v1alpha2.Jenkins
mySeedJob = seedJobConfig{
SeedJob: v1alpha2.SeedJob{
ID: "jenkins-operator",
CredentialID: "jenkins-operator",
JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType,
Targets: "cicd/jobs/*.jenkins",
Description: "Jenkins Operator repository",
RepositoryBranch: "master",
RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git",
PollSCM: "1 1 1 1 1",
UnstableOnDeprecation: true,
BuildPeriodically: "1 1 1 1 1",
FailOnMissingPlugin: true,
IgnoreMissingFiles: true,
//AdditionalClasspath: can fail with the seed job agent
GitHubPushTrigger: true,
},
}
groovyScripts = v1alpha2.GroovyScripts{
Customization: v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{
{
Name: userConfigurationConfigMapName,
},
},
Secret: v1alpha2.SecretRef{
Name: userConfigurationSecretName,
},
},
}
casc = v1alpha2.ConfigurationAsCode{
Customization: v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{
{
Name: userConfigurationConfigMapName,
},
},
Secret: v1alpha2.SecretRef{
Name: userConfigurationSecretName,
},
},
}
userConfigurationSecretData = map[string]string{
systemMessageEnvName: systemMessage,
numberOfExecutorsEnvName: fmt.Sprintf("%d", numberOfExecutors),
}
)
BeforeEach(func() {
namespace = createNamespace()
createUserConfigurationSecret(namespace.Name, userConfigurationSecretData)
createUserConfigurationConfigMap(namespace.Name, numberOfExecutorsEnvName, fmt.Sprintf("${%s}", systemMessageEnvName))
jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName)
createDefaultLimitsForContainersInNamespace(namespace.Name)
createKubernetesCredentialsProviderSecret(namespace.Name, mySeedJob)
})
AfterEach(func() {
destroyNamespace(namespace)
})
Context("when deploying CR to cluster", func() {
It("creates Jenkins instance and configures it", func() {
waitForJenkinsBaseConfigurationToComplete(jenkins)
verifyJenkinsMasterPodAttributes(jenkins)
verifyServices(jenkins)
jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(jenkins, namespace.Name)
defer cleanUpFunc()
verifyPlugins(jenkinsClient, jenkins)
waitForJenkinsUserConfigurationToComplete(jenkins)
verifyUserConfiguration(jenkinsClient, numberOfExecutors, systemMessage)
verifyJenkinsSeedJobs(jenkinsClient, []seedJobConfig{mySeedJob})
})
})
})
var _ = Describe("Jenkins controller priority class", func() {
const (
jenkinsCRName = "k8s-ete-priority-class-existing"
priorityClassName = "system-cluster-critical"
)
var (
namespace *corev1.Namespace
jenkins *v1alpha2.Jenkins
groovyScripts = v1alpha2.GroovyScripts{
Customization: v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{},
},
}
casc = v1alpha2.ConfigurationAsCode{
Customization: v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{},
},
}
)
BeforeEach(func() {
namespace = createNamespace()
jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, nil, groovyScripts, casc, priorityClassName)
})
AfterEach(func() {
destroyNamespace(namespace)
})
Context("when deploying CR with priority class to cluster", func() {
It("creates Jenkins instance and configures it", func() {
waitForJenkinsBaseConfigurationToComplete(jenkins)
verifyJenkinsMasterPodAttributes(jenkins)
})
})
})

View File

@ -3,25 +3,24 @@ package e2e
import (
"context"
"fmt"
"testing"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources"
"github.com/jenkinsci/kubernetes-operator/pkg/constants"
framework "github.com/operator-framework/operator-sdk/pkg/test"
"github.com/stretchr/testify/require"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
@ -29,31 +28,25 @@ const (
userConfigurationSecretName = "user-secret"
)
func getJenkins(t *testing.T, namespace, name string) *v1alpha2.Jenkins {
func getJenkins(namespace, name string) *v1alpha2.Jenkins {
jenkins := &v1alpha2.Jenkins{}
namespaceName := types.NamespacedName{Namespace: namespace, Name: name}
if err := framework.Global.Client.Get(context.TODO(), namespaceName, jenkins); err != nil {
t.Fatal(err)
}
Expect(k8sClient.Get(context.TODO(), namespaceName, jenkins)).Should(Succeed())
return jenkins
}
func getJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) *corev1.Pod {
lo := metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(),
func getJenkinsMasterPod(jenkins *v1alpha2.Jenkins) *corev1.Pod {
lo := &client.ListOptions{
LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)),
Namespace: jenkins.Namespace,
}
podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo)
if err != nil {
t.Fatal(err)
}
if len(podList.Items) != 1 {
t.Fatalf("Jenkins pod not found, pod list: %+v", podList)
}
return &podList.Items[0]
pods := &corev1.PodList{}
Expect(k8sClient.List(context.TODO(), pods, lo)).Should(Succeed())
Expect(pods.Items).Should(HaveLen(1), fmt.Sprintf("Jenkins pod not found, pod list: %+v", pods.Items))
return &pods.Items[0]
}
func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins {
func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins {
var seedJobs []v1alpha2.SeedJob
if seedJob != nil {
seedJobs = append(seedJobs, *seedJob...)
@ -90,6 +83,8 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S
InitialDelaySeconds: int32(80),
TimeoutSeconds: int32(4),
FailureThreshold: int32(10),
SuccessThreshold: int32(1),
PeriodSeconds: int32(1),
},
LivenessProbe: &corev1.Probe{
Handler: corev1.Handler{
@ -102,6 +97,8 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S
InitialDelaySeconds: int32(80),
TimeoutSeconds: int32(4),
FailureThreshold: int32(10),
SuccessThreshold: int32(1),
PeriodSeconds: int32(1),
},
VolumeMounts: []corev1.VolumeMount{
{
@ -146,25 +143,23 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S
Name: resources.GetResourceName(jenkins),
},
}
updateJenkinsCR(t, jenkins)
updateJenkinsCR(jenkins)
t.Logf("Jenkins CR %+v", *jenkins)
if err := framework.Global.Client.Create(context.TODO(), jenkins, nil); err != nil {
t.Fatal(err)
}
_, _ = fmt.Fprintf(GinkgoWriter, "Jenkins CR %+v\n", *jenkins)
Expect(k8sClient.Create(context.TODO(), jenkins)).Should(Succeed())
return jenkins
}
func createJenkinsAPIClientFromServiceAccount(t *testing.T, jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) {
t.Log("Creating Jenkins API client from service account")
func createJenkinsAPIClientFromServiceAccount(jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) {
podName := resources.GetJenkinsMasterPodName(jenkins)
clientSet, err := kubernetes.NewForConfig(framework.Global.KubeConfig)
clientSet, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
}
config := configuration.Configuration{Jenkins: jenkins, ClientSet: *clientSet, Config: framework.Global.KubeConfig}
config := configuration.Configuration{Jenkins: jenkins, ClientSet: *clientSet, Config: cfg}
r := base.New(config, jenkinsclient.JenkinsAPIConnectionSettings{})
token, _, err := r.Configuration.Exec(podName, resources.JenkinsMasterContainerName, []string{"cat", "/var/run/secrets/kubernetes.io/serviceaccount/token"})
@ -175,12 +170,12 @@ func createJenkinsAPIClientFromServiceAccount(t *testing.T, jenkins *v1alpha2.Je
return jenkinsclient.NewBearerTokenAuthorization(jenkinsAPIURL, token.String())
}
func createJenkinsAPIClientFromSecret(t *testing.T, jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) {
t.Log("Creating Jenkins API client from secret")
func createJenkinsAPIClientFromSecret(jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) {
_, _ = fmt.Fprintf(GinkgoWriter, "Creating Jenkins API client from secret\n")
adminSecret := &corev1.Secret{}
namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: resources.GetOperatorCredentialsSecretName(jenkins)}
if err := framework.Global.Client.Get(context.TODO(), namespaceName, adminSecret); err != nil {
if err := k8sClient.Get(context.TODO(), namespaceName, adminSecret); err != nil {
return nil, err
}
@ -191,19 +186,19 @@ func createJenkinsAPIClientFromSecret(t *testing.T, jenkins *v1alpha2.Jenkins, j
)
}
func verifyJenkinsAPIConnection(t *testing.T, jenkins *v1alpha2.Jenkins, namespace string) (jenkinsclient.Jenkins, func()) {
func verifyJenkinsAPIConnection(jenkins *v1alpha2.Jenkins, namespace string) (jenkinsclient.Jenkins, func()) {
By("establishing Jenkins API connection")
var service corev1.Service
err := framework.Global.Client.Get(context.TODO(), types.NamespacedName{
err := k8sClient.Get(context.TODO(), types.NamespacedName{
Namespace: jenkins.Namespace,
Name: resources.GetJenkinsHTTPServiceName(jenkins),
}, &service)
require.NoError(t, err)
Expect(err).NotTo(HaveOccurred())
podName := resources.GetJenkinsMasterPodName(jenkins)
port, cleanUpFunc, waitFunc, portForwardFunc, err := setupPortForwardToPod(t, namespace, podName, int(constants.DefaultHTTPPortInt32))
if err != nil {
t.Fatal(err)
}
port, cleanUpFunc, waitFunc, portForwardFunc, err := setupPortForwardToPod(namespace, podName, int(constants.DefaultHTTPPortInt32))
Expect(err).NotTo(HaveOccurred())
go portForwardFunc()
waitFunc()
@ -213,41 +208,33 @@ func verifyJenkinsAPIConnection(t *testing.T, jenkins *v1alpha2.Jenkins, namespa
UseNodePort: false,
}.BuildJenkinsAPIUrl(service.Name, service.Namespace, service.Spec.Ports[0].Port, service.Spec.Ports[0].NodePort)
var client jenkinsclient.Jenkins
var jenkinsClient jenkinsclient.Jenkins
if jenkins.Spec.JenkinsAPISettings.AuthorizationStrategy == v1alpha2.ServiceAccountAuthorizationStrategy {
client, err = createJenkinsAPIClientFromServiceAccount(t, jenkins, jenkinsAPIURL)
jenkinsClient, err = createJenkinsAPIClientFromServiceAccount(jenkins, jenkinsAPIURL)
} else {
client, err = createJenkinsAPIClientFromSecret(t, jenkins, jenkinsAPIURL)
jenkinsClient, err = createJenkinsAPIClientFromSecret(jenkins, jenkinsAPIURL)
}
if err != nil {
defer cleanUpFunc()
t.Fatal(err)
Fail(err.Error())
}
t.Log("I can establish connection to Jenkins API")
return client, cleanUpFunc
_, _ = fmt.Fprintf(GinkgoWriter, "I can establish connection to Jenkins API\n")
return jenkinsClient, cleanUpFunc
}
func restartJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) {
t.Log("Restarting Jenkins master pod")
jenkinsPod := getJenkinsMasterPod(t, jenkins)
err := framework.Global.Client.Delete(context.TODO(), jenkinsPod)
if err != nil {
t.Fatal(err)
}
t.Log("Jenkins master pod has been restarted")
}
/*func restartJenkinsMasterPod(jenkins *v1alpha2.Jenkins) {
_, _ = fmt.Fprintf(GinkgoWriter, "Restarting Jenkins master pod")
jenkinsPod := getJenkinsMasterPod(jenkins)
Expect(k8sClient.Delete(context.TODO(), jenkinsPod)).Should(Succeed())
_, _ = fmt.Fprintf(GinkgoWriter, "Jenkins master pod has been restarted")
}*/
func getJenkinsService(t *testing.T, jenkins *v1alpha2.Jenkins, serviceKind string) *corev1.Service {
func getJenkinsService(jenkins *v1alpha2.Jenkins, serviceKind string) *corev1.Service {
service := &corev1.Service{}
serviceName := constants.OperatorName + "-" + serviceKind + "-" + jenkins.ObjectMeta.Name
lo := metav1.ListOptions{
FieldSelector: fields.SelectorFromSet(fields.Set{"metadata.name": serviceName}).String(),
}
serviceList, err := framework.Global.KubeClient.CoreV1().Services(jenkins.Namespace).List(lo)
Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: serviceName, Namespace: jenkins.Namespace}, service)).Should(Succeed())
require.NoError(t, err)
require.Equal(t, 1, len(serviceList.Items), fmt.Sprintf("'%s' service not found", serviceName))
return &serviceList.Items[0]
return service
}

View File

@ -1,69 +0,0 @@
package e2e
import (
"flag"
"testing"
"github.com/jenkinsci/kubernetes-operator/pkg/apis"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/constants"
framework "github.com/operator-framework/operator-sdk/pkg/test"
"github.com/operator-framework/operator-sdk/pkg/test/e2eutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
jenkinsOperatorDeploymentName = constants.OperatorName
seedJobConfigurationParameterName = "seed-job-config"
)
var (
seedJobConfigurationFile *string
)
func TestMain(m *testing.M) {
seedJobConfigurationFile = flag.String(seedJobConfigurationParameterName, "", "path to seed job config")
framework.MainEntry(m)
}
func setupTest(t *testing.T) (string, *framework.Context) {
ctx := framework.NewContext(t)
err := ctx.InitializeClusterResources(nil)
if err != nil {
t.Fatalf("could not initialize cluster resources: %v", err)
}
defer func() {
showLogsIfTestHasFailed(t, ctx)
if t.Failed() && ctx != nil {
ctx.Cleanup()
}
}()
jenkinsServiceList := &v1alpha2.JenkinsList{
TypeMeta: metav1.TypeMeta{
Kind: v1alpha2.Kind,
APIVersion: v1alpha2.SchemeGroupVersion.String(),
},
}
err = framework.AddToFrameworkScheme(apis.AddToScheme, jenkinsServiceList)
if err != nil {
t.Fatalf("could not add scheme to framework scheme: %v", err)
}
namespace, err := ctx.GetOperatorNamespace()
if err != nil {
t.Fatalf("could not get namespace: %v", err)
}
t.Logf("Test namespace '%s'", namespace)
// wait for jenkins-operator to be ready
err = e2eutil.WaitForDeployment(t, framework.Global.KubeClient, namespace, jenkinsOperatorDeploymentName, 1, retryInterval, timeout)
if err != nil {
t.Fatal(err)
}
return namespace, ctx
}

View File

@ -4,17 +4,14 @@
package e2e
import (
"testing"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
)
const (
skipTestSafeRestart = false
skipTestPriorityClass = false
//skipTestSafeRestart = false
//skipTestPriorityClass = false
)
func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) {
t.Log("Update Jenkins CR")
func updateJenkinsCR(jenkins *v1alpha2.Jenkins) {
// do nothing
}

View File

@ -3,9 +3,7 @@
package e2e
import (
"testing"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources"
corev1 "k8s.io/api/core/v1"
@ -16,9 +14,7 @@ const (
skipTestPriorityClass = true
)
func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) {
t.Log("Update Jenkins CR: OpenShift")
func updateJenkinsCR(jenkins *v1alpha2.Jenkins) {
jenkins.Spec.Master.Containers[0].Image = "quay.io/openshift/origin-jenkins"
jenkins.Spec.Master.Containers[0].Command = []string{
"bash",

View File

@ -2,6 +2,8 @@
package e2e
// TODO
/*
import (
"context"
"testing"
@ -100,3 +102,4 @@ func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) {
jenkins.Spec.Master.Plugins = jenkins.Spec.Master.Plugins[0:3] // remove devoptics plugin
}
}
*/

View File

@ -1,155 +0,0 @@
package e2e
import (
"bytes"
"fmt"
"io"
"sort"
"testing"
framework "github.com/operator-framework/operator-sdk/pkg/test"
v1 "k8s.io/api/core/v1"
"k8s.io/api/events/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
var (
podLogTailLimit int64 = 15
kubernetesEventsLimit int64 = 15
// MUST match the labels in the deployment manifest: deploy/operator.yaml
operatorPodLabels = map[string]string{
"name": "jenkins-operator",
}
)
func getOperatorPod(namespace string) (*v1.Pod, error) {
listOptions := metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(operatorPodLabels).String(),
}
podList, err := framework.Global.KubeClient.CoreV1().Pods(namespace).List(listOptions)
if err != nil {
return nil, err
}
if len(podList.Items) != 1 {
return nil, fmt.Errorf("expected exactly one pod, got: '%+v'", podList)
}
return &podList.Items[0], nil
}
func getOperatorLogs(namespace string) (string, error) {
pod, err := getOperatorPod(namespace)
if err != nil {
return "", err
}
logOptions := v1.PodLogOptions{TailLines: &podLogTailLimit}
req := framework.Global.KubeClient.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &logOptions)
podLogs, err := req.Stream()
if err != nil {
return "", err
}
defer func() {
if podLogs != nil {
_ = podLogs.Close()
}
}()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
return "", err
}
logs := buf.String()
return logs, nil
}
func printOperatorLogs(t *testing.T, namespace string) {
t.Logf("Operator logs in '%s' namespace:\n", namespace)
logs, err := getOperatorLogs(namespace)
if err != nil {
t.Errorf("Couldn't get the operator pod logs: %s", err)
} else {
t.Logf("Last %d lines of log from operator:\n %s", podLogTailLimit, logs)
}
}
func getKubernetesEvents(namespace string) ([]v1beta1.Event, error) {
listOptions := metav1.ListOptions{
Limit: kubernetesEventsLimit,
}
events, err := framework.Global.KubeClient.EventsV1beta1().Events(namespace).List(listOptions)
if err != nil {
return nil, err
}
sort.SliceStable(events.Items, func(i, j int) bool {
return events.Items[i].CreationTimestamp.Unix() < events.Items[j].CreationTimestamp.Unix()
})
return events.Items, nil
}
func printKubernetesEvents(t *testing.T, namespace string) {
t.Logf("Kubernetes events in '%s' namespace:\n", namespace)
events, err := getKubernetesEvents(namespace)
if err != nil {
t.Errorf("Couldn't get kubernetes events: %s", err)
} else {
t.Logf("Last %d events from kubernetes:\n", kubernetesEventsLimit)
for _, event := range events {
t.Logf("%+v\n\n", event)
}
}
}
func getKubernetesPods(namespace string) (*v1.PodList, error) {
return framework.Global.KubeClient.CoreV1().Pods(namespace).List(metav1.ListOptions{})
}
func printKubernetesPods(t *testing.T, namespace string) {
t.Logf("All pods in '%s' namespace:\n", namespace)
podList, err := getKubernetesPods(namespace)
if err != nil {
t.Errorf("Couldn't get kubernetes pods: %s", err)
}
for _, pod := range podList.Items {
t.Logf("%+v\n\n", pod)
}
}
func showLogsIfTestHasFailed(t *testing.T, ctx *framework.Context) {
namespace, err := ctx.GetOperatorNamespace()
if err != nil {
t.Fatalf("Failed to get '%s' namespace", err)
}
if t.Failed() {
t.Log("Test failed. Bellow here you can check logs:")
printKubernetesEvents(t, namespace)
printKubernetesPods(t, namespace)
printOperatorLogs(t, namespace)
}
}
func showLogsAndCleanup(t *testing.T, ctx *framework.Context) {
namespace, err := ctx.GetOperatorNamespace()
if err != nil {
t.Fatalf("Failed to get '%s' namespace", err)
}
showLogsIfTestHasFailed(t, ctx)
ctx.Cleanup()
if err = waitUntilNamespaceDestroyed(namespace); err != nil {
t.Fatalf("Failed to wait for namespace until destroyed '%s'", err)
}
}

View File

@ -7,9 +7,9 @@ import (
"net/url"
"os"
"strings"
"testing"
framework "github.com/operator-framework/operator-sdk/pkg/test"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -49,11 +49,9 @@ func getFreePort() (int, error) {
return l.Addr().(*net.TCPAddr).Port, nil
}
func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int) (port int, cleanUpFunc func(), waitFunc func(), portForwardFunc func(), err error) {
func setupPortForwardToPod(namespace, podName string, podPort int) (port int, cleanUpFunc func(), waitFunc func(), portForwardFunc func(), err error) {
port, err = getFreePort()
if err != nil {
t.Fatal(err)
}
Expect(err).NotTo(HaveOccurred())
stream := genericclioptions.IOStreams{
In: os.Stdin,
@ -68,7 +66,7 @@ func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int)
readyCh := make(chan struct{})
req := portForwardToPodRequest{
config: framework.Global.KubeConfig,
config: cfg,
pod: v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
@ -83,9 +81,9 @@ func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int)
}
waitFunc = func() {
t.Log("Waiting for the port-forward.")
_, _ = fmt.Fprintf(GinkgoWriter, "Waiting for the port-forward.\n")
<-readyCh
t.Log("The port-forward is established.")
_, _ = fmt.Fprintf(GinkgoWriter, "The port-forward is established.\n")
}
portForwardFunc = func() {
@ -96,7 +94,7 @@ func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int)
}
cleanUpFunc = func() {
t.Log("Closing port-forward")
_, _ = fmt.Fprintf(GinkgoWriter, "Closing port-forward\n")
close(stopCh)
}

View File

@ -1,5 +1,7 @@
package e2e
// TODO
/*
import (
"context"
"testing"
@ -109,3 +111,4 @@ func checkBaseConfigurationCompleteTimeIsNotSet(t *testing.T, jenkins *v1alpha2.
t.Fatalf("Status.BaseConfigurationCompletedTime is set after pod restart, status %+v", jenkinsStatus.Status)
}
}
*/

View File

@ -1,5 +1,7 @@
package e2e
// TODO
/*
import (
"context"
"testing"
@ -214,3 +216,4 @@ func resetJenkinsStatus(t *testing.T, jenkins *v1alpha2.Jenkins) {
err := framework.Global.Client.Update(context.TODO(), jenkins)
require.NoError(t, err)
}
*/

View File

@ -2,24 +2,19 @@ package e2e
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"testing"
"text/template"
"time"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/internal/render"
"github.com/jenkinsci/kubernetes-operator/internal/try"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/user/seedjobs"
"github.com/jenkinsci/kubernetes-operator/pkg/constants"
framework "github.com/operator-framework/operator-sdk/pkg/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -32,11 +27,12 @@ type seedJobConfig struct {
PrivateKey string `json:"privateKey,omitempty"`
}
type seedJobsConfig struct {
/*type seedJobsConfig struct {
SeedJobs []seedJobConfig `json:"seedJobs,omitempty"`
}
}*/
func TestSeedJobs(t *testing.T) {
// FIXME
/*func TestSeedJobs(t *testing.T) {
t.Parallel()
if seedJobConfigurationFile == nil || len(*seedJobConfigurationFile) == 0 {
t.Skipf("Skipping test because flag '%+v' is not set", seedJobConfigurationFile)
@ -81,8 +77,9 @@ func loadSeedJobsConfig(t *testing.T) seedJobsConfig {
assert.NotEmpty(t, result.SeedJobs)
return result
}
*/
func createKubernetesCredentialsProviderSecret(t *testing.T, namespace string, config seedJobConfig) {
func createKubernetesCredentialsProviderSecret(namespace string, config seedJobConfig) {
if config.JenkinsCredentialType == v1alpha2.NoJenkinsCredentialCredentialType {
return
}
@ -105,31 +102,32 @@ func createKubernetesCredentialsProviderSecret(t *testing.T, namespace string, c
},
}
err := framework.Global.Client.Create(context.TODO(), secret, nil)
require.NoError(t, err)
Expect(k8sClient.Create(context.TODO(), secret)).Should(Succeed())
}
func verifyJenkinsSeedJobs(t *testing.T, jenkinsClient jenkinsclient.Jenkins, seedJobs []seedJobConfig) {
func verifyJenkinsSeedJobs(jenkinsClient jenkinsclient.Jenkins, seedJobs []seedJobConfig) {
By("creating Jenkins jobs by seed jobs")
var err error
for _, seedJob := range seedJobs {
if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType {
err = verifyIfJenkinsCredentialExists(jenkinsClient, seedJob.CredentialID)
assert.NoErrorf(t, err, "Jenkins credential '%s' not created for seed job ID '%s'", seedJob.CredentialID, seedJob.ID)
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins credential '%s' not created for seed job ID '%s'", seedJob.CredentialID, seedJob.ID))
}
verifySeedJobProperties(t, jenkinsClient, seedJob)
verifySeedJobProperties(jenkinsClient, seedJob)
for _, requireJobName := range seedJob.JobNames {
err = try.Until(func() (end bool, err error) {
_, err = jenkinsClient.GetJob(requireJobName)
return err == nil, err
}, time.Second*2, time.Minute*2)
assert.NoErrorf(t, err, "Jenkins job '%s' not created by seed job ID '%s'", requireJobName, seedJob.ID)
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins job '%s' not created by seed job ID '%s'", requireJobName, seedJob.ID))
}
}
}
func verifySeedJobProperties(t *testing.T, jenkinsClient jenkinsclient.Jenkins, seedJob seedJobConfig) {
func verifySeedJobProperties(jenkinsClient jenkinsclient.Jenkins, seedJob seedJobConfig) {
data := struct {
ID string
CredentialID string
@ -163,10 +161,10 @@ func verifySeedJobProperties(t *testing.T, jenkinsClient jenkinsclient.Jenkins,
}
groovyScript, err := render.Render(verifySeedJobPropertiesGroovyScriptTemplate, data)
assert.NoError(t, err, groovyScript)
Expect(err).NotTo(HaveOccurred(), groovyScript)
logs, err := jenkinsClient.ExecuteScript(groovyScript)
assert.NoError(t, err, logs, groovyScript)
Expect(err).NotTo(HaveOccurred(), logs, groovyScript)
}
func verifyIfJenkinsCredentialExists(jenkinsClient jenkinsclient.Jenkins, credentialName string) error {

View File

@ -1,33 +1,27 @@
/*
Copyright 2021.
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 controllers
package e2e
import (
"context"
"fmt"
"path/filepath"
"testing"
"time"
jenkinsiov1alpha2 "github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/controllers"
jenkinsClient "github.com/jenkinsci/kubernetes-operator/pkg/client"
"github.com/jenkinsci/kubernetes-operator/pkg/constants"
"github.com/jenkinsci/kubernetes-operator/pkg/event"
"github.com/jenkinsci/kubernetes-operator/pkg/notifications"
e "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
@ -37,11 +31,8 @@ import (
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var (
//cfg *rest.Config
cfg *rest.Config
k8sClient client.Client
testEnv *envtest.Environment
)
@ -55,45 +46,55 @@ func TestAPIs(t *testing.T) {
}
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(false)))
By("bootstrapping test environment")
useExistingCluster := true
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
//BinaryAssetsDirectory: path.Join("..", "..", "testbin", "bin"),
UseExistingCluster: &useExistingCluster,
}
cfg, err := testEnv.Start()
var err error
cfg, err = testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())
err = jenkinsiov1alpha2.AddToScheme(scheme.Scheme)
err = v1alpha2.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
//setup manager
// setup manager
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme.Scheme,
})
Expect(err).NotTo(HaveOccurred())
//setup controller
// setup controller
clientSet, err := kubernetes.NewForConfig(cfg)
Expect(err).NotTo(HaveOccurred())
// setup events
events, err := event.New(cfg, constants.OperatorName)
Expect(err).NotTo(HaveOccurred())
notificationEvents := make(chan e.Event)
go notifications.Listen(notificationEvents, events, k8sClient)
// validate jenkins API connection
jenkinsAPIConnectionSettings := jenkinsClient.JenkinsAPIConnectionSettings{}
jenkinsAPIConnectionSettings := jenkinsClient.JenkinsAPIConnectionSettings{
Hostname: "192.168.99.100", // FIXME minikube ip
UseNodePort: true,
}
err = (&JenkinsReconciler{
err = (&controllers.JenkinsReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
JenkinsAPIConnectionSettings: jenkinsAPIConnectionSettings,
ClientSet: *clientSet,
Config: *cfg,
NotificationEvents: &notificationEvents,
KubernetesClusterDomain: "",
KubernetesClusterDomain: "cluster.local",
}).SetupWithManager(k8sManager)
Expect(err).NotTo(HaveOccurred())
@ -105,7 +106,6 @@ var _ = BeforeSuite(func(done Done) {
k8sClient = k8sManager.GetClient()
Expect(k8sClient).NotTo(BeNil())
close(done)
}, 60)
var _ = AfterSuite(func() {
@ -113,3 +113,39 @@ var _ = AfterSuite(func() {
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())
})
func createNamespace() *corev1.Namespace {
By("creating temporary namespace")
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%d", time.Now().Unix()),
},
}
Expect(k8sClient.Create(context.TODO(), namespace)).Should(Succeed())
return namespace
}
func destroyNamespace(namespace *corev1.Namespace) {
By("deleting temporary namespace")
Expect(k8sClient.Delete(context.TODO(), namespace)).Should(Succeed())
Eventually(func() (bool, error) {
namespaces := &corev1.NamespaceList{}
err := k8sClient.List(context.TODO(), namespaces)
if err != nil {
return false, err
}
exists := false
for _, namespaceItem := range namespaces.Items {
if namespaceItem.Name == namespace.Name {
exists = true
break
}
}
return !exists, nil
}, time.Second*120, time.Second).Should(BeTrue())
}

View File

@ -1,134 +0,0 @@
package e2e
import (
goctx "context"
"net/http"
"testing"
"time"
"golang.org/x/net/context"
"github.com/jenkinsci/kubernetes-operator/internal/try"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client"
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources"
framework "github.com/operator-framework/operator-sdk/pkg/test"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var (
retryInterval = time.Second * 5
timeout = time.Second * 60
)
// checkConditionFunc is used to check if a condition for the jenkins CR is set
type checkConditionFunc func(*v1alpha2.Jenkins, error) bool
func waitForJenkinsBaseConfigurationToComplete(t *testing.T, jenkins *v1alpha2.Jenkins) {
t.Log("Waiting for Jenkins base configuration to complete")
_, err := WaitUntilJenkinsConditionSet(retryInterval, 170, jenkins, func(jenkins *v1alpha2.Jenkins, err error) bool {
t.Logf("Current Jenkins status: '%+v', error '%s'", jenkins.Status, err)
return err == nil && jenkins.Status.BaseConfigurationCompletedTime != nil
})
if err != nil {
t.Fatal(err)
}
t.Log("Jenkins pod is running")
// update jenkins CR because Operator sets default values
namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name}
err = framework.Global.Client.Get(goctx.TODO(), namespacedName, jenkins)
assert.NoError(t, err)
}
func waitForRecreateJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) {
err := wait.Poll(retryInterval, 30*retryInterval, func() (bool, error) {
lo := metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(),
}
podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo)
if err != nil {
return false, err
}
if len(podList.Items) != 1 {
return false, nil
}
return podList.Items[0].DeletionTimestamp == nil, nil
})
if err != nil {
t.Fatal(err)
}
t.Log("Jenkins pod has been recreated")
}
func waitForJenkinsUserConfigurationToComplete(t *testing.T, jenkins *v1alpha2.Jenkins) {
t.Log("Waiting for Jenkins user configuration to complete")
_, err := WaitUntilJenkinsConditionSet(retryInterval, 110, jenkins, func(jenkins *v1alpha2.Jenkins, err error) bool {
t.Logf("Current Jenkins status: '%+v', error '%s'", jenkins.Status, err)
return err == nil && jenkins.Status.UserConfigurationCompletedTime != nil
})
if err != nil {
t.Fatal(err)
}
t.Log("Jenkins pod is running")
}
func waitForJenkinsSafeRestart(t *testing.T, jenkinsClient jenkinsclient.Jenkins) {
err := try.Until(func() (end bool, err error) {
status, err := jenkinsClient.Poll()
if err != nil {
return false, err
}
if status != http.StatusOK {
return false, errors.Wrap(err, "couldn't poll data from Jenkins API")
}
return true, nil
}, time.Second, time.Second*70)
require.NoError(t, err)
}
// WaitUntilJenkinsConditionSet retries until the specified condition check becomes true for the jenkins CR
func WaitUntilJenkinsConditionSet(retryInterval time.Duration, retries int, jenkins *v1alpha2.Jenkins, checkCondition checkConditionFunc) (*v1alpha2.Jenkins, error) {
jenkinsStatus := &v1alpha2.Jenkins{}
err := wait.Poll(retryInterval, time.Duration(retries)*retryInterval, func() (bool, error) {
namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name}
err := framework.Global.Client.Get(goctx.TODO(), namespacedName, jenkinsStatus)
return checkCondition(jenkinsStatus, err), nil
})
if err != nil {
return nil, err
}
return jenkinsStatus, nil
}
func waitUntilNamespaceDestroyed(namespace string) error {
err := try.Until(func() (bool, error) {
var namespaceList v1.NamespaceList
err := framework.Global.Client.List(context.TODO(), &namespaceList, &client.ListOptions{})
if err != nil {
return true, err
}
exists := false
for _, namespaceItem := range namespaceList.Items {
if namespaceItem.Name == namespace {
exists = true
break
}
}
return !exists, nil
}, time.Second, time.Second*120)
return err
}

87
test/e2e/wait_test.go Normal file
View File

@ -0,0 +1,87 @@
package e2e
import (
"context"
"fmt"
"time"
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
var (
retryInterval = time.Second * 5
)
func waitForJenkinsBaseConfigurationToComplete(jenkins *v1alpha2.Jenkins) {
By("waiting for Jenkins base configuration phase to complete")
Eventually(func() (*metav1.Time, error) {
actualJenkins := &v1alpha2.Jenkins{}
err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, actualJenkins)
if err != nil {
return nil, err
}
return actualJenkins.Status.BaseConfigurationCompletedTime, nil
}, time.Duration(170)*retryInterval, retryInterval).Should(Not(BeNil()))
_, _ = fmt.Fprintf(GinkgoWriter, "Jenkins pod is running\n")
// update jenkins CR because Operator sets default values
namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name}
Expect(k8sClient.Get(context.TODO(), namespacedName, jenkins)).Should(Succeed())
}
/*func waitForRecreateJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) {
err := wait.Poll(retryInterval, 30*retryInterval, func() (bool, error) {
lo := metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(),
}
podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo)
if err != nil {
return false, err
}
if len(podList.Items) != 1 {
return false, nil
}
return podList.Items[0].DeletionTimestamp == nil, nil
})
if err != nil {
t.Fatal(err)
}
_, _ = fmt.Fprintf(GinkgoWriter,"Jenkins pod has been recreated")
}*/
func waitForJenkinsUserConfigurationToComplete(jenkins *v1alpha2.Jenkins) {
By("waiting for Jenkins user configuration phase to complete")
Eventually(func() (*metav1.Time, error) {
actualJenkins := &v1alpha2.Jenkins{}
err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, actualJenkins)
if err != nil {
return nil, err
}
return actualJenkins.Status.UserConfigurationCompletedTime, nil
}, time.Duration(110)*retryInterval, retryInterval).Should(Not(BeNil()))
_, _ = fmt.Fprintf(GinkgoWriter, "Jenkins instance is up and ready\n")
}
/*func waitForJenkinsSafeRestart(t *testing.T, jenkinsClient jenkinsclient.Jenkins) {
err := try.Until(func() (end bool, err error) {
status, err := jenkinsClient.Poll()
if err != nil {
return false, err
}
if status != http.StatusOK {
return false, errors.Wrap(err, "couldn't poll data from Jenkins API")
}
return true, nil
}, time.Second, time.Second*70)
require.NoError(t, err)
}*/

View File

@ -1,5 +1,4 @@
# Set POSIX sh for maximum interoperability
SHELL := /bin/sh
SHELL := /bin/bash
PATH := $(GOPATH)/bin:$(PATH)
OSFLAG :=
@ -60,7 +59,7 @@ GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static"
GOOSARCHES = linux/amd64
PACKAGES = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor)
PACKAGES_FOR_UNIT_TESTS = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor | grep -v e2e)
PACKAGES_FOR_UNIT_TESTS = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor | grep -v e2e | grep -v controllers)
# Run all the e2e tests by default
E2E_TEST_SELECTOR ?= .*
@ -94,3 +93,5 @@ GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif
ENVTEST_ASSETS_DIR=$(shell pwd)/testbin