You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ho...@apache.org on 2021/09/28 17:51:41 UTC
[solr-operator] branch main updated: Upgrade to use Kubebuilder 3
(#321)
This is an automated email from the ASF dual-hosted git repository.
houston pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-operator.git
The following commit(s) were added to refs/heads/main by this push:
new 86107e4 Upgrade to use Kubebuilder 3 (#321)
86107e4 is described below
commit 86107e4c3d2945a15d7f08bbb081eb1418bd6ead
Author: Houston Putman <ho...@apache.org>
AuthorDate: Tue Sep 28 13:51:33 2021 -0400
Upgrade to use Kubebuilder 3 (#321)
Minimum version of Kubernetes supported is now v1.19
---
.github/workflows/tests.yaml | 10 -
.gitignore | 1 +
Makefile | 239 +--
NOTICE | 2 +
PROJECT | 35 +-
README.md | 17 +
api/v1beta1/groupversion_info.go | 6 +-
api/v1beta1/solrcloud_types.go | 173 ++-
api/v1beta1/zz_generated.deepcopy.go | 66 +-
build/Dockerfile | 6 +-
build/build.sh | 15 +-
config/certmanager/certificate.yaml | 39 -
config/certmanager/kustomizeconfig.yaml | 31 -
config/crd/bases/solr.apache.org_solrclouds.yaml | 6 +-
config/crd/kustomization.yaml | 16 +-
config/crd/kustomizeconfig.yaml | 6 +-
config/crd/patches/cainjection_in_solrbackups.yaml | 5 +-
config/crd/patches/cainjection_in_solrclouds.yaml | 5 +-
.../cainjection_in_solrprometheusexporters.yaml | 5 +-
config/crd/patches/webhook_in_solrbackups.yaml | 21 +-
config/crd/patches/webhook_in_solrclouds.yaml | 21 +-
.../webhook_in_solrprometheusexporters.yaml | 21 +-
config/default/kustomization.yaml | 40 +-
config/default/manager_auth_proxy_patch.yaml | 17 +-
...ebhook_patch.yaml => manager_config_patch.yaml} | 22 +-
config/default/webhookcainjection_patch.yaml | 30 -
.../kustomization_base.yaml} | 10 +-
config/manager/manager.yaml | 47 +-
.../solr_operator_config.yaml} | 22 +-
.../kustomization.yaml} | 3 +
.../monitor.yaml} | 33 +-
.../auth_proxy_client_clusterrole.yaml} | 15 +-
config/rbac/auth_proxy_role.yaml | 14 +-
config/rbac/auth_proxy_role_binding.yaml | 8 +-
config/rbac/auth_proxy_service.yaml | 12 +-
config/rbac/kustomization.yaml | 14 +-
config/rbac/leader_election_role.yaml | 11 +-
config/rbac/leader_election_role_binding.yaml | 6 +-
config/rbac/role.yaml | 24 +-
config/rbac/role_binding.yaml | 2 +-
config/rbac/service_account.yaml | 2 +-
...ction_role.yaml => solrbackup_editor_role.yaml} | 30 +-
...proxy_role.yaml => solrbackup_viewer_role.yaml} | 21 +-
...ection_role.yaml => solrcloud_editor_role.yaml} | 30 +-
..._proxy_role.yaml => solrcloud_viewer_role.yaml} | 21 +-
...aml => solrprometheusexporter_editor_role.yaml} | 30 +-
...aml => solrprometheusexporter_viewer_role.yaml} | 26 +-
config/webhook/kustomizeconfig.yaml | 40 -
version/version.go => controllers/common.go | 18 +-
controllers/controller_utils_test.go | 674 ++++++---
controllers/solrbackup_controller.go | 103 +-
controllers/solrbackup_controller_test.go | 74 -
controllers/solrcloud_controller.go | 371 +++--
.../solrcloud_controller_basic_auth_test.go | 294 ++++
.../solrcloud_controller_externaldns_test.go | 1179 +++++++--------
controllers/solrcloud_controller_ingress_test.go | 1270 +++++++---------
controllers/solrcloud_controller_storage_test.go | 730 +++------
controllers/solrcloud_controller_test.go | 1519 ++++++++-----------
controllers/solrcloud_controller_tls_test.go | 1563 +++++++++-----------
controllers/solrcloud_controller_zk_test.go | 1026 ++++++-------
controllers/solrprometheusexporter_controller.go | 340 +++--
.../solrprometheusexporter_controller_test.go | 1504 ++++++-------------
.../solrprometheusexporter_controller_tls_test.go | 487 ++++++
controllers/suite_test.go | 137 +-
controllers/util/backup_util.go | 19 +-
controllers/util/common.go | 30 +-
controllers/util/solr_update_util_test.go | 3 +
controllers/util/solr_util.go | 55 +-
controllers/util/zk_util.go | 37 +-
controllers/util/zk_util_test.go | 17 +-
.../zk_api}/groupversion_info.go | 10 +-
controllers/zk_api/zkcluster.go | 788 ++++++++++
controllers/zk_api/zkstatus.go | 223 +++
controllers/zk_api/zz_generated.deepcopy.go | 486 ++++++
dependency_licenses.csv | 11 +-
docs/README.md | 17 +
docs/development.md | 17 +
docs/local_tutorial.md | 17 +
docs/release-instructions.md | 17 +
docs/running-the-operator.md | 17 +
docs/solr-backup/README.md | 17 +
docs/solr-cloud/README.md | 17 +
docs/solr-cloud/dependencies.md | 17 +
docs/solr-cloud/managed-updates.md | 17 +
docs/solr-cloud/solr-cloud-crd.md | 17 +
docs/solr-prometheus-exporter/README.md | 17 +
docs/upgrade-notes.md | 47 +-
docs/upgrading-to-apache.md | 17 +
go.mod | 25 +-
go.sum | 1050 +------------
hack/check_license.sh | 4 +-
hack/headers/header.go.md | 16 +
hack/install_dependencies.sh | 80 -
hack/zk-operator/check-version.sh | 4 +-
hack/zk-operator/update-crd.sh | 2 +-
helm/solr-operator/Chart.yaml | 23 +-
helm/solr-operator/crds/crds.yaml | 6 +-
helm/solr-operator/templates/deployment.yaml | 18 +
helm/solr-operator/templates/role.yaml | 24 +-
helm/solr/Chart.yaml | 9 +-
main.go | 88 +-
version/version.go | 4 +-
versions.props | 1 +
103 files changed, 7982 insertions(+), 7867 deletions(-)
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 16ece48..20d297d 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -38,16 +38,6 @@ jobs:
with:
go-version: ${{ matrix.go }}
- - uses: actions/cache@v2
- with:
- path: |
- /usr/local/bin/kustomize
- /usr/local/kubebuilder
- ~/go/bin
- key: ${{ runner.os }}-build-dependencies-${{ hashFiles('hack/install_dependencies') }}
- restore-keys: |
- ${{ runner.os }}-build-dependencies
-
# Cleanup & Install dependencies
- run: make clean
- run: make install-dependencies
diff --git a/.gitignore b/.gitignore
index efe19ad..bf3ff90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@
*.so
*.dylib
bin
+testbin
release-artifacts
.DS_Store
diff --git a/Makefile b/Makefile
index 6cef885..9fec979 100644
--- a/Makefile
+++ b/Makefile
@@ -13,16 +13,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
+
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
-CRD_OPTIONS ?= "crd:trivialVersions=true"
+CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false"
# Image URL to use all building/pushing image targets
NAME ?= solr-operator
REPOSITORY ?= $(or $(NAMESPACE:%/=%), apache)
IMG = $(REPOSITORY)/$(NAME)
# Default tag from info in version/version.go
-VERSION_SUFFIX = $(shell cat version/version.go | grep -E 'VersionSuffix([[:space:]]+)string' | grep -o '["''].*["'']' | xargs)
-TMP_VERSION = $(shell cat version/version.go | grep -E 'Version([[:space:]]+)string' | grep -o '["''].*["'']' | xargs)
+VERSION_SUFFIX = $(shell cat version/version.go | grep -E 'VersionSuffix([[:space:]]+)=' | grep -o '["''].*["'']' | xargs)
+TMP_VERSION = $(shell cat version/version.go | grep -E 'Version([[:space:]]+)=' | grep -o '["''].*["'']' | xargs)
ifneq (,$(VERSION_SUFFIX))
VERSION = $(TMP_VERSION)-$(VERSION_SUFFIX)
else
@@ -33,13 +35,16 @@ GIT_SHA = $(shell git rev-parse --short HEAD)
GOOS = $(shell go env GOOS)
ARCH = $(shell go env GOARCH)
+KUSTOMIZE_VERSION=4.3.0
+CONTROLLER_GEN_VERSION=v0.5.0
+
GO111MODULE ?= on
-# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
-ifeq (,$(shell go env GOBIN))
-GOBIN=$(shell go env GOPATH)/bin
-else
-GOBIN=$(shell go env GOBIN)
-endif
+
+# Setting SHELL to bash allows bash commands to be executed by recipes.
+# This is a requirement for 'setup-envtest.sh' in the test target.
+# Options are set to exit when a recipe line exits non-zero or a piped command fails.
+SHELL = /usr/bin/env bash -o pipefail
+.SHELLFLAGS = -ec
all: generate
@@ -50,97 +55,121 @@ version:
tag:
@echo $(TAG)
-###
-# Setup
-###
+##@ General
+
+# The help target prints out all targets with their descriptions organized
+# beneath their categories. The categories are represented by '##@' and the
+# target descriptions by '##'. The awk commands is responsible for reading the
+# entire set of makefiles included in this invocation, looking for lines of the
+# file as xyz: ## something, and then pretty-format the target and help. Then,
+# if there's a line with ##@ something, that gets pretty-printed as a category.
+# More info on the usage of ANSI control characters for terminal formatting:
+# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
+# More info on the awk command:
+# http://linuxcommand.org/lc3_adv_awk.php
+
+help: ## Display this help.
+ @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
-clean:
+##@ Setup
+
+clean: ## Clean build directories across the project
rm -rf ./bin
+ rm -rf ./testbin
rm -rf ./release-artifacts
rm -rf ./helm/*/charts ./helm/*/Chart.lock
rm -rf ./cover.out
rm -rf ./generated-check
-mod-tidy:
+mod-tidy: ## Make sure the go mod files are up-to-date
export GO111MODULE=on; go mod tidy
-.install-dependencies:
- ./hack/install_dependencies.sh
-install-dependencies: .install-dependencies mod-tidy
+##@ Development
-build-release-artifacts: clean prepare docker-build
- ./hack/release/artifacts/create_artifacts.sh -d $(or $(ARTIFACTS_DIR),release-artifacts)
+prepare: fmt generate manifests fetch-licenses-list mod-tidy ## Prepare the code for a PR or merge, should ensure that "make check" succeeds
-###
-# Building
-###
+manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects
+ rm -rf generated-check/api
+ $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=solr-operator-role webhook paths="./api/..." paths="./controllers/." output:rbac:artifacts:config=$(or $(TMP_CONFIG_OUTPUT_DIRECTORY),config)/rbac output:crd:artifacts:config=$(or $(TMP_CONFIG_OUTPUT_DIRECTORY),config)/crd/bases
+ CONFIG_DIRECTORY=$(or $(TMP_CONFIG_OUTPUT_DIRECTORY),config) HELM_DIRECTORY=$(or $(TMP_HELM_OUTPUT_DIRECTORY),helm) ./hack/config/copy_crds_roles_helm.sh
+ CONFIG_DIRECTORY=$(or $(TMP_CONFIG_OUTPUT_DIRECTORY),config) ./hack/config/add_crds_roles_headers.sh
+
+generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations
+ $(CONTROLLER_GEN) object:headerFile="./hack/headers/header.go.txt" paths=$(or $(TMP_API_DIRECTORY),"./...")
-# Prepare the code for a PR or merge
-prepare: fmt generate manifests fetch-licenses-list mod-tidy
+fmt: ## Run go fmt against code.
+ go fmt ./...
-# Build solr-operator binary
-build: generate
- BIN=solr-operator GIT_SHA=${GIT_SHA} ARCH=${ARCH} GOOS=${GOOS} ./build/build.sh
+fetch-licenses-list: go-licenses ## Fetch the list of license types
+ $(GO_LICENSES) csv . | grep -v -E "solr-operator" | sort > dependency_licenses.csv
-# Run against the configured Kubernetes cluster in ~/.kube/config
-run: generate fmt manifests
+fetch-licenses-full: go-licenses ## Fetch all licenses
+ $(GO_LICENSES) save . --save_path licenses --force
+
+build-release-artifacts: clean prepare docker-build ## Build all release artifacts for the Solr Operator
+ ./hack/release/artifacts/create_artifacts.sh -d $(or $(ARTIFACTS_DIR),release-artifacts)
+
+##@ Build
+
+build: generate ## Build manager binary.
+ GIT_SHA=${GIT_SHA} ARCH=${ARCH} GOOS=${GOOS} ./build/build.sh
+
+run: manifests generate fmt vet ## Run a controller from your host
go run ./main.go
-# Install CRDs into a cluster
-install: manifests
- kubectl replace -k config/crd || kubectl create -k config/crd
- kubectl replace -f config/dependencies || kubectl create -f config/dependencies
+##@ Docker
-# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
-deploy: manifests install
- cd config/manager && touch kustomization.yaml && kustomize edit add resource manager.yaml && kustomize edit set image apache/solr-operator=${IMG}:${TAG}
- kubectl apply -k config/default
+docker-build: ## Build the docker image for the Solr Operator
+ docker build --build-arg GIT_SHA=$(GIT_SHA) . -t solr-operator -f ./build/Dockerfile
+ docker tag solr-operator $(IMG):$(TAG)
+ docker tag solr-operator $(IMG):latest
+
+docker-push: ## Push the docker image for the Solr Operator
+ docker push $(IMG):$(TAG)
+ docker push $(IMG):latest
-# Generate code
-generate:
- controller-gen object:headerFile=./hack/headers/header.go.txt paths=$(or $(TMP_API_DIRECTORY),"./...")
+##@ Deployment
-# Generate manifests e.g. CRD, RBAC etc.
-manifests:
- rm -rf generated-check/api
- controller-gen $(CRD_OPTIONS) rbac:roleName=solr-operator-role webhook paths="./..." output:rbac:artifacts:config=$(or $(TMP_CONFIG_OUTPUT_DIRECTORY),config)/rbac output:crd:artifacts:config=$(or $(TMP_CONFIG_OUTPUT_DIRECTORY),config)/crd/bases
- CONFIG_DIRECTORY=$(or $(TMP_CONFIG_OUTPUT_DIRECTORY),config) HELM_DIRECTORY=$(or $(TMP_HELM_OUTPUT_DIRECTORY),helm) ./hack/config/copy_crds_roles_helm.sh
- CONFIG_DIRECTORY=$(or $(TMP_CONFIG_OUTPUT_DIRECTORY),config) ./hack/config/add_crds_roles_headers.sh
+install: manifests ## Install CRDs into the K8s cluster specified in ~/.kube/config
+ kubectl replace -k config/crd || kubectl create -k config/crd
+ kubectl replace -f config/dependencies || kubectl create -f config/dependencies
-# Run go fmt against code
-fmt:
- go fmt ./...
+uninstall: ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config
+ kubectl delete -k config/crd
+ kubectl delete -f config/dependencies
-# Fetch the list of license types
-fetch-licenses-list:
- go-licenses csv . | grep -v -E "solr-operator" | sort > dependency_licenses.csv
+prepare-deploy-kustomize: kustomize
+ cd config/manager && \
+ cp kustomization_base.yaml kustomization.yaml && \
+ $(KUSTOMIZE) edit add resource manager.yaml && \
+ $(KUSTOMIZE) edit set image apache/solr-operator=${IMG}:${TAG}
-# Fetch all licenses
-fetch-licenses-full:
- go-licenses save . --save_path licenses --force
+deploy: manifests prepare-deploy-kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config
+ kubectl apply -k config/default
+
+undeploy: prepare-deploy-kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config
+ kubectl delete -k config/default
-###
-# Tests and Checks
-###
+##@ Tests and Checks
-check: lint test
+check: lint test ## Do all checks, lints and tests for the Solr Operator
-lint: check-zk-op-version check-mod vet check-format check-licenses check-manifests check-generated check-helm
+lint: check-zk-op-version check-mod vet check-format check-licenses check-manifests check-generated check-helm ## Lint the codebase to check for formatting and correctness
-check-format:
+check-format: ## Check the codebase to make sure it adheres to golang standards
./hack/check_format.sh
-check-licenses:
+check-licenses: go-licenses ## Ensure the licenses for dependencies are valid and the license list file is up-to-date
@echo "Check license headers on necessary files"
./hack/check_license.sh
@echo "Check list of dependency licenses"
- go-licenses csv . 2>/dev/null | grep -v -E "solr-operator" | sort | diff dependency_licenses.csv -
+ $(GO_LICENSES) csv . 2>/dev/null | grep -v -E "solr-operator" | sort | diff dependency_licenses.csv -
-check-zk-op-version:
+check-zk-op-version: ## Ensure the zookeeper-operator version is standard throughout the codebase
./hack/zk-operator/check-version.sh
-check-manifests:
+check-manifests: ## Ensure the manifest files (CRDs, RBAC, etc) are up-to-date across the project, including the helm charts
rm -rf generated-check
mkdir -p generated-check
cp -r helm generated-check/helm
@@ -150,7 +179,7 @@ check-manifests:
diff --recursive config generated-check/config
diff --recursive helm generated-check/helm
-check-generated:
+check-generated: ## Ensure the generated code is up-to-date
rm -rf generated-check
mkdir -p generated-check
cp -r api generated-check/api
@@ -158,12 +187,7 @@ check-generated:
@echo "Check to make sure the generated code is up to date"
diff --recursive api generated-check/api
-check-helm:
- helm lint helm/*
- # Check that the ArtifactHub Metadata is correct
- docker run --rm --name artifact-hub-check -v "${PWD}/helm:/etc/helm" artifacthub/ah ah lint -k helm -p /etc/helm
-
-check-mod:
+check-mod: ## Ensure the go mod files are up-to-date
rm -rf generated-check
mkdir -p generated-check/existing-go-mod generated-check/go-mod
cp go.* generated-check/existing-go-mod/.
@@ -174,44 +198,63 @@ check-mod:
diff go.mod generated-check/go-mod/go.mod
diff go.sum generated-check/go-mod/go.sum
-# Run go vet against code
-vet:
+check-helm: ## Ensure the helm charts lint successfully and can be read by ArtifactHub
+ helm lint helm/*
+ # Check that the ArtifactHub Metadata is correct
+ docker run --rm --name artifact-hub-check -v "${PWD}/helm:/etc/helm" artifacthub/ah ah lint -k helm -p /etc/helm
+
+vet: ## Run go vet against code.
go vet ./...
-check-git:
- @echo "Check to make sure the repo does not have uncommitted code"
+check-git: ## Check to make sure the repo does not have uncommitted code
git diff --exit-code
-# Run tests
-test:
- go test ./... -coverprofile cover.out
-
+ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
+test: manifests generate ## Run the unit tests
+ 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.8.3/hack/setup-envtest.sh
+ source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); GINKGO_EDITOR_INTEGRATION=true go test ./... -coverprofile cover.out
-###
-# Helm
-###
+##@ Helm
# Build the dependencies for all Helm charts
-helm-dependency-build:
+helm-dependency-build: ## Build the dependencies for all Helm charts
helm dependency build helm/solr-operator
helm dependency build helm/solr
-# Deploy the current version of the Solr Operator via its helm chart
-helm-deploy-operator: helm-dependency-build docker-build
+
+helm-deploy-operator: helm-dependency-build docker-build ## Deploy the current version of the Solr Operator via its helm chart
helm install solr-operator helm/solr-operator --set image.version=$(TAG) --set image.repository=$(IMG) --set image.pullPolicy=Never
-###
-# Docker Building & Pushing
-###
+##@ Dependencies
-# Build the docker image for the operator
-docker-build:
- docker build --build-arg GIT_SHA=$(GIT_SHA) . -t solr-operator -f ./build/Dockerfile
- docker tag solr-operator $(IMG):$(TAG)
- docker tag solr-operator $(IMG):latest
+install-dependencies: controller-gen kustomize go-licenses ## Install necessary dependencies for building and testing the Solr Operator
-# Push the docker image for the operator
-docker-push:
- docker push $(IMG):$(TAG)
- docker push $(IMG):latest
+CONTROLLER_GEN = $(PROJECT_DIR)/bin/controller-gen
+controller-gen: ## Download controller-gen locally if necessary.
+ $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION))
+
+KUSTOMIZE = $(PROJECT_DIR)/bin/kustomize
+# Uncomment and remove other lines once kustomize supports "go install"
+# https://github.com/kubernetes-sigs/kustomize/issues/3618
+# $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v4@v$(KUSTOMIZE_VERSION))
+kustomize: ## Download kustomize locally if necessary.
+ifeq (,$(wildcard $(KUSTOMIZE)))
+ @echo "Downloading kustomize"
+ (cd /tmp && curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash -s -- $(KUSTOMIZE_VERSION) $(PROJECT_DIR)/bin)
+endif
+
+GO_LICENSES = $(PROJECT_DIR)/bin/go-licenses
+go-licenses: ## Download go-licenses locally if necessary.
+ $(call go-get-tool,$(GO_LICENSES),github.com/google/go-licenses@latest)
+
+# go-get-tool will 'go get' any package $2 and install it to $1.
+PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
+define go-get-tool
+@[ -f $(1) ] || { \
+set -e ;\
+echo "Downloading $(2)" ;\
+GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
+}
+endef
diff --git a/NOTICE b/NOTICE
index e3db3cb..9ccae0b 100644
--- a/NOTICE
+++ b/NOTICE
@@ -38,4 +38,6 @@ Copyright (c) 2020 Dell Inc., or its subsidiaries. All Rights Reserved.
The Zookeeper Operator is used as an optional dependency, however the ZookeeperCluster CRD is used within the repository.
config/dependencies/zookeeper_cluster_crd.yaml
+The API code was also copied to the following directory
+ controllers/zk_api
This was taken from https://github.com/pravega/zookeeper-operator (version v0.2.12)
diff --git a/PROJECT b/PROJECT
index d161e4e..36fa076 100644
--- a/PROJECT
+++ b/PROJECT
@@ -1,13 +1,34 @@
-version: "2"
-domain: apache.org
+domain: solr.apache.org
+layout:
+- go.kubebuilder.io/v3
+projectName: operator-3
repo: github.com/apache/solr-operator
resources:
-- group: solr
- version: v1beta1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: solr.apache.org
+ group: solr
kind: SolrCloud
-- group: solr
+ path: github.com/apache/solr-operator/api/v1beta1
+ version: v1beta1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: solr.apache.org
+ group: solr
+ kind: SolrPrometheusExporter
+ path: github.com/apache/solr-operator/api/v1beta1
version: v1beta1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: solr.apache.org
+ group: solr
kind: SolrBackup
-- group: solr
+ path: github.com/apache/solr-operator/api/v1beta1
version: v1beta1
- kind: SolrPrometheusExporter
+version: "3"
diff --git a/README.md b/README.md
index cfb76e1..07a9765 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,20 @@
+<!--
+ 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.
+ -->
+
# Solr Operator
[](https://github.com/apache/solr-operator/releases)
[](http://www.apache.org/licenses/LICENSE-2.0.html)
diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go
index b209235..9dfc906 100644
--- a/api/v1beta1/groupversion_info.go
+++ b/api/v1beta1/groupversion_info.go
@@ -15,9 +15,9 @@
* limitations under the License.
*/
-// Package v1beta1 contains API Schema definitions for the solr v1beta1 API group
-// +kubebuilder:object:generate=true
-// +groupName=solr.apache.org
+// Package v1beta1 contains API Schema definitions for the solr.apache.org v1beta1 API group
+//+kubebuilder:object:generate=true
+//+groupName=solr.apache.org
package v1beta1
import (
diff --git a/api/v1beta1/solrcloud_types.go b/api/v1beta1/solrcloud_types.go
index 0b3c532..1522401 100644
--- a/api/v1beta1/solrcloud_types.go
+++ b/api/v1beta1/solrcloud_types.go
@@ -19,13 +19,14 @@ package v1beta1
import (
"fmt"
- zk "github.com/pravega/zookeeper-operator/pkg/apis/zookeeper/v1beta1"
+ "strconv"
+ "strings"
+
+ "k8s.io/apimachinery/pkg/util/intstr"
+
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/intstr"
- "strconv"
- "strings"
)
const (
@@ -43,11 +44,11 @@ const (
DefaultBusyBoxImageRepo = "library/busybox"
DefaultBusyBoxImageVersion = "1.28.0-glibc"
- DefaultZkReplicas = int32(3)
- DefaultZkStorage = "5Gi"
- DefaultZkRepo = "pravega/zookeeper"
- DefaultZkVersion = ""
- DefaultZkVolumeReclaimPolicy = zk.VolumeReclaimPolicyRetain
+ DefaultZkReplicas = int32(3)
+ DefaultZkStorage = "5Gi"
+ DefaultZkRepo = "pravega/zookeeper"
+ DefaultZkVersion = ""
+ DefaultZkVolumeReclaimPolicy VolumeReclaimPolicy = "Retain"
SolrTechnologyLabel = "solr-cloud"
ZookeeperTechnologyLabel = "zookeeper"
@@ -154,7 +155,7 @@ func (spec *SolrCloudSpec) withDefaults() (changed bool) {
spec.SolrGCTune = DefaultSolrGCTune
}
- changed = spec.SolrAddressability.withDefaults() || changed
+ changed = spec.SolrAddressability.withDefaults(spec.SolrTLS != nil) || changed
changed = spec.UpdateStrategy.withDefaults() || changed
@@ -423,7 +424,7 @@ type SolrAddressabilityOptions struct {
PodPort int `json:"podPort,omitempty"`
// CommonServicePort defines the port to have the common Solr service listen on.
- // Defaults to 80
+ // Defaults to 80 (when not using TLS) or 443 (when using TLS)
// +optional
CommonServicePort int `json:"commonServicePort,omitempty"`
@@ -433,9 +434,9 @@ type SolrAddressabilityOptions struct {
KubeDomain string `json:"kubeDomain,omitempty"`
}
-func (opts *SolrAddressabilityOptions) withDefaults() (changed bool) {
+func (opts *SolrAddressabilityOptions) withDefaults(usesTLS bool) (changed bool) {
if opts.External != nil {
- changed = opts.External.withDefaults()
+ changed = opts.External.withDefaults(usesTLS)
}
if opts.PodPort == 0 {
changed = true
@@ -443,7 +444,11 @@ func (opts *SolrAddressabilityOptions) withDefaults() (changed bool) {
}
if opts.CommonServicePort == 0 {
changed = true
- opts.CommonServicePort = 80
+ if usesTLS {
+ opts.CommonServicePort = 443
+ } else {
+ opts.CommonServicePort = 80
+ }
}
return changed
}
@@ -497,7 +502,7 @@ type ExternalAddressability struct {
// If using method=Ingress, your ingress controller is required to listen on this port.
// If your ingress controller is not listening on the podPort, then this option is required for solr to be addressable via an Ingress.
//
- // Defaults to 80 if HideNodes=false and method=Ingress, otherwise this is optional.
+ // Defaults to 80 (without TLS) or 443 (with TLS) if HideNodes=false and method=Ingress, otherwise this is optional.
// +optional
NodePortOverride int `json:"nodePortOverride,omitempty"`
@@ -529,16 +534,20 @@ const (
LoadBalancer ExternalAddressabilityMethod = "LoadBalancer"
)
-func (opts *ExternalAddressability) withDefaults() (changed bool) {
+func (opts *ExternalAddressability) withDefaults(usesTLS bool) (changed bool) {
// You can't use an externalAddress for Solr Nodes if the Nodes are hidden externally
if opts.UseExternalAddress && (opts.HideNodes || opts.IngressTLSTerminationSecret != "") {
changed = true
opts.UseExternalAddress = false
}
- // If the Ingress method is used, default the nodePortOverride to 80, since that is the port that most ingress controllers listen on.
+ // If the Ingress method is used, default the nodePortOverride to 80 or 443, since that is the port that most ingress controllers listen on.
if !opts.HideNodes && opts.Method == Ingress && opts.NodePortOverride == 0 {
changed = true
- opts.NodePortOverride = 80
+ if usesTLS {
+ opts.NodePortOverride = 443
+ } else {
+ opts.NodePortOverride = 80
+ }
}
// If a headless service is used, aka not using individual node services, then a nodePortOverride is not allowed.
if !opts.UsesIndividualNodeServices() && opts.NodePortOverride > 0 {
@@ -684,12 +693,12 @@ type ZookeeperSpec struct {
// PersistentVolumeClaimSpec and VolumeReclaimPolicy can be specified in here.
// At anypoint only one of Persistence or Ephemeral should be present in the manifest
// +optional
- Persistence *zk.Persistence `json:"persistence,omitempty"`
+ Persistence *ZKPersistence `json:"persistence,omitempty"`
// Ephemeral is the configuration which helps create ephemeral storage
// At anypoint only one of Persistence or Ephemeral should be present in the manifest
// +optional
- Ephemeral *zk.Ephemeral `json:"ephemeral,omitempty"`
+ Ephemeral *ZKEphemeral `json:"ephemeral,omitempty"`
// Pod resources for zookeeper pod
// +optional
@@ -712,7 +721,129 @@ type ZookeeperSpec struct {
// ZooKeeper ACL to use when connecting with ZK for reading operations.
// This ACL should have READ permission in the given chRoot.
// +optional
- Config zk.ZookeeperConfig `json:"config,omitempty"`
+ Config ZookeeperConfig `json:"config,omitempty"`
+}
+
+type ZKPersistence struct {
+ // VolumeReclaimPolicy is a zookeeper operator configuration. If it's set to Delete,
+ // the corresponding PVCs will be deleted by the operator when zookeeper cluster is deleted.
+ // The default value is Retain.
+ VolumeReclaimPolicy VolumeReclaimPolicy `json:"reclaimPolicy,omitempty"`
+ // PersistentVolumeClaimSpec is the spec to describe PVC for the container
+ // This field is optional. If no PVC is specified default persistentvolume
+ // will get created.
+ PersistentVolumeClaimSpec corev1.PersistentVolumeClaimSpec `json:"spec,omitempty"`
+ // Annotations specifies the annotations to attach to pvc the operator
+ // creates.
+ Annotations map[string]string `json:"annotations,omitempty"`
+}
+
+type ZKEphemeral struct {
+ //EmptyDirVolumeSource is optional and this will create the emptydir volume
+ //It has two parameters Medium and SizeLimit which are optional as well
+ //Medium specifies What type of storage medium should back this directory.
+ //SizeLimit specifies Total amount of local storage required for this EmptyDir volume.
+ EmptyDirVolumeSource corev1.EmptyDirVolumeSource `json:"emptydirvolumesource,omitempty"`
+}
+
+// ZookeeperConfig is the current configuration of each Zookeeper node, which
+// sets these values in the config-map
+type ZookeeperConfig struct {
+ // InitLimit is the amount of time, in ticks, to allow followers to connect
+ // and sync to a leader.
+ //
+ // Default value is 10.
+ InitLimit int `json:"initLimit,omitempty"`
+
+ // TickTime is the length of a single tick, which is the basic time unit used
+ // by Zookeeper, as measured in milliseconds
+ //
+ // The default value is 2000.
+ TickTime int `json:"tickTime,omitempty"`
+
+ // SyncLimit is the amount of time, in ticks, to allow followers to sync with
+ // Zookeeper.
+ //
+ // The default value is 2.
+ SyncLimit int `json:"syncLimit,omitempty"`
+
+ // Clients can submit requests faster than ZooKeeper can process them, especially
+ // if there are a lot of clients. Zookeeper will throttle Clients so that requests
+ // won't exceed global outstanding limit.
+ //
+ // The default value is 1000
+ GlobalOutstandingLimit int `json:"globalOutstandingLimit,omitempty"`
+
+ // To avoid seeks ZooKeeper allocates space in the transaction log file in
+ // blocks of preAllocSize kilobytes
+ //
+ // The default value is 64M
+ PreAllocSize int `json:"preAllocSize,omitempty"`
+
+ // ZooKeeper records its transactions using snapshots and a transaction log
+ // The number of transactions recorded in the transaction log before a snapshot
+ // can be taken is determined by snapCount
+ //
+ // The default value is 100,000
+ SnapCount int `json:"snapCount,omitempty"`
+
+ // Zookeeper maintains an in-memory list of last committed requests for fast
+ // synchronization with followers
+ //
+ // The default value is 500
+ CommitLogCount int `json:"commitLogCount,omitempty"`
+
+ // Snapshot size limit in Kb
+ //
+ // The defult value is 4GB
+ SnapSizeLimitInKb int `json:"snapSizeLimitInKb,omitempty"`
+
+ // Limits the total number of concurrent connections that can be made to a
+ //zookeeper server
+ //
+ // The defult value is 0, indicating no limit
+ MaxCnxns int `json:"maxCnxns,omitempty"`
+
+ // Limits the number of concurrent connections that a single client, identified
+ // by IP address, may make to a single member of the ZooKeeper ensemble.
+ //
+ // The default value is 60
+ MaxClientCnxns int `json:"maxClientCnxns,omitempty"`
+
+ // The minimum session timeout in milliseconds that the server will allow the
+ // client to negotiate
+ //
+ // The default value is 4000
+ MinSessionTimeout int `json:"minSessionTimeout,omitempty"`
+
+ // The maximum session timeout in milliseconds that the server will allow the
+ // client to negotiate.
+ //
+ // The default value is 40000
+ MaxSessionTimeout int `json:"maxSessionTimeout,omitempty"`
+
+ // Retain the snapshots according to retain count
+ //
+ // The default value is 3
+ AutoPurgeSnapRetainCount int `json:"autoPurgeSnapRetainCount,omitempty"`
+
+ // The time interval in hours for which the purge task has to be triggered
+ //
+ // Disabled by default
+ AutoPurgePurgeInterval int `json:"autoPurgePurgeInterval,omitempty"`
+
+ // QuorumListenOnAllIPs when set to true the ZooKeeper server will listen for
+ // connections from its peers on all available IP addresses, and not only the
+ // address configured in the server list of the configuration file. It affects
+ // the connections handling the ZAB protocol and the Fast Leader Election protocol.
+ //
+ // The default value is false.
+ QuorumListenOnAllIPs bool `json:"quorumListenOnAllIPs,omitempty"`
+
+ // key-value map of additional zookeeper configuration parameters
+ // +kubebuilder:pruning:PreserveUnknownFields
+ // +optional
+ AdditionalConfig map[string]string `json:"additionalConfig,omitempty"`
}
func (z *ZookeeperSpec) WithDefaults() (changed bool) {
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index 062bd35..c8f06df 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -22,7 +22,6 @@
package v1beta1
import (
- zookeeperv1beta1 "github.com/pravega/zookeeper-operator/pkg/apis/zookeeper/v1beta1"
"k8s.io/api/core/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
@@ -1326,6 +1325,45 @@ func (in *VolumePersistenceSource) DeepCopy() *VolumePersistenceSource {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ZKEphemeral) DeepCopyInto(out *ZKEphemeral) {
+ *out = *in
+ in.EmptyDirVolumeSource.DeepCopyInto(&out.EmptyDirVolumeSource)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZKEphemeral.
+func (in *ZKEphemeral) DeepCopy() *ZKEphemeral {
+ if in == nil {
+ return nil
+ }
+ out := new(ZKEphemeral)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ZKPersistence) DeepCopyInto(out *ZKPersistence) {
+ *out = *in
+ in.PersistentVolumeClaimSpec.DeepCopyInto(&out.PersistentVolumeClaimSpec)
+ if in.Annotations != nil {
+ in, out := &in.Annotations, &out.Annotations
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZKPersistence.
+func (in *ZKPersistence) DeepCopy() *ZKPersistence {
+ if in == nil {
+ return nil
+ }
+ out := new(ZKPersistence)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ZookeeperACL) DeepCopyInto(out *ZookeeperACL) {
*out = *in
}
@@ -1341,6 +1379,28 @@ func (in *ZookeeperACL) DeepCopy() *ZookeeperACL {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ZookeeperConfig) DeepCopyInto(out *ZookeeperConfig) {
+ *out = *in
+ if in.AdditionalConfig != nil {
+ in, out := &in.AdditionalConfig, &out.AdditionalConfig
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZookeeperConfig.
+func (in *ZookeeperConfig) DeepCopy() *ZookeeperConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(ZookeeperConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ZookeeperConnectionInfo) DeepCopyInto(out *ZookeeperConnectionInfo) {
*out = *in
if in.ExternalConnectionString != nil {
@@ -1452,12 +1512,12 @@ func (in *ZookeeperSpec) DeepCopyInto(out *ZookeeperSpec) {
}
if in.Persistence != nil {
in, out := &in.Persistence, &out.Persistence
- *out = new(zookeeperv1beta1.Persistence)
+ *out = new(ZKPersistence)
(*in).DeepCopyInto(*out)
}
if in.Ephemeral != nil {
in, out := &in.Ephemeral, &out.Ephemeral
- *out = new(zookeeperv1beta1.Ephemeral)
+ *out = new(ZKEphemeral)
(*in).DeepCopyInto(*out)
}
in.ZookeeperPod.DeepCopyInto(&out.ZookeeperPod)
diff --git a/build/Dockerfile b/build/Dockerfile
index bf93b73..6c61ad3 100644
--- a/build/Dockerfile
+++ b/build/Dockerfile
@@ -17,8 +17,8 @@ WORKDIR /workspace
ARG GO111MODULE=on
# Download necessary libraries
-RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0; \
- go install github.com/google/go-licenses@latest
+RUN GOBIN=$(pwd)/bin go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0; \
+ GOBIN=$(pwd)/bin go install github.com/google/go-licenses@latest
# Copy the Go Modules manifests
COPY go.mod go.sum ./
@@ -63,6 +63,6 @@ WORKDIR /
COPY --from=builder workspace/bin/solr-operator .
COPY --from=builder workspace/LICENSE workspace/NOTICE etc/licenses/
COPY --from=builder workspace/licenses etc/licenses/dependencies
-USER nonroot:nonroot
+USER 65532:65532
ENTRYPOINT ["/solr-operator"]
\ No newline at end of file
diff --git a/build/build.sh b/build/build.sh
index c35afe6..f02a7b0 100755
--- a/build/build.sh
+++ b/build/build.sh
@@ -19,17 +19,18 @@ set -o errexit
set -o nounset
set -o pipefail
-if [ -z "${ARCH}" ]; then
- echo "ARCH must be set"
- exit 1
+if [ -z "${ARCH:-}" ]; then
+ ARCH=$(go env GOARCH)
fi
-if [ -z "${BIN}" ]; then
- echo "BIN must be set"
- exit 1
+if [ -z "${GOOS:-}" ]; then
+ GOOS=$(go env GOOS)
+fi
+if [ -z "${BIN:-}" ]; then
+ BIN="solr-operator"
fi
export CGO_ENABLED=0
export GOARCH="${ARCH}"
-export GOOS=${GOOS:-}
+export GOOS="${GOOS:-}"
go build \
-ldflags "-X 'github.com/apache/solr-operator/version.GitSHA=${GIT_SHA}' -X 'github.com/apache/solr-operator/version.BuildTime=$(date)'" \
diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml
deleted file mode 100644
index 092ab68..0000000
--- a/config/certmanager/certificate.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-# 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.
-
-# The following manifests contain a self-signed issuer CR and a certificate CR.
-# More document can be found at https://docs.cert-manager.io
-apiVersion: certmanager.k8s.io/v1alpha1
-kind: Issuer
-metadata:
- name: selfsigned-issuer
- namespace: system
-spec:
- selfSigned: {}
----
-apiVersion: certmanager.k8s.io/v1alpha1
-kind: Certificate
-metadata:
- name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml
- namespace: system
-spec:
- # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize
- commonName: $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc
- dnsNames:
- - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local
- issuerRef:
- kind: Issuer
- name: selfsigned-issuer
- secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize
diff --git a/config/certmanager/kustomizeconfig.yaml b/config/certmanager/kustomizeconfig.yaml
deleted file mode 100644
index 8611a1b..0000000
--- a/config/certmanager/kustomizeconfig.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-# 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.
-
-# This configuration is for teaching kustomize how to update name ref and var substitution
-nameReference:
-- kind: Issuer
- group: certmanager.k8s.io
- fieldSpecs:
- - kind: Certificate
- group: certmanager.k8s.io
- path: spec/issuerRef/name
-
-varReference:
-- kind: Certificate
- group: certmanager.k8s.io
- path: spec/commonName
-- kind: Certificate
- group: certmanager.k8s.io
- path: spec/dnsNames
diff --git a/config/crd/bases/solr.apache.org_solrclouds.yaml b/config/crd/bases/solr.apache.org_solrclouds.yaml
index 08af67a..2f80d77 100644
--- a/config/crd/bases/solr.apache.org_solrclouds.yaml
+++ b/config/crd/bases/solr.apache.org_solrclouds.yaml
@@ -5562,7 +5562,7 @@ spec:
description: Customize how Solr is addressed both internally and externally in Kubernetes.
properties:
commonServicePort:
- description: CommonServicePort defines the port to have the common Solr service listen on. Defaults to 80
+ description: CommonServicePort defines the port to have the common Solr service listen on. Defaults to 80 (when not using TLS) or 443 (when using TLS)
type: integer
external:
description: External defines the way in which this SolrCloud nodes should be made addressable externally, from outside the Kubernetes cluster. If none is provided, the Solr Cloud will not be made addressable externally.
@@ -5591,7 +5591,7 @@ spec:
- ExternalDNS
type: string
nodePortOverride:
- description: "NodePortOverride defines the port to have all Solr node service(s) listen on and advertise itself as if advertising through an Ingress or LoadBalancer. This overrides the default usage of the podPort. \n This is option is only used when HideNodes=false, otherwise the the port each Solr Node will advertise itself with the podPort. This option is also unavailable with the ExternalDNS method. \n If using method=Ingress, your ingress controller is requir [...]
+ description: "NodePortOverride defines the port to have all Solr node service(s) listen on and advertise itself as if advertising through an Ingress or LoadBalancer. This overrides the default usage of the podPort. \n This is option is only used when HideNodes=false, otherwise the the port each Solr Node will advertise itself with the podPort. This option is also unavailable with the ExternalDNS method. \n If using method=Ingress, your ingress controller is requir [...]
type: integer
useExternalAddress:
description: "Use the external address to advertise the SolrNode, defaults to false. \n If false, the external address will be available, however Solr (and clients using the CloudSolrClient in SolrJ) will only be aware of the internal URLs. If true, Solr will startup with the hostname of the external address. \n NOTE: This option cannot be true when hideNodes is set to true. So it will be auto-set to false if that is the case."
@@ -6050,8 +6050,8 @@ spec:
reclaimPolicy:
description: VolumeReclaimPolicy is a zookeeper operator configuration. If it's set to Delete, the corresponding PVCs will be deleted by the operator when zookeeper cluster is deleted. The default value is Retain.
enum:
- - Delete
- Retain
+ - Delete
type: string
spec:
description: PersistentVolumeClaimSpec is the spec to describe PVC for the container This field is optional. If no PVC is specified default persistentvolume will get created.
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index e0aed34..8887f8b 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -18,25 +18,25 @@
# It should be run by config/default
resources:
- bases/solr.apache.org_solrclouds.yaml
-- bases/solr.apache.org_solrbackups.yaml
- bases/solr.apache.org_solrprometheusexporters.yaml
-# +kubebuilder:scaffold:crdkustomizeresource
+- bases/solr.apache.org_solrbackups.yaml
+#+kubebuilder:scaffold:crdkustomizeresource
-patchesStrategicMerge: []
+patchesStrategicMerge:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
# patches here are for enabling the conversion webhook for each CRD
#- patches/webhook_in_solrclouds.yaml
-#- patches/webhook_in_solrbackups.yaml
#- patches/webhook_in_solrprometheusexporters.yaml
-# +kubebuilder:scaffold:crdkustomizewebhookpatch
+#- patches/webhook_in_solrbackups.yaml
+#+kubebuilder:scaffold:crdkustomizewebhookpatch
# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
# patches here are for enabling the CA injection for each CRD
#- patches/cainjection_in_solrclouds.yaml
-#- patches/cainjection_in_solrbackups.yaml
#- patches/cainjection_in_solrprometheusexporters.yaml
-# +kubebuilder:scaffold:crdkustomizecainjectionpatch
+#- patches/cainjection_in_solrbackups.yaml
+#+kubebuilder:scaffold:crdkustomizecainjectionpatch
# the following config is for teaching kustomize how to do kustomization for CRDs.
configurations:
-- kustomizeconfig.yaml
\ No newline at end of file
+- kustomizeconfig.yaml
diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml
index 428556d..4c3210e 100644
--- a/config/crd/kustomizeconfig.yaml
+++ b/config/crd/kustomizeconfig.yaml
@@ -19,13 +19,15 @@ nameReference:
version: v1
fieldSpecs:
- kind: CustomResourceDefinition
+ version: v1
group: apiextensions.k8s.io
- path: spec/conversion/webhookClientConfig/service/name
+ path: spec/conversion/webhook/clientConfig/service/name
namespace:
- kind: CustomResourceDefinition
+ version: v1
group: apiextensions.k8s.io
- path: spec/conversion/webhookClientConfig/service/namespace
+ path: spec/conversion/webhook/clientConfig/service/namespace
create: false
varReference:
diff --git a/config/crd/patches/cainjection_in_solrbackups.yaml b/config/crd/patches/cainjection_in_solrbackups.yaml
index 265d90b..12f8834 100644
--- a/config/crd/patches/cainjection_in_solrbackups.yaml
+++ b/config/crd/patches/cainjection_in_solrbackups.yaml
@@ -14,10 +14,9 @@
# limitations under the License.
# The following patch adds a directive for certmanager to inject CA into the CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: solrbackups.solr.apache.org
diff --git a/config/crd/patches/cainjection_in_solrclouds.yaml b/config/crd/patches/cainjection_in_solrclouds.yaml
index 20c7d43..9cadcc1 100644
--- a/config/crd/patches/cainjection_in_solrclouds.yaml
+++ b/config/crd/patches/cainjection_in_solrclouds.yaml
@@ -14,10 +14,9 @@
# limitations under the License.
# The following patch adds a directive for certmanager to inject CA into the CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: solrclouds.solr.apache.org
diff --git a/config/crd/patches/cainjection_in_solrprometheusexporters.yaml b/config/crd/patches/cainjection_in_solrprometheusexporters.yaml
index 4d6a001..8899fb4 100644
--- a/config/crd/patches/cainjection_in_solrprometheusexporters.yaml
+++ b/config/crd/patches/cainjection_in_solrprometheusexporters.yaml
@@ -14,10 +14,9 @@
# limitations under the License.
# The following patch adds a directive for certmanager to inject CA into the CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: solrprometheusexporters.solr.apache.org
diff --git a/config/crd/patches/webhook_in_solrbackups.yaml b/config/crd/patches/webhook_in_solrbackups.yaml
index cf6eaf4..7972cd7 100644
--- a/config/crd/patches/webhook_in_solrbackups.yaml
+++ b/config/crd/patches/webhook_in_solrbackups.yaml
@@ -13,20 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# The following patch enables conversion webhook for CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: solrbackups.solr.apache.org
spec:
conversion:
strategy: Webhook
- webhookClientConfig:
- # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
- # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
- caBundle: Cg==
- service:
- namespace: system
- name: webhook-service
- path: /convert
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/config/crd/patches/webhook_in_solrclouds.yaml b/config/crd/patches/webhook_in_solrclouds.yaml
index 8d9ee4f..62e6e8b 100644
--- a/config/crd/patches/webhook_in_solrclouds.yaml
+++ b/config/crd/patches/webhook_in_solrclouds.yaml
@@ -13,20 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# The following patch enables conversion webhook for CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: solrclouds.solr.apache.org
spec:
conversion:
strategy: Webhook
- webhookClientConfig:
- # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
- # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
- caBundle: Cg==
- service:
- namespace: system
- name: webhook-service
- path: /convert
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/config/crd/patches/webhook_in_solrprometheusexporters.yaml b/config/crd/patches/webhook_in_solrprometheusexporters.yaml
index 3f210db..8ee1d80 100644
--- a/config/crd/patches/webhook_in_solrprometheusexporters.yaml
+++ b/config/crd/patches/webhook_in_solrprometheusexporters.yaml
@@ -13,20 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# The following patch enables conversion webhook for CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: solrprometheusexporters.solr.apache.org
spec:
conversion:
strategy: Webhook
- webhookClientConfig:
- # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
- # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
- caBundle: Cg==
- service:
- namespace: system
- name: webhook-service
- path: /convert
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml
index 4ce1eae..26844ca 100644
--- a/config/default/kustomization.yaml
+++ b/config/default/kustomization.yaml
@@ -14,14 +14,14 @@
# limitations under the License.
# Adds namespace to all resources.
-namespace: default
+namespace: solr-operator
# Value of this field is prepended to the
# names of all resources, e.g. a deployment named
# "wordpress" becomes "alices-wordpress".
# Note that it should also match with the prefix (text before '-') of the namespace
# field above.
-# namePrefix: solr-operator-
+# namePrefix: solr-operator
# Labels to add to all resources and selectors.
#commonLabels:
@@ -30,24 +30,28 @@ namespace: default
bases:
- ../rbac
- ../manager
-# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml
+# [CRD] This is managed by 'make install'
+#- ../crd
+# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
+# crd/kustomization.yaml
#- ../webhook
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
#- ../certmanager
+# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
+#- ../prometheus
patchesStrategicMerge:
- # Protect the /metrics endpoint by putting it behind auth.
- # Only one of manager_auth_proxy_patch.yaml and
- # manager_prometheus_metrics_patch.yaml should be enabled.
-#- manager_auth_proxy_patch.yaml
- # If you want your controller-manager to expose the /metrics
- # endpoint w/o any authn/z, uncomment the following line and
- # comment manager_auth_proxy_patch.yaml.
- # Only one of manager_auth_proxy_patch.yaml and
- # manager_prometheus_metrics_patch.yaml should be enabled.
-- manager_prometheus_metrics_patch.yaml
+# Protect the /metrics endpoint by putting it behind auth.
+# If you want your controller-manager to expose the /metrics
+# endpoint w/o any authn/z, please comment the following line.
+- manager_auth_proxy_patch.yaml
-# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml
+# Mount the controller config file for loading manager configurations
+# through a ComponentConfig type
+#- manager_config_patch.yaml
+
+# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
+# crd/kustomization.yaml
#- manager_webhook_patch.yaml
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
@@ -61,16 +65,16 @@ vars:
#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
# objref:
# kind: Certificate
-# group: certmanager.k8s.io
-# version: v1alpha1
+# group: cert-manager.io
+# version: v1
# name: serving-cert # this name should match the one in certificate.yaml
# fieldref:
# fieldpath: metadata.namespace
#- name: CERTIFICATE_NAME
# objref:
# kind: Certificate
-# group: certmanager.k8s.io
-# version: v1alpha1
+# group: cert-manager.io
+# version: v1
# name: serving-cert # this name should match the one in certificate.yaml
#- name: SERVICE_NAMESPACE # namespace of the service
# objref:
diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml
index 9d53a61..9e92386 100644
--- a/config/default/manager_auth_proxy_patch.yaml
+++ b/config/default/manager_auth_proxy_patch.yaml
@@ -13,19 +13,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# This patch inject a sidecar container which is a HTTP proxy for the controller manager,
-# it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
+# This patch inject a sidecar container which is a HTTP proxy for the
+# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
apiVersion: apps/v1
kind: Deployment
metadata:
- name: controller-manager
- namespace: system
+ name: solr-operator
+ namespace: solr-operator
spec:
template:
spec:
containers:
- name: kube-rbac-proxy
- image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.0
+ image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0
args:
- "--secure-listen-address=0.0.0.0:8443"
- "--upstream=http://127.0.0.1:8080/"
@@ -34,7 +34,8 @@ spec:
ports:
- containerPort: 8443
name: https
- - name: manager
+ - name: solr-operator
args:
- - "--metrics-addr=127.0.0.1:8080"
- - "--enable-leader-election"
+ - "--health-probe-bind-address=:8081"
+ - "--metrics-bind-address=127.0.0.1:8080"
+ - "--leader-elect"
diff --git a/config/default/manager_webhook_patch.yaml b/config/default/manager_config_patch.yaml
similarity index 73%
rename from config/default/manager_webhook_patch.yaml
rename to config/default/manager_config_patch.yaml
index 39e68ee..027c1bd 100644
--- a/config/default/manager_webhook_patch.yaml
+++ b/config/default/manager_config_patch.yaml
@@ -16,22 +16,20 @@
apiVersion: apps/v1
kind: Deployment
metadata:
- name: solr-operator
+ name: solr-operator
+ namespace: solr-operator
spec:
template:
spec:
containers:
- name: solr-operator
- ports:
- - containerPort: 9443
- name: webhook-server
- protocol: TCP
+ args:
+ - "--config=solr_operator_config.yaml"
volumeMounts:
- - mountPath: /tmp/k8s-webhook-server/serving-certs
- name: cert
- readOnly: true
+ - name: solr-operator-config
+ mountPath: /solr_operator_config.yaml
+ subPath: solr_operator_config.yaml
volumes:
- - name: cert
- secret:
- defaultMode: 420
- secretName: webhook-server-secret
+ - name: solr-operator-config
+ configMap:
+ name: solr-operator-config
diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml
deleted file mode 100644
index c6e7625..0000000
--- a/config/default/webhookcainjection_patch.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-# 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.
-
-# This patch add annotation to admission webhook config and
-# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize.
-apiVersion: admissionregistration.k8s.io/v1beta1
-kind: MutatingWebhookConfiguration
-metadata:
- name: mutating-webhook-configuration
- annotations:
- certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
----
-apiVersion: admissionregistration.k8s.io/v1beta1
-kind: ValidatingWebhookConfiguration
-metadata:
- name: validating-webhook-configuration
- annotations:
- certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
diff --git a/config/certmanager/kustomization.yaml b/config/manager/kustomization_base.yaml
similarity index 84%
rename from config/certmanager/kustomization.yaml
rename to config/manager/kustomization_base.yaml
index af7f093..0bf0ff9 100644
--- a/config/certmanager/kustomization.yaml
+++ b/config/manager/kustomization_base.yaml
@@ -13,8 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-resources:
-- certificate.yaml
+generatorOptions:
+ disableNameSuffixHash: true
-configurations:
-- kustomizeconfig.yaml
+configMapGenerator:
+ - name: solr-operator-config
+ files:
+ - solr_operator_config.yaml
diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml
index be51fe2..5cfc3b9 100644
--- a/config/manager/manager.yaml
+++ b/config/manager/manager.yaml
@@ -13,30 +13,59 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+apiVersion: v1
+kind: Namespace
+metadata:
+ labels:
+ control-plane: solr-operator
+ name: solr-operator
---
apiVersion: apps/v1
kind: Deployment
metadata:
- name: solr-operator
+ name: solr-operator
labels:
control-plane: solr-operator
+ namespace: solr-operator
spec:
selector:
matchLabels:
- control-plane: solr-operator
+ control-plane: solr-operator
replicas: 1
template:
metadata:
labels:
- control-plane: solr-operator
+ control-plane: solr-operator
spec:
- serviceAccountName: solr-operator
+ securityContext:
+ runAsNonRoot: true
containers:
- args:
- - -zk-operator=true
+ - --leader-elect
image: apache/solr-operator:latest
imagePullPolicy: IfNotPresent
name: solr-operator
+ securityContext:
+ allowPrivilegeEscalation: false
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8081
+ initialDelaySeconds: 15
+ periodSeconds: 20
+ readinessProbe:
+ httpGet:
+ path: /readyz
+ port: 8081
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ resources:
+ limits:
+ cpu: 100m
+ memory: 30Mi
+ requests:
+ cpu: 100m
+ memory: 20Mi
env:
- name: POD_NAMESPACE
valueFrom:
@@ -46,11 +75,5 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.name
- resources:
- limits:
- cpu: 200m
- memory: 30Mi
- requests:
- cpu: 100m
- memory: 20Mi
+ serviceAccountName: solr-operator
terminationGracePeriodSeconds: 10
diff --git a/config/webhook/service.yaml b/config/manager/solr_operator_config.yaml
similarity index 75%
rename from config/webhook/service.yaml
rename to config/manager/solr_operator_config.yaml
index 4d9e502..4b98a6f 100644
--- a/config/webhook/service.yaml
+++ b/config/manager/solr_operator_config.yaml
@@ -13,14 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-apiVersion: v1
-kind: Service
-metadata:
- name: webhook-service
- namespace: system
-spec:
- ports:
- - port: 443
- targetPort: 9443
- selector:
- control-plane: controller-manager
+apiVersion: controller-runtime.sigs.k8s.io/v1alpha1
+kind: SolrOperatorConfig
+health:
+ healthProbeBindAddress: :8081
+metrics:
+ bindAddress: 127.0.0.1:8080
+webhook:
+ port: 9443
+leaderElection:
+ leaderElect: true
+ resourceName: 88488bdc.solr.apache.org
diff --git a/config/webhook/manifests.yaml b/config/prometheus/kustomization.yaml
similarity index 96%
rename from config/webhook/manifests.yaml
rename to config/prometheus/kustomization.yaml
index ae1e83e..0e67cbd 100644
--- a/config/webhook/manifests.yaml
+++ b/config/prometheus/kustomization.yaml
@@ -12,3 +12,6 @@
# 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.
+
+resources:
+- monitor.yaml
diff --git a/config/default/manager_prometheus_metrics_patch.yaml b/config/prometheus/monitor.yaml
similarity index 63%
rename from config/default/manager_prometheus_metrics_patch.yaml
rename to config/prometheus/monitor.yaml
index 57287cb..80be4e1 100644
--- a/config/default/manager_prometheus_metrics_patch.yaml
+++ b/config/prometheus/monitor.yaml
@@ -13,21 +13,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# This patch enables Prometheus scraping for the manager pod.
-apiVersion: apps/v1
-kind: Deployment
+# Prometheus Monitor Service (Metrics)
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
metadata:
- name: solr-operator
+ labels:
+ control-plane: solr-operator
+ name: solr-operator-metrics-monitor
+ namespace: system
spec:
- template:
- metadata:
- annotations:
- prometheus.io/scrape: 'true'
- spec:
- containers:
- # Expose the prometheus metrics on default port
- - name: solr-operator
- ports:
- - containerPort: 8080
- name: metrics
- protocol: TCP
+ endpoints:
+ - path: /metrics
+ port: https
+ scheme: https
+ bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+ tlsConfig:
+ insecureSkipVerify: true
+ selector:
+ matchLabels:
+ control-plane: solr-operator
diff --git a/config/webhook/kustomization.yaml b/config/rbac/auth_proxy_client_clusterrole.yaml
similarity index 82%
rename from config/webhook/kustomization.yaml
rename to config/rbac/auth_proxy_client_clusterrole.yaml
index dc29f05..65c9996 100644
--- a/config/webhook/kustomization.yaml
+++ b/config/rbac/auth_proxy_client_clusterrole.yaml
@@ -13,9 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-resources:
-- manifests.yaml
-- service.yaml
-
-configurations:
-- kustomizeconfig.yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: solr-operator-metrics-reader
+rules:
+- nonResourceURLs:
+ - "/metrics"
+ verbs:
+ - get
diff --git a/config/rbac/auth_proxy_role.yaml b/config/rbac/auth_proxy_role.yaml
index e6101d9..8e520dc 100644
--- a/config/rbac/auth_proxy_role.yaml
+++ b/config/rbac/auth_proxy_role.yaml
@@ -16,13 +16,17 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
- name: proxy-role
+ name: solr-operator-proxy-role
rules:
-- apiGroups: ["authentication.k8s.io"]
+- apiGroups:
+ - authentication.k8s.io
resources:
- tokenreviews
- verbs: ["create"]
-- apiGroups: ["authorization.k8s.io"]
+ verbs:
+ - create
+- apiGroups:
+ - authorization.k8s.io
resources:
- subjectaccessreviews
- verbs: ["create"]
+ verbs:
+ - create
diff --git a/config/rbac/auth_proxy_role_binding.yaml b/config/rbac/auth_proxy_role_binding.yaml
index af4dd5b..f835e77 100644
--- a/config/rbac/auth_proxy_role_binding.yaml
+++ b/config/rbac/auth_proxy_role_binding.yaml
@@ -16,12 +16,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
- name: proxy-rolebinding
+ name: solr-operator-proxy-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
- name: proxy-role
+ name: solr-operator-proxy-role
subjects:
- kind: ServiceAccount
- name: default
- namespace: system
+ name: solr-operator
+ namespace: solr-operator
diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml
index 5d55eea..aab1a36 100644
--- a/config/rbac/auth_proxy_service.yaml
+++ b/config/rbac/auth_proxy_service.yaml
@@ -16,18 +16,14 @@
apiVersion: v1
kind: Service
metadata:
- annotations:
- prometheus.io/port: "8443"
- prometheus.io/scheme: https
- prometheus.io/scrape: "true"
labels:
- control-plane: controller-manager
- name: controller-manager-metrics-service
- namespace: system
+ control-plane: solr-operator
+ name: solr-operator-metrics-service
+ namespace: solr-operator
spec:
ports:
- name: https
port: 8443
targetPort: https
selector:
- control-plane: controller-manager
+ control-plane: solr-operator
diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml
index e6fcde4..7a7d01f 100644
--- a/config/rbac/kustomization.yaml
+++ b/config/rbac/kustomization.yaml
@@ -14,14 +14,20 @@
# limitations under the License.
resources:
+# All RBAC will be applied under this service account in
+# the deployment namespace. You may comment out this resource
+# if your manager will use a service account that exists at
+# runtime. Be sure to update RoleBinding and ClusterRoleBinding
+# subjects if changing service account names.
- service_account.yaml
- role.yaml
- role_binding.yaml
- leader_election_role.yaml
- leader_election_role_binding.yaml
-# Comment the following 3 lines if you want to disable
+# Comment the following 4 lines if you want to disable
# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
# which protects your /metrics endpoint.
-# - auth_proxy_service.yaml
-# - auth_proxy_role.yaml
-# - auth_proxy_role_binding.yaml
\ No newline at end of file
+- auth_proxy_service.yaml
+- auth_proxy_role.yaml
+- auth_proxy_role_binding.yaml
+- auth_proxy_client_clusterrole.yaml
diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml
index 4c63d18..0d3b4a6 100644
--- a/config/rbac/leader_election_role.yaml
+++ b/config/rbac/leader_election_role.yaml
@@ -17,7 +17,7 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
- name: leader-election-role
+ name: solr-operator-leader-election-role
rules:
- apiGroups:
- ""
@@ -32,16 +32,21 @@ rules:
- patch
- delete
- apiGroups:
- - ""
+ - coordination.k8s.io
resources:
- - configmaps/status
+ - leases
verbs:
- get
+ - list
+ - watch
+ - create
- update
- patch
+ - delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
+ - patch
diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml
index ca40a71..d604468 100644
--- a/config/rbac/leader_election_role_binding.yaml
+++ b/config/rbac/leader_election_role_binding.yaml
@@ -16,12 +16,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
- name: leader-election-rolebinding
+ name: solr-operator-leader-election-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
- name: leader-election-role
+ name: solr-operator-leader-election-role
subjects:
- kind: ServiceAccount
name: solr-operator
- namespace: default
+ namespace: solr-operator
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 30d176f..406f5d6 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -38,8 +38,6 @@ rules:
- configmaps/status
verbs:
- get
- - patch
- - update
- apiGroups:
- ""
resources:
@@ -100,8 +98,6 @@ rules:
- services/status
verbs:
- get
- - patch
- - update
- apiGroups:
- apps
resources:
@@ -138,8 +134,6 @@ rules:
- statefulsets/status
verbs:
- get
- - patch
- - update
- apiGroups:
- batch
resources:
@@ -158,8 +152,6 @@ rules:
- jobs/status
verbs:
- get
- - patch
- - update
- apiGroups:
- networking.k8s.io
resources:
@@ -178,8 +170,6 @@ rules:
- ingresses/status
verbs:
- get
- - patch
- - update
- apiGroups:
- solr.apache.org
resources:
@@ -195,6 +185,12 @@ rules:
- apiGroups:
- solr.apache.org
resources:
+ - solrbackups/finalizers
+ verbs:
+ - update
+- apiGroups:
+ - solr.apache.org
+ resources:
- solrbackups/status
verbs:
- get
@@ -241,6 +237,12 @@ rules:
- apiGroups:
- solr.apache.org
resources:
+ - solrprometheusexporters/finalizers
+ verbs:
+ - update
+- apiGroups:
+ - solr.apache.org
+ resources:
- solrprometheusexporters/status
verbs:
- get
@@ -264,5 +266,3 @@ rules:
- zookeeperclusters/status
verbs:
- get
- - patch
- - update
diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml
index 616a5b4..f7a5485 100644
--- a/config/rbac/role_binding.yaml
+++ b/config/rbac/role_binding.yaml
@@ -24,4 +24,4 @@ roleRef:
subjects:
- kind: ServiceAccount
name: solr-operator
- namespace: default
+ namespace: solr-operator
diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml
index 1f0554a..487e312 100644
--- a/config/rbac/service_account.yaml
+++ b/config/rbac/service_account.yaml
@@ -17,4 +17,4 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: solr-operator
- namespace: default
\ No newline at end of file
+ namespace: solr-operator
diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/solrbackup_editor_role.yaml
similarity index 82%
copy from config/rbac/leader_election_role.yaml
copy to config/rbac/solrbackup_editor_role.yaml
index 4c63d18..e716242 100644
--- a/config/rbac/leader_election_role.yaml
+++ b/config/rbac/solrbackup_editor_role.yaml
@@ -13,35 +13,27 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# permissions to do leader election.
+# permissions for end users to edit solrbackups.
apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
+kind: ClusterRole
metadata:
- name: leader-election-role
+ name: solrbackup-editor-role
rules:
- apiGroups:
- - ""
+ - solr.apache.org
resources:
- - configmaps
+ - solrbackups
verbs:
- - get
- - list
- - watch
- create
- - update
- - patch
- delete
-- apiGroups:
- - ""
- resources:
- - configmaps/status
- verbs:
- get
- - update
+ - list
- patch
+ - update
+ - watch
- apiGroups:
- - ""
+ - solr.apache.org
resources:
- - events
+ - solrbackups/status
verbs:
- - create
+ - get
diff --git a/config/rbac/auth_proxy_role.yaml b/config/rbac/solrbackup_viewer_role.yaml
similarity index 78%
copy from config/rbac/auth_proxy_role.yaml
copy to config/rbac/solrbackup_viewer_role.yaml
index e6101d9..034cfc4 100644
--- a/config/rbac/auth_proxy_role.yaml
+++ b/config/rbac/solrbackup_viewer_role.yaml
@@ -13,16 +13,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# permissions for end users to view solrbackups.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
- name: proxy-role
+ name: solrbackup-viewer-role
rules:
-- apiGroups: ["authentication.k8s.io"]
+- apiGroups:
+ - solr.apache.org
resources:
- - tokenreviews
- verbs: ["create"]
-- apiGroups: ["authorization.k8s.io"]
+ - solrbackups
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - solr.apache.org
resources:
- - subjectaccessreviews
- verbs: ["create"]
+ - solrbackups/status
+ verbs:
+ - get
diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/solrcloud_editor_role.yaml
similarity index 82%
copy from config/rbac/leader_election_role.yaml
copy to config/rbac/solrcloud_editor_role.yaml
index 4c63d18..56ea542 100644
--- a/config/rbac/leader_election_role.yaml
+++ b/config/rbac/solrcloud_editor_role.yaml
@@ -13,35 +13,27 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# permissions to do leader election.
+# permissions for end users to edit solrclouds.
apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
+kind: ClusterRole
metadata:
- name: leader-election-role
+ name: solrcloud-editor-role
rules:
- apiGroups:
- - ""
+ - solr.apache.org
resources:
- - configmaps
+ - solrclouds
verbs:
- - get
- - list
- - watch
- create
- - update
- - patch
- delete
-- apiGroups:
- - ""
- resources:
- - configmaps/status
- verbs:
- get
- - update
+ - list
- patch
+ - update
+ - watch
- apiGroups:
- - ""
+ - solr.apache.org
resources:
- - events
+ - solrclouds/status
verbs:
- - create
+ - get
diff --git a/config/rbac/auth_proxy_role.yaml b/config/rbac/solrcloud_viewer_role.yaml
similarity index 79%
copy from config/rbac/auth_proxy_role.yaml
copy to config/rbac/solrcloud_viewer_role.yaml
index e6101d9..326ed3a 100644
--- a/config/rbac/auth_proxy_role.yaml
+++ b/config/rbac/solrcloud_viewer_role.yaml
@@ -13,16 +13,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# permissions for end users to view solrclouds.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
- name: proxy-role
+ name: solrcloud-viewer-role
rules:
-- apiGroups: ["authentication.k8s.io"]
+- apiGroups:
+ - solr.apache.org
resources:
- - tokenreviews
- verbs: ["create"]
-- apiGroups: ["authorization.k8s.io"]
+ - solrclouds
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - solr.apache.org
resources:
- - subjectaccessreviews
- verbs: ["create"]
+ - solrclouds/status
+ verbs:
+ - get
diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/solrprometheusexporter_editor_role.yaml
similarity index 81%
copy from config/rbac/leader_election_role.yaml
copy to config/rbac/solrprometheusexporter_editor_role.yaml
index 4c63d18..49f2a0f 100644
--- a/config/rbac/leader_election_role.yaml
+++ b/config/rbac/solrprometheusexporter_editor_role.yaml
@@ -13,35 +13,27 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# permissions to do leader election.
+# permissions for end users to edit solrprometheusexporters.
apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
+kind: ClusterRole
metadata:
- name: leader-election-role
+ name: solrprometheusexporter-editor-role
rules:
- apiGroups:
- - ""
+ - solr.apache.org
resources:
- - configmaps
+ - solrprometheusexporters
verbs:
- - get
- - list
- - watch
- create
- - update
- - patch
- delete
-- apiGroups:
- - ""
- resources:
- - configmaps/status
- verbs:
- get
- - update
+ - list
- patch
+ - update
+ - watch
- apiGroups:
- - ""
+ - solr.apache.org
resources:
- - events
+ - solrprometheusexporters/status
verbs:
- - create
+ - get
diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/solrprometheusexporter_viewer_role.yaml
similarity index 78%
copy from config/rbac/leader_election_role.yaml
copy to config/rbac/solrprometheusexporter_viewer_role.yaml
index 4c63d18..fd359cd 100644
--- a/config/rbac/leader_election_role.yaml
+++ b/config/rbac/solrprometheusexporter_viewer_role.yaml
@@ -13,35 +13,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# permissions to do leader election.
+# permissions for end users to view solrprometheusexporters.
apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
+kind: ClusterRole
metadata:
- name: leader-election-role
+ name: solrprometheusexporter-viewer-role
rules:
- apiGroups:
- - ""
+ - solr.apache.org
resources:
- - configmaps
+ - solrprometheusexporters
verbs:
- get
- list
- watch
- - create
- - update
- - patch
- - delete
- apiGroups:
- - ""
+ - solr.apache.org
resources:
- - configmaps/status
+ - solrprometheusexporters/status
verbs:
- get
- - update
- - patch
-- apiGroups:
- - ""
- resources:
- - events
- verbs:
- - create
diff --git a/config/webhook/kustomizeconfig.yaml b/config/webhook/kustomizeconfig.yaml
deleted file mode 100644
index 55b3851..0000000
--- a/config/webhook/kustomizeconfig.yaml
+++ /dev/null
@@ -1,40 +0,0 @@
-# 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.
-
-# the following config is for teaching kustomize where to look at when substituting vars.
-# It requires kustomize v2.1.0 or newer to work properly.
-nameReference:
-- kind: Service
- version: v1
- fieldSpecs:
- - kind: MutatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/name
- - kind: ValidatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/name
-
-namespace:
-- kind: MutatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/namespace
- create: true
-- kind: ValidatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/namespace
- create: true
-
-varReference:
-- path: metadata/annotations
diff --git a/version/version.go b/controllers/common.go
similarity index 67%
copy from version/version.go
copy to controllers/common.go
index 9ab1448..38a056e 100644
--- a/version/version.go
+++ b/controllers/common.go
@@ -15,12 +15,16 @@
* limitations under the License.
*/
-package version
+package controllers
-var (
- // Version information for the Solr Operator
- Version string = "v0.5.0"
- VersionSuffix string = "prerelease"
- BuildTime string
- GitSHA string
+import (
+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
+ "time"
)
+
+// Set the requeueAfter if it has not been set, or is greater than the new time to requeue at
+func updateRequeueAfter(requeueOrNot *reconcile.Result, newWait time.Duration) {
+ if requeueOrNot.RequeueAfter <= 0 || requeueOrNot.RequeueAfter > newWait {
+ requeueOrNot.RequeueAfter = newWait
+ }
+}
diff --git a/controllers/controller_utils_test.go b/controllers/controller_utils_test.go
index db69fae..c0de366 100644
--- a/controllers/controller_utils_test.go
+++ b/controllers/controller_utils_test.go
@@ -18,169 +18,425 @@
package controllers
import (
- "fmt"
- "testing"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
"github.com/apache/solr-operator/controllers/util"
- zkv1beta1 "github.com/pravega/zookeeper-operator/pkg/apis/zookeeper/v1beta1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
- solr "github.com/apache/solr-operator/api/v1beta1"
- "github.com/onsi/gomega"
- "github.com/stretchr/testify/assert"
+ zk_api "github.com/apache/solr-operator/controllers/zk_api"
"golang.org/x/net/context"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
- extv1 "k8s.io/api/extensions/v1beta1"
+ netv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/api/resource"
- "k8s.io/apimachinery/pkg/runtime"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/reconcile"
)
-func emptyRequests(requests chan reconcile.Request) {
- for len(requests) > 0 {
- <-requests
+// Add one to an optional offset
+func resolveOffset(additionalOffset []int) (offset int) {
+ if len(additionalOffset) == 0 {
+ offset = 0
+ } else {
+ offset = additionalOffset[0]
}
+ return offset + 1
+}
+
+func resourceKey(parentResource client.Object, name string) types.NamespacedName {
+ return types.NamespacedName{Name: name, Namespace: parentResource.GetNamespace()}
+}
+
+func expectSolrCloud(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, additionalOffset ...int) *solrv1beta1.SolrCloud {
+ return expectSolrCloudWithChecks(ctx, solrCloud, nil, resolveOffset(additionalOffset))
+}
+
+func expectSolrCloudWithChecks(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, additionalChecks func(Gomega, *solrv1beta1.SolrCloud), additionalOffset ...int) *solrv1beta1.SolrCloud {
+ foundSolrCloud := &solrv1beta1.SolrCloud{}
+ EventuallyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(solrCloud, solrCloud.Name), foundSolrCloud)).To(Succeed(), "Expected SolrCloud does not exist")
+ if additionalChecks != nil {
+ additionalChecks(g, foundSolrCloud)
+ }
+ }).Should(Succeed())
+
+ return foundSolrCloud
+}
+
+func expectSolrCloudWithConsistentChecks(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, additionalChecks func(Gomega, *solrv1beta1.SolrCloud), additionalOffset ...int) *solrv1beta1.SolrCloud {
+ foundSolrCloud := &solrv1beta1.SolrCloud{}
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(solrCloud, solrCloud.Name), foundSolrCloud)).To(Succeed(), "Expected SolrCloud does not exist")
+ if additionalChecks != nil {
+ additionalChecks(g, foundSolrCloud)
+ }
+ }).Should(Succeed())
+
+ return foundSolrCloud
+}
+
+func expectSolrCloudStatus(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, additionalOffset ...int) *solrv1beta1.SolrCloudStatus {
+ return expectSolrCloudStatusWithChecks(ctx, solrCloud, nil, resolveOffset(additionalOffset))
+}
+
+func expectSolrCloudStatusWithChecks(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, additionalChecks func(Gomega, *solrv1beta1.SolrCloudStatus), additionalOffset ...int) *solrv1beta1.SolrCloudStatus {
+ foundSolrCloud := &solrv1beta1.SolrCloud{}
+ EventuallyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(solrCloud, solrCloud.Name), foundSolrCloud)).To(Succeed(), "Expected SolrCloud does not exist")
+ if additionalChecks != nil {
+ additionalChecks(g, &foundSolrCloud.Status)
+ }
+ }).Should(Succeed())
+
+ return &foundSolrCloud.Status
+}
+
+func expectSolrCloudStatusConsistentWithChecks(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, additionalChecks func(Gomega, *solrv1beta1.SolrCloudStatus), additionalOffset ...int) *solrv1beta1.SolrCloudStatus {
+ foundSolrCloud := &solrv1beta1.SolrCloud{}
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(solrCloud, solrCloud.Name), foundSolrCloud)).To(Succeed(), "Expected SolrCloud does not exist")
+ if additionalChecks != nil {
+ additionalChecks(g, &foundSolrCloud.Status)
+ }
+ }).Should(Succeed())
+
+ return &foundSolrCloud.Status
+}
+
+func expectSolrPrometheusExporter(ctx context.Context, solrPrometheusExporter *solrv1beta1.SolrPrometheusExporter, additionalOffset ...int) *solrv1beta1.SolrPrometheusExporter {
+ return expectSolrPrometheusExporterWithChecks(ctx, solrPrometheusExporter, nil, resolveOffset(additionalOffset))
+}
+
+func expectSolrPrometheusExporterWithChecks(ctx context.Context, solrPrometheusExporter *solrv1beta1.SolrPrometheusExporter, additionalChecks func(Gomega, *solrv1beta1.SolrPrometheusExporter), additionalOffset ...int) *solrv1beta1.SolrPrometheusExporter {
+ foundSolrPrometheusExporter := &solrv1beta1.SolrPrometheusExporter{}
+ EventuallyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(solrPrometheusExporter, solrPrometheusExporter.Name), foundSolrPrometheusExporter)).To(Succeed(), "Expected SolrPrometheusExporter does not exist")
+ if additionalChecks != nil {
+ additionalChecks(g, foundSolrPrometheusExporter)
+ }
+ }).Should(Succeed())
+
+ return foundSolrPrometheusExporter
+}
+
+func expectSolrPrometheusExporterWithConsistentChecks(ctx context.Context, solrPrometheusExporter *solrv1beta1.SolrCloud, additionalChecks func(Gomega, *solrv1beta1.SolrCloud), additionalOffset ...int) *solrv1beta1.SolrCloud {
+ foundSolrPrometheusExporter := &solrv1beta1.SolrCloud{}
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(solrPrometheusExporter, solrPrometheusExporter.Name), foundSolrPrometheusExporter)).To(Succeed(), "Expected SolrPrometheusExporter does not exist")
+ if additionalChecks != nil {
+ additionalChecks(g, foundSolrPrometheusExporter)
+ }
+ }).Should(Succeed())
+
+ return foundSolrPrometheusExporter
}
-func expectStatefulSet(t *testing.T, g *gomega.GomegaWithT, requests chan reconcile.Request, expectedRequest reconcile.Request, statefulSetKey types.NamespacedName) *appsv1.StatefulSet {
- stateful := &appsv1.StatefulSet{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), statefulSetKey, stateful) }, timeout).
- Should(gomega.Succeed())
+func expectSecret(ctx context.Context, parentResource client.Object, secretName string, additionalOffset ...int) *corev1.Secret {
+ return expectSecretWithChecks(ctx, parentResource, secretName, nil, resolveOffset(additionalOffset))
+}
+
+func expectSecretWithChecks(ctx context.Context, parentResource client.Object, secretName string, additionalChecks func(Gomega, *corev1.Secret), additionalOffset ...int) *corev1.Secret {
+ found := &corev1.Secret{}
+ EventuallyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, secretName), found)).To(Succeed(), "Expected Secret does not exist")
+ if additionalChecks != nil {
+ additionalChecks(g, found)
+ }
+ }).Should(Succeed())
+
+ return found
+}
+
+func expectStatefulSet(ctx context.Context, parentResource client.Object, statefulSetName string, additionalOffset ...int) *appsv1.StatefulSet {
+ return expectStatefulSetWithChecks(ctx, parentResource, statefulSetName, nil, resolveOffset(additionalOffset))
+}
- // Verify the statefulSet Specs
- testMapContainsOther(t, "StatefulSet pod template selector", stateful.Spec.Template.Labels, stateful.Spec.Selector.MatchLabels)
- assert.GreaterOrEqual(t, len(stateful.Spec.Selector.MatchLabels), 1, "StatefulSet pod template selector must have at least 1 label")
+func expectStatefulSetWithChecks(ctx context.Context, parentResource client.Object, statefulSetName string, additionalChecks func(Gomega, *appsv1.StatefulSet), additionalOffset ...int) *appsv1.StatefulSet {
+ statefulSet := &appsv1.StatefulSet{}
+ EventuallyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, statefulSetName), statefulSet)).To(Succeed(), "Expected StatefulSet does not exist")
- // Delete the StatefulSet and expect Reconcile to be called for StatefulSet deletion
- g.Expect(testClient.Delete(context.TODO(), stateful)).NotTo(gomega.HaveOccurred())
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
- g.Eventually(func() error { return testClient.Get(context.TODO(), statefulSetKey, stateful) }, timeout).
- Should(gomega.Succeed())
+ testMapContainsOtherWithGomega(g, "StatefulSet pod template selector", statefulSet.Spec.Template.Labels, statefulSet.Spec.Selector.MatchLabels)
+ g.Expect(len(statefulSet.Spec.Selector.MatchLabels)).To(BeNumerically(">=", 1), "StatefulSet pod template selector must have at least 1 label")
- // Manually delete StatefulSet since GC isn't enabled in the test control plane
- g.Eventually(func() error { return testClient.Delete(context.TODO(), stateful) }, timeout).
- Should(gomega.MatchError("statefulsets.apps \"" + statefulSetKey.Name + "\" not found"))
+ if additionalChecks != nil {
+ additionalChecks(g, statefulSet)
+ }
+ }).Should(Succeed())
+
+ By("recreating the StatefulSet after it is deleted")
+ ExpectWithOffset(resolveOffset(additionalOffset), k8sClient.Delete(ctx, statefulSet)).To(Succeed())
+ EventuallyWithOffset(
+ resolveOffset(additionalOffset),
+ func() (types.UID, error) {
+ newResource := &appsv1.StatefulSet{}
+ err := k8sClient.Get(ctx, resourceKey(parentResource, statefulSetName), newResource)
+ if err != nil {
+ return "", err
+ }
+ return newResource.UID, nil
+ }).Should(And(Not(BeEmpty()), Not(Equal(statefulSet.UID))), "New StatefulSet, with new UID, not created.")
+
+ return statefulSet
+}
+
+func expectStatefulSetWithConsistentChecks(ctx context.Context, parentResource client.Object, statefulSetName string, additionalChecks func(Gomega, *appsv1.StatefulSet), additionalOffset ...int) *appsv1.StatefulSet {
+ statefulSet := &appsv1.StatefulSet{}
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, statefulSetName), statefulSet)).To(Succeed(), "Expected StatefulSet does not exist")
+
+ testMapContainsOtherWithGomega(g, "StatefulSet pod template selector", statefulSet.Spec.Template.Labels, statefulSet.Spec.Selector.MatchLabels)
+ g.Expect(len(statefulSet.Spec.Selector.MatchLabels)).To(BeNumerically(">=", 1), "StatefulSet pod template selector must have at least 1 label")
+
+ if additionalChecks != nil {
+ additionalChecks(g, statefulSet)
+ }
+ }).Should(Succeed())
+
+ return statefulSet
+}
- return stateful
+func expectNoStatefulSet(ctx context.Context, parentResource client.Object, statefulSetName string, additionalOffset ...int) {
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func() error {
+ return k8sClient.Get(ctx, resourceKey(parentResource, statefulSetName), &appsv1.StatefulSet{})
+ }).Should(MatchError("statefulsets.apps \""+statefulSetName+"\" not found"), "StatefulSet exists when it should not")
}
-func expectNoStatefulSet(g *gomega.GomegaWithT, statefulSetKey types.NamespacedName) {
- stateful := &appsv1.StatefulSet{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), statefulSetKey, stateful) }, timeout).
- Should(gomega.MatchError("StatefulSet.apps \"" + statefulSetKey.Name + "\" not found"))
+func expectService(ctx context.Context, parentResource client.Object, serviceName string, selectorLables map[string]string, isHeadless bool, additionalOffset ...int) *corev1.Service {
+ return expectServiceWithChecks(ctx, parentResource, serviceName, selectorLables, isHeadless, nil, resolveOffset(additionalOffset))
}
-func expectService(t *testing.T, g *gomega.GomegaWithT, requests chan reconcile.Request, expectedRequest reconcile.Request, serviceKey types.NamespacedName, selectorLables map[string]string) *corev1.Service {
+func expectServiceWithChecks(ctx context.Context, parentResource client.Object, serviceName string, selectorLables map[string]string, isHeadless bool, additionalChecks func(Gomega, *corev1.Service), additionalOffset ...int) *corev1.Service {
service := &corev1.Service{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), serviceKey, service) }, timeout).
- Should(gomega.Succeed())
+ EventuallyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ Expect(k8sClient.Get(ctx, resourceKey(parentResource, serviceName), service)).To(Succeed(), "Expected Service does not exist")
- assert.Equal(t, selectorLables, service.Spec.Selector, "Service is not pointing to the correct Pods.")
+ g.Expect(service.Spec.Selector).To(Equal(selectorLables), "Service is not pointing to the correct Pods.")
- // Delete the Service and expect Reconcile to be called for Service deletion
- g.Expect(testClient.Delete(context.TODO(), service)).NotTo(gomega.HaveOccurred())
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
- g.Eventually(func() error { return testClient.Get(context.TODO(), serviceKey, service) }, timeout).
- Should(gomega.Succeed())
+ if isHeadless {
+ g.Expect(service.Spec.ClusterIP).To(Equal("None"), "The clusterIP field of a headless service should be None")
+ } else {
+ g.Expect(service.Spec.ClusterIP).To(Not(Equal("None")), "The clusterIP field of a non-headless service should not be None")
+ }
- // Manually delete Service since GC isn't enabled in the test control plane
- g.Eventually(func() error { return testClient.Delete(context.TODO(), service) }, timeout).
- Should(gomega.MatchError("services \"" + serviceKey.Name + "\" not found"))
+ if additionalChecks != nil {
+ additionalChecks(g, service)
+ }
+ }).Should(Succeed())
+
+ By("recreating the Service after it is deleted")
+ ExpectWithOffset(resolveOffset(additionalOffset), k8sClient.Delete(ctx, service)).To(Succeed())
+ EventuallyWithOffset(
+ resolveOffset(additionalOffset),
+ func() (types.UID, error) {
+ newResource := &corev1.Service{}
+ err := k8sClient.Get(ctx, resourceKey(parentResource, serviceName), newResource)
+ if err != nil {
+ return "", err
+ }
+ return newResource.UID, nil
+ }).Should(And(Not(BeEmpty()), Not(Equal(service.UID))), "New Service, with new UID, not created.")
return service
}
-func expectNoService(g *gomega.GomegaWithT, serviceKey types.NamespacedName, message string) {
+func expectServiceWithConsistentChecks(ctx context.Context, parentResource client.Object, serviceName string, selectorLables map[string]string, isHeadless bool, additionalChecks func(Gomega, *corev1.Service), additionalOffset ...int) *corev1.Service {
service := &corev1.Service{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), serviceKey, service) }, timeout).
- Should(gomega.MatchError("Service \""+serviceKey.Name+"\" not found"), message)
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ Expect(k8sClient.Get(ctx, resourceKey(parentResource, serviceName), service)).To(Succeed(), "Expected Service does not exist")
+
+ g.Expect(service.Spec.Selector).To(Equal(selectorLables), "Service is not pointing to the correct Pods.")
+
+ if isHeadless {
+ g.Expect(service.Spec.ClusterIP).To(Equal("None"), "The clusterIP field of a headless service should be None")
+ } else {
+ g.Expect(service.Spec.ClusterIP).To(Not(Equal("None")), "The clusterIP field of a non-headless service should not be None")
+ }
+
+ if additionalChecks != nil {
+ additionalChecks(g, service)
+ }
+ }).Should(Succeed())
+
+ return service
+}
+
+func expectNoService(ctx context.Context, parentResource client.Object, serviceName string, message string, additionalOffset ...int) {
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func() error {
+ return k8sClient.Get(ctx, resourceKey(parentResource, serviceName), &corev1.Service{})
+ }).Should(MatchError("services \""+serviceName+"\" not found"), message, "Service exists when it should not")
+}
+
+func expectNoServices(ctx context.Context, parentResource client.Object, message string, serviceNames []string, additionalOffset ...int) {
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ for _, serviceName := range serviceNames {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, serviceName), &corev1.Service{})).To(MatchError("services \""+serviceName+"\" not found"), message)
+ }
+ }).Should(Succeed())
}
-func expectIngress(g *gomega.GomegaWithT, requests chan reconcile.Request, expectedRequest reconcile.Request, ingressKey types.NamespacedName) *extv1.Ingress {
- ingress := &extv1.Ingress{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), ingressKey, ingress) }, timeout).
- Should(gomega.Succeed())
+func expectIngress(ctx context.Context, parentResource client.Object, ingressName string, additionalOffset ...int) *netv1.Ingress {
+ return expectIngressWithChecks(ctx, parentResource, ingressName, nil, resolveOffset(additionalOffset))
+}
- // Delete the Ingress and expect Reconcile to be called for Ingress deletion
- g.Expect(testClient.Delete(context.TODO(), ingress)).NotTo(gomega.HaveOccurred())
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
- g.Eventually(func() error { return testClient.Get(context.TODO(), ingressKey, ingress) }, timeout).
- Should(gomega.Succeed())
+func expectIngressWithChecks(ctx context.Context, parentResource client.Object, ingressName string, additionalChecks func(Gomega, *netv1.Ingress), additionalOffset ...int) *netv1.Ingress {
+ ingress := &netv1.Ingress{}
+ EventuallyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, ingressName), ingress)).To(Succeed(), "Expected Ingress does not exist")
- // Manually delete Ingress since GC isn't enabled in the test control plane
- g.Eventually(func() error { return testClient.Delete(context.TODO(), ingress) }, timeout).
- Should(gomega.MatchError("ingresses.extensions \"" + ingressKey.Name + "\" not found"))
+ if additionalChecks != nil {
+ additionalChecks(g, ingress)
+ }
+ }).Should(Succeed())
+
+ By("recreating the Ingress after it is deleted")
+ ExpectWithOffset(resolveOffset(additionalOffset), k8sClient.Delete(ctx, ingress)).To(Succeed())
+ EventuallyWithOffset(
+ resolveOffset(additionalOffset),
+ func() (types.UID, error) {
+ newResource := &netv1.Ingress{}
+ err := k8sClient.Get(ctx, resourceKey(parentResource, ingressName), newResource)
+ if err != nil {
+ return "", err
+ }
+ return newResource.UID, nil
+ }).Should(And(Not(BeEmpty()), Not(Equal(ingress.UID))), "New Ingress, with new UID, not created.")
return ingress
}
-func expectNoIngress(g *gomega.GomegaWithT, ingressKey types.NamespacedName) {
- ingress := &extv1.Ingress{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), ingressKey, ingress) }, timeout).
- Should(gomega.MatchError("Ingress.extensions \"" + ingressKey.Name + "\" not found"))
+func expectIngressWithConsistentChecks(ctx context.Context, parentResource client.Object, ingressName string, additionalChecks func(Gomega, *netv1.Ingress), additionalOffset ...int) *netv1.Ingress {
+ ingress := &netv1.Ingress{}
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, ingressName), ingress)).To(Succeed(), "Expected Ingress does not exist")
+
+ if additionalChecks != nil {
+ additionalChecks(g, ingress)
+ }
+ }).Should(Succeed())
+
+ return ingress
}
-func expectConfigMap(t *testing.T, g *gomega.GomegaWithT, requests chan reconcile.Request, expectedRequest reconcile.Request, configMapKey types.NamespacedName, configMapData map[string]string) *corev1.ConfigMap {
- configMap := &corev1.ConfigMap{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), configMapKey, configMap) }, timeout).
- Should(gomega.Succeed())
+func expectNoIngress(ctx context.Context, parentResource client.Object, ingressName string, additionalOffset ...int) {
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func() error {
+ return k8sClient.Get(ctx, resourceKey(parentResource, ingressName), &netv1.Ingress{})
+ }).Should(MatchError("ingresses.networking.k8s.io \""+ingressName+"\" not found"), "Ingress exists when it should not")
+}
- // Verify the ConfigMap Specs
- for k, d := range configMapData {
- assert.Equal(t, configMap.Data[k], d, "ConfigMap does not have the correct data.")
- }
+func expectConfigMap(ctx context.Context, parentResource client.Object, configMapName string, configMapData map[string]string, additionalOffset ...int) *corev1.ConfigMap {
+ return expectConfigMapWithChecks(ctx, parentResource, configMapName, configMapData, nil, resolveOffset(additionalOffset))
+}
- // Delete the ConfigMap and expect Reconcile to be called for Deployment deletion
- g.Expect(testClient.Delete(context.TODO(), configMap)).NotTo(gomega.HaveOccurred())
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
- g.Eventually(func() error { return testClient.Get(context.TODO(), configMapKey, configMap) }, timeout).
- Should(gomega.Succeed())
+func expectConfigMapWithChecks(ctx context.Context, parentResource client.Object, configMapName string, configMapData map[string]string, additionalChecks func(Gomega, *corev1.ConfigMap), additionalOffset ...int) *corev1.ConfigMap {
+ configMap := &corev1.ConfigMap{}
+ EventuallyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, configMapName), configMap)).To(Succeed(), "Expected ConfigMap does not exist")
- // Manually delete ConfigMap since GC isn't enabled in the test control plane
- g.Eventually(func() error { return testClient.Delete(context.TODO(), configMap) }, timeout).
- Should(gomega.MatchError("configmaps \"" + configMapKey.Name + "\" not found"))
+ // Verify the ConfigMap Data
+ g.Expect(configMap.Data).To(Equal(configMapData), "ConfigMap does not have the correct data.")
+
+ if additionalChecks != nil {
+ additionalChecks(g, configMap)
+ }
+ }).Should(Succeed())
+
+ By("recreating the ConfigMap after it is deleted")
+ ExpectWithOffset(resolveOffset(additionalOffset), k8sClient.Delete(ctx, configMap)).To(Succeed())
+ EventuallyWithOffset(
+ resolveOffset(additionalOffset),
+ func() (types.UID, error) {
+ newResource := &corev1.ConfigMap{}
+ err := k8sClient.Get(ctx, resourceKey(parentResource, configMapName), newResource)
+ if err != nil {
+ return "", err
+ }
+ return newResource.UID, nil
+ }).Should(And(Not(BeEmpty()), Not(Equal(configMap.UID))), "New ConfigMap, with new UID, not created.")
return configMap
}
-func expectNoConfigMap(g *gomega.GomegaWithT, configMapKey types.NamespacedName) {
+func expectConfigMapWithConsistentChecks(ctx context.Context, parentResource client.Object, configMapName string, configMapData map[string]string, additionalChecks func(Gomega, *corev1.ConfigMap), additionalOffset ...int) *corev1.ConfigMap {
configMap := &corev1.ConfigMap{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), configMapKey, configMap) }, timeout).
- Should(gomega.MatchError("ConfigMap \"" + configMapKey.Name + "\" not found"))
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, configMapName), configMap)).To(Succeed(), "Expected ConfigMap does not exist")
+
+ // Verify the ConfigMap Data
+ g.Expect(configMap.Data).To(Equal(configMapData), "ConfigMap does not have the correct data.")
+
+ if additionalChecks != nil {
+ additionalChecks(g, configMap)
+ }
+ }).Should(Succeed())
+
+ return configMap
}
-func expectDeployment(t *testing.T, g *gomega.GomegaWithT, requests chan reconcile.Request, expectedRequest reconcile.Request, deploymentKey types.NamespacedName, configMapName string) *appsv1.Deployment {
- deploy := &appsv1.Deployment{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), deploymentKey, deploy) }, timeout).
- Should(gomega.Succeed())
+func expectNoConfigMap(ctx context.Context, parentResource client.Object, configMapName string, additionalOffset ...int) {
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func() error {
+ return k8sClient.Get(ctx, resourceKey(parentResource, configMapName), &corev1.ConfigMap{})
+ }).Should(MatchError("configmaps \""+configMapName+"\" not found"), "ConfigMap exists when it should not")
+}
- // Verify the deployment Specs
- testMapContainsOther(t, "Deployment pod template selector", deploy.Spec.Template.Labels, deploy.Spec.Selector.MatchLabels)
- assert.GreaterOrEqual(t, len(deploy.Spec.Selector.MatchLabels), 1, "Deployment pod template selector must have at least 1 label")
+func expectDeployment(ctx context.Context, parentResource client.Object, deploymentName string, additionalOffset ...int) *appsv1.Deployment {
+ return expectDeploymentWithChecks(ctx, parentResource, deploymentName, nil, resolveOffset(additionalOffset))
+}
+
+func expectDeploymentWithChecks(ctx context.Context, parentResource client.Object, deploymentName string, additionalChecks func(Gomega, *appsv1.Deployment), additionalOffset ...int) *appsv1.Deployment {
+ deployment := &appsv1.Deployment{}
+ EventuallyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, deploymentName), deployment)).To(Succeed(), "Expected Deployment does not exist")
- if configMapName != "" {
- if assert.LessOrEqual(t, 1, len(deploy.Spec.Template.Spec.Volumes), "Deployment should have at least 1 volume, the configMap. Less were found.") {
- assert.Equal(t, configMapName, deploy.Spec.Template.Spec.Volumes[0].ConfigMap.Name, "Deployment configMap volume has the wrong name.")
+ // Verify the Deployment Specs
+ testMapContainsOtherWithGomega(g, "Deployment pod template selector", deployment.Spec.Template.Labels, deployment.Spec.Selector.MatchLabels)
+ g.Expect(len(deployment.Spec.Selector.MatchLabels)).To(BeNumerically(">=", 1), "Deployment pod template selector must have at least 1 label")
+
+ if additionalChecks != nil {
+ additionalChecks(g, deployment)
}
- }
+ }).Should(Succeed())
+
+ By("recreating the Deployment after it is deleted")
+ ExpectWithOffset(resolveOffset(additionalOffset), k8sClient.Delete(ctx, deployment)).To(Succeed())
+ EventuallyWithOffset(
+ resolveOffset(additionalOffset),
+ func() (types.UID, error) {
+ newResource := &appsv1.Deployment{}
+ err := k8sClient.Get(ctx, resourceKey(parentResource, deploymentName), newResource)
+ if err != nil {
+ return "", err
+ }
+ return newResource.UID, nil
+ }).Should(And(Not(BeEmpty()), Not(Equal(deployment.UID))), "New Deployment, with new UID, not created.")
+
+ return deployment
+}
+
+func expectDeploymentWithConsistentChecks(ctx context.Context, parentResource client.Object, deploymentName string, additionalChecks func(Gomega, *appsv1.Deployment), additionalOffset ...int) *appsv1.Deployment {
+ deployment := &appsv1.Deployment{}
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func(g Gomega) {
+ g.Expect(k8sClient.Get(ctx, resourceKey(parentResource, deploymentName), deployment)).To(Succeed(), "Expected Deployment does not exist")
- // Delete the Deployment and expect Reconcile to be called for Deployment deletion
- g.Expect(testClient.Delete(context.TODO(), deploy)).NotTo(gomega.HaveOccurred())
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
- g.Eventually(func() error { return testClient.Get(context.TODO(), deploymentKey, deploy) }, timeout).
- Should(gomega.Succeed())
+ // Verify the Deployment Specs
+ testMapContainsOtherWithGomega(g, "Deployment pod template selector", deployment.Spec.Template.Labels, deployment.Spec.Selector.MatchLabels)
+ g.Expect(len(deployment.Spec.Selector.MatchLabels)).To(BeNumerically(">=", 1), "Deployment pod template selector must have at least 1 label")
+
+ if additionalChecks != nil {
+ additionalChecks(g, deployment)
+ }
+ }).Should(Succeed())
- // Manually delete Deployment since GC isn't enabled in the test control plane
- g.Eventually(func() error { return testClient.Delete(context.TODO(), deploy) }, timeout).
- Should(gomega.MatchError("deployments.apps \"" + deploymentKey.Name + "\" not found"))
+ return deployment
+}
- return deploy
+func expectNoDeployment(ctx context.Context, parentResource client.Object, deploymentName string, additionalOffset ...int) {
+ ConsistentlyWithOffset(resolveOffset(additionalOffset), func() error {
+ return k8sClient.Get(ctx, resourceKey(parentResource, deploymentName), &appsv1.Deployment{})
+ }).Should(MatchError("deployments.apps \""+deploymentName+"\" not found"), "Deployment exists when it should not")
}
func createBasicAuthSecret(name string, key string, ns string) *corev1.Secret {
@@ -188,8 +444,9 @@ func createBasicAuthSecret(name string, key string, ns string) *corev1.Secret {
return &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns}, Data: secretData, Type: corev1.SecretTypeBasicAuth}
}
-func expectInitdbVolumeMount(t *testing.T, podTemplate *corev1.PodTemplateSpec) {
- assert.NotNil(t, podTemplate.Spec.Volumes)
+func expectInitdbVolumeMount(podTemplate *corev1.PodTemplateSpec, additionalOffset ...int) {
+ offset := resolveOffset(additionalOffset)
+ ExpectWithOffset(offset, podTemplate.Spec.Volumes).To(Not(BeNil()), "No volumes given for Pod")
var initdbVol *corev1.Volume = nil
for _, vol := range podTemplate.Spec.Volumes {
if vol.Name == "initdb" {
@@ -197,11 +454,10 @@ func expectInitdbVolumeMount(t *testing.T, podTemplate *corev1.PodTemplateSpec)
break
}
}
- assert.NotNil(t, initdbVol, fmt.Sprintf("initdb volume not found in pod template; volumes: %v", podTemplate.Spec.Volumes))
- assert.NotNil(t, initdbVol.VolumeSource.EmptyDir, "initdb volume should be an emptyDir")
+ ExpectWithOffset(offset, initdbVol).To(Not(BeNil()), "initdb volume not found in pod template; volumes: %v", podTemplate.Spec.Volumes)
+ ExpectWithOffset(offset, initdbVol.VolumeSource.EmptyDir).To(Not(BeNil()), "initdb volume should be an emptyDir")
- assert.NotNil(t, podTemplate.Spec.Containers)
- assert.True(t, len(podTemplate.Spec.Containers) > 0)
+ ExpectWithOffset(offset, podTemplate.Spec.Containers).To(Not(BeEmpty()), "No containers specified for Pod")
mainContainer := podTemplate.Spec.Containers[0]
var initdbMount *corev1.VolumeMount = nil
for _, m := range mainContainer.VolumeMounts {
@@ -210,11 +466,12 @@ func expectInitdbVolumeMount(t *testing.T, podTemplate *corev1.PodTemplateSpec)
break
}
}
- assert.NotNil(t, initdbMount)
- assert.Equal(t, util.InitdbPath, initdbMount.MountPath)
+ ExpectWithOffset(offset, initdbMount).To(Not(BeNil()), "No volume mount found for initdb")
+ ExpectWithOffset(offset, initdbMount.MountPath).To(Equal(util.InitdbPath), "Incorrect Mount Path")
}
-func expectInitContainer(t *testing.T, podTemplate *corev1.PodTemplateSpec, expName string, expVolMountName string, expVolMountPath string) *corev1.Container {
+func expectInitContainer(podTemplate *corev1.PodTemplateSpec, expName string, expVolMountName string, expVolMountPath string, additionalOffset ...int) *corev1.Container {
+ offset := resolveOffset(additionalOffset)
var expInitContainer *corev1.Container = nil
for _, cnt := range podTemplate.Spec.InitContainers {
if cnt.Name == expName {
@@ -222,8 +479,8 @@ func expectInitContainer(t *testing.T, podTemplate *corev1.PodTemplateSpec, expN
break
}
}
- assert.NotNil(t, expInitContainer, "Didn't find the "+expName+" InitContainer!")
- assert.Equal(t, 3, len(expInitContainer.Command), "Wrong command length for "+expName+" init container")
+ ExpectWithOffset(offset, expInitContainer).To(Not(BeNil()), "Didn't find the %s InitContainer", expName)
+ ExpectWithOffset(offset, expInitContainer.Command).To(HaveLen(3), "Wrong command length for %s init container", expName)
var volMount *corev1.VolumeMount = nil
for _, m := range expInitContainer.VolumeMounts {
@@ -232,8 +489,8 @@ func expectInitContainer(t *testing.T, podTemplate *corev1.PodTemplateSpec, expN
break
}
}
- assert.NotNil(t, volMount, "No "+expVolMountName+" volumeMount for "+expName+" InitContainer")
- assert.Equal(t, expVolMountPath, volMount.MountPath, "Wrong mount path "+volMount.MountPath+" for "+expName+" InitContainer")
+ ExpectWithOffset(offset, volMount).To(Not(BeNil()), "No %s volumeMount for the %s InitContainer", expVolMountName, expName)
+ ExpectWithOffset(offset, volMount.MountPath).To(Equal(expVolMountPath), "Wrong mount path for the %s InitContainer", expName)
return expInitContainer
}
@@ -249,62 +506,64 @@ func filterVarsByName(envVars []corev1.EnvVar, f func(string) bool) []corev1.Env
return filtered
}
-func testPodEnvVariables(t *testing.T, expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar) {
- testGenericPodEnvVariables(t, expectedEnvVars, foundEnvVars, "SOLR_OPTS")
+func testPodEnvVariables(expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, additionalOffset ...int) {
+ testGenericPodEnvVariables(expectedEnvVars, foundEnvVars, "SOLR_OPTS", resolveOffset(additionalOffset))
}
-func testMetricsPodEnvVariables(t *testing.T, expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar) {
- testGenericPodEnvVariables(t, expectedEnvVars, foundEnvVars, "JAVA_OPTS")
+func testPodEnvVariablesWithGomega(g Gomega, expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, additionalOffset ...int) {
+ testGenericPodEnvVariablesWithGomega(g, expectedEnvVars, foundEnvVars, "SOLR_OPTS", resolveOffset(additionalOffset))
}
-func testGenericPodEnvVariables(t *testing.T, expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, lastVarName string) {
- matchCount := 0
- for _, envVar := range foundEnvVars {
- if expectedVal, match := expectedEnvVars[envVar.Name]; match {
- matchCount += 1
- assert.Equal(t, expectedVal, envVar.Value, "Wrong value for env variable '%s' in podSpec", envVar.Name)
- }
- }
- assert.Equal(t, len(expectedEnvVars), matchCount, "Not all expected env variables found in podSpec")
- assert.Equal(t, lastVarName, foundEnvVars[len(foundEnvVars)-1].Name, lastVarName+" must be the last envVar set, as it uses other envVars.")
+func testMetricsPodEnvVariables(expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, additionalOffset ...int) {
+ testGenericPodEnvVariables(expectedEnvVars, foundEnvVars, "JAVA_OPTS", resolveOffset(additionalOffset))
}
-func testPodTolerations(t *testing.T, expectedTolerations []corev1.Toleration, foundTolerations []corev1.Toleration) {
- assert.EqualValues(t, expectedTolerations, foundTolerations, "Expected tolerations and found tolerations don't match")
+func testMetricsPodEnvVariablesWithGomega(g Gomega, expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, additionalOffset ...int) {
+ testGenericPodEnvVariablesWithGomega(g, expectedEnvVars, foundEnvVars, "JAVA_OPTS", resolveOffset(additionalOffset))
}
-func testPodProbe(t *testing.T, expectedProbe *corev1.Probe, foundProbe *corev1.Probe, probeType string) {
- assert.EqualValuesf(t, expectedProbe, foundProbe, "Incorrect default container %s probe", probeType)
+func testGenericPodEnvVariables(expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, lastVarName string, additionalOffset ...int) {
+ testGenericPodEnvVariablesWithGomega(Default, expectedEnvVars, foundEnvVars, lastVarName, resolveOffset(additionalOffset))
}
-func testPodLifecycle(t *testing.T, expectedLifecycle *corev1.Lifecycle, foundLifecycle *corev1.Lifecycle) {
- assert.EqualValuesf(t, expectedLifecycle, foundLifecycle, "Expected Lifecycle and found Lifecyle don't match")
+func testGenericPodEnvVariablesWithGomega(g Gomega, expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, lastVarName string, additionalOffset ...int) {
+ offset := resolveOffset(additionalOffset)
+ matchCount := 0
+ for _, envVar := range foundEnvVars {
+ if expectedVal, match := expectedEnvVars[envVar.Name]; match {
+ matchCount += 1
+ g.ExpectWithOffset(offset, envVar.Value).To(Equal(expectedVal), "Wrong value for env variable '%s' in podSpec", envVar.Name)
+ }
+ }
+ g.ExpectWithOffset(offset, matchCount).To(Equal(len(expectedEnvVars)), "Not all expected env variables found in podSpec")
+ g.ExpectWithOffset(offset, foundEnvVars[len(foundEnvVars)-1].Name).To(Equal(lastVarName), "%s must be the last envVar set, as it uses other envVars.", lastVarName)
}
-func testMapsEqual(t *testing.T, mapName string, expected map[string]string, found map[string]string) {
- assert.Equal(t, expected, found, "Expected and found %s are not the same", mapName)
+func testMapContainsOther(mapName string, base map[string]string, other map[string]string, additionalOffset ...int) {
+ testMapContainsOtherWithGomega(Default, mapName, base, other, resolveOffset(additionalOffset))
}
-func testMapContainsOther(t *testing.T, mapName string, base map[string]string, other map[string]string) {
+func testMapContainsOtherWithGomega(g Gomega, mapName string, base map[string]string, other map[string]string, additionalOffset ...int) {
+ offset := resolveOffset(additionalOffset)
for k, v := range other {
- foundV, foundExists := base[k]
- assert.True(t, foundExists, "Expected key '%s' does not exist in found %s", k, mapName)
- if foundExists {
- assert.Equal(t, v, foundV, "Wrong value for %s key '%s'", mapName, k)
- }
+ g.ExpectWithOffset(offset, base).To(HaveKeyWithValue(k, v), "Expected key '%s' is not correct in found %s", k, mapName)
}
}
-func testACLEnvVars(t *testing.T, actualEnvVars []corev1.EnvVar) {
+func testACLEnvVars(actualEnvVars []corev1.EnvVar, hasReadOnly bool, additionalOffset ...int) {
+ testACLEnvVarsWithGomega(Default, actualEnvVars, hasReadOnly, resolveOffset(additionalOffset))
+}
+
+func testACLEnvVarsWithGomega(g Gomega, actualEnvVars []corev1.EnvVar, hasReadOnly bool, additionalOffset ...int) {
/*
This test verifies ACL related env vars are set correctly and in the correct order, but expects a very specific config to be used in your test SolrCloud config:
-
- AllACL: &solr.ZookeeperACL{
+ set hasReadOnly = false if ReadOnlyACL is not provided
+ AllACL: &solrv1beta1.ZookeeperACL{
SecretRef: "secret-name",
UsernameKey: "user",
PasswordKey: "pass",
},
- ReadOnlyACL: &solr.ZookeeperACL{
+ ReadOnlyACL: &solrv1beta1.ZookeeperACL{
SecretRef: "read-secret-name",
UsernameKey: "read-only-user",
PasswordKey: "read-only-pass",
@@ -333,71 +592,68 @@ func testACLEnvVars(t *testing.T, actualEnvVars []corev1.EnvVar) {
},
},
},
- {
- Name: "ZK_READ_ACL_USERNAME",
- ValueFrom: &corev1.EnvVarSource{
- SecretKeyRef: &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: "read-secret-name"},
- Key: "read-only-user",
- Optional: &f,
+ }
+ if hasReadOnly {
+ zkAclEnvVars = append(zkAclEnvVars,
+ corev1.EnvVar{
+ Name: "ZK_READ_ACL_USERNAME",
+ ValueFrom: &corev1.EnvVarSource{
+ SecretKeyRef: &corev1.SecretKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{Name: "read-secret-name"},
+ Key: "read-only-user",
+ Optional: &f,
+ },
},
},
- },
- {
- Name: "ZK_READ_ACL_PASSWORD",
- ValueFrom: &corev1.EnvVarSource{
- SecretKeyRef: &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: "read-secret-name"},
- Key: "read-only-pass",
- Optional: &f,
+ corev1.EnvVar{
+ Name: "ZK_READ_ACL_PASSWORD",
+ ValueFrom: &corev1.EnvVarSource{
+ SecretKeyRef: &corev1.SecretKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{Name: "read-secret-name"},
+ Key: "read-only-pass",
+ Optional: &f,
+ },
},
},
- },
- {
- Name: "SOLR_ZK_CREDS_AND_ACLS",
- Value: "-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider -DzkDigestUsername=$(ZK_ALL_ACL_USERNAME) -DzkDigestPassword=$(ZK_ALL_ACL_PASSWORD) -DzkDigestReadonlyUsername=$(ZK_READ_ACL_USERNAME) -DzkDigestReadonlyPassword=$(ZK_READ_ACL_PASSWORD)",
- ValueFrom: nil,
- },
- }
- assert.Equal(t, zkAclEnvVars, actualEnvVars, "ZK ACL Env Vars are not correct")
+ corev1.EnvVar{
+ Name: "SOLR_ZK_CREDS_AND_ACLS",
+ Value: "-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider -DzkDigestUsername=$(ZK_ALL_ACL_USERNAME) -DzkDigestPassword=$(ZK_ALL_ACL_PASSWORD) -DzkDigestReadonlyUsername=$(ZK_READ_ACL_USERNAME) -DzkDigestReadonlyPassword=$(ZK_READ_ACL_PASSWORD)",
+ ValueFrom: nil,
+ })
+ } else {
+ zkAclEnvVars = append(zkAclEnvVars,
+ corev1.EnvVar{
+ Name: "SOLR_ZK_CREDS_AND_ACLS",
+ Value: "-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider -DzkDigestUsername=$(ZK_ALL_ACL_USERNAME) -DzkDigestPassword=$(ZK_ALL_ACL_PASSWORD)",
+ ValueFrom: nil,
+ })
+ }
+ g.ExpectWithOffset(resolveOffset(additionalOffset), actualEnvVars).To(Equal(zkAclEnvVars), "ZK ACL Env Vars are not correct")
}
-func cleanupTest(g *gomega.GomegaWithT, namespace string) {
- deleteOpts := []client.DeleteAllOfOption{
- client.InNamespace(namespace),
- }
-
- cleanupObjects := []runtime.Object{
+func cleanupTest(ctx context.Context, parentResource client.Object) {
+ cleanupObjects := []client.Object{
// Solr Operator CRDs, modify this list whenever CRDs are added/deleted
- &solr.SolrCloud{}, &solr.SolrBackup{}, &solr.SolrPrometheusExporter{},
-
- // Dependency CRDs
- &zkv1beta1.ZookeeperCluster{},
+ &solrv1beta1.SolrCloud{}, &solrv1beta1.SolrBackup{}, &solrv1beta1.SolrPrometheusExporter{},
+ &zk_api.ZookeeperCluster{},
// All dependent Kubernetes types, in order of dependence (deployment then replicaSet then pod)
- &corev1.ConfigMap{}, &batchv1.Job{}, &extv1.Ingress{},
+ &corev1.ConfigMap{}, &batchv1.Job{}, &netv1.Ingress{},
&corev1.PersistentVolumeClaim{}, &corev1.PersistentVolume{},
&appsv1.StatefulSet{}, &appsv1.Deployment{}, &appsv1.ReplicaSet{}, &corev1.Pod{}, &corev1.PersistentVolumeClaim{},
&corev1.Secret{},
}
- cleanupTestObjects(g, namespace, deleteOpts, cleanupObjects)
-
- // Delete all Services separately (https://github.com/kubernetes/kubernetes/pull/85802#issuecomment-561239845)
- opts := []client.ListOption{
- client.InNamespace(namespace),
- }
- services := &corev1.ServiceList{}
- g.Eventually(func() error { return testClient.List(context.TODO(), services, opts...) }, timeout).Should(gomega.Succeed())
-
- for _, item := range services.Items {
- g.Eventually(func() error { return testClient.Delete(context.TODO(), &item) }, timeout).Should(gomega.Succeed())
+ By("deleting all managed resources")
+ for _, obj := range cleanupObjects {
+ Expect(k8sClient.DeleteAllOf(ctx, obj, client.InNamespace(parentResource.GetNamespace()))).To(Succeed())
}
-}
-func cleanupTestObjects(g *gomega.GomegaWithT, namespace string, deleteOpts []client.DeleteAllOfOption, objects []runtime.Object) {
- // Delete all SolrClouds
- for _, obj := range objects {
- g.Eventually(func() error { return testClient.DeleteAllOf(context.TODO(), obj, deleteOpts...) }, timeout).Should(gomega.Succeed())
+ By("deleting all services individually")
+ // Clean up Services individually, since they do not support delete collection
+ serviceList := &corev1.ServiceList{}
+ Expect(k8sClient.List(ctx, serviceList, client.InNamespace(parentResource.GetNamespace()))).To(Succeed(), "List all of the services to delete in the namespace")
+ for _, item := range serviceList.Items {
+ Expect(k8sClient.Delete(ctx, &item)).To(Succeed())
}
}
@@ -573,15 +829,15 @@ var (
},
},
}
- one = int64(1)
- two = int64(2)
- four = int32(4)
- five = int32(5)
- podSecurityContext = corev1.PodSecurityContext{
+ one = int64(1)
+ two = int64(2)
+ four = int32(4)
+ five = int32(5)
+ testPodSecurityContext = corev1.PodSecurityContext{
RunAsUser: &one,
RunAsGroup: &two,
}
- extraVolumes = []solr.AdditionalVolume{
+ extraVolumes = []solrv1beta1.AdditionalVolume{
{
Name: "vol1",
Source: corev1.VolumeSource{
@@ -595,7 +851,7 @@ var (
},
},
}
- affinity = &corev1.Affinity{
+ testAffinity = &corev1.Affinity{
PodAffinity: &corev1.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
@@ -605,7 +861,7 @@ var (
PreferredDuringSchedulingIgnoredDuringExecution: nil,
},
}
- resources = corev1.ResourceRequirements{
+ testResources = corev1.ResourceRequirements{
Limits: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: *resource.NewMilliQuantity(5300, resource.DecimalSI),
},
@@ -646,7 +902,7 @@ var (
},
}
testServiceAccountName = "test-service-account"
- zkConf = zkv1beta1.ZookeeperConfig{
+ zkConf = solrv1beta1.ZookeeperConfig{
InitLimit: 1,
SyncLimit: 5,
PreAllocSize: 2,
diff --git a/controllers/solrbackup_controller.go b/controllers/solrbackup_controller.go
index 96b6f6b..bef5a13 100644
--- a/controllers/solrbackup_controller.go
+++ b/controllers/solrbackup_controller.go
@@ -20,13 +20,13 @@ package controllers
import (
"context"
"fmt"
- corev1 "k8s.io/api/core/v1"
"reflect"
"time"
"github.com/apache/solr-operator/controllers/util"
"github.com/go-logr/logr"
batchv1 "k8s.io/api/batch/v1"
+ corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -35,6 +35,7 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+ "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
@@ -43,26 +44,31 @@ import (
// SolrBackupReconciler reconciles a SolrBackup object
type SolrBackupReconciler struct {
client.Client
- Log logr.Logger
- scheme *runtime.Scheme
+ Scheme *runtime.Scheme
config *rest.Config
}
-// +kubebuilder:rbac:groups="",resources=pods/exec,verbs=create
-// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list
-// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds,verbs=get;list;watch
-// +kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds/status,verbs=get
-// +kubebuilder:rbac:groups=solr.apache.org,resources=solrbackups,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=solr.apache.org,resources=solrbackups/status,verbs=get;update;patch
-
-func (r *SolrBackupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
- _ = context.Background()
- _ = r.Log.WithValues("solrbackup", req.NamespacedName)
+//+kubebuilder:rbac:groups="",resources=pods/exec,verbs=create
+//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list
+//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=get
+//+kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds,verbs=get;list;watch
+//+kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds/status,verbs=get
+//+kubebuilder:rbac:groups=solr.apache.org,resources=solrbackups,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=solr.apache.org,resources=solrbackups/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=solr.apache.org,resources=solrbackups/finalizers,verbs=update
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
+func (r *SolrBackupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ logger := log.FromContext(ctx)
+
// Fetch the SolrBackup instance
backup := &solrv1beta1.SolrBackup{}
- err := r.Get(context.TODO(), req.NamespacedName, backup)
+ err := r.Get(ctx, req.NamespacedName, backup)
if err != nil {
if errors.IsNotFound(err) {
// Object not found, return. Created objects are automatically garbage collected.
@@ -77,8 +83,8 @@ func (r *SolrBackupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
changed := backup.WithDefaults()
if changed {
- r.Log.Info("Setting default settings for solr-backup", "namespace", backup.Namespace, "name", backup.Name)
- if err := r.Update(context.TODO(), backup); err != nil {
+ logger.Info("Setting default settings for solr-backup")
+ if err := r.Update(ctx, backup); err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{Requeue: true}, nil
@@ -88,10 +94,10 @@ func (r *SolrBackupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
// to check on the status of the async solr backup calls
requeueOrNot := reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}
- solrCloud, allCollectionsComplete, collectionActionTaken, err := reconcileSolrCloudBackup(r, backup)
+ solrCloud, allCollectionsComplete, collectionActionTaken, err := r.reconcileSolrCloudBackup(ctx, backup, logger)
if err != nil {
// TODO Should we be failing the backup for some sub-set of errors here?
- r.Log.Error(err, "Error while taking SolrCloud backup")
+ logger.Error(err, "Error while taking SolrCloud backup")
}
if allCollectionsComplete && collectionActionTaken {
// Requeue immediately to start the persisting job
@@ -107,9 +113,9 @@ func (r *SolrBackupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
if backup.Spec.Persistence != nil {
// We will count on the Job updates to be notified
requeueOrNot = reconcile.Result{}
- err = persistSolrCloudBackups(r, backup, solrCloud)
+ err = r.persistSolrCloudBackups(ctx, backup, solrCloud, logger)
if err != nil {
- r.Log.Error(err, "Error while persisting SolrCloud backup")
+ logger.Error(err, "Error while persisting SolrCloud backup")
}
} else {
// Persistence not configured for this backup, mark as finished.
@@ -129,8 +135,8 @@ func (r *SolrBackupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
}
if !reflect.DeepEqual(oldStatus, backup.Status) {
- r.Log.Info("Updating status for solr-backup", "namespace", backup.Namespace, "name", backup.Name)
- err = r.Status().Update(context.TODO(), backup)
+ logger.Info("Updating status for solr-backup")
+ err = r.Status().Update(ctx, backup)
}
if backup.Status.Finished {
@@ -140,13 +146,13 @@ func (r *SolrBackupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
return requeueOrNot, err
}
-func reconcileSolrCloudBackup(r *SolrBackupReconciler, backup *solrv1beta1.SolrBackup) (solrCloud *solrv1beta1.SolrCloud, collectionBackupsFinished bool, actionTaken bool, err error) {
+func (r *SolrBackupReconciler) reconcileSolrCloudBackup(ctx context.Context, backup *solrv1beta1.SolrBackup, logger logr.Logger) (solrCloud *solrv1beta1.SolrCloud, collectionBackupsFinished bool, actionTaken bool, err error) {
// Get the solrCloud that this backup is for.
solrCloud = &solrv1beta1.SolrCloud{}
- err = r.Get(context.TODO(), types.NamespacedName{Namespace: backup.Namespace, Name: backup.Spec.SolrCloud}, solrCloud)
+ err = r.Get(ctx, types.NamespacedName{Namespace: backup.Namespace, Name: backup.Spec.SolrCloud}, solrCloud)
if err != nil && errors.IsNotFound(err) {
- r.Log.Error(err, "Could not find cloud to backup", "namespace", backup.Namespace, "backupName", backup.Name, "solrCloudName", backup.Spec.SolrCloud)
+ logger.Error(err, "Could not find cloud to backup", "solrCloud", backup.Spec.SolrCloud)
return nil, collectionBackupsFinished, actionTaken, err
} else if err != nil {
return nil, collectionBackupsFinished, actionTaken, err
@@ -155,7 +161,7 @@ func reconcileSolrCloudBackup(r *SolrBackupReconciler, backup *solrv1beta1.SolrB
var httpHeaders map[string]string
if solrCloud.Spec.SolrSecurity != nil {
basicAuthSecret := &corev1.Secret{}
- if err := r.Get(context.TODO(), types.NamespacedName{Name: solrCloud.BasicAuthSecretName(), Namespace: solrCloud.Namespace}, basicAuthSecret); err != nil {
+ if err := r.Get(ctx, types.NamespacedName{Name: solrCloud.BasicAuthSecretName(), Namespace: solrCloud.Namespace}, basicAuthSecret); err != nil {
return nil, collectionBackupsFinished, actionTaken, err
}
httpHeaders = map[string]string{"Authorization": util.BasicAuthHeader(basicAuthSecret)}
@@ -189,18 +195,17 @@ func reconcileSolrCloudBackup(r *SolrBackupReconciler, backup *solrv1beta1.SolrB
// Make sure that all solr nodes are active and have the backupRestore shared volume mounted
cloudReady := solrCloud.Status.BackupRestoreReady && (solrCloud.Status.Replicas == solrCloud.Status.ReadyReplicas)
if !cloudReady {
- r.Log.Info("Cloud not ready for backup backup", "namespace", backup.Namespace, "cloud", solrCloud.Name, "backup", backup.Name)
+ logger.Info("Cloud not ready for backup backup", "solrCloud", solrCloud.Name)
return solrCloud, collectionBackupsFinished, actionTaken, errors.NewServiceUnavailable("Cloud is not ready for backups or restores")
}
// Only set the solr version at the start of the backup. This shouldn't change throughout the backup.
backup.Status.SolrVersion = solrCloud.Status.Version
-
}
// Go through each collection specified and reconcile the backup.
for _, collection := range backup.Spec.Collections {
- _, err = reconcileSolrCollectionBackup(backup, solrCloud, backupRepository, collection, httpHeaders)
+ _, err = reconcileSolrCollectionBackup(backup, solrCloud, backupRepository, collection, httpHeaders, logger)
}
// First check if the collection backups have been completed
@@ -209,7 +214,7 @@ func reconcileSolrCloudBackup(r *SolrBackupReconciler, backup *solrv1beta1.SolrB
return solrCloud, collectionBackupsFinished, actionTaken, err
}
-func reconcileSolrCollectionBackup(backup *solrv1beta1.SolrBackup, solrCloud *solrv1beta1.SolrCloud, backupRepository *solrv1beta1.SolrBackupRepository, collection string, httpHeaders map[string]string) (finished bool, err error) {
+func reconcileSolrCollectionBackup(backup *solrv1beta1.SolrBackup, solrCloud *solrv1beta1.SolrCloud, backupRepository *solrv1beta1.SolrBackupRepository, collection string, httpHeaders map[string]string, logger logr.Logger) (finished bool, err error) {
now := metav1.Now()
collectionBackupStatus := solrv1beta1.CollectionBackupStatus{}
collectionBackupStatus.Collection = collection
@@ -225,7 +230,7 @@ func reconcileSolrCollectionBackup(backup *solrv1beta1.SolrBackup, solrCloud *so
// If the collection backup hasn't started, start it
if !collectionBackupStatus.InProgress && !collectionBackupStatus.Finished {
// Start the backup by calling solr
- started, err := util.StartBackupForCollection(solrCloud, backupRepository, backup, collection, httpHeaders)
+ started, err := util.StartBackupForCollection(solrCloud, backupRepository, backup, collection, httpHeaders, logger)
if err != nil {
return true, err
}
@@ -235,7 +240,7 @@ func reconcileSolrCollectionBackup(backup *solrv1beta1.SolrBackup, solrCloud *so
}
} else if collectionBackupStatus.InProgress {
// Check the state of the backup, when it is in progress, and update the state accordingly
- finished, successful, asyncStatus, error := util.CheckBackupForCollection(solrCloud, collection, backup.Name, httpHeaders)
+ finished, successful, asyncStatus, error := util.CheckBackupForCollection(solrCloud, collection, backup.Name, httpHeaders, logger)
if error != nil {
return false, error
}
@@ -250,7 +255,7 @@ func reconcileSolrCollectionBackup(backup *solrv1beta1.SolrBackup, solrCloud *so
collectionBackupStatus.FinishTime = &now
}
- err = util.DeleteAsyncInfoForBackup(solrCloud, collection, backup.Name, httpHeaders)
+ err = util.DeleteAsyncInfoForBackup(solrCloud, collection, backup.Name, httpHeaders, logger)
} else {
collectionBackupStatus.AsyncBackupStatus = asyncStatus
}
@@ -265,7 +270,7 @@ func reconcileSolrCollectionBackup(backup *solrv1beta1.SolrBackup, solrCloud *so
return collectionBackupStatus.Finished, err
}
-func persistSolrCloudBackups(r *SolrBackupReconciler, backup *solrv1beta1.SolrBackup, solrCloud *solrv1beta1.SolrCloud) (err error) {
+func (r *SolrBackupReconciler) persistSolrCloudBackups(ctx context.Context, backup *solrv1beta1.SolrBackup, solrCloud *solrv1beta1.SolrCloud, logger logr.Logger) (err error) {
if backup.Status.PersistenceStatus.Finished {
return nil
}
@@ -281,18 +286,16 @@ func persistSolrCloudBackups(r *SolrBackupReconciler, backup *solrv1beta1.SolrBa
if util.IsRepoManaged(backupRepository) {
persistenceJob := util.GenerateBackupPersistenceJobForCloud(backupRepository, backup, solrCloud)
- if err := controllerutil.SetControllerReference(backup, persistenceJob, r.scheme); err != nil {
+ if err := controllerutil.SetControllerReference(backup, persistenceJob, r.Scheme); err != nil {
return err
}
foundPersistenceJob := &batchv1.Job{}
- err = r.Get(context.TODO(), types.NamespacedName{Name: persistenceJob.Name, Namespace: persistenceJob.Namespace}, foundPersistenceJob)
+ err = r.Get(ctx, types.NamespacedName{Name: persistenceJob.Name, Namespace: persistenceJob.Namespace}, foundPersistenceJob)
if err == nil && !backup.Status.PersistenceStatus.InProgress {
-
- }
- if err != nil && errors.IsNotFound(err) {
- r.Log.Info("Creating Persistence Job", "namespace", persistenceJob.Namespace, "name", persistenceJob.Name)
- err = r.Create(context.TODO(), persistenceJob)
+ } else if err != nil && errors.IsNotFound(err) {
+ logger.Info("Creating Persistence Job", "job", persistenceJob.Name)
+ err = r.Create(ctx, persistenceJob)
backup.Status.PersistenceStatus.InProgress = true
if backup.Status.PersistenceStatus.StartTime == nil {
backup.Status.PersistenceStatus.StartTime = &now
@@ -327,16 +330,12 @@ func persistSolrCloudBackups(r *SolrBackupReconciler, backup *solrv1beta1.SolrBa
}
}
+// SetupWithManager sets up the controller with the Manager.
func (r *SolrBackupReconciler) SetupWithManager(mgr ctrl.Manager) error {
- return r.SetupWithManagerAndReconciler(mgr, r)
-}
+ r.config = mgr.GetConfig()
-func (r *SolrBackupReconciler) SetupWithManagerAndReconciler(mgr ctrl.Manager, reconciler reconcile.Reconciler) error {
- ctrlBuilder := ctrl.NewControllerManagedBy(mgr).
+ return ctrl.NewControllerManagedBy(mgr).
For(&solrv1beta1.SolrBackup{}).
- Owns(&batchv1.Job{})
-
- r.config = mgr.GetConfig()
- r.scheme = mgr.GetScheme()
- return ctrlBuilder.Complete(reconciler)
+ Owns(&batchv1.Job{}).
+ Complete(r)
}
diff --git a/controllers/solrbackup_controller_test.go b/controllers/solrbackup_controller_test.go
deleted file mode 100644
index eb1d478..0000000
--- a/controllers/solrbackup_controller_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 controllers
-
-import (
- solr "github.com/apache/solr-operator/api/v1beta1"
- "github.com/onsi/gomega"
- "golang.org/x/net/context"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/types"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/manager"
- "sigs.k8s.io/controller-runtime/pkg/reconcile"
- "testing"
-)
-
-var _ reconcile.Reconciler = &SolrBackupReconciler{}
-
-var (
- expectedBackupRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: "foo-back", Namespace: "default"}}
-)
-
-func TestBackupReconcile(t *testing.T) {
- g := gomega.NewGomegaWithT(t)
- instance := &solr.SolrBackup{ObjectMeta: metav1.ObjectMeta{Name: expectedBackupRequest.Name, Namespace: expectedBackupRequest.Namespace}}
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrBackupReconciler := &SolrBackupReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrBackup"),
- }
- newRec, requests := SetupTestReconcile(solrBackupReconciler)
- g.Expect(solrBackupReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- // Create the SolrBackup object
- err = testClient.Create(context.TODO(), instance)
- // The instance object may not be a valid object because it might be missing some required fields.
- // Please modify the instance object by adding required fields and then remove the following if statement.
- if apierrors.IsInvalid(err) {
- t.Logf("failed to create object, got an invalid object error: %v", err)
- return
- }
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedBackupRequest)))
-}
diff --git a/controllers/solrcloud_controller.go b/controllers/solrcloud_controller.go
index 8f11ad6..cc27f62 100644
--- a/controllers/solrcloud_controller.go
+++ b/controllers/solrcloud_controller.go
@@ -21,29 +21,30 @@ import (
"context"
"crypto/md5"
"fmt"
+ "k8s.io/apimachinery/pkg/runtime"
"reflect"
"sort"
"strings"
"time"
- solr "github.com/apache/solr-operator/api/v1beta1"
+ solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
"github.com/apache/solr-operator/controllers/util"
+ "github.com/apache/solr-operator/controllers/zk_api"
"github.com/go-logr/logr"
- zk "github.com/pravega/zookeeper-operator/pkg/apis/zookeeper/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
- netv1 "k8s.io/api/networking/v1beta1"
+ netv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
- "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
+ "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
@@ -52,8 +53,7 @@ import (
// SolrCloudReconciler reconciles a SolrCloud object
type SolrCloudReconciler struct {
client.Client
- scheme *runtime.Scheme
- Log logr.Logger
+ Scheme *runtime.Scheme
}
var useZkCRD bool
@@ -62,31 +62,34 @@ func UseZkCRD(useCRD bool) {
useZkCRD = useCRD
}
-// +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;delete
-// +kubebuilder:rbac:groups="",resources=pods/status,verbs=get
-// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups="",resources=services/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=apps,resources=statefulsets/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups="",resources=configmaps/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list;watch;delete
-// +kubebuilder:rbac:groups=zookeeper.pravega.io,resources=zookeeperclusters,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=zookeeper.pravega.io,resources=zookeeperclusters/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds/finalizers,verbs=update
-
-func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
- _ = context.Background()
-
- logger := r.Log.WithValues("namespace", req.Namespace, "solrCloud", req.Name)
- // Fetch the SolrCloud instance
- instance := &solr.SolrCloud{}
- err := r.Get(context.TODO(), req.NamespacedName, instance)
+//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;delete
+//+kubebuilder:rbac:groups="",resources=pods/status,verbs=get
+//+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups="",resources=services/status,verbs=get
+//+kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=apps,resources=statefulsets/status,verbs=get
+//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get
+//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups="",resources=configmaps/status,verbs=get
+//+kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list;watch;delete
+//+kubebuilder:rbac:groups=zookeeper.pravega.io,resources=zookeeperclusters,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=zookeeper.pravega.io,resources=zookeeperclusters/status,verbs=get
+//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=solr.apache.org,resources=solrclouds/finalizers,verbs=update
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
+func (r *SolrCloudReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ logger := log.FromContext(ctx)
+
+ instance := &solrv1beta1.SolrCloud{}
+ err := r.Get(ctx, req.NamespacedName, instance)
if err != nil {
if errors.IsNotFound(err) {
// Object not found, return. Created objects are automatically garbage collected.
@@ -100,7 +103,7 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
changed := instance.WithDefaults()
if changed {
logger.Info("Setting default settings for SolrCloud")
- if err := r.Update(context.TODO(), instance); err != nil {
+ if err := r.Update(ctx, instance); err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{Requeue: true}, nil
@@ -109,10 +112,10 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// When working with the clouds, some actions outside of kube may need to be retried after a few seconds
requeueOrNot := reconcile.Result{}
- newStatus := solr.SolrCloudStatus{}
+ newStatus := solrv1beta1.SolrCloudStatus{}
blockReconciliationOfStatefulSet := false
- if err := reconcileZk(r, logger, instance, &newStatus); err != nil {
+ if err := r.reconcileZk(ctx, logger, instance, &newStatus); err != nil {
return requeueOrNot, err
}
@@ -122,21 +125,21 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// Check if the Common Service already exists
commonServiceLogger := logger.WithValues("service", commonService.Name)
foundCommonService := &corev1.Service{}
- err = r.Get(context.TODO(), types.NamespacedName{Name: commonService.Name, Namespace: commonService.Namespace}, foundCommonService)
+ err = r.Get(ctx, types.NamespacedName{Name: commonService.Name, Namespace: commonService.Namespace}, foundCommonService)
if err != nil && errors.IsNotFound(err) {
commonServiceLogger.Info("Creating Common Service")
- if err = controllerutil.SetControllerReference(instance, commonService, r.scheme); err == nil {
- err = r.Create(context.TODO(), commonService)
+ if err = controllerutil.SetControllerReference(instance, commonService, r.Scheme); err == nil {
+ err = r.Create(ctx, commonService)
}
} else if err == nil {
var needsUpdate bool
- needsUpdate, err = util.OvertakeControllerRef(instance, foundCommonService, r.scheme)
+ needsUpdate, err = util.OvertakeControllerRef(instance, foundCommonService, r.Scheme)
needsUpdate = util.CopyServiceFields(commonService, foundCommonService, commonServiceLogger) || needsUpdate
// Update the found Service and write the result back if there are any changes
if needsUpdate && err == nil {
commonServiceLogger.Info("Updating Common Service")
- err = r.Update(context.TODO(), foundCommonService)
+ err = r.Update(ctx, foundCommonService)
}
}
if err != nil {
@@ -149,7 +152,7 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// Generate a service for every Node
if instance.UsesIndividualNodeServices() {
for _, nodeName := range solrNodeNames {
- err, ip := reconcileNodeService(r, logger, instance, nodeName)
+ err, ip := r.reconcileNodeService(ctx, logger, instance, nodeName)
if err != nil {
return requeueOrNot, err
}
@@ -172,21 +175,21 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// Check if the HeadlessService already exists
headlessServiceLogger := logger.WithValues("service", headless.Name)
foundHeadless := &corev1.Service{}
- err = r.Get(context.TODO(), types.NamespacedName{Name: headless.Name, Namespace: headless.Namespace}, foundHeadless)
+ err = r.Get(ctx, types.NamespacedName{Name: headless.Name, Namespace: headless.Namespace}, foundHeadless)
if err != nil && errors.IsNotFound(err) {
headlessServiceLogger.Info("Creating Headless Service")
- if err = controllerutil.SetControllerReference(instance, headless, r.scheme); err == nil {
- err = r.Create(context.TODO(), headless)
+ if err = controllerutil.SetControllerReference(instance, headless, r.Scheme); err == nil {
+ err = r.Create(ctx, headless)
}
} else if err == nil {
var needsUpdate bool
- needsUpdate, err = util.OvertakeControllerRef(instance, foundHeadless, r.scheme)
+ needsUpdate, err = util.OvertakeControllerRef(instance, foundHeadless, r.Scheme)
needsUpdate = util.CopyServiceFields(headless, foundHeadless, headlessServiceLogger) || needsUpdate
// Update the found HeadlessService and write the result back if there are any changes
if needsUpdate && err == nil {
headlessServiceLogger.Info("Updating Headless Service")
- err = r.Update(context.TODO(), foundHeadless)
+ err = r.Update(ctx, foundHeadless)
}
}
if err != nil {
@@ -203,7 +206,7 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
providedConfigMapName := instance.Spec.CustomSolrKubeOptions.ConfigMapOptions.ProvidedConfigMap
foundConfigMap := &corev1.ConfigMap{}
nn := types.NamespacedName{Name: providedConfigMapName, Namespace: instance.Namespace}
- err = r.Get(context.TODO(), nn, foundConfigMap)
+ err = r.Get(ctx, nn, foundConfigMap)
if err != nil {
return requeueOrNot, err // if they passed a providedConfigMap name, then it must exist
}
@@ -254,21 +257,21 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// Check if the ConfigMap already exists
configMapLogger := logger.WithValues("configMap", configMap.Name)
foundConfigMap := &corev1.ConfigMap{}
- err = r.Get(context.TODO(), types.NamespacedName{Name: configMap.Name, Namespace: configMap.Namespace}, foundConfigMap)
+ err = r.Get(ctx, types.NamespacedName{Name: configMap.Name, Namespace: configMap.Namespace}, foundConfigMap)
if err != nil && errors.IsNotFound(err) {
configMapLogger.Info("Creating ConfigMap")
- if err = controllerutil.SetControllerReference(instance, configMap, r.scheme); err == nil {
- err = r.Create(context.TODO(), configMap)
+ if err = controllerutil.SetControllerReference(instance, configMap, r.Scheme); err == nil {
+ err = r.Create(ctx, configMap)
}
} else if err == nil {
var needsUpdate bool
- needsUpdate, err = util.OvertakeControllerRef(instance, foundConfigMap, r.scheme)
+ needsUpdate, err = util.OvertakeControllerRef(instance, foundConfigMap, r.Scheme)
needsUpdate = util.CopyConfigMapFields(configMap, foundConfigMap, configMapLogger) || needsUpdate
// Update the found ConfigMap and write the result back if there are any changes
if needsUpdate && err == nil {
configMapLogger.Info("Updating ConfigMap")
- err = r.Update(context.TODO(), foundConfigMap)
+ err = r.Update(ctx, foundConfigMap)
}
}
if err != nil {
@@ -280,7 +283,7 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if instance.Spec.SolrSecurity != nil {
sec := instance.Spec.SolrSecurity
- if sec.AuthenticationType != solr.Basic {
+ if sec.AuthenticationType != solrv1beta1.Basic {
return requeueOrNot, fmt.Errorf("%s not supported! Only 'Basic' authentication is supported by the Solr operator",
instance.Spec.SolrSecurity.AuthenticationType)
}
@@ -296,7 +299,6 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
}
}
- ctx := context.TODO()
basicAuthSecret := &corev1.Secret{}
// user has the option of providing a secret with credentials the operator should use to make requests to Solr
@@ -316,10 +318,10 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
err = r.Get(ctx, types.NamespacedName{Name: instance.BasicAuthSecretName(), Namespace: instance.Namespace}, basicAuthSecret)
if err != nil && errors.IsNotFound(err) {
authSecret, bootstrapSecret := util.GenerateBasicAuthSecretWithBootstrap(instance)
- if err := controllerutil.SetControllerReference(instance, authSecret, r.scheme); err != nil {
+ if err := controllerutil.SetControllerReference(instance, authSecret, r.Scheme); err != nil {
return requeueOrNot, err
}
- if err := controllerutil.SetControllerReference(instance, bootstrapSecret, r.scheme); err != nil {
+ if err := controllerutil.SetControllerReference(instance, bootstrapSecret, r.Scheme); err != nil {
return requeueOrNot, err
}
err = r.Create(ctx, authSecret)
@@ -390,7 +392,7 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// Check if the StatefulSet already exists
statefulSetLogger := logger.WithValues("statefulSet", statefulSet.Name)
foundStatefulSet := &appsv1.StatefulSet{}
- err = r.Get(context.TODO(), types.NamespacedName{Name: statefulSet.Name, Namespace: statefulSet.Namespace}, foundStatefulSet)
+ err = r.Get(ctx, types.NamespacedName{Name: statefulSet.Name, Namespace: statefulSet.Namespace}, foundStatefulSet)
// Set the annotation for a scheduled restart, if necessary.
if nextRestartAnnotation, reconcileWaitDuration, err := util.ScheduleNextRestart(instance.Spec.UpdateStrategy.RestartSchedule, foundStatefulSet.Spec.Template.Annotations); err != nil {
@@ -413,8 +415,8 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// Update or Create the StatefulSet
if err != nil && errors.IsNotFound(err) {
statefulSetLogger.Info("Creating StatefulSet")
- if err = controllerutil.SetControllerReference(instance, statefulSet, r.scheme); err == nil {
- err = r.Create(context.TODO(), statefulSet)
+ if err = controllerutil.SetControllerReference(instance, statefulSet, r.Scheme); err == nil {
+ err = r.Create(ctx, statefulSet)
}
// Find which labels the PVCs will be using, to use for the finalizer
pvcLabelSelector = statefulSet.Spec.Selector.MatchLabels
@@ -425,13 +427,13 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// Check to see if the StatefulSet needs an update
var needsUpdate bool
- needsUpdate, err = util.OvertakeControllerRef(instance, foundStatefulSet, r.scheme)
+ needsUpdate, err = util.OvertakeControllerRef(instance, foundStatefulSet, r.Scheme)
needsUpdate = util.CopyStatefulSetFields(statefulSet, foundStatefulSet, statefulSetLogger) || needsUpdate
// Update the found StatefulSet and write the result back if there are any changes
if needsUpdate && err == nil {
statefulSetLogger.Info("Updating StatefulSet")
- err = r.Update(context.TODO(), foundStatefulSet)
+ err = r.Update(ctx, foundStatefulSet)
}
}
if err != nil {
@@ -440,7 +442,7 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
} else {
// If we are blocking the reconciliation of the statefulSet, we still want to find information about it.
foundStatefulSet := &appsv1.StatefulSet{}
- err = r.Get(context.TODO(), types.NamespacedName{Name: instance.StatefulSetName(), Namespace: instance.Namespace}, foundStatefulSet)
+ err = r.Get(ctx, types.NamespacedName{Name: instance.StatefulSetName(), Namespace: instance.Namespace}, foundStatefulSet)
if err == nil {
// Find the status
statefulSetStatus = foundStatefulSet.Status
@@ -454,7 +456,7 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// Do not reconcile the storage finalizer unless we have PVC Labels that we know the Solr data PVCs are using.
// Otherwise it will delete all PVCs possibly
if len(pvcLabelSelector) > 0 {
- if err := r.reconcileStorageFinalizer(instance, pvcLabelSelector, logger); err != nil {
+ if err := r.reconcileStorageFinalizer(ctx, instance, pvcLabelSelector, logger); err != nil {
logger.Error(err, "Cannot delete PVCs while garbage collecting after deletion.")
updateRequeueAfter(&requeueOrNot, time.Second*15)
}
@@ -462,14 +464,14 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
var outOfDatePods, outOfDatePodsNotStarted []corev1.Pod
var availableUpdatedPodCount int
- outOfDatePods, outOfDatePodsNotStarted, availableUpdatedPodCount, err = reconcileCloudStatus(r, instance, logger, &newStatus, statefulSetStatus)
+ outOfDatePods, outOfDatePodsNotStarted, availableUpdatedPodCount, err = r.reconcileCloudStatus(ctx, instance, logger, &newStatus, statefulSetStatus)
if err != nil {
return requeueOrNot, err
}
// Manage the updating of out-of-spec pods, if the Managed UpdateStrategy has been specified.
totalPodCount := int(*instance.Spec.Replicas)
- if instance.Spec.UpdateStrategy.Method == solr.ManagedUpdate && len(outOfDatePods)+len(outOfDatePodsNotStarted) > 0 {
+ if instance.Spec.UpdateStrategy.Method == solrv1beta1.ManagedUpdate && len(outOfDatePods)+len(outOfDatePodsNotStarted) > 0 {
updateLogger := logger.WithName("ManagedUpdateSelector")
// The out of date pods that have not been started, should all be updated immediately.
@@ -491,7 +493,7 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
podsToUpdate = append(podsToUpdate, additionalPodsToUpdate...)
for _, pod := range podsToUpdate {
- err = r.Delete(context.Background(), &pod, client.Preconditions{
+ err = r.Delete(ctx, &pod, client.Preconditions{
UID: &pod.UID,
})
if err != nil {
@@ -505,28 +507,28 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
}
extAddressabilityOpts := instance.Spec.SolrAddressability.External
- if extAddressabilityOpts != nil && extAddressabilityOpts.Method == solr.Ingress {
+ if extAddressabilityOpts != nil && extAddressabilityOpts.Method == solrv1beta1.Ingress {
// Generate Ingress
ingress := util.GenerateIngress(instance, solrNodeNames)
// Check if the Ingress already exists
ingressLogger := logger.WithValues("ingress", ingress.Name)
foundIngress := &netv1.Ingress{}
- err = r.Get(context.TODO(), types.NamespacedName{Name: ingress.Name, Namespace: ingress.Namespace}, foundIngress)
+ err = r.Get(ctx, types.NamespacedName{Name: ingress.Name, Namespace: ingress.Namespace}, foundIngress)
if err != nil && errors.IsNotFound(err) {
ingressLogger.Info("Creating Ingress")
- if err = controllerutil.SetControllerReference(instance, ingress, r.scheme); err == nil {
- err = r.Create(context.TODO(), ingress)
+ if err = controllerutil.SetControllerReference(instance, ingress, r.Scheme); err == nil {
+ err = r.Create(ctx, ingress)
}
} else if err == nil {
var needsUpdate bool
- needsUpdate, err = util.OvertakeControllerRef(instance, foundIngress, r.scheme)
+ needsUpdate, err = util.OvertakeControllerRef(instance, foundIngress, r.Scheme)
needsUpdate = util.CopyIngressFields(ingress, foundIngress, ingressLogger) || needsUpdate
// Update the found Ingress and write the result back if there are any changes
if needsUpdate && err == nil {
ingressLogger.Info("Updating Ingress")
- err = r.Update(context.TODO(), foundIngress)
+ err = r.Update(ctx, foundIngress)
}
}
if err != nil {
@@ -537,7 +539,7 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if !reflect.DeepEqual(instance.Status, newStatus) {
instance.Status = newStatus
logger.Info("Updating SolrCloud Status", "status", instance.Status)
- err = r.Status().Update(context.TODO(), instance)
+ err = r.Status().Update(ctx, instance)
if err != nil {
return requeueOrNot, err
}
@@ -546,10 +548,11 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return requeueOrNot, nil
}
-func reconcileCloudStatus(r *SolrCloudReconciler, solrCloud *solr.SolrCloud, logger logr.Logger, newStatus *solr.SolrCloudStatus, statefulSetStatus appsv1.StatefulSetStatus) (outOfDatePods []corev1.Pod, outOfDatePodsNotStarted []corev1.Pod, availableUpdatedPodCount int, err error) {
+func (r *SolrCloudReconciler) reconcileCloudStatus(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, logger logr.Logger,
+ newStatus *solrv1beta1.SolrCloudStatus, statefulSetStatus appsv1.StatefulSetStatus) (outOfDatePods []corev1.Pod, outOfDatePodsNotStarted []corev1.Pod, availableUpdatedPodCount int, err error) {
foundPods := &corev1.PodList{}
selectorLabels := solrCloud.SharedLabels()
- selectorLabels["technology"] = solr.SolrTechnologyLabel
+ selectorLabels["technology"] = solrv1beta1.SolrTechnologyLabel
labelSelector := labels.SelectorFromSet(selectorLabels)
listOps := &client.ListOptions{
@@ -557,14 +560,14 @@ func reconcileCloudStatus(r *SolrCloudReconciler, solrCloud *solr.SolrCloud, log
LabelSelector: labelSelector,
}
- err = r.List(context.TODO(), foundPods, listOps)
+ err = r.List(ctx, foundPods, listOps)
if err != nil {
return outOfDatePods, outOfDatePodsNotStarted, availableUpdatedPodCount, err
}
var otherVersions []string
nodeNames := make([]string, len(foundPods.Items))
- nodeStatusMap := map[string]solr.SolrNodeStatus{}
+ nodeStatusMap := map[string]solrv1beta1.SolrNodeStatus{}
updateRevision := statefulSetStatus.UpdateRevision
@@ -582,7 +585,7 @@ func reconcileCloudStatus(r *SolrCloudReconciler, solrCloud *solr.SolrCloud, log
allPodsBackupReady := true
for idx, p := range foundPods.Items {
nodeNames[idx] = p.Name
- nodeStatus := solr.SolrNodeStatus{}
+ nodeStatus := solrv1beta1.SolrNodeStatus{}
nodeStatus.Name = p.Name
nodeStatus.NodeName = p.Spec.NodeName
nodeStatus.InternalAddress = solrCloud.UrlScheme(false) + "://" + solrCloud.InternalNodeUrl(nodeStatus.Name, true)
@@ -591,7 +594,7 @@ func reconcileCloudStatus(r *SolrCloudReconciler, solrCloud *solr.SolrCloud, log
}
if len(p.Status.ContainerStatuses) > 0 {
// The first container should always be running solr
- nodeStatus.Version = solr.ImageVersion(p.Spec.Containers[0].Image)
+ nodeStatus.Version = solrv1beta1.ImageVersion(p.Spec.Containers[0].Image)
if nodeStatus.Version != solrCloud.Spec.SolrImage.Tag {
otherVersions = append(otherVersions, nodeStatus.Version)
}
@@ -645,7 +648,7 @@ func reconcileCloudStatus(r *SolrCloudReconciler, solrCloud *solr.SolrCloud, log
}
sort.Strings(nodeNames)
- newStatus.SolrNodes = make([]solr.SolrNodeStatus, len(nodeNames))
+ newStatus.SolrNodes = make([]solrv1beta1.SolrNodeStatus, len(nodeNames))
for idx, nodeName := range nodeNames {
newStatus.SolrNodes[idx] = nodeStatusMap[nodeName]
}
@@ -671,8 +674,7 @@ func reconcileCloudStatus(r *SolrCloudReconciler, solrCloud *solr.SolrCloud, log
return outOfDatePods, outOfDatePodsNotStarted, availableUpdatedPodCount, nil
}
-func isPodReadyForBackup(pod *corev1.Pod, solrCloud *solr.SolrCloud) bool {
-
+func isPodReadyForBackup(pod *corev1.Pod, solrCloud *solrv1beta1.SolrCloud) bool {
// If solrcloud doesn't request backup support then everything is 'ready' implicitly
if len(solrCloud.Spec.BackupRepositories) == 0 {
return false
@@ -687,31 +689,31 @@ func isPodReadyForBackup(pod *corev1.Pod, solrCloud *solr.SolrCloud) bool {
return true
}
-func reconcileNodeService(r *SolrCloudReconciler, logger logr.Logger, instance *solr.SolrCloud, nodeName string) (err error, ip string) {
+func (r *SolrCloudReconciler) reconcileNodeService(ctx context.Context, logger logr.Logger, instance *solrv1beta1.SolrCloud, nodeName string) (err error, ip string) {
// Generate Node Service
service := util.GenerateNodeService(instance, nodeName)
// Check if the Node Service already exists
nodeServiceLogger := logger.WithValues("service", service.Name)
foundService := &corev1.Service{}
- err = r.Get(context.TODO(), types.NamespacedName{Name: service.Name, Namespace: service.Namespace}, foundService)
+ err = r.Get(ctx, types.NamespacedName{Name: service.Name, Namespace: service.Namespace}, foundService)
if err != nil && errors.IsNotFound(err) {
nodeServiceLogger.Info("Creating Node Service")
- if err = controllerutil.SetControllerReference(instance, service, r.scheme); err == nil {
- err = r.Create(context.TODO(), service)
+ if err = controllerutil.SetControllerReference(instance, service, r.Scheme); err == nil {
+ err = r.Create(ctx, service)
}
} else if err == nil {
ip = foundService.Spec.ClusterIP
// Check to see if the Service needs an update
var needsUpdate bool
- needsUpdate, err = util.OvertakeControllerRef(instance, foundService, r.scheme)
+ needsUpdate, err = util.OvertakeControllerRef(instance, foundService, r.Scheme)
needsUpdate = util.CopyServiceFields(service, foundService, nodeServiceLogger) || needsUpdate
if needsUpdate && err == nil {
// Update the found Node service because there are differences between our version and the existing version
nodeServiceLogger.Info("Updating Node Service")
- err = r.Update(context.TODO(), foundService)
+ err = r.Update(ctx, foundService)
}
}
if err != nil {
@@ -720,8 +722,7 @@ func reconcileNodeService(r *SolrCloudReconciler, logger logr.Logger, instance *
return nil, ip
}
-
-func reconcileZk(r *SolrCloudReconciler, logger logr.Logger, instance *solr.SolrCloud, newStatus *solr.SolrCloudStatus) error {
+func (r *SolrCloudReconciler) reconcileZk(ctx context.Context, logger logr.Logger, instance *solrv1beta1.SolrCloud, newStatus *solrv1beta1.SolrCloudStatus) error {
zkRef := instance.Spec.ZookeeperRef
if zkRef.ConnectionInfo != nil {
@@ -736,22 +737,22 @@ func reconcileZk(r *SolrCloudReconciler, logger logr.Logger, instance *solr.Solr
// Check if the ZookeeperCluster already exists
zkLogger := logger.WithValues("zookeeperCluster", zkCluster.Name)
- foundZkCluster := &zk.ZookeeperCluster{}
- err := r.Get(context.TODO(), types.NamespacedName{Name: zkCluster.Name, Namespace: zkCluster.Namespace}, foundZkCluster)
+ foundZkCluster := &zk_api.ZookeeperCluster{}
+ err := r.Get(ctx, types.NamespacedName{Name: zkCluster.Name, Namespace: zkCluster.Namespace}, foundZkCluster)
if err != nil && errors.IsNotFound(err) {
zkLogger.Info("Creating Zookeeer Cluster")
- if err = controllerutil.SetControllerReference(instance, zkCluster, r.scheme); err == nil {
- err = r.Create(context.TODO(), zkCluster)
+ if err = controllerutil.SetControllerReference(instance, zkCluster, r.Scheme); err == nil {
+ err = r.Create(ctx, zkCluster)
}
} else if err == nil {
var needsUpdate bool
- needsUpdate, err = util.OvertakeControllerRef(instance, foundZkCluster, r.scheme)
+ needsUpdate, err = util.OvertakeControllerRef(instance, foundZkCluster, r.Scheme)
needsUpdate = util.CopyZookeeperClusterFields(zkCluster, foundZkCluster, zkLogger) || needsUpdate
// Update the found ZookeeperCluster and write the result back if there are any changes
if needsUpdate && err == nil {
zkLogger.Info("Updating Zookeeer Cluster")
- err = r.Update(context.TODO(), foundZkCluster)
+ err = r.Update(ctx, foundZkCluster)
}
}
external := &foundZkCluster.Status.ExternalClientEndpoint
@@ -763,7 +764,7 @@ func reconcileZk(r *SolrCloudReconciler, logger logr.Logger, instance *solr.Solr
for i := range internal {
internal[i] = fmt.Sprintf("%s-%d.%s-headless.%s.svc.%s:%d", zkCluster.Name, i, zkCluster.Name, zkCluster.Namespace, kubeDomain, zkCluster.ZookeeperPorts().Client)
}
- newStatus.ZookeeperConnectionInfo = solr.ZookeeperConnectionInfo{
+ newStatus.ZookeeperConnectionInfo = solrv1beta1.ZookeeperConnectionInfo{
InternalConnectionString: strings.Join(internal, ","),
ExternalConnectionString: external,
ChRoot: pzk.ChRoot,
@@ -778,34 +779,34 @@ func reconcileZk(r *SolrCloudReconciler, logger logr.Logger, instance *solr.Solr
// Logic derived from:
// - https://book.kubebuilder.io/reference/using-finalizers.html
// - https://github.com/pravega/zookeeper-operator/blob/v0.2.9/pkg/controller/zookeepercluster/zookeepercluster_controller.go#L629
-func (r *SolrCloudReconciler) reconcileStorageFinalizer(cloud *solr.SolrCloud, pvcLabelSelector map[string]string, logger logr.Logger) error {
+func (r *SolrCloudReconciler) reconcileStorageFinalizer(ctx context.Context, cloud *solrv1beta1.SolrCloud, pvcLabelSelector map[string]string, logger logr.Logger) error {
// If persistentStorage is being used by the cloud, and the reclaim policy is set to "Delete",
// then set a finalizer for the storage on the cloud, and delete the PVCs if the solrcloud has been deleted.
- if cloud.Spec.StorageOptions.PersistentStorage != nil && cloud.Spec.StorageOptions.PersistentStorage.VolumeReclaimPolicy == solr.VolumeReclaimPolicyDelete {
+ if cloud.Spec.StorageOptions.PersistentStorage != nil && cloud.Spec.StorageOptions.PersistentStorage.VolumeReclaimPolicy == solrv1beta1.VolumeReclaimPolicyDelete {
if cloud.ObjectMeta.DeletionTimestamp.IsZero() {
// The object is not being deleted, so if it does not have our finalizer,
// then lets add the finalizer and update the object
if !util.ContainsString(cloud.ObjectMeta.Finalizers, util.SolrStorageFinalizer) {
cloud.ObjectMeta.Finalizers = append(cloud.ObjectMeta.Finalizers, util.SolrStorageFinalizer)
- if err := r.Update(context.Background(), cloud); err != nil {
+ if err := r.Update(ctx, cloud); err != nil {
return err
}
}
- return r.cleanupOrphanPVCs(cloud, pvcLabelSelector, logger)
+ return r.cleanupOrphanPVCs(ctx, cloud, pvcLabelSelector, logger)
} else if util.ContainsString(cloud.ObjectMeta.Finalizers, util.SolrStorageFinalizer) {
// The object is being deleted
logger.Info("Deleting PVCs for SolrCloud")
// Our finalizer is present, so let's delete all existing PVCs
- if err := r.cleanUpAllPVCs(cloud, pvcLabelSelector, logger); err != nil {
+ if err := r.cleanUpAllPVCs(ctx, cloud, pvcLabelSelector, logger); err != nil {
return err
}
logger.Info("Deleted PVCs for SolrCloud")
// remove our finalizer from the list and update it.
cloud.ObjectMeta.Finalizers = util.RemoveString(cloud.ObjectMeta.Finalizers, util.SolrStorageFinalizer)
- if err := r.Update(context.Background(), cloud); err != nil {
+ if err := r.Update(ctx, cloud); err != nil {
return err
}
}
@@ -813,15 +814,15 @@ func (r *SolrCloudReconciler) reconcileStorageFinalizer(cloud *solr.SolrCloud, p
// remove our finalizer from the list and update it, because there is no longer a need to delete PVCs after the cloud is deleted.
logger.Info("Removing storage finalizer for SolrCloud")
cloud.ObjectMeta.Finalizers = util.RemoveString(cloud.ObjectMeta.Finalizers, util.SolrStorageFinalizer)
- if err := r.Update(context.Background(), cloud); err != nil {
+ if err := r.Update(ctx, cloud); err != nil {
return err
}
}
return nil
}
-func (r *SolrCloudReconciler) getPVCCount(cloud *solr.SolrCloud, pvcLabelSelector map[string]string) (pvcCount int, err error) {
- pvcList, err := r.getPVCList(cloud, pvcLabelSelector)
+func (r *SolrCloudReconciler) getPVCCount(ctx context.Context, cloud *solrv1beta1.SolrCloud, pvcLabelSelector map[string]string) (pvcCount int, err error) {
+ pvcList, err := r.getPVCList(ctx, cloud, pvcLabelSelector)
if err != nil {
return -1, err
}
@@ -829,10 +830,10 @@ func (r *SolrCloudReconciler) getPVCCount(cloud *solr.SolrCloud, pvcLabelSelecto
return pvcCount, nil
}
-func (r *SolrCloudReconciler) cleanupOrphanPVCs(cloud *solr.SolrCloud, pvcLabelSelector map[string]string, logger logr.Logger) (err error) {
+func (r *SolrCloudReconciler) cleanupOrphanPVCs(ctx context.Context, cloud *solrv1beta1.SolrCloud, pvcLabelSelector map[string]string, logger logr.Logger) (err error) {
// this check should make sure we do not delete the PVCs before the STS has scaled down
if cloud.Status.ReadyReplicas == cloud.Status.Replicas {
- pvcList, err := r.getPVCList(cloud, pvcLabelSelector)
+ pvcList, err := r.getPVCList(ctx, cloud, pvcLabelSelector)
if err != nil {
return err
}
@@ -840,7 +841,7 @@ func (r *SolrCloudReconciler) cleanupOrphanPVCs(cloud *solr.SolrCloud, pvcLabelS
for _, pvcItem := range pvcList.Items {
// delete only Orphan PVCs
if util.IsPVCOrphan(pvcItem.Name, *cloud.Spec.Replicas) {
- r.deletePVC(pvcItem, logger)
+ r.deletePVC(ctx, pvcItem, logger)
}
}
}
@@ -848,7 +849,7 @@ func (r *SolrCloudReconciler) cleanupOrphanPVCs(cloud *solr.SolrCloud, pvcLabelS
return nil
}
-func (r *SolrCloudReconciler) getPVCList(cloud *solr.SolrCloud, pvcLabelSelector map[string]string) (pvList corev1.PersistentVolumeClaimList, err error) {
+func (r *SolrCloudReconciler) getPVCList(ctx context.Context, cloud *solrv1beta1.SolrCloud, pvcLabelSelector map[string]string) (pvList corev1.PersistentVolumeClaimList, err error) {
selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
MatchLabels: pvcLabelSelector,
})
@@ -857,22 +858,22 @@ func (r *SolrCloudReconciler) getPVCList(cloud *solr.SolrCloud, pvcLabelSelector
LabelSelector: selector,
}
pvcList := &corev1.PersistentVolumeClaimList{}
- err = r.Client.List(context.TODO(), pvcList, pvclistOps)
+ err = r.Client.List(ctx, pvcList, pvclistOps)
return *pvcList, err
}
-func (r *SolrCloudReconciler) cleanUpAllPVCs(cloud *solr.SolrCloud, pvcLabelSelector map[string]string, logger logr.Logger) (err error) {
- pvcList, err := r.getPVCList(cloud, pvcLabelSelector)
+func (r *SolrCloudReconciler) cleanUpAllPVCs(ctx context.Context, cloud *solrv1beta1.SolrCloud, pvcLabelSelector map[string]string, logger logr.Logger) (err error) {
+ pvcList, err := r.getPVCList(ctx, cloud, pvcLabelSelector)
if err != nil {
return err
}
for _, pvcItem := range pvcList.Items {
- r.deletePVC(pvcItem, logger)
+ r.deletePVC(ctx, pvcItem, logger)
}
return nil
}
-func (r *SolrCloudReconciler) deletePVC(pvcItem corev1.PersistentVolumeClaim, logger logr.Logger) {
+func (r *SolrCloudReconciler) deletePVC(ctx context.Context, pvcItem corev1.PersistentVolumeClaim, logger logr.Logger) {
pvcDelete := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: pvcItem.Name,
@@ -880,19 +881,63 @@ func (r *SolrCloudReconciler) deletePVC(pvcItem corev1.PersistentVolumeClaim, lo
},
}
logger.Info("Deleting PVC for SolrCloud", "PVC", pvcItem.Name)
- err := r.Client.Delete(context.TODO(), pvcDelete)
+ err := r.Client.Delete(ctx, pvcDelete)
if err != nil {
logger.Error(err, "Error deleting PVC for SolrCloud", "PVC", pvcDelete.Name)
}
}
-func (r *SolrCloudReconciler) SetupWithManager(mgr ctrl.Manager) error {
- return r.SetupWithManagerAndReconciler(mgr, r)
+// Ensure the TLS config is ready, such as verifying the TLS secret exists, to enable TLS on SolrCloud pods
+func (r *SolrCloudReconciler) reconcileTLSConfig(instance *solrv1beta1.SolrCloud) (*util.TLSCerts, error) {
+ tls := util.TLSCertsForSolrCloud(instance)
+
+ // Has the user configured a secret containing the TLS cert files that we need to mount into the Solr pods?
+ serverCert := tls.ServerConfig.Options
+ if serverCert.PKCS12Secret != nil {
+ // Ensure one or the other have been configured, but not both
+ if serverCert.MountedTLSDir != nil {
+ return nil, fmt.Errorf("invalid TLS config, either supply `solrTLS.pkcs12Secret` or `solrTLS.mountedTLSDir` but not both")
+ }
+
+ _, err := tls.ServerConfig.VerifyKeystoreAndTruststoreSecretConfig(&r.Client)
+ if err != nil {
+ return nil, err
+ }
+
+ // is there a client TLS config too?
+ if tls.ClientConfig != nil {
+ if tls.ClientConfig.Options.PKCS12Secret == nil {
+ // cannot mix options with the client cert, if the server cert comes from a secret, so too must the client, not a mountedTLSDir
+ return nil, fmt.Errorf("invalid TLS config, the 'solrClientTLS.pkcs12Secret' option is required when using a secret for server cert")
+ }
+
+ // shouldn't configure a client cert if it's the same as the server cert
+ if tls.ClientConfig.Options.PKCS12Secret == tls.ServerConfig.Options.PKCS12Secret {
+ return nil, fmt.Errorf("invalid TLS config, the 'solrClientTLS.pkcs12Secret' option should not be the same as the 'solrTLS.pkcs12Secret'")
+ }
+
+ _, err := tls.ClientConfig.VerifyKeystoreAndTruststoreSecretConfig(&r.Client)
+ if err != nil {
+ return nil, err
+ }
+ }
+ } else if serverCert.MountedTLSDir != nil {
+ // per-pod TLS files get mounted into a dir on the pod dynamically using some external agent / CSI driver type mechanism
+ // make sure the client cert, if configured, is also using the mounted dir option as mixing the two approaches is not supported
+ if tls.ClientConfig != nil && tls.ClientConfig.Options.MountedTLSDir == nil {
+ return nil, fmt.Errorf("invalid TLS config, client cert must also use 'mountedTLSDir' when using 'solrTLS.mountedTLSDir'")
+ }
+ } else {
+ return nil, fmt.Errorf("invalid TLS config, must supply either 'pkcs12Secret' or 'mountedTLSDir' for the server cert")
+ }
+
+ return tls, nil
}
-func (r *SolrCloudReconciler) SetupWithManagerAndReconciler(mgr ctrl.Manager, reconciler reconcile.Reconciler) error {
+// SetupWithManager sets up the controller with the Manager.
+func (r *SolrCloudReconciler) SetupWithManager(mgr ctrl.Manager) error {
ctrlBuilder := ctrl.NewControllerManagedBy(mgr).
- For(&solr.SolrCloud{}).
+ For(&solrv1beta1.SolrCloud{}).
Owns(&corev1.ConfigMap{}).
Owns(&appsv1.StatefulSet{}).
Owns(&corev1.Service{}).
@@ -916,17 +961,16 @@ func (r *SolrCloudReconciler) SetupWithManagerAndReconciler(mgr ctrl.Manager, re
}
if useZkCRD {
- ctrlBuilder = ctrlBuilder.Owns(&zk.ZookeeperCluster{})
+ ctrlBuilder = ctrlBuilder.Owns(&zk_api.ZookeeperCluster{})
}
- r.scheme = mgr.GetScheme()
- return ctrlBuilder.Complete(reconciler)
+ return ctrlBuilder.Complete(r)
}
func (r *SolrCloudReconciler) indexAndWatchForProvidedConfigMaps(mgr ctrl.Manager, ctrlBuilder *builder.Builder) (*builder.Builder, error) {
- if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &solr.SolrCloud{}, ".spec.customSolrKubeOptions.configMapOptions.providedConfigMap", func(rawObj runtime.Object) []string {
+ if err := mgr.GetFieldIndexer().IndexField(context.Background(), &solrv1beta1.SolrCloud{}, ".spec.customSolrKubeOptions.configMapOptions.providedConfigMap", func(rawObj client.Object) []string {
// grab the SolrCloud object, extract the used configMap...
- solrCloud := rawObj.(*solr.SolrCloud)
+ solrCloud := rawObj.(*solrv1beta1.SolrCloud)
if solrCloud.Spec.CustomSolrKubeOptions.ConfigMapOptions == nil {
return nil
}
@@ -947,9 +991,9 @@ func (r *SolrCloudReconciler) indexAndWatchForProvidedConfigMaps(mgr ctrl.Manage
func (r *SolrCloudReconciler) indexAndWatchForTLSSecret(mgr ctrl.Manager, ctrlBuilder *builder.Builder) (*builder.Builder, error) {
field := ".spec.solrTLS.pkcs12Secret"
- if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &solr.SolrCloud{}, field, func(rawObj runtime.Object) []string {
+ if err := mgr.GetFieldIndexer().IndexField(context.Background(), &solrv1beta1.SolrCloud{}, field, func(rawObj client.Object) []string {
// grab the SolrCloud object, extract the used configMap...
- solrCloud := rawObj.(*solr.SolrCloud)
+ solrCloud := rawObj.(*solrv1beta1.SolrCloud)
if solrCloud.Spec.SolrTLS == nil || solrCloud.Spec.SolrTLS.PKCS12Secret == nil {
return nil
}
@@ -967,9 +1011,9 @@ func (r *SolrCloudReconciler) indexAndWatchForTLSSecret(mgr ctrl.Manager, ctrlBu
func (r *SolrCloudReconciler) indexAndWatchForClientTLSSecret(mgr ctrl.Manager, ctrlBuilder *builder.Builder) (*builder.Builder, error) {
field := ".spec.solrClientTLS.pkcs12Secret"
- if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &solr.SolrCloud{}, field, func(rawObj runtime.Object) []string {
+ if err := mgr.GetFieldIndexer().IndexField(context.Background(), &solrv1beta1.SolrCloud{}, field, func(rawObj client.Object) []string {
// grab the SolrCloud object, extract the used configMap...
- solrCloud := rawObj.(*solr.SolrCloud)
+ solrCloud := rawObj.(*solrv1beta1.SolrCloud)
if solrCloud.Spec.SolrClientTLS == nil || solrCloud.Spec.SolrClientTLS.PKCS12Secret == nil {
return nil
}
@@ -985,15 +1029,15 @@ func (r *SolrCloudReconciler) indexAndWatchForClientTLSSecret(mgr ctrl.Manager,
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})), nil
}
-func (r *SolrCloudReconciler) findSolrCloudByFieldValueFunc(field string) *handler.EnqueueRequestsFromMapFunc {
- return &handler.EnqueueRequestsFromMapFunc{
- ToRequests: handler.ToRequestsFunc(func(a handler.MapObject) []reconcile.Request {
- foundClouds := &solr.SolrCloudList{}
+func (r *SolrCloudReconciler) findSolrCloudByFieldValueFunc(field string) handler.EventHandler {
+ return handler.EnqueueRequestsFromMapFunc(
+ func(obj client.Object) []reconcile.Request {
+ foundClouds := &solrv1beta1.SolrCloudList{}
listOps := &client.ListOptions{
- FieldSelector: fields.OneTermEqualSelector(field, a.Meta.GetName()),
- Namespace: a.Meta.GetNamespace(),
+ FieldSelector: fields.OneTermEqualSelector(field, obj.GetName()),
+ Namespace: obj.GetNamespace(),
}
- err := r.List(context.TODO(), foundClouds, listOps)
+ err := r.List(context.Background(), foundClouds, listOps)
if err != nil {
return []reconcile.Request{}
}
@@ -1008,60 +1052,5 @@ func (r *SolrCloudReconciler) findSolrCloudByFieldValueFunc(field string) *handl
}
}
return requests
- }),
- }
-}
-
-// Ensure the TLS config is ready, such as verifying the TLS secret exists, to enable TLS on SolrCloud pods
-func (r *SolrCloudReconciler) reconcileTLSConfig(instance *solr.SolrCloud) (*util.TLSCerts, error) {
- tls := util.TLSCertsForSolrCloud(instance)
-
- // Has the user configured a secret containing the TLS cert files that we need to mount into the Solr pods?
- serverCert := tls.ServerConfig.Options
- if serverCert.PKCS12Secret != nil {
- // Ensure one or the other have been configured, but not both
- if serverCert.MountedTLSDir != nil {
- return nil, fmt.Errorf("invalid TLS config, either supply `solrTLS.pkcs12Secret` or `solrTLS.mountedTLSDir` but not both")
- }
-
- _, err := tls.ServerConfig.VerifyKeystoreAndTruststoreSecretConfig(&r.Client)
- if err != nil {
- return nil, err
- }
-
- // is there a client TLS config too?
- if tls.ClientConfig != nil {
- if tls.ClientConfig.Options.PKCS12Secret == nil {
- // cannot mix options with the client cert, if the server cert comes from a secret, so too must the client, not a mountedTLSDir
- return nil, fmt.Errorf("invalid TLS config, the 'solrClientTLS.pkcs12Secret' option is required when using a secret for server cert")
- }
-
- // shouldn't configure a client cert if it's the same as the server cert
- if tls.ClientConfig.Options.PKCS12Secret == tls.ServerConfig.Options.PKCS12Secret {
- return nil, fmt.Errorf("invalid TLS config, the 'solrClientTLS.pkcs12Secret' option should not be the same as the 'solrTLS.pkcs12Secret'")
- }
-
- _, err := tls.ClientConfig.VerifyKeystoreAndTruststoreSecretConfig(&r.Client)
- if err != nil {
- return nil, err
- }
- }
- } else if serverCert.MountedTLSDir != nil {
- // per-pod TLS files get mounted into a dir on the pod dynamically using some external agent / CSI driver type mechanism
- // make sure the client cert, if configured, is also using the mounted dir option as mixing the two approaches is not supported
- if tls.ClientConfig != nil && tls.ClientConfig.Options.MountedTLSDir == nil {
- return nil, fmt.Errorf("invalid TLS config, client cert must also use 'mountedTLSDir' when using 'solrTLS.mountedTLSDir'")
- }
- } else {
- return nil, fmt.Errorf("invalid TLS config, must supply either 'pkcs12Secret' or 'mountedTLSDir' for the server cert")
- }
-
- return tls, nil
-}
-
-// Set the requeueAfter if it has not been set, or is greater than the new time to requeue at
-func updateRequeueAfter(requeueOrNot *reconcile.Result, newWait time.Duration) {
- if requeueOrNot.RequeueAfter <= 0 || requeueOrNot.RequeueAfter > newWait {
- requeueOrNot.RequeueAfter = newWait
- }
+ })
}
diff --git a/controllers/solrcloud_controller_basic_auth_test.go b/controllers/solrcloud_controller_basic_auth_test.go
new file mode 100644
index 0000000..433f67e
--- /dev/null
+++ b/controllers/solrcloud_controller_basic_auth_test.go
@@ -0,0 +1,294 @@
+/*
+ * 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 controllers
+
+import (
+ "context"
+ "fmt"
+ solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
+ "github.com/apache/solr-operator/controllers/util"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var _ = FDescribe("SolrCloud controller - Basic Auth", func() {
+ var (
+ ctx context.Context
+
+ solrCloud *solrv1beta1.SolrCloud
+ )
+
+ BeforeEach(func() {
+ ctx = context.Background()
+
+ replicas := int32(1)
+ solrCloud = &solrv1beta1.SolrCloud{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ Namespace: "default",
+ },
+ Spec: solrv1beta1.SolrCloudSpec{
+ Replicas: &replicas,
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
+ },
+ },
+ }
+ })
+
+ JustBeforeEach(func() {
+ By("creating the SolrCloud")
+ Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed())
+
+ By("defaulting the missing SolrCloud values")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.WithDefaults()).To(BeFalse(), "The SolrCloud spec should not need to be defaulted eventually")
+ })
+ })
+
+ AfterEach(func() {
+ cleanupTest(ctx, solrCloud)
+ })
+
+ FContext("Boostrap Security JSON", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{
+ AuthenticationType: solrv1beta1.Basic,
+ ProbesRequireAuth: true,
+ }
+ })
+ FIt("has the correct resources", func() {
+ expectStatefulSetBasicAuthConfig(ctx, solrCloud, true)
+ })
+ })
+
+ FContext("Boostrap Security JSON with ZK ACLs", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{
+ AuthenticationType: solrv1beta1.Basic,
+ ProbesRequireAuth: true,
+ }
+ solrCloud.Spec.ZookeeperRef.ConnectionInfo.AllACL = &solrv1beta1.ZookeeperACL{
+ SecretRef: "secret-name",
+ UsernameKey: "user",
+ PasswordKey: "pass",
+ }
+ solrCloud.Spec.ZookeeperRef.ConnectionInfo.ReadOnlyACL = &solrv1beta1.ZookeeperACL{
+ SecretRef: "read-secret-name",
+ UsernameKey: "read-only-user",
+ PasswordKey: "read-only-pass",
+ }
+ })
+ FIt("has the correct resources", func() {
+ expectStatefulSetBasicAuthConfig(ctx, solrCloud, true)
+ })
+ })
+
+ FContext("Boostrap Security JSON - Delete Secret", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{
+ AuthenticationType: solrv1beta1.Basic,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("Testing that the statefulSet exists and has the correct information")
+ expectStatefulSetBasicAuthConfig(ctx, solrCloud, true)
+
+ By("Deleting bootstrapped secret")
+ expectSecretWithChecks(ctx, solrCloud, solrCloud.SecurityBootstrapSecretName(), func(g Gomega, found *corev1.Secret) {
+ g.Expect(k8sClient.Delete(ctx, found)).To(Succeed(), "Cannot delete bootstrapped secret")
+ })
+
+ By("Making sure the statefulSet is updated after deleting the bootstrap secret")
+ expectStatefulSetBasicAuthConfig(ctx, solrCloud, false /* bootstrap secret not found */)
+ })
+ })
+
+ FContext("User Provided Credentials", func() {
+ BeforeEach(func() {
+ basicAuthSecretName := "my-basic-auth-secret"
+ solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{
+ AuthenticationType: solrv1beta1.Basic,
+ BasicAuthSecret: basicAuthSecretName,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("Making sure that no statefulSet exists until the BasicAuth Secret is created")
+ expectNoStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ By("Create the basicAuth secret")
+ basicAuthSecret := createBasicAuthSecret(solrCloud.Spec.SolrSecurity.BasicAuthSecret, solrv1beta1.DefaultBasicAuthUsername, solrCloud.Namespace)
+ Expect(k8sClient.Create(ctx, basicAuthSecret)).To(Succeed(), "Could not create the necessary basicAuth secret")
+
+ By("Make sure the StatefulSet is created and configured correctly")
+ expectStatefulSetBasicAuthConfig(ctx, solrCloud, false)
+ })
+ })
+})
+
+var boostrapedSecretKeys = []string{
+ "admin",
+ "solr",
+ "security.json",
+}
+
+func expectStatefulSetBasicAuthConfig(ctx context.Context, sc *solrv1beta1.SolrCloud, expectBootstrapSecret bool) *appsv1.StatefulSet {
+ Expect(sc.Spec.SolrSecurity).To(Not(BeNil()), "solrSecurity is not configured for this SolrCloud instance!")
+
+ stateful := expectStatefulSetWithChecks(ctx, sc, sc.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ expectBasicAuthConfigOnPodTemplateWithGomega(g, sc, &found.Spec.Template, expectBootstrapSecret)
+ })
+
+ expectSecretWithChecks(ctx, sc, sc.BasicAuthSecretName(), func(innerG Gomega, found *corev1.Secret) {
+ innerG.Expect(found.Data).To(HaveKey(corev1.BasicAuthUsernameKey), "The Basic auth secret does not have a username entry for key %s", corev1.BasicAuthUsernameKey)
+ innerG.Expect(string(found.Data[corev1.BasicAuthUsernameKey])).To(Equal(solrv1beta1.DefaultBasicAuthUsername), "password should be set for k8s-oper in the basic auth secret")
+ innerG.Expect(found.Data).To(HaveKey(corev1.BasicAuthPasswordKey), "The Basic auth secret does not have a password entry for key %s", corev1.BasicAuthPasswordKey)
+ innerG.Expect(found.Data[corev1.BasicAuthPasswordKey]).To(Not(BeEmpty()), "password should be set for k8s-oper in the basic auth secret and not be empty")
+ })
+
+ // verify a security.json gets bootstrapped if not using a user-provided secret
+ if sc.Spec.SolrSecurity.BasicAuthSecret == "" && expectBootstrapSecret {
+ bootstrapSecret := expectSecretWithChecks(ctx, sc, sc.SecurityBootstrapSecretName(), func(g Gomega, found *corev1.Secret) {
+ for _, key := range boostrapedSecretKeys {
+ g.Expect(found.Data).To(HaveKey(key), "The Boostrapped Security secret does not key %s", key)
+ g.Expect(found.Data[key]).To(Not(BeEmpty()), "The Boostrapped Security secret key should not be empty: %s", key)
+ }
+ })
+
+ if sc.Spec.CustomSolrKubeOptions.PodOptions != nil {
+ probePaths := util.GetCustomProbePaths(sc)
+ if len(probePaths) > 0 {
+ securityJson := string(bootstrapSecret.Data["security.json"])
+ Expect(securityJson).To(ContainSubstring(util.DefaultProbePath), "bootstrapped security.json should have an authz rule for probe path: %s", util.DefaultProbePath)
+ for _, p := range probePaths {
+ p = p[len("/solr"):] // drop the /solr part on the path
+ Expect(securityJson).To(ContainSubstring(p), "bootstrapped security.json should have an authz rule for probe path: %s", p)
+ }
+ }
+ }
+ }
+
+ return stateful
+}
+
+// Ensures config is setup for basic-auth enabled Solr pods
+func expectBasicAuthConfigOnPodTemplate(solrCloud *solrv1beta1.SolrCloud, podTemplate *corev1.PodTemplateSpec, expectBootstrapSecret bool) *corev1.Container {
+ return expectBasicAuthConfigOnPodTemplateWithGomega(Default, solrCloud, podTemplate, expectBootstrapSecret)
+}
+
+// Ensures config is setup for basic-auth enabled Solr pods
+func expectBasicAuthConfigOnPodTemplateWithGomega(g Gomega, solrCloud *solrv1beta1.SolrCloud, podTemplate *corev1.PodTemplateSpec, expectBootstrapSecret bool) *corev1.Container {
+ // check the env vars needed for the probes to work with auth
+ g.Expect(podTemplate.Spec.Containers).To(Not(BeEmpty()), "Solr Pod requires containers")
+ mainContainer := podTemplate.Spec.Containers[0]
+ g.Expect(mainContainer).To(Not(BeNil()), "Didn't find the main solrcloud-node container in the sts!")
+ g.Expect(mainContainer.Env).To(Not(BeEmpty()), "Didn't find the main solrcloud-node container in the sts!")
+
+ // probes with auth
+ if solrCloud.Spec.SolrSecurity.ProbesRequireAuth {
+ g.Expect(podTemplate.Spec.Volumes).To(Not(BeEmpty()), "Solr Pod requires volumes when using BasicAuth")
+ secretName := solrCloud.BasicAuthSecretName()
+ var basicAuthSecretVol *corev1.Volume = nil
+ for _, vol := range podTemplate.Spec.Volumes {
+ if vol.Name == secretName {
+ basicAuthSecretVol = &vol
+ break
+ }
+ }
+ g.Expect(basicAuthSecretVol).To(Not(BeNil()), "No basic auth secret volume found in Pod spec")
+ g.Expect(basicAuthSecretVol.VolumeSource.Secret).To(Not(BeNil()), "Didn't find the basic auth secret volume in sts config!")
+ g.Expect(basicAuthSecretVol.VolumeSource.Secret.SecretName).To(Equal(solrCloud.BasicAuthSecretName()), "Wrong secret name used for basic auth volume")
+
+ var basicAuthSecretVolMount *corev1.VolumeMount = nil
+ for _, m := range podTemplate.Spec.Containers[0].VolumeMounts {
+ if m.Name == secretName {
+ basicAuthSecretVolMount = &m
+ break
+ }
+ }
+ g.Expect(basicAuthSecretVolMount).To(Not(BeNil()), "No Basic Auth volume mount used in Solr container")
+ g.Expect(basicAuthSecretVolMount.MountPath).To(Equal("/etc/secrets/"+secretName), "Wrong path used to mount Basic Auth volume")
+
+ expProbeCmd := fmt.Sprintf("JAVA_TOOL_OPTIONS=\"-Dbasicauth=$(cat /etc/secrets/%s-solrcloud-basic-auth/username):$(cat /etc/secrets/%s-solrcloud-basic-auth/password)\" java "+
+ "-Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory "+
+ "-Dsolr.install.dir=\"/opt/solr\" -Dlog4j.configurationFile=\"/opt/solr/server/resources/log4j2-console.xml\" "+
+ "-classpath \"/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/*:/opt/solr/server/lib/ext/*:/opt/solr/server/lib/*\" "+
+ "org.apache.solr.util.SolrCLI api -get http://localhost:8983/solr/admin/info/system",
+ solrCloud.Name, solrCloud.Name)
+ g.Expect(mainContainer.LivenessProbe).To(Not(BeNil()), "main container should have a liveness probe defined")
+ g.Expect(mainContainer.LivenessProbe.Exec).To(Not(BeNil()), "liveness probe should have an exec when auth is enabled")
+ g.Expect(mainContainer.LivenessProbe.Exec.Command).To(Not(BeEmpty()), "liveness probe command cannot be empty")
+ g.Expect(mainContainer.LivenessProbe.Exec.Command[2]).To(Equal(expProbeCmd), "liveness probe should invoke java with auth opts")
+ g.Expect(mainContainer.LivenessProbe.TimeoutSeconds).To(BeEquivalentTo(5), "liveness probe default timeout should be increased when using basicAuth")
+ g.Expect(mainContainer.ReadinessProbe).To(Not(BeNil()), "main container should have a readiness probe defined")
+ g.Expect(mainContainer.ReadinessProbe.Exec).To(Not(BeNil()), "readiness probe should have an exec when auth is enabled")
+ g.Expect(mainContainer.ReadinessProbe.Exec.Command).To(Not(BeEmpty()), "readiness probe command cannot be empty")
+ g.Expect(mainContainer.ReadinessProbe.Exec.Command[2]).To(Equal(expProbeCmd), "readiness probe should invoke java with auth opts")
+ g.Expect(mainContainer.ReadinessProbe.TimeoutSeconds).To(BeEquivalentTo(5), "readiness probe default timeout should be increased when using basicAuth")
+ }
+
+ // if no user-provided auth secret, then check that security.json gets bootstrapped correctly
+ if solrCloud.Spec.SolrSecurity.BasicAuthSecret == "" {
+ // initContainers
+ g.Expect(podTemplate.Spec.InitContainers).To(Not(BeEmpty()), "The Solr Pod template requires an init container to bootstrap the security.json")
+ var expInitContainer *corev1.Container = nil
+ for _, cnt := range podTemplate.Spec.InitContainers {
+ if cnt.Name == "setup-zk" {
+ expInitContainer = &cnt
+ break
+ }
+ }
+
+ if expectBootstrapSecret {
+ // if the zookeeperRef has ACLs set, verify the env vars were set correctly for this initContainer
+ allACL, _ := solrCloud.Spec.ZookeeperRef.GetACLs()
+ if allACL != nil {
+ g.Expect(expInitContainer.Env).To(HaveLen(10), "Wrong number of env vars using ACLs and Basic Auth")
+ g.Expect(expInitContainer.Env[len(expInitContainer.Env)-2].Name).To(Equal("SOLR_OPTS"), "Env var SOLR_OPTS is misplaced the Solr Pod env vars")
+ g.Expect(expInitContainer.Env[len(expInitContainer.Env)-1].Name).To(Equal("SECURITY_JSON"), "Env var SECURITY_JSON is misplaced the Solr Pod env vars")
+ testACLEnvVarsWithGomega(g, expInitContainer.Env[3:len(expInitContainer.Env)-2], true)
+ } // else this ref not using ACLs
+
+ g.Expect(expInitContainer).To(Not(BeNil()), "Didn't find the setup-zk InitContainer in the sts!")
+ expCmd := "ZK_SECURITY_JSON=$(/opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost ${ZK_HOST} -cmd get /security.json); " +
+ "if [ ${#ZK_SECURITY_JSON} -lt 3 ]; then " +
+ "echo $SECURITY_JSON > /tmp/security.json; " +
+ "/opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost ${ZK_HOST} -cmd putfile /security.json /tmp/security.json; echo \"put security.json in ZK\"; fi"
+ g.Expect(expInitContainer.Command[2]).To(ContainSubstring(expCmd), "setup-zk initContainer not configured to bootstrap security.json!")
+ } else {
+ g.Expect(expInitContainer).To(Or(
+ BeNil(),
+ WithTransform(
+ func(container corev1.Container) string {
+ if len(container.Command) < 3 {
+ return ""
+ }
+ return container.Command[2]
+ },
+ Not(ContainSubstring("SECURITY_JSON")))), "setup-zk initContainer not reconciled after bootstrap secret deleted")
+ }
+
+ }
+
+ return &mainContainer // return as a convenience in case tests want to do more checking on the main container
+}
diff --git a/controllers/solrcloud_controller_externaldns_test.go b/controllers/solrcloud_controller_externaldns_test.go
index 696c769..4c2361a 100644
--- a/controllers/solrcloud_controller_externaldns_test.go
+++ b/controllers/solrcloud_controller_externaldns_test.go
@@ -18,631 +18,425 @@
package controllers
import (
- "testing"
-
- "k8s.io/apimachinery/pkg/types"
-
+ "context"
+ solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
"github.com/apache/solr-operator/controllers/util"
- "github.com/stretchr/testify/assert"
-
- solr "github.com/apache/solr-operator/api/v1beta1"
- "github.com/onsi/gomega"
- "golang.org/x/net/context"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/manager"
- "sigs.k8s.io/controller-runtime/pkg/reconcile"
)
-var _ reconcile.Reconciler = &SolrCloudReconciler{}
+var _ = FDescribe("SolrCloud controller - External DNS", func() {
+ var (
+ ctx context.Context
+
+ solrCloud *solrv1beta1.SolrCloud
+ )
-func TestEDSCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
+ BeforeEach(func() {
+ ctx = context.Background()
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
+ replicas := int32(2)
+ solrCloud = &solrv1beta1.SolrCloud{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ Namespace: "default",
+ },
+ Spec: solrv1beta1.SolrCloudSpec{
+ Replicas: &replicas,
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
+ },
+ CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
+ CommonServiceOptions: &solrv1beta1.ServiceOptions{
+ Annotations: testCommonServiceAnnotations,
+ Labels: testCommonServiceLabels,
+ },
+ HeadlessServiceOptions: &solrv1beta1.ServiceOptions{
+ Annotations: testHeadlessServiceAnnotations,
+ Labels: testHeadlessServiceLabels,
+ },
},
},
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.ExternalDNS,
+ }
+ })
+
+ JustBeforeEach(func() {
+ By("creating the SolrCloud")
+ Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed())
+
+ By("defaulting the missing SolrCloud values")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.WithDefaults()).To(BeFalse(), "The SolrCloud spec should not need to be defaulted eventually")
+ })
+ })
+
+ AfterEach(func() {
+ cleanupTest(ctx, solrCloud)
+ })
+
+ FContext("Full ExternalDNS", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.ExternalDNS,
UseExternalAddress: true,
DomainName: testDomain,
},
PodPort: 3000,
CommonServicePort: 4000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- HeadlessServiceOptions: &solr.ServiceOptions{
- Annotations: testHeadlessServiceAnnotations,
- Labels: testHeadlessServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "There is no need for host aliases because traffic is going directly to pods.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + instance.Namespace + "." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
- "external-dns.alpha.kubernetes.io/hostname": instance.Namespace + "." + testDomain,
- })
- testMapsEqual(t, "common service annotations", expectedCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 4000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check the headless Service
- service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
- expectedHeadlessServiceAnnotations := util.MergeLabelsOrAnnotations(testHeadlessServiceAnnotations, map[string]string{
- "external-dns.alpha.kubernetes.io/hostname": instance.Namespace + "." + testDomain,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + "." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": solrCloud.Namespace + "." + testDomain,
+ })
+ Expect(commonService.Annotations).To(Equal(expectedCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(4000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ expectedHeadlessServiceAnnotations := util.MergeLabelsOrAnnotations(testHeadlessServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": solrCloud.Namespace + "." + testDomain,
+ })
+ Expect(headlessService.Annotations).To(Equal(expectedHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ Expect(headlessService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(headlessService.Spec.Ports[0].Port).To(Equal(int32(3000)), "Wrong port on headless Service")
+ Expect(headlessService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on headless Service")
+
+ By("making sure no individual Solr Node Services exist")
+ expectNoServices(ctx, solrCloud, "Node service shouldn't exist, but it does.", solrCloud.GetAllSolrNodeNames())
+
+ By("making sure no Ingress was created")
+ expectNoIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":4000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+"."+testDomain+":4000"), "Wrong external common address in status")
+ })
+ })
})
- testMapsEqual(t, "headless service annotations", expectedHeadlessServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 3000, service.Spec.Ports[0].Port, "Wrong port on headless Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on headless Service")
-
- // Make sure individual Node services don't exist
- nodeNames := instance.GetAllSolrNodeNames()
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- expectNoService(g, nodeSKey, "Node service shouldn't exist, but it does.")
- }
-
- // Check the ingress
- expectNoIngress(g, cloudIKey)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":4000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+"."+testDomain+":4000", *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
-
-func TestEDSNoNodesCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.ExternalDNS,
+
+ FContext("Hiding Nodes from ExternalDNS", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.ExternalDNS,
UseExternalAddress: true,
HideNodes: true,
DomainName: testDomain,
},
PodPort: 2000,
CommonServicePort: 5000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- HeadlessServiceOptions: &solr.ServiceOptions{
- Annotations: testHeadlessServiceAnnotations,
- Labels: testHeadlessServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "There is no need for host aliases because traffic is going directly to pods.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + cloudHsKey.Name + "." + instance.Namespace,
- "SOLR_PORT": "2000",
- "SOLR_NODE_PORT": "2000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "2000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
- "external-dns.alpha.kubernetes.io/hostname": instance.Namespace + "." + testDomain,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("ensuring the SolrCloud resource is updated with correct specs")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults")
+ g.Expect(found.Spec.SolrAddressability.External.UseExternalAddress).To(BeFalse(), "useExternalAddress should be set to 'false' when hideNodes is 'true'")
+ })
+
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "2000",
+ "SOLR_NODE_PORT": "2000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "2000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": solrCloud.Namespace + "." + testDomain,
+ })
+ Expect(commonService.Annotations).To(Equal(expectedCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(5000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ Expect(headlessService.Annotations).To(Equal(testHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ Expect(headlessService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(headlessService.Spec.Ports[0].Port).To(Equal(int32(2000)), "Wrong port on headless Service")
+ Expect(headlessService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on headless Service")
+
+ By("making sure no individual Solr Node Services exist")
+ expectNoServices(ctx, solrCloud, "Node service shouldn't exist, but it does.", solrCloud.GetAllSolrNodeNames())
+
+ By("making sure no Ingress was created")
+ expectNoIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":5000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+"."+testDomain+":5000"), "Wrong external common address in status")
+ })
+ })
})
- testMapsEqual(t, "common service annotations", expectedCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 5000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check the headless Service
- service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "headless service annotations", testHeadlessServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 2000, service.Spec.Ports[0].Port, "Wrong port on headless Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on headless Service")
-
- // Make sure individual Node services don't exist
- nodeNames := instance.GetAllSolrNodeNames()
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- expectNoService(g, nodeSKey, "Node service shouldn't exist, but it does.")
- }
-
- // Check the ingress
- expectNoIngress(g, cloudIKey)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":5000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+"."+testDomain+":5000", *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
-
-func TestEDSNoCommonCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.ExternalDNS,
+
+ FContext("Hiding Common from ExternalDNS", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.ExternalDNS,
UseExternalAddress: true,
HideCommon: true,
DomainName: testDomain,
},
PodPort: 3000,
CommonServicePort: 2000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- HeadlessServiceOptions: &solr.ServiceOptions{
- Annotations: testHeadlessServiceAnnotations,
- Labels: testHeadlessServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "There is no need for host aliases because traffic is going directly to pods.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + instance.Namespace + "." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "common service annotations", testCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 2000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check the headless Service
- service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
- expectedHeadlessServiceAnnotations := util.MergeLabelsOrAnnotations(testHeadlessServiceAnnotations, map[string]string{
- "external-dns.alpha.kubernetes.io/hostname": instance.Namespace + "." + testDomain,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("ensuring the SolrCloud resource is updated with correct specs")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults")
+ g.Expect(found.Spec.SolrAddressability.External.UseExternalAddress).To(BeTrue(), "useExternalAddress should not be set to 'false' when hideNodes is 'false'")
+ })
+
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + "." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(2000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ expectedHeadlessServiceAnnotations := util.MergeLabelsOrAnnotations(testHeadlessServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": solrCloud.Namespace + "." + testDomain,
+ })
+ Expect(headlessService.Annotations).To(Equal(expectedHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ Expect(headlessService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(headlessService.Spec.Ports[0].Port).To(Equal(int32(3000)), "Wrong port on headless Service")
+ Expect(headlessService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on headless Service")
+
+ By("making sure no individual Solr Node Services exist")
+ expectNoServices(ctx, solrCloud, "Node service shouldn't exist, but it does.", solrCloud.GetAllSolrNodeNames())
+
+ By("making sure no Ingress was created")
+ expectNoIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":2000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(BeNil(), "External common address in status should be nil")
+ })
+ })
})
- testMapsEqual(t, "headless service annotations", expectedHeadlessServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 3000, service.Spec.Ports[0].Port, "Wrong port on headless Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on headless Service")
-
- // Make sure individual Node services don't exist
- nodeNames := instance.GetAllSolrNodeNames()
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- expectNoService(g, nodeSKey, "Node service shouldn't exist, but it does.")
- }
-
- // Check the ingress
- expectNoIngress(g, cloudIKey)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":2000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.Nil(t, instance.Status.ExternalCommonAddress, "External common address in status should be nil")
-}
-
-func TestEDSUseInternalAddressCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.ExternalDNS,
+
+ FContext("Use internal address for addressability in Solr", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.ExternalDNS,
UseExternalAddress: false,
DomainName: testDomain,
NodePortOverride: 454,
},
PodPort: 3000,
CommonServicePort: 4000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- HeadlessServiceOptions: &solr.ServiceOptions{
- Annotations: testHeadlessServiceAnnotations,
- Labels: testHeadlessServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "There is no need for host aliases because traffic is going directly to pods.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + cloudHsKey.Name + "." + instance.Namespace,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
- "external-dns.alpha.kubernetes.io/hostname": instance.Namespace + "." + testDomain,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("ensuring the SolrCloud resource is updated with correct specs")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults")
+ g.Expect(found.Spec.SolrAddressability.External.NodePortOverride).To(Equal(0), "nodePortOverride should not be set to '0' when used with ExternalDNS")
+ })
+
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": solrCloud.Namespace + "." + testDomain,
+ })
+ Expect(commonService.Annotations).To(Equal(expectedCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(4000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ expectedHeadlessServiceAnnotations := util.MergeLabelsOrAnnotations(testHeadlessServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": solrCloud.Namespace + "." + testDomain,
+ })
+ Expect(headlessService.Annotations).To(Equal(expectedHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ Expect(headlessService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(headlessService.Spec.Ports[0].Port).To(Equal(int32(3000)), "Wrong port on headless Service")
+ Expect(headlessService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on headless Service")
+
+ By("making sure no individual Solr Node Services exist")
+ expectNoServices(ctx, solrCloud, "Node service shouldn't exist, but it does.", solrCloud.GetAllSolrNodeNames())
+
+ By("making sure no Ingress was created")
+ expectNoIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":4000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+"."+testDomain+":4000"), "Wrong external common address in status")
+ })
+ })
})
- testMapsEqual(t, "common service annotations", expectedCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 4000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check the headless Service
- service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
- expectedHeadlessServiceAnnotations := util.MergeLabelsOrAnnotations(testHeadlessServiceAnnotations, map[string]string{
- "external-dns.alpha.kubernetes.io/hostname": instance.Namespace + "." + testDomain,
- })
- testMapsEqual(t, "headless service annotations", expectedHeadlessServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 3000, service.Spec.Ports[0].Port, "Wrong port on headless Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on headless Service")
-
- // Make sure individual Node services don't exist
- nodeNames := instance.GetAllSolrNodeNames()
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- expectNoService(g, nodeSKey, "Node service shouldn't exist, but it does.")
- }
-
- // Check the ingress
- expectNoIngress(g, cloudIKey)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":4000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+"."+testDomain+":4000", *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
-
-func TestEDSExtraDomainsCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.ExternalDNS,
+
+ FContext("Use extra domains with ExternalDNS", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.ExternalDNS,
UseExternalAddress: true,
DomainName: testDomain,
AdditionalDomainNames: testAdditionalDomains,
},
PodPort: 3000,
CommonServicePort: 4000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- HeadlessServiceOptions: &solr.ServiceOptions{
- Annotations: testHeadlessServiceAnnotations,
- Labels: testHeadlessServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "There is no need for host aliases because traffic is going directly to pods.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + instance.Namespace + "." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- hostnameAnnotation := instance.Namespace + "." + testDomain
- for _, domain := range testAdditionalDomains {
- hostnameAnnotation += "," + instance.Namespace + "." + domain
- }
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
- "external-dns.alpha.kubernetes.io/hostname": hostnameAnnotation,
- })
- testMapsEqual(t, "common service annotations", expectedCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 4000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check the headless Service
- service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
- expectedHeadlessServiceAnnotations := util.MergeLabelsOrAnnotations(testHeadlessServiceAnnotations, map[string]string{
- "external-dns.alpha.kubernetes.io/hostname": hostnameAnnotation,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + "." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ hostnameAnnotation := solrCloud.Namespace + "." + testDomain
+ for _, domain := range testAdditionalDomains {
+ hostnameAnnotation += "," + solrCloud.Namespace + "." + domain
+ }
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": hostnameAnnotation,
+ })
+ Expect(commonService.Annotations).To(Equal(expectedCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(4000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ expectedHeadlessServiceAnnotations := util.MergeLabelsOrAnnotations(testHeadlessServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": hostnameAnnotation,
+ })
+ Expect(headlessService.Annotations).To(Equal(expectedHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ Expect(headlessService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(headlessService.Spec.Ports[0].Port).To(Equal(int32(3000)), "Wrong port on headless Service")
+ Expect(headlessService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on headless Service")
+
+ By("making sure no individual Solr Node Services exist")
+ expectNoServices(ctx, solrCloud, "Node service shouldn't exist, but it does.", solrCloud.GetAllSolrNodeNames())
+
+ By("making sure no Ingress was created")
+ expectNoIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":4000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+"."+testDomain+":4000"), "Wrong external common address in status")
+ })
+ })
})
- testMapsEqual(t, "headless service annotations", expectedHeadlessServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 3000, service.Spec.Ports[0].Port, "Wrong port on headless Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on headless Service")
-
- // Make sure individual Node services don't exist
- nodeNames := instance.GetAllSolrNodeNames()
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- expectNoService(g, nodeSKey, "Node service shouldn't exist, but it does.")
- }
-
- // Check the ingress
- expectNoIngress(g, cloudIKey)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":4000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+"."+testDomain+":4000", *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
-
-func TestEDSKubeDomainCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.ExternalDNS,
+
+ FContext("Use explicit kube domain & use internal address for Solr", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.ExternalDNS,
UseExternalAddress: true,
HideNodes: true,
DomainName: testDomain,
@@ -650,98 +444,131 @@ func TestEDSKubeDomainCloudReconcile(t *testing.T) {
PodPort: 2000,
CommonServicePort: 5000,
KubeDomain: testKubeDomain,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- HeadlessServiceOptions: &solr.ServiceOptions{
- Annotations: testHeadlessServiceAnnotations,
- Labels: testHeadlessServiceLabels,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("ensuring the SolrCloud resource is updated with correct specs")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults")
+ g.Expect(found.Spec.SolrAddressability.External.UseExternalAddress).To(BeFalse(), "useExternalAddress should be set to 'false' when hideNodes is 'true'")
+ })
+
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain,
+ "SOLR_PORT": "2000",
+ "SOLR_NODE_PORT": "2000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "2000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": solrCloud.Namespace + "." + testDomain,
+ })
+ Expect(commonService.Annotations).To(Equal(expectedCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(5000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ Expect(headlessService.Annotations).To(Equal(testHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ Expect(headlessService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(headlessService.Spec.Ports[0].Port).To(Equal(int32(2000)), "Wrong port on headless Service")
+ Expect(headlessService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on headless Service")
+
+ By("making sure no individual Solr Node Services exist")
+ expectNoServices(ctx, solrCloud, "Node service shouldn't exist, but it does.", solrCloud.GetAllSolrNodeNames())
+
+ By("making sure no Ingress was created")
+ expectNoIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+".svc."+testKubeDomain+":5000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+"."+testDomain+":5000"), "Wrong external common address in status")
+ })
+ })
+ })
+
+ FContext("Use explicit kube domain & use external address for Solr", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.ExternalDNS,
+ UseExternalAddress: true,
+ DomainName: testDomain,
},
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "There is no need for host aliases because traffic is going directly to pods.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + cloudHsKey.Name + "." + instance.Namespace + ".svc." + testKubeDomain,
- "SOLR_PORT": "2000",
- "SOLR_NODE_PORT": "2000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "2000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
- "external-dns.alpha.kubernetes.io/hostname": instance.Namespace + "." + testDomain,
+ PodPort: 2000,
+ CommonServicePort: 5000,
+ KubeDomain: testKubeDomain,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + "." + testDomain,
+ "SOLR_PORT": "2000",
+ "SOLR_NODE_PORT": "2000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "2000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ expectedCommonServiceAnnotations := util.MergeLabelsOrAnnotations(testCommonServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": solrCloud.Namespace + "." + testDomain,
+ })
+ Expect(commonService.Annotations).To(Equal(expectedCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(5000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ expectedHeadlessServiceAnnotations := util.MergeLabelsOrAnnotations(testHeadlessServiceAnnotations, map[string]string{
+ "external-dns.alpha.kubernetes.io/hostname": solrCloud.Namespace + "." + testDomain,
+ })
+ Expect(headlessService.Annotations).To(Equal(expectedHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ Expect(headlessService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(headlessService.Spec.Ports[0].Port).To(Equal(int32(2000)), "Wrong port on headless Service")
+ Expect(headlessService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on headless Service")
+
+ By("making sure no individual Solr Node Services exist")
+ expectNoServices(ctx, solrCloud, "Node service shouldn't exist, but it does.", solrCloud.GetAllSolrNodeNames())
+
+ By("making sure no Ingress was created")
+ expectNoIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+".svc."+testKubeDomain+":5000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+"."+testDomain+":5000"), "Wrong external common address in status")
+ })
+ })
})
- testMapsEqual(t, "common service annotations", expectedCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 5000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check the headless Service
- service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "headless service annotations", testHeadlessServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 2000, service.Spec.Ports[0].Port, "Wrong port on headless Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on headless Service")
-
- // Make sure individual Node services don't exist
- nodeNames := instance.GetAllSolrNodeNames()
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- expectNoService(g, nodeSKey, "Node service shouldn't exist, but it does.")
- }
-
- // Check the ingress
- expectNoIngress(g, cloudIKey)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+".svc."+testKubeDomain+":5000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+"."+testDomain+":5000", *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
+})
diff --git a/controllers/solrcloud_controller_ingress_test.go b/controllers/solrcloud_controller_ingress_test.go
index fc6e6eb..8bc1043 100644
--- a/controllers/solrcloud_controller_ingress_test.go
+++ b/controllers/solrcloud_controller_ingress_test.go
@@ -18,177 +18,155 @@
package controllers
import (
- "strconv"
- "testing"
-
- extv1 "k8s.io/api/extensions/v1beta1"
- "k8s.io/apimachinery/pkg/types"
-
+ "context"
+ solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
"github.com/apache/solr-operator/controllers/util"
- "github.com/stretchr/testify/assert"
-
- solr "github.com/apache/solr-operator/api/v1beta1"
- "github.com/onsi/gomega"
- "golang.org/x/net/context"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ netv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/manager"
- "sigs.k8s.io/controller-runtime/pkg/reconcile"
+ "strconv"
)
-var _ reconcile.Reconciler = &SolrCloudReconciler{}
+var _ = FDescribe("SolrCloud controller - Ingress", func() {
+ var (
+ ctx context.Context
-func TestIngressCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
+ solrCloud *solrv1beta1.SolrCloud
+ )
- replicas := int32(4)
+ replicas := 3
+ BeforeEach(func() {
+ ctx = context.Background()
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- Replicas: &replicas,
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
+ int32Replicas := int32(replicas)
+ solrCloud = &solrv1beta1.SolrCloud{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ Namespace: "default",
+ },
+ Spec: solrv1beta1.SolrCloudSpec{
+ Replicas: &int32Replicas,
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
+ },
+ CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
+ CommonServiceOptions: &solrv1beta1.ServiceOptions{
+ Annotations: testCommonServiceAnnotations,
+ Labels: testCommonServiceLabels,
+ },
+ IngressOptions: &solrv1beta1.IngressOptions{
+ Annotations: testIngressAnnotations,
+ Labels: testIngressLabels,
+ },
+ NodeServiceOptions: &solrv1beta1.ServiceOptions{
+ Annotations: testNodeServiceAnnotations,
+ Labels: testNodeServiceLabels,
+ },
+ HeadlessServiceOptions: &solrv1beta1.ServiceOptions{
+ Annotations: testHeadlessServiceAnnotations,
+ Labels: testHeadlessServiceLabels,
+ },
},
},
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.Ingress,
+ }
+ })
+
+ JustBeforeEach(func() {
+ By("creating the SolrCloud")
+ Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed())
+
+ By("defaulting the missing SolrCloud values")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.WithDefaults()).To(BeFalse(), "The SolrCloud spec should not need to be defaulted eventually")
+ })
+ })
+
+ AfterEach(func() {
+ cleanupTest(ctx, solrCloud)
+ })
+
+ FContext("Full Ingress", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.Ingress,
UseExternalAddress: true,
DomainName: testDomain,
NodePortOverride: 100,
},
PodPort: 3000,
CommonServicePort: 4000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- IngressOptions: &solr.IngressOptions{
- Annotations: testIngressAnnotations,
- Labels: testIngressLabels,
- },
- NodeServiceOptions: &solr.ServiceOptions{
- Annotations: testNodeServiceAnnotations,
- Labels: testNodeServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- // Add an additional check for reconcile, so that the services will have IP addresses for the hostAliases to use
- // Otherwise the reconciler will have 'blockReconciliationOfStatefulSet' set to true, and the stateful set will not be created
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.EqualValues(t, replicas, len(statefulSet.Spec.Template.Spec.HostAliases), "Since external address is used for advertising, host aliases should be used for every node.")
- for i, hostAlias := range statefulSet.Spec.Template.Spec.HostAliases {
- assert.EqualValues(t, cloudSsKey.Namespace+"-"+statefulSet.Name+"-"+strconv.Itoa(i)+"."+testDomain, hostAlias.Hostnames[0], "Since external address is used for advertising, host aliases should be used for every node.")
- }
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": instance.Namespace + "-$(POD_HOSTNAME)." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "common service annotations", testCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 4000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check that the headless Service does not exist
- expectNoService(g, cloudHsKey, "Headless service shouldn't exist, but it does.")
-
- // Check on individual node services
- nodeNames := instance.GetAllSolrNodeNames()
- assert.EqualValues(t, replicas, len(nodeNames), "SolrCloud has incorrect number of nodeNames.")
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- service := expectService(t, g, requests, expectedCloudRequest, nodeSKey, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}))
- expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), map[string]string{"service-type": "external"})
- testMapsEqual(t, "node '"+nodeName+"' service labels", util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels), service.Labels)
- testMapsEqual(t, "node '"+nodeName+"' service annotations", testNodeServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 100, service.Spec.Ports[0].Port, "Wrong port on node Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on node Service")
- }
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
- // Check the ingress
- ingress := expectIngress(g, requests, expectedCloudRequest, cloudIKey)
- testMapsEqual(t, "ingress labels", util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), testIngressLabels), ingress.Labels)
- testMapsEqual(t, "ingress annotations", ingressLabelsWithDefaults(testIngressAnnotations), ingress.Annotations)
- testIngressRules(t, ingress, true, int(replicas), []string{testDomain}, 4000, 100)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":4000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+instance.Namespace+"-"+instance.Name+"-solrcloud"+"."+testDomain, *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
-
-func TestIngressNoNodesCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(HaveLen(replicas), "Since external address is used for advertising, host aliases should be used for every node.")
+ for i, hostAlias := range statefulSet.Spec.Template.Spec.HostAliases {
+ Expect(hostAlias.Hostnames[0]).To(Equal(solrCloud.Namespace+"-"+statefulSet.Name+"-"+strconv.Itoa(i)+"."+testDomain), "Since external address is used for advertising, host aliases should be used for every node.")
+ }
- replicas := int32(4)
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": solrCloud.Namespace + "-$(POD_HOSTNAME)." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(4000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("ensuring the Solr Headless Service does not exist")
+ expectNoService(ctx, solrCloud, solrCloud.HeadlessServiceName(), "Headless service shouldn't exist, but it does.")
+
+ By("making sure the individual Solr Node Services exist and route correctly")
+ nodeNames := solrCloud.GetAllSolrNodeNames()
+ Expect(nodeNames).To(HaveLen(replicas), "SolrCloud has incorrect number of nodeNames.")
+ for _, nodeName := range nodeNames {
+ service := expectService(ctx, solrCloud, nodeName, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}), false)
+ expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), map[string]string{"service-type": "external"})
+ Expect(service.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels)), "Incorrect node '"+nodeName+"' service labels")
+ Expect(service.Annotations).To(Equal(testNodeServiceAnnotations), "Incorrect node '"+nodeName+"' service annotations")
+ Expect(service.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(service.Spec.Ports[0].Port).To(Equal(int32(100)), "Wrong port on node Service")
+ Expect(service.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on node Service")
+ }
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- Replicas: &replicas,
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.Ingress,
+ By("making sure Ingress was created correctly")
+ ingress := expectIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+ Expect(ingress.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testIngressLabels)), "Incorrect ingress labels")
+ Expect(ingress.Annotations).To(Equal(ingressLabelsWithDefaults(testIngressAnnotations)), "Incorrect ingress annotations")
+ testIngressRules(solrCloud, ingress, true, replicas, 4000, 100, testDomain)
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":4000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.Namespace+"-"+solrCloud.Name+"-solrcloud"+"."+testDomain), "Wrong external common address in status")
+ })
+ })
+ })
+
+ FContext("Hide Nodes from external connections", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.Ingress,
UseExternalAddress: true,
HideNodes: true,
DomainName: testDomain,
@@ -196,127 +174,72 @@ func TestIngressNoNodesCloudReconcile(t *testing.T) {
},
PodPort: 3000,
CommonServicePort: 4000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- IngressOptions: &solr.IngressOptions{
- Annotations: testIngressAnnotations,
- Labels: testIngressLabels,
- },
- NodeServiceOptions: &solr.ServiceOptions{
- Annotations: testNodeServiceAnnotations,
- Labels: testNodeServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- // The default value of UseExternalAddress needs to be set to False
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "Since external address is not used, there is no need for host aliases.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + cloudHsKey.Name + "." + cloudHsKey.Namespace,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "common service annotations", testCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 4000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check the headless Service
- service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "headless service annotations", nil, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 3000, service.Spec.Ports[0].Port, "Wrong port on headless Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on headless Service")
-
- // Make sure individual Node services don't exist
- nodeNames := instance.GetAllSolrNodeNames()
- assert.EqualValues(t, replicas, len(nodeNames), "SolrCloud has incorrect number of nodeNames.")
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- expectNoService(g, nodeSKey, "Node service shouldn't exist, but it does.")
- }
-
- // Check the ingress
- ingress := expectIngress(g, requests, expectedCloudRequest, cloudIKey)
- testMapsEqual(t, "ingress labels", util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), testIngressLabels), ingress.Labels)
- testMapsEqual(t, "ingress annotations", ingressLabelsWithDefaults(testIngressAnnotations), ingress.Annotations)
- testIngressRules(t, ingress, true, 0, []string{testDomain}, 4000, 100)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":4000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+instance.Namespace+"-"+instance.Name+"-solrcloud"+"."+testDomain, *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
-
-func TestIngressNoCommonCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
-
- replicas := int32(4)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- Replicas: &replicas,
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.Ingress,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("ensuring the SolrCloud resource is updated with correct specs")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.Spec.SolrAddressability.External).To(Not(BeNil()), "Solr External addressability settings should not be nullified while setting defaults")
+ g.Expect(found.Spec.SolrAddressability.External.UseExternalAddress).To(BeFalse(), "useExternalAddress should be set to 'false' when hideNodes is 'true'")
+ g.Expect(found.Spec.SolrAddressability.External.NodePortOverride).To(Equal(0), "nodePortOverride should be set to '0' when hideNodes is 'true'")
+ })
+
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(4000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ Expect(headlessService.Annotations).To(Equal(testHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ Expect(headlessService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(headlessService.Spec.Ports[0].Port).To(Equal(int32(3000)), "Wrong port on headless Service")
+ Expect(headlessService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on headless Service")
+
+ By("making sure no individual Solr Node Services exist")
+ expectNoServices(ctx, solrCloud, "Node service shouldn't exist, but it does.", solrCloud.GetAllSolrNodeNames())
+
+ By("making sure Ingress was created correctly")
+ ingress := expectIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+ Expect(ingress.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testIngressLabels)), "Incorrect ingress labels")
+ Expect(ingress.Annotations).To(Equal(ingressLabelsWithDefaults(testIngressAnnotations)), "Incorrect ingress annotations")
+ testIngressRules(solrCloud, ingress, true, 0, 4000, 100, testDomain)
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":4000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.Namespace+"-"+solrCloud.Name+"-solrcloud"+"."+testDomain), "Wrong external common address in status")
+ })
+ })
+ })
+
+ FContext("Hide Common endpoint from external connections", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.Ingress,
UseExternalAddress: true,
HideCommon: true,
DomainName: testDomain,
@@ -324,260 +247,144 @@ func TestIngressNoCommonCloudReconcile(t *testing.T) {
},
PodPort: 3000,
CommonServicePort: 4000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- IngressOptions: &solr.IngressOptions{
- Annotations: testIngressAnnotations,
- Labels: testIngressLabels,
- },
- NodeServiceOptions: &solr.ServiceOptions{
- Annotations: testNodeServiceAnnotations,
- Labels: testNodeServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- // Add an additional check for reconcile, so that the services will have IP addresses for the hostAliases to use
- // Otherwise the reconciler will have 'blockReconciliationOfStatefulSet' set to true, and the stateful set will not be created
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.EqualValues(t, replicas, len(statefulSet.Spec.Template.Spec.HostAliases), "Since external address is used for advertising, host aliases should be used for every node.")
- for i, hostAlias := range statefulSet.Spec.Template.Spec.HostAliases {
- assert.EqualValues(t, cloudSsKey.Namespace+"-"+statefulSet.Name+"-"+strconv.Itoa(i)+"."+testDomain, hostAlias.Hostnames[0], "Since external address is used for advertising, host aliases should be used for every node.")
- }
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": instance.Namespace + "-$(POD_HOSTNAME)." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "common service annotations", testCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 4000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check that the headless Service does not exist
- expectNoService(g, cloudHsKey, "Headless service shouldn't exist, but it does.")
-
- // Check on individual node services
- nodeNames := instance.GetAllSolrNodeNames()
- assert.EqualValues(t, replicas, len(nodeNames), "SolrCloud has incorrect number of nodeNames.")
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- service := expectService(t, g, requests, expectedCloudRequest, nodeSKey, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}))
- expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), map[string]string{"service-type": "external"})
- testMapsEqual(t, "node '"+nodeName+"' service labels", util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels), service.Labels)
- testMapsEqual(t, "node '"+nodeName+"' service annotations", testNodeServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 100, service.Spec.Ports[0].Port, "Wrong port on node Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on node Service")
- }
-
- // Check the ingress
- ingress := expectIngress(g, requests, expectedCloudRequest, cloudIKey)
- testMapsEqual(t, "ingress labels", util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), testIngressLabels), ingress.Labels)
- testMapsEqual(t, "ingress annotations", ingressLabelsWithDefaults(testIngressAnnotations), ingress.Annotations)
- testIngressRules(t, ingress, false, int(replicas), []string{testDomain}, 4000, 100)
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":4000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.Nil(t, instance.Status.ExternalCommonAddress, "External common address in Status should be nil.")
-}
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
-func TestIngressUseInternalAddressCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(HaveLen(replicas), "Since external address is used for advertising, host aliases should be used for every node.")
+ for i, hostAlias := range statefulSet.Spec.Template.Spec.HostAliases {
+ Expect(hostAlias.Hostnames[0]).To(Equal(solrCloud.Namespace+"-"+statefulSet.Name+"-"+strconv.Itoa(i)+"."+testDomain), "Since external address is used for advertising, host aliases should be used for every node.")
+ }
- replicas := int32(4)
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": solrCloud.Namespace + "-$(POD_HOSTNAME)." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(4000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("ensuring the Solr Headless Service does not exist")
+ expectNoService(ctx, solrCloud, solrCloud.HeadlessServiceName(), "Headless service shouldn't exist, but it does.")
+
+ By("making sure the individual Solr Node Services exist and route correctly")
+ nodeNames := solrCloud.GetAllSolrNodeNames()
+ Expect(nodeNames).To(HaveLen(replicas), "SolrCloud has incorrect number of nodeNames.")
+ for _, nodeName := range nodeNames {
+ service := expectService(ctx, solrCloud, nodeName, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}), false)
+ expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), map[string]string{"service-type": "external"})
+ Expect(service.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels)), "Incorrect node '"+nodeName+"' service labels")
+ Expect(service.Annotations).To(Equal(testNodeServiceAnnotations), "Incorrect node '"+nodeName+"' service annotations")
+ Expect(service.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(service.Spec.Ports[0].Port).To(Equal(int32(100)), "Wrong port on node Service")
+ Expect(service.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on node Service")
+ }
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- Replicas: &replicas,
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.Ingress,
+ By("making sure Ingress was created correctly")
+ ingress := expectIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+ Expect(ingress.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testIngressLabels)), "Incorrect ingress labels")
+ Expect(ingress.Annotations).To(Equal(ingressLabelsWithDefaults(testIngressAnnotations)), "Incorrect ingress annotations")
+ testIngressRules(solrCloud, ingress, false, replicas, 4000, 100, testDomain)
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":4000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(BeNil(), "External common address in status should be nil when hideCommon=true")
+ })
+ })
+ })
+
+ FContext("Use internal address for addressability in Solr", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.Ingress,
UseExternalAddress: false,
DomainName: testDomain,
NodePortOverride: 100,
},
PodPort: 3000,
CommonServicePort: 4000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- IngressOptions: &solr.IngressOptions{
- Annotations: testIngressAnnotations,
- Labels: testIngressLabels,
- },
- NodeServiceOptions: &solr.ServiceOptions{
- Annotations: testNodeServiceAnnotations,
- Labels: testNodeServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- // Additional reconcile for defaulting of Spec
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "Since external address is not used, there is no need for host aliases.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + expectedCloudRequest.Namespace,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "common service annotations", testCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 4000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check that the headless Service does not exist
- expectNoService(g, cloudHsKey, "Headless service shouldn't exist, but it does.")
-
- // Check on individual node services
- nodeNames := instance.GetAllSolrNodeNames()
- assert.EqualValues(t, replicas, len(nodeNames), "SolrCloud has incorrect number of nodeNames.")
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- service := expectService(t, g, requests, expectedCloudRequest, nodeSKey, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}))
- expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), map[string]string{"service-type": "external"})
- testMapsEqual(t, "node '"+nodeName+"' service labels", util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels), service.Labels)
- testMapsEqual(t, "node '"+nodeName+"' service annotations", testNodeServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 100, service.Spec.Ports[0].Port, "Wrong port on node Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on node Service")
- }
-
- // Check the ingress
- ingress := expectIngress(g, requests, expectedCloudRequest, cloudIKey)
- testMapsEqual(t, "ingress labels", util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), testIngressLabels), ingress.Labels)
- testMapsEqual(t, "ingress annotations", ingressLabelsWithDefaults(testIngressAnnotations), ingress.Annotations)
- testIngressRules(t, ingress, true, int(replicas), []string{testDomain}, 4000, 100)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":4000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+instance.Namespace+"-"+instance.Name+"-solrcloud"+"."+testDomain, *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
-
-func TestIngressExtraDomainsCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
-
- replicas := int32(4)
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(4000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("ensuring the Solr Headless Service does not exist")
+ expectNoService(ctx, solrCloud, solrCloud.HeadlessServiceName(), "Headless service shouldn't exist, but it does.")
+
+ By("making sure the individual Solr Node Services exist and route correctly")
+ nodeNames := solrCloud.GetAllSolrNodeNames()
+ Expect(nodeNames).To(HaveLen(replicas), "SolrCloud has incorrect number of nodeNames.")
+ for _, nodeName := range nodeNames {
+ service := expectService(ctx, solrCloud, nodeName, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}), false)
+ expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), map[string]string{"service-type": "external"})
+ Expect(service.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels)), "Incorrect node '"+nodeName+"' service labels")
+ Expect(service.Annotations).To(Equal(testNodeServiceAnnotations), "Incorrect node '"+nodeName+"' service annotations")
+ Expect(service.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(service.Spec.Ports[0].Port).To(Equal(int32(100)), "Wrong port on node Service")
+ Expect(service.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on node Service")
+ }
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- Replicas: &replicas,
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.Ingress,
+ By("making sure Ingress was created correctly")
+ ingress := expectIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+ Expect(ingress.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testIngressLabels)), "Incorrect ingress labels")
+ Expect(ingress.Annotations).To(Equal(ingressLabelsWithDefaults(testIngressAnnotations)), "Incorrect ingress annotations")
+ testIngressRules(solrCloud, ingress, true, replicas, 4000, 100, testDomain)
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":4000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.Namespace+"-"+solrCloud.Name+"-solrcloud"+"."+testDomain), "Wrong external common address in status")
+ })
+ })
+ })
+
+ FContext("Use extra domains", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.Ingress,
UseExternalAddress: true,
DomainName: testDomain,
AdditionalDomainNames: testAdditionalDomains,
@@ -585,241 +392,216 @@ func TestIngressExtraDomainsCloudReconcile(t *testing.T) {
},
PodPort: 3000,
CommonServicePort: 4000,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- IngressOptions: &solr.IngressOptions{
- Annotations: testIngressAnnotations,
- Labels: testIngressLabels,
- },
- NodeServiceOptions: &solr.ServiceOptions{
- Annotations: testNodeServiceAnnotations,
- Labels: testNodeServiceLabels,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- // Add an additional check for reconcile, so that the services will have IP addresses for the hostAliases to use
- // Otherwise the reconciler will have 'blockReconciliationOfStatefulSet' set to true, and the stateful set will not be created
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.EqualValues(t, replicas, len(statefulSet.Spec.Template.Spec.HostAliases), "Since external address is used for advertising, host aliases should be used for every node.")
- for i, hostAlias := range statefulSet.Spec.Template.Spec.HostAliases {
- assert.EqualValues(t, cloudSsKey.Namespace+"-"+statefulSet.Name+"-"+strconv.Itoa(i)+"."+testDomain, hostAlias.Hostnames[0], "Since external address is used for advertising, host aliases should be used for every node.")
- }
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": instance.Namespace + "-$(POD_HOSTNAME)." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "common service annotations", testCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 4000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check that the headless Service does not exist
- expectNoService(g, cloudHsKey, "Headless service shouldn't exist, but it does.")
-
- // Check on individual node services
- nodeNames := instance.GetAllSolrNodeNames()
- assert.EqualValues(t, replicas, len(nodeNames), "SolrCloud has incorrect number of nodeNames.")
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- service := expectService(t, g, requests, expectedCloudRequest, nodeSKey, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}))
- expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), map[string]string{"service-type": "external"})
- testMapsEqual(t, "node '"+nodeName+"' service labels", util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels), service.Labels)
- testMapsEqual(t, "node '"+nodeName+"' service annotations", testNodeServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 100, service.Spec.Ports[0].Port, "Wrong port on node Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on node Service")
- }
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
- // Check the ingress
- ingress := expectIngress(g, requests, expectedCloudRequest, cloudIKey)
- testMapsEqual(t, "ingress labels", util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), testIngressLabels), ingress.Labels)
- testMapsEqual(t, "ingress annotations", ingressLabelsWithDefaults(testIngressAnnotations), ingress.Annotations)
- testIngressRules(t, ingress, true, int(replicas), append([]string{testDomain}, testAdditionalDomains...), 4000, 100)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+":4000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+instance.Namespace+"-"+instance.Name+"-solrcloud"+"."+testDomain, *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
-func TestIngressKubeDomainCloudReconcile(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(HaveLen(replicas), "Since external address is used for advertising, host aliases should be used for every node.")
+ for i, hostAlias := range statefulSet.Spec.Template.Spec.HostAliases {
+ Expect(hostAlias.Hostnames[0]).To(Equal(solrCloud.Namespace+"-"+statefulSet.Name+"-"+strconv.Itoa(i)+"."+testDomain), "Since external address is used for advertising, host aliases should be used for every node.")
+ }
- replicas := int32(4)
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": solrCloud.Namespace + "-$(POD_HOSTNAME)." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(4000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("ensuring the Solr Headless Service does not exist")
+ expectNoService(ctx, solrCloud, solrCloud.HeadlessServiceName(), "Headless service shouldn't exist, but it does.")
+
+ By("making sure the individual Solr Node Services exist and route correctly")
+ nodeNames := solrCloud.GetAllSolrNodeNames()
+ Expect(nodeNames).To(HaveLen(replicas), "SolrCloud has incorrect number of nodeNames.")
+ for _, nodeName := range nodeNames {
+ service := expectService(ctx, solrCloud, nodeName, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}), false)
+ expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), map[string]string{"service-type": "external"})
+ Expect(service.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels)), "Incorrect node '"+nodeName+"' service labels")
+ Expect(service.Annotations).To(Equal(testNodeServiceAnnotations), "Incorrect node '"+nodeName+"' service annotations")
+ Expect(service.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(service.Spec.Ports[0].Port).To(Equal(int32(100)), "Wrong port on node Service")
+ Expect(service.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on node Service")
+ }
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- Replicas: &replicas,
- SolrAddressability: solr.SolrAddressabilityOptions{
- External: &solr.ExternalAddressability{
- Method: solr.Ingress,
+ By("making sure Ingress was created correctly")
+ ingress := expectIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+ Expect(ingress.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testIngressLabels)), "Incorrect ingress labels")
+ Expect(ingress.Annotations).To(Equal(ingressLabelsWithDefaults(testIngressAnnotations)), "Incorrect ingress annotations")
+ testIngressRules(solrCloud, ingress, true, replicas, 4000, 100, append([]string{testDomain}, testAdditionalDomains...)...)
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+":4000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.Namespace+"-"+solrCloud.Name+"-solrcloud"+"."+testDomain), "Wrong external common address in status")
+ })
+ })
+ })
+
+ FContext("Use explicit kube domain & use internal address for Solr", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.Ingress,
UseExternalAddress: false,
DomainName: testDomain,
NodePortOverride: 100,
},
PodPort: 3000,
KubeDomain: testKubeDomain,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
- },
- IngressOptions: &solr.IngressOptions{
- Annotations: testIngressAnnotations,
- Labels: testIngressLabels,
- },
- NodeServiceOptions: &solr.ServiceOptions{
- Annotations: testNodeServiceAnnotations,
- Labels: testNodeServiceLabels,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.Namespace + ".svc." + testKubeDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(80)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("ensuring the Solr Headless Service does not exist")
+ expectNoService(ctx, solrCloud, solrCloud.HeadlessServiceName(), "Headless service shouldn't exist, but it does.")
+
+ By("making sure the individual Solr Node Services exist and route correctly")
+ nodeNames := solrCloud.GetAllSolrNodeNames()
+ Expect(nodeNames).To(HaveLen(replicas), "SolrCloud has incorrect number of nodeNames.")
+ for _, nodeName := range nodeNames {
+ service := expectService(ctx, solrCloud, nodeName, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}), false)
+ expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), map[string]string{"service-type": "external"})
+ Expect(service.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels)), "Incorrect node '"+nodeName+"' service labels")
+ Expect(service.Annotations).To(Equal(testNodeServiceAnnotations), "Incorrect node '"+nodeName+"' service annotations")
+ Expect(service.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(service.Spec.Ports[0].Port).To(Equal(int32(100)), "Wrong port on node Service")
+ Expect(service.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on node Service")
+ }
+
+ By("making sure Ingress was created correctly")
+ ingress := expectIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+ Expect(ingress.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testIngressLabels)), "Incorrect ingress labels")
+ Expect(ingress.Annotations).To(Equal(ingressLabelsWithDefaults(testIngressAnnotations)), "Incorrect ingress annotations")
+ testIngressRules(solrCloud, ingress, true, replicas, 80, 100, testDomain)
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+".svc."+testKubeDomain), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.Namespace+"-"+solrCloud.Name+"-solrcloud"+"."+testDomain), "Wrong external common address in status")
+ })
+ })
+ })
+
+ FContext("Use explicit kube domain & use external address for Solr", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.SolrAddressability = solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.Ingress,
+ UseExternalAddress: true,
+ DomainName: testDomain,
+ NodePortOverride: 100,
},
- },
- },
- }
+ PodPort: 3000,
+ KubeDomain: testKubeDomain,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- // Additional reconcile for defaulting of Spec
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "Since external address is not used, there is no need for host aliases.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "foo-clo-solrcloud-zookeeper-0.foo-clo-solrcloud-zookeeper-headless.default.svc." + testKubeDomain + ":2181,foo-clo-solrcloud-zookeeper-1.foo-clo-solrcloud-zookeeper-headless.default.svc." + testKubeDomain + ":2181,foo-clo-solrcloud-zookeeper-2.foo-clo-solrcloud-zookeeper-headless.default.svc." + testKubeDomain + ":2181/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + expectedCloudRequest.Namespace + ".svc." + testKubeDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Equal(t, []string{"solr", "stop", "-p", "3000"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command, "Incorrect pre-stop command")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "common service annotations", testCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 80, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check that the headless Service does not exist
- expectNoService(g, cloudHsKey, "Headless service shouldn't exist, but it does.")
-
- // Check on individual node services
- nodeNames := instance.GetAllSolrNodeNames()
- assert.EqualValues(t, replicas, len(nodeNames), "SolrCloud has incorrect number of nodeNames.")
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- service := expectService(t, g, requests, expectedCloudRequest, nodeSKey, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}))
- expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), map[string]string{"service-type": "external"})
- testMapsEqual(t, "node '"+nodeName+"' service labels", util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels), service.Labels)
- testMapsEqual(t, "node '"+nodeName+"' service annotations", testNodeServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 100, service.Spec.Ports[0].Port, "Wrong port on node Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on node Service")
- }
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(HaveLen(replicas), "Since external address is used for advertising, host aliases should be used for every node.")
+ for i, hostAlias := range statefulSet.Spec.Template.Spec.HostAliases {
+ Expect(hostAlias.Hostnames[0]).To(Equal(solrCloud.Namespace+"-"+statefulSet.Name+"-"+strconv.Itoa(i)+"."+testDomain), "Since external address is used for advertising, host aliases should be used for every node.")
+ }
- // Check the ingress
- ingress := expectIngress(g, requests, expectedCloudRequest, cloudIKey)
- testMapsEqual(t, "ingress labels", util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), testIngressLabels), ingress.Labels)
- testMapsEqual(t, "ingress annotations", ingressLabelsWithDefaults(testIngressAnnotations), ingress.Annotations)
- testIngressRules(t, ingress, true, int(replicas), []string{testDomain}, 80, 100)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+".svc."+testKubeDomain, instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.NotNil(t, instance.Status.ExternalCommonAddress, "External common address in Status should not be nil.")
- assert.EqualValues(t, "http://"+instance.Namespace+"-"+instance.Name+"-solrcloud"+"."+testDomain, *instance.Status.ExternalCommonAddress, "Wrong external common address in status")
-}
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": solrCloud.Namespace + "-$(POD_HOSTNAME)." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "3000"}), "Incorrect pre-stop command")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(80)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("ensuring the Solr Headless Service does not exist")
+ expectNoService(ctx, solrCloud, solrCloud.HeadlessServiceName(), "Headless service shouldn't exist, but it does.")
+
+ By("making sure the individual Solr Node Services exist and route correctly")
+ nodeNames := solrCloud.GetAllSolrNodeNames()
+ Expect(nodeNames).To(HaveLen(replicas), "SolrCloud has incorrect number of nodeNames.")
+ for _, nodeName := range nodeNames {
+ service := expectService(ctx, solrCloud, nodeName, util.MergeLabelsOrAnnotations(statefulSet.Spec.Selector.MatchLabels, map[string]string{"statefulset.kubernetes.io/pod-name": nodeName}), false)
+ expectedNodeServiceLabels := util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), map[string]string{"service-type": "external"})
+ Expect(service.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedNodeServiceLabels, testNodeServiceLabels)), "Incorrect node '"+nodeName+"' service labels")
+ Expect(service.Annotations).To(Equal(testNodeServiceAnnotations), "Incorrect node '"+nodeName+"' service annotations")
+ Expect(service.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(service.Spec.Ports[0].Port).To(Equal(int32(100)), "Wrong port on node Service")
+ Expect(service.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on node Service")
+ }
-func testIngressRules(t *testing.T, ingress *extv1.Ingress, withCommon bool, withNodes int, domainNames []string, commonPort int, nodePort int) {
+ By("making sure Ingress was created correctly")
+ ingress := expectIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+ Expect(ingress.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testIngressLabels)), "Incorrect ingress labels")
+ Expect(ingress.Annotations).To(Equal(ingressLabelsWithDefaults(testIngressAnnotations)), "Incorrect ingress annotations")
+ testIngressRules(solrCloud, ingress, true, replicas, 80, 100, testDomain)
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+".svc."+testKubeDomain), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(Not(BeNil()), "External common address in status should not be nil")
+ g.Expect(*found.ExternalCommonAddress).To(Equal("http://"+solrCloud.Namespace+"-"+solrCloud.Name+"-solrcloud"+"."+testDomain), "Wrong external common address in status")
+ })
+ })
+ })
+})
+
+func testIngressRules(solrCloud *solrv1beta1.SolrCloud, ingress *netv1.Ingress, withCommon bool, withNodes int, commonPort int, nodePort int, domainNames ...string) {
expected := 0
if withCommon {
expected += 1
@@ -830,7 +612,7 @@ func testIngressRules(t *testing.T, ingress *extv1.Ingress, withCommon bool, wit
perDomain := expected
numDomains := len(domainNames)
expected *= numDomains
- assert.EqualValues(t, expected, len(ingress.Spec.Rules), "Wrong number of ingress rules.")
+ Expect(ingress.Spec.Rules).To(HaveLen(expected), "Wrong number of ingress rules.")
for i := 0; i < perDomain; i++ {
// Common Rules
ruleName := "common"
@@ -852,13 +634,15 @@ func testIngressRules(t *testing.T, ingress *extv1.Ingress, withCommon bool, wit
for j, domainName := range domainNames {
ruleIndex := j + i*numDomains
rule := ingress.Spec.Rules[ruleIndex]
- expectedHost := expectedCloudRequest.Namespace + "-" + expectedCloudRequest.Name + "-solrcloud" + hostAppend + "." + domainName
- assert.EqualValues(t, expectedHost, rule.Host, "Wrong host for ingress rule: "+ruleName)
- assert.EqualValues(t, 1, len(rule.HTTP.Paths), "Wrong number of path rules in ingress host: "+ruleName)
+ expectedHost := solrCloud.Namespace + "-" + solrCloud.Name + "-solrcloud" + hostAppend + "." + domainName
+ Expect(rule.Host).To(Equal(expectedHost), "Wrong host for ingress rule: "+ruleName)
+ Expect(rule.HTTP.Paths).To(HaveLen(1), "Wrong number of path rules in ingress host: "+ruleName)
path := rule.HTTP.Paths[0]
- assert.EqualValues(t, "", path.Path, "There should be no path value for ingress rule: "+ruleName)
- assert.EqualValues(t, expectedCloudRequest.Name+"-solrcloud-"+serviceSuffix, path.Backend.ServiceName, "Wrong service name for ingress rule: "+ruleName)
- assert.EqualValues(t, port, path.Backend.ServicePort.IntVal, "Wrong port name for ingress rule: "+ruleName)
+ Expect(path.Path).To(Equal(""), "There should be no path value for ingress rule: "+ruleName)
+ Expect(path.Backend.Service).To(Not(BeNil()), "Backend Service should not be nil")
+ Expect(path.Backend.Service.Name).To(Equal(solrCloud.Name+"-solrcloud-"+serviceSuffix), "Wrong service name for ingress rule: "+ruleName)
+ Expect(path.Backend.Service.Port.Number).To(Equal(int32(port)), "Wrong port name for ingress rule: "+ruleName)
+ Expect(path.Backend.Service.Port.Name).To(BeEmpty(), "Port name should not be used in ingress rules")
}
}
}
diff --git a/controllers/solrcloud_controller_storage_test.go b/controllers/solrcloud_controller_storage_test.go
index d4169eb..86f28cb 100644
--- a/controllers/solrcloud_controller_storage_test.go
+++ b/controllers/solrcloud_controller_storage_test.go
@@ -18,417 +18,251 @@
package controllers
import (
- "testing"
-
+ "context"
+ solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
+ "github.com/apache/solr-operator/controllers/util"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
-
- "github.com/apache/solr-operator/controllers/util"
- "github.com/stretchr/testify/assert"
-
- solr "github.com/apache/solr-operator/api/v1beta1"
- "github.com/onsi/gomega"
- "golang.org/x/net/context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/manager"
- "sigs.k8s.io/controller-runtime/pkg/reconcile"
)
-var _ reconcile.Reconciler = &SolrCloudReconciler{}
+var _ = FDescribe("SolrCloud controller - Storage", func() {
+ var (
+ ctx context.Context
+
+ solrCloud *solrv1beta1.SolrCloud
+ )
-func TestPersistentStorageVolumesRetain(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
+ BeforeEach(func() {
+ ctx = context.Background()
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
+ solrCloud = &solrv1beta1.SolrCloud{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ Namespace: "default",
+ },
+ Spec: solrv1beta1.SolrCloudSpec{
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
+ },
+ SolrJavaMem: "-Xmx4G",
+ SolrOpts: "extra-opts",
+ CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
+ PodOptions: &solrv1beta1.PodOptions{
+ EnvVariables: extraVars,
+ PodSecurityContext: &testPodSecurityContext,
+ Volumes: extraVolumes,
+ Affinity: testAffinity,
+ Resources: testResources,
+ },
},
},
- SolrJavaMem: "-Xmx4G",
- SolrOpts: "extra-opts",
- SolrLogLevel: "DEBUG",
- StorageOptions: solr.SolrDataStorageOptions{
- PersistentStorage: &solr.SolrPersistentDataStorageOptions{
- VolumeReclaimPolicy: solr.VolumeReclaimPolicyRetain,
- PersistentVolumeClaimTemplate: solr.PersistentVolumeClaimTemplate{
- ObjectMeta: solr.TemplateMeta{
+ }
+ })
+
+ JustBeforeEach(func() {
+ By("creating the SolrCloud")
+ Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed())
+
+ By("defaulting the missing SolrCloud values")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.WithDefaults()).To(BeFalse(), "The SolrCloud spec should not need to be defaulted eventually")
+ })
+ })
+
+ AfterEach(func() {
+ cleanupTest(ctx, solrCloud)
+ })
+
+ FContext("Persistent Data - Custom name & labels", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
+ PersistentStorage: &solrv1beta1.SolrPersistentDataStorageOptions{
+ VolumeReclaimPolicy: solrv1beta1.VolumeReclaimPolicyRetain,
+ PersistentVolumeClaimTemplate: solrv1beta1.PersistentVolumeClaimTemplate{
+ ObjectMeta: solrv1beta1.TemplateMeta{
Name: "other-data-1",
Labels: map[string]string{"base": "here"},
},
},
},
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- PodOptions: &solr.PodOptions{
- EnvVariables: extraVars,
- PodSecurityContext: &podSecurityContext,
- Volumes: extraVolumes,
- Affinity: affinity,
- Resources: resources,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Fetch new value of instance to check finalizers
- foundInstance := &solr.SolrCloud{}
- g.Eventually(func() error {
- return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, foundInstance)
- }, timeout).Should(gomega.Succeed())
- assert.Equal(t, 0, len(foundInstance.GetFinalizers()), "The solrcloud should have no finalizers when the reclaim policy is Retain")
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 2, len(statefulSet.Spec.Template.Spec.Volumes), "Pod has wrong number of volumes")
- assert.Equal(t, instance.Spec.StorageOptions.PersistentStorage.PersistentVolumeClaimTemplate.ObjectMeta.Name, statefulSet.Spec.VolumeClaimTemplates[0].Name, "Data volume claim doesn't exist")
- assert.Equal(t, "solr-cloud", statefulSet.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCTechnologyLabel], "PVC Technology label doesn't match")
- assert.Equal(t, "data", statefulSet.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCStorageLabel], "PVC Storage label doesn't match")
- assert.Equal(t, "foo-clo", statefulSet.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCInstanceLabel], "PVC Instance label doesn't match")
- assert.Equal(t, "here", statefulSet.Spec.VolumeClaimTemplates[0].Labels["base"], "Additional PVC label doesn't match")
-}
-
-func TestPersistentStorageVolumesDelete(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrJavaMem: "-Xmx4G",
- SolrOpts: "extra-opts",
- SolrLogLevel: "DEBUG",
- StorageOptions: solr.SolrDataStorageOptions{
- PersistentStorage: &solr.SolrPersistentDataStorageOptions{
- VolumeReclaimPolicy: solr.VolumeReclaimPolicyDelete,
- PersistentVolumeClaimTemplate: solr.PersistentVolumeClaimTemplate{
- ObjectMeta: solr.TemplateMeta{
- Name: "other-data-2",
- Annotations: map[string]string{"base": "here"},
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the SolrCloud finalizers")
+ expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when the reclaim policy is Retain")
+ })
+
+ By("testing the Solr StatefulSet PVC Spec")
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(2), "Pod has wrong number of volumes")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Name).To(Equal(solrCloud.Spec.StorageOptions.PersistentStorage.PersistentVolumeClaimTemplate.ObjectMeta.Name), "Data volume claim doesn't exist")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCTechnologyLabel]).To(Equal("solr-cloud"), "PVC Technology label doesn't match")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCStorageLabel]).To(Equal("data"), "PVC Storage label doesn't match")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCInstanceLabel]).To(Equal(solrCloud.Name), "PVC Instance label doesn't match")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Labels["base"]).To(Equal("here"), "Additional PVC label doesn't match")
+ })
+ })
+ })
+
+ FContext("Persistent Data - PVC Delete on Reclaim", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
+ PersistentStorage: &solrv1beta1.SolrPersistentDataStorageOptions{
+ VolumeReclaimPolicy: solrv1beta1.VolumeReclaimPolicyDelete,
+ PersistentVolumeClaimTemplate: solrv1beta1.PersistentVolumeClaimTemplate{
+ ObjectMeta: solrv1beta1.TemplateMeta{
+ Name: "other-data-2",
+ Labels: map[string]string{"base": "here"},
},
},
},
- EphemeralStorage: &solr.SolrEphemeralDataStorageOptions{},
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- PodOptions: &solr.PodOptions{
- EnvVariables: extraVars,
- PodSecurityContext: &podSecurityContext,
- Volumes: extraVolumes,
- Affinity: affinity,
- Resources: resources,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Fetch new value of instance to check finalizers
- foundInstance := &solr.SolrCloud{}
- g.Eventually(func() error {
- return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, foundInstance)
- }, timeout).Should(gomega.Succeed())
- assert.Equal(t, 1, len(foundInstance.GetFinalizers()), "The solrcloud should have 1 storage finalizer when persistent storage reclaim policy is set to Delete")
- assert.Equal(t, util.SolrStorageFinalizer, foundInstance.GetFinalizers()[0], "Incorrect finalizer set for deleting persistent storage.")
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 2, len(statefulSet.Spec.Template.Spec.Volumes), "Pod has wrong number of volumes")
- assert.Equal(t, instance.Spec.StorageOptions.PersistentStorage.PersistentVolumeClaimTemplate.ObjectMeta.Name, statefulSet.Spec.VolumeClaimTemplates[0].Name, "Data volume claim doesn't exist")
- assert.Equal(t, "solr-cloud", statefulSet.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCTechnologyLabel], "PVC Technology label doesn't match")
- assert.Equal(t, "data", statefulSet.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCStorageLabel], "PVC Storage label doesn't match")
- assert.Equal(t, "foo-clo", statefulSet.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCInstanceLabel], "PVC Instance label doesn't match")
- assert.Equal(t, "here", statefulSet.Spec.VolumeClaimTemplates[0].Annotations["base"], "Additional PVC label doesn't match")
-
- // Explicitly delete, make sure that finalizers are removed from the object so that kubernetes can delete it.
- testClient.Delete(context.TODO(), instance)
- for i := 0; i < 5; i++ {
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)), "Cloud has not been deleted, thus the finalizers have not been removed from the object.")
- err = testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance)
- if err != nil {
- break
- }
- }
- g.Expect(err).To(gomega.HaveOccurred(), "Cloud has not been deleted, thus the finalizers have not been removed from the object.")
-}
-
-func TestDefaultEphemeralStorage(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrJavaMem: "-Xmx4G",
- SolrOpts: "extra-opts",
- SolrLogLevel: "DEBUG",
- StorageOptions: solr.SolrDataStorageOptions{
- EphemeralStorage: &solr.SolrEphemeralDataStorageOptions{},
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- PodOptions: &solr.PodOptions{
- EnvVariables: extraVars,
- PodSecurityContext: &podSecurityContext,
- Volumes: extraVolumes,
- Affinity: affinity,
- Resources: resources,
- },
- },
- },
- }
-
- testForDefaultEmptyDirSpecs(t, g, instance)
-}
-
-func TestDefaultEphemeralStorageWhenNilEmptyDir(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrJavaMem: "-Xmx4G",
- SolrOpts: "extra-opts",
- SolrLogLevel: "DEBUG",
- StorageOptions: solr.SolrDataStorageOptions{
- EphemeralStorage: &solr.SolrEphemeralDataStorageOptions{
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the SolrCloud finalizers")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.GetFinalizers()).To(HaveLen(1), "The solrcloud should have no finalizers when the reclaim policy is Retain")
+ g.Expect(found.GetFinalizers()[0]).To(Equal(util.SolrStorageFinalizer), "Incorrect finalizer set for deleting persistent storage.")
+ })
+
+ By("testing the Solr StatefulSet PVC Spec")
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(2), "Pod has wrong number of volumes")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Name).To(Equal(solrCloud.Spec.StorageOptions.PersistentStorage.PersistentVolumeClaimTemplate.ObjectMeta.Name), "Data volume claim doesn't exist")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCTechnologyLabel]).To(Equal("solr-cloud"), "PVC Technology label doesn't match")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCStorageLabel]).To(Equal("data"), "PVC Storage label doesn't match")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCInstanceLabel]).To(Equal(solrCloud.Name), "PVC Instance label doesn't match")
+ g.Expect(found.Spec.VolumeClaimTemplates[0].Labels["base"]).To(Equal("here"), "Additional PVC label doesn't match")
+ })
+
+ By("ensuring that the PVCs are deleted and the finalizer is removed when the SolrCloud is deleted")
+ Expect(k8sClient.Delete(ctx, solrCloud)).To(Succeed(), "Could not delete solrCloud")
+
+ Eventually(func() error {
+ return k8sClient.Get(ctx, resourceKey(solrCloud, solrCloud.Name), &solrv1beta1.SolrCloud{})
+ }).Should(MatchError("solrclouds.solr.apache.org \""+solrCloud.Name+"\" not found"), "Cloud has not been deleted, thus the finalizers have not been removed from the object.")
+ })
+ })
+
+ FContext("Ephemeral Data - Default", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
+ EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{},
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the SolrCloud finalizers")
+ expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
+ })
+
+ By("testing the Solr StatefulSet PVC Spec")
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
+ g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
+ dataVolume := found.Spec.Template.Spec.Volumes[1]
+ g.Expect(dataVolume.EmptyDir).To(Not(BeNil()), "The data volume should be an empty-dir.")
+ g.Expect(dataVolume.HostPath).To(BeNil(), "The data volume should not be a hostPath volume.")
+ })
+ })
+ })
+
+ FContext("Ephemeral Data - Nil EmptyDir", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
+ EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{
EmptyDir: nil,
},
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- PodOptions: &solr.PodOptions{
- EnvVariables: extraVars,
- PodSecurityContext: &podSecurityContext,
- Volumes: extraVolumes,
- Affinity: affinity,
- Resources: resources,
- },
- },
- },
- }
-
- testForDefaultEmptyDirSpecs(t, g, instance)
-}
-
-func TestEphemeralStorageWithEmptyDirSpecs(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrJavaMem: "-Xmx4G",
- SolrOpts: "extra-opts",
- SolrLogLevel: "DEBUG",
- StorageOptions: solr.SolrDataStorageOptions{
- EphemeralStorage: &solr.SolrEphemeralDataStorageOptions{
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the SolrCloud finalizers")
+ expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
+ })
+
+ By("testing the Solr StatefulSet PVC Spec")
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
+ g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
+ dataVolume := found.Spec.Template.Spec.Volumes[1]
+ g.Expect(dataVolume.EmptyDir).To(Not(BeNil()), "The data volume should be an empty-dir.")
+ g.Expect(dataVolume.HostPath).To(BeNil(), "The data volume should not be a hostPath volume.")
+ })
+ })
+ })
+
+ FContext("Ephemeral Data - EmptyDir with Specs", func() {
+ BeforeEach(func() {
+ solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
+ EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: corev1.StorageMediumMemory,
SizeLimit: resource.NewQuantity(1028*1028*1028, resource.BinarySI),
},
},
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- PodOptions: &solr.PodOptions{
- EnvVariables: extraVars,
- PodSecurityContext: &podSecurityContext,
- Volumes: extraVolumes,
- Affinity: affinity,
- Resources: resources,
- },
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Fetch new value of instance to check finalizers
- foundInstance := &solr.SolrCloud{}
- g.Eventually(func() error {
- return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, foundInstance)
- }, timeout).Should(gomega.Succeed())
- assert.Equal(t, 0, len(foundInstance.GetFinalizers()), "The solrcloud should have no finalizers when ephemeral storage is used")
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 3, len(statefulSet.Spec.Template.Spec.Volumes), "Pod has wrong number of volumes")
- assert.Equal(t, 0, len(statefulSet.Spec.VolumeClaimTemplates), "No data volume claims should exist when using ephemeral storage")
- dataVolume := statefulSet.Spec.Template.Spec.Volumes[1]
- assert.NotNil(t, dataVolume.EmptyDir, "The data volume should be an empty-dir.")
- assert.Nil(t, dataVolume.HostPath, "The data volume should not be a hostPath volume.")
- assert.EqualValues(t, instance.Spec.StorageOptions.EphemeralStorage.EmptyDir, dataVolume.EmptyDir, "The empty dir settings do not match with what was provided.")
-}
-
-func TestEphemeralStorageWithHostPathSpecs(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
-
- hostPathType := corev1.HostPathDirectoryOrCreate
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrJavaMem: "-Xmx4G",
- SolrOpts: "extra-opts",
- SolrLogLevel: "DEBUG",
- StorageOptions: solr.SolrDataStorageOptions{
- EphemeralStorage: &solr.SolrEphemeralDataStorageOptions{
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the SolrCloud finalizers")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
+ })
+
+ By("testing the Solr StatefulSet PVC Spec")
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
+ g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
+ dataVolume := found.Spec.Template.Spec.Volumes[1]
+ g.Expect(dataVolume.EmptyDir).To(Not(BeNil()), "The data volume should be an empty-dir.")
+ g.Expect(dataVolume.HostPath).To(BeNil(), "The data volume should not be a hostPath volume.")
+ g.Expect(dataVolume.EmptyDir).To(Equal(solrCloud.Spec.StorageOptions.EphemeralStorage.EmptyDir), "The empty dir settings do not match with what was provided.")
+ })
+ })
+ })
+
+ FContext("Ephemeral Data - HostPath with Specs", func() {
+ hostPathType := corev1.HostPathDirectoryOrCreate
+ BeforeEach(func() {
+ solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
+ EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{
HostPath: &corev1.HostPathVolumeSource{
Path: "/tmp",
Type: &hostPathType,
},
},
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- PodOptions: &solr.PodOptions{
- EnvVariables: extraVars,
- PodSecurityContext: &podSecurityContext,
- Volumes: extraVolumes,
- Affinity: affinity,
- Resources: resources,
- },
- },
- },
- }
-
- testHostPathSpecs(t, g, instance)
-}
-
-func TestEphemeralStorageWithHostPathAndEmptyDirSpecs(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
-
- hostPathType := corev1.HostPathDirectoryOrCreate
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
- },
- SolrJavaMem: "-Xmx4G",
- SolrOpts: "extra-opts",
- SolrLogLevel: "DEBUG",
- StorageOptions: solr.SolrDataStorageOptions{
- EphemeralStorage: &solr.SolrEphemeralDataStorageOptions{
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the SolrCloud finalizers")
+ expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
+ })
+
+ By("testing the Solr StatefulSet PVC Spec")
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
+ g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
+ dataVolume := found.Spec.Template.Spec.Volumes[1]
+ g.Expect(dataVolume.EmptyDir).To(BeNil(), "The data volume should not be an empty-dir.")
+ g.Expect(dataVolume.HostPath).To(Not(BeNil()), "The data volume should be a hostPath volume.")
+ g.Expect(dataVolume.HostPath).To(Equal(solrCloud.Spec.StorageOptions.EphemeralStorage.HostPath), "The hostPath settings do not match with what was provided.")
+ })
+ })
+ })
+
+ FContext("Ephemeral Data - HostPath and EmptyDir (HostPath should win)", func() {
+ hostPathType := corev1.HostPathDirectoryOrCreate
+ BeforeEach(func() {
+ solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
+ EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{
HostPath: &corev1.HostPathVolumeSource{
Path: "/tmp",
Type: &hostPathType,
@@ -438,115 +272,23 @@ func TestEphemeralStorageWithHostPathAndEmptyDirSpecs(t *testing.T) {
SizeLimit: resource.NewQuantity(1028*1028*1028, resource.BinarySI),
},
},
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- PodOptions: &solr.PodOptions{
- EnvVariables: extraVars,
- PodSecurityContext: &podSecurityContext,
- Volumes: extraVolumes,
- Affinity: affinity,
- Resources: resources,
- },
- },
- },
- }
-
- testHostPathSpecs(t, g, instance)
-}
-
-func testHostPathSpecs(t *testing.T, g *gomega.GomegaWithT, instance *solr.SolrCloud) {
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Fetch new value of instance to check finalizers
- foundInstance := &solr.SolrCloud{}
- g.Eventually(func() error {
- return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, foundInstance)
- }, timeout).Should(gomega.Succeed())
- assert.Equal(t, 0, len(foundInstance.GetFinalizers()), "The solrcloud should have no finalizers when ephemeral storage is used")
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 3, len(statefulSet.Spec.Template.Spec.Volumes), "Pod has wrong number of volumes")
- assert.Equal(t, 0, len(statefulSet.Spec.VolumeClaimTemplates), "No data volume claims should exist when using ephemeral storage")
- dataVolume := statefulSet.Spec.Template.Spec.Volumes[1]
- assert.NotNil(t, dataVolume.HostPath, "The data volume should be a hostPath volume.")
- assert.Nil(t, dataVolume.EmptyDir, "The data volume should not be an emptyDir volume.")
- assert.EqualValues(t, instance.Spec.StorageOptions.EphemeralStorage.HostPath, dataVolume.HostPath, "The hostPath settings do not match with what was provided.")
-}
-
-func testForDefaultEmptyDirSpecs(t *testing.T, g *gomega.GomegaWithT, instance *solr.SolrCloud) {
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Fetch new value of instance to check finalizers
- foundInstance := &solr.SolrCloud{}
- g.Eventually(func() error {
- return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, foundInstance)
- }, timeout).Should(gomega.Succeed())
- assert.Equal(t, 0, len(foundInstance.GetFinalizers()), "The solrcloud should have no finalizers when ephemeral storage is used")
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 3, len(statefulSet.Spec.Template.Spec.Volumes), "Pod has wrong number of volumes")
- assert.Equal(t, 0, len(statefulSet.Spec.VolumeClaimTemplates), "No data volume claims should exist when using ephemeral storage")
- dataVolume := statefulSet.Spec.Template.Spec.Volumes[1]
- assert.NotNil(t, dataVolume.EmptyDir, "The data volume should be an empty-dir.")
-}
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the SolrCloud finalizers")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
+ })
+
+ By("testing the Solr StatefulSet PVC Spec")
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
+ g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
+ dataVolume := found.Spec.Template.Spec.Volumes[1]
+ g.Expect(dataVolume.EmptyDir).To(BeNil(), "The data volume should not be an empty-dir.")
+ g.Expect(dataVolume.HostPath).To(Not(BeNil()), "The data volume should be a hostPath volume.")
+ g.Expect(dataVolume.HostPath).To(Equal(solrCloud.Spec.StorageOptions.EphemeralStorage.HostPath), "The hostPath settings do not match with what was provided.")
+ })
+ })
+ })
+})
diff --git a/controllers/solrcloud_controller_test.go b/controllers/solrcloud_controller_test.go
index 353876d..17a410d 100644
--- a/controllers/solrcloud_controller_test.go
+++ b/controllers/solrcloud_controller_test.go
@@ -18,950 +18,629 @@
package controllers
import (
+ "context"
"crypto/md5"
"fmt"
- "strconv"
- "time"
-
- "strings"
- "testing"
-
- solr "github.com/apache/solr-operator/api/v1beta1"
+ solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
"github.com/apache/solr-operator/controllers/util"
- "github.com/onsi/gomega"
- zookeeperv1beta1 "github.com/pravega/zookeeper-operator/pkg/apis/zookeeper/v1beta1"
- "github.com/stretchr/testify/assert"
- "golang.org/x/net/context"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/types"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/manager"
- "sigs.k8s.io/controller-runtime/pkg/reconcile"
+ "strconv"
+ "strings"
)
-var _ reconcile.Reconciler = &SolrCloudReconciler{}
+var _ = FDescribe("SolrCloud controller - General", func() {
+ var (
+ ctx context.Context
-var (
- expectedCloudRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: "foo-clo", Namespace: "default"}}
- cloudSsKey = types.NamespacedName{Name: "foo-clo-solrcloud", Namespace: "default"}
- cloudCsKey = types.NamespacedName{Name: "foo-clo-solrcloud-common", Namespace: "default"}
- cloudHsKey = types.NamespacedName{Name: "foo-clo-solrcloud-headless", Namespace: "default"}
- cloudIKey = types.NamespacedName{Name: "foo-clo-solrcloud-common", Namespace: "default"}
- cloudCMKey = types.NamespacedName{Name: "foo-clo-solrcloud-configmap", Namespace: "default"}
- cloudZkKey = types.NamespacedName{Name: "foo-clo-solrcloud-zookeeper", Namespace: "default"}
-)
+ solrCloud *solrv1beta1.SolrCloud
+ )
-func TestCloudReconcile(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
+ BeforeEach(func() {
+ ctx = context.Background()
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
- },
+ solrCloud = &solrv1beta1.SolrCloud{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ Namespace: "default",
},
- SolrJavaMem: "-Xmx4G",
- SolrOpts: "extra-opts",
- SolrLogLevel: "DEBUG",
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- PodOptions: &solr.PodOptions{
- EnvVariables: extraVars,
- PodSecurityContext: &podSecurityContext,
- Volumes: extraVolumes,
- Affinity: affinity,
- Resources: resources,
- SidecarContainers: extraContainers1,
- InitContainers: extraContainers2,
+ Spec: solrv1beta1.SolrCloudSpec{},
+ }
+ })
+
+ JustBeforeEach(func() {
+ By("creating the SolrCloud")
+ Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed())
+
+ By("defaulting the missing SolrCloud values")
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ g.Expect(found.WithDefaults()).To(BeFalse(), "The SolrCloud spec should not need to be defaulted eventually")
+ })
+ })
+
+ AfterEach(func() {
+ cleanupTest(ctx, solrCloud)
+ })
+
+ FContext("Regular Solr Cloud", func() {
+ BeforeEach(func() {
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
},
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Update the status of the SolrCloud
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- // Check extra containers
- assert.Equal(t, 3, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires the solr container plus the desired sidecars.")
- assert.EqualValues(t, extraContainers1, statefulSet.Spec.Template.Spec.Containers[1:])
-
- assert.Equal(t, 3, len(statefulSet.Spec.Template.Spec.InitContainers), "Solr StatefulSet requires a default initContainer plus the additional specified.")
- assert.EqualValues(t, extraContainers2, statefulSet.Spec.Template.Spec.InitContainers[1:])
-
- // Check the update strategy
- assert.EqualValues(t, appsv1.OnDeleteStatefulSetStrategyType, statefulSet.Spec.UpdateStrategy.Type, "Incorrect statefulset update strategy")
- assert.EqualValues(t, appsv1.ParallelPodManagement, statefulSet.Spec.PodManagementPolicy, "Incorrect statefulset pod management policy")
-
- // Host Alias Tests
- assert.Nil(t, statefulSet.Spec.Template.Spec.HostAliases, "There is no need for host aliases because traffic is going directly to pods.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + instance.HeadlessServiceName() + "." + instance.Namespace,
- "SOLR_JAVA_MEM": "-Xmx4G",
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "SOLR_LOG_LEVEL": "DEBUG",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) extra-opts",
- }
- foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
- assert.Equal(t, extraVars, foundEnv[len(foundEnv)-3:len(foundEnv)-1], "Extra Env Vars are not the same as the ones provided in podOptions")
- // Note that this check changes the variable foundEnv, so the values are no longer valid afterwards.
- // TODO: Make this not invalidate foundEnv
- testPodEnvVariables(t, expectedEnvVars, append(foundEnv[:len(foundEnv)-3], foundEnv[len(foundEnv)-1]))
-
- // Other Pod Options Checks
- assert.Equal(t, podSecurityContext, *statefulSet.Spec.Template.Spec.SecurityContext, "PodSecurityContext is not the same as the one provided in podOptions")
- assert.Equal(t, affinity, statefulSet.Spec.Template.Spec.Affinity, "Affinity is not the same as the one provided in podOptions")
- assert.Equal(t, resources.Limits, statefulSet.Spec.Template.Spec.Containers[0].Resources.Limits, "Resources.Limits is not the same as the one provided in podOptions")
- assert.Equal(t, resources.Requests, statefulSet.Spec.Template.Spec.Containers[0].Resources.Requests, "Resources.Requests is not the same as the one provided in podOptions")
- extraVolumes[0].DefaultContainerMount.Name = extraVolumes[0].Name
- assert.Equal(t, len(extraVolumes)+1, len(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts), "Container has wrong number of volumeMounts")
- assert.Equal(t, *extraVolumes[0].DefaultContainerMount, statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts[1], "Additional Volume from podOptions not mounted into container properly.")
- assert.Equal(t, len(extraVolumes)+2, len(statefulSet.Spec.Template.Spec.Volumes), "Pod has wrong number of volumes")
- assert.Equal(t, extraVolumes[0].Name, statefulSet.Spec.Template.Spec.Volumes[2].Name, "Additional Volume from podOptions not loaded into pod properly.")
- assert.Equal(t, extraVolumes[0].Source, statefulSet.Spec.Template.Spec.Volumes[2].VolumeSource, "Additional Volume from podOptions not loaded into pod properly.")
-
- // Check the client Service
- clientService := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- expetedLabels, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: clientService.Spec.Selector})
- assert.EqualValues(t, expetedLabels.String(), instance.Status.PodSelector, "Incorrect pod selector object in ")
-
- // Check the headless Service
- expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
-
- // Check the ingress
- expectNoIngress(g, cloudIKey)
-}
-
-func TestCustomKubeOptionsCloudReconcile(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
- replicas := int32(4)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{
- Name: expectedCloudRequest.Name,
- Namespace: expectedCloudRequest.Namespace,
- Labels: map[string]string{"base": "here"},
- },
- Spec: solr.SolrCloudSpec{
- SolrImage: &solr.ContainerImage{
- ImagePullSecret: testImagePullSecretName,
- },
- Replicas: &replicas,
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
+ SolrJavaMem: "-Xmx4G",
+ SolrOpts: "extra-opts",
+ SolrLogLevel: "DEBUG",
+ CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
+ PodOptions: &solrv1beta1.PodOptions{
+ EnvVariables: extraVars,
+ PodSecurityContext: &testPodSecurityContext,
+ Volumes: extraVolumes,
+ Affinity: testAffinity,
+ Resources: testResources,
+ SidecarContainers: extraContainers1,
+ InitContainers: extraContainers2,
+ },
},
- },
- UpdateStrategy: solr.SolrUpdateStrategy{
- Method: solr.StatefulSetUpdate,
- RestartSchedule: "@every 30m",
- },
- SolrGCTune: "gc Options",
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- PodOptions: &solr.PodOptions{
- Annotations: testPodAnnotations,
- Labels: testPodLabels,
- Tolerations: testTolerations,
- NodeSelector: testNodeSelectors,
- LivenessProbe: testProbeLivenessNonDefaults,
- ReadinessProbe: testProbeReadinessNonDefaults,
- StartupProbe: testProbeStartup,
- Lifecycle: testLifecycle,
- PriorityClassName: testPriorityClass,
- ImagePullSecrets: testAdditionalImagePullSecrets,
- TerminationGracePeriodSeconds: &testTerminationGracePeriodSeconds,
- ServiceAccountName: testServiceAccountName,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ // Check extra containers
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(3), "Solr StatefulSet requires the solr container plus the desired sidecars.")
+ Expect(statefulSet.Spec.Template.Spec.Containers[1:]).To(Equal(extraContainers1), "Solr StatefulSet has incorrect extra containers")
+
+ Expect(statefulSet.Spec.Template.Spec.InitContainers).To(HaveLen(3), "Solr StatefulSet requires a default initContainer plus the additional specified.")
+ Expect(statefulSet.Spec.Template.Spec.InitContainers[1:]).To(Equal(extraContainers2), "Solr StatefulSet has incorrect extra initContainers")
+
+ // Check the update strategy
+ Expect(statefulSet.Spec.UpdateStrategy.Type).To(Equal(appsv1.OnDeleteStatefulSetStrategyType), "Incorrect statefulset update strategy")
+ Expect(statefulSet.Spec.PodManagementPolicy).To(Equal(appsv1.ParallelPodManagement), "Incorrect statefulset pod management policy")
+
+ // Host Alias Tests
+ Expect(statefulSet.Spec.Template.Spec.HostAliases).To(BeNil(), "There is no need for host aliases because traffic is going directly to pods.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_JAVA_MEM": "-Xmx4G",
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_LOG_LEVEL": "DEBUG",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) extra-opts",
+ }
+ foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
+ // Note that this check changes the variable foundEnv, so the values are no longer valid afterwards.
+ // TODO: Make this not invalidate foundEnv
+ Expect(foundEnv[len(foundEnv)-3:len(foundEnv)-1]).To(Equal(extraVars), "Extra Env Vars are not the same as the ones provided in podOptions")
+
+ testPodEnvVariables(expectedEnvVars, append(foundEnv[:len(foundEnv)-3], foundEnv[len(foundEnv)-1]))
+
+ // Other Pod Options Checks
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PostStart).To(BeNil(), "Post-start command should be nil since there is no chRoot to ensure exists.")
+ Expect(statefulSet.Spec.Template.Spec.SecurityContext).To(Not(BeNil()), "PodSecurityContext is not the same as the one provided in podOptions")
+ Expect(*statefulSet.Spec.Template.Spec.SecurityContext).To(Equal(testPodSecurityContext), "PodSecurityContext is not the same as the one provided in podOptions")
+ Expect(statefulSet.Spec.Template.Spec.Affinity).To(Equal(testAffinity), "Affinity is not the same as the one provided in podOptions")
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Resources.Limits).To(Equal(testResources.Limits), "Resources.Limits is not the same as the one provided in podOptions")
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Resources.Requests).To(Equal(testResources.Requests), "Resources.Requests is not the same as the one provided in podOptions")
+ extraVolumes[0].DefaultContainerMount.Name = extraVolumes[0].Name
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+1), "Container has wrong number of volumeMounts")
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(*extraVolumes[0].DefaultContainerMount), "Additional Volume from podOptions not mounted into container properly.")
+ Expect(statefulSet.Spec.Template.Spec.Volumes).To(HaveLen(len(extraVolumes)+2), "Pod has wrong number of volumes")
+ Expect(statefulSet.Spec.Template.Spec.Volumes[2].Name).To(Equal(extraVolumes[0].Name), "Additional Volume from podOptions not loaded into pod properly.")
+ Expect(statefulSet.Spec.Template.Spec.Volumes[2].VolumeSource).To(Equal(extraVolumes[0].Source), "Additional Volume from podOptions not loaded into pod properly.")
+
+ By("testing the Solr Common Service")
+ expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+
+ By("testing the Solr Headless Service")
+ expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+
+ By("making sure no Ingress was created")
+ expectNoIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+ })
+ })
+
+ FContext("Solr Cloud with Custom Kube Options", func() {
+ BeforeEach(func() {
+ replicas := int32(4)
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ SolrImage: &solrv1beta1.ContainerImage{
+ ImagePullSecret: testImagePullSecretName,
},
- StatefulSetOptions: &solr.StatefulSetOptions{
- Annotations: testSSAnnotations,
- Labels: testSSLabels,
- PodManagementPolicy: appsv1.OrderedReadyPodManagement,
+ Replicas: &replicas,
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
},
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
+ UpdateStrategy: solrv1beta1.SolrUpdateStrategy{
+ Method: solrv1beta1.StatefulSetUpdate,
+ RestartSchedule: "@every 30m",
},
- HeadlessServiceOptions: &solr.ServiceOptions{
- Annotations: testHeadlessServiceAnnotations,
- Labels: testHeadlessServiceLabels,
+ SolrGCTune: "gc Options",
+ CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
+ PodOptions: &solrv1beta1.PodOptions{
+ Annotations: testPodAnnotations,
+ Labels: testPodLabels,
+ Tolerations: testTolerations,
+ NodeSelector: testNodeSelectors,
+ LivenessProbe: testProbeLivenessNonDefaults,
+ ReadinessProbe: testProbeReadinessNonDefaults,
+ StartupProbe: testProbeStartup,
+ Lifecycle: testLifecycle, PriorityClassName: testPriorityClass,
+ ImagePullSecrets: testAdditionalImagePullSecrets,
+ TerminationGracePeriodSeconds: &testTerminationGracePeriodSeconds,
+ ServiceAccountName: testServiceAccountName,
+ },
+ StatefulSetOptions: &solrv1beta1.StatefulSetOptions{
+ Annotations: testSSAnnotations,
+ Labels: testSSLabels,
+ PodManagementPolicy: appsv1.OrderedReadyPodManagement,
+ },
+ CommonServiceOptions: &solrv1beta1.ServiceOptions{
+ Annotations: testCommonServiceAnnotations,
+ Labels: testCommonServiceLabels,
+ },
+ HeadlessServiceOptions: &solrv1beta1.ServiceOptions{
+ Annotations: testHeadlessServiceAnnotations,
+ Labels: testHeadlessServiceLabels,
+ },
+ ConfigMapOptions: &solrv1beta1.ConfigMapOptions{
+ Annotations: testConfigMapAnnotations,
+ Labels: testConfigMapLabels,
+ },
},
- ConfigMapOptions: &solr.ConfigMapOptions{
- Annotations: testConfigMapAnnotations,
- Labels: testConfigMapLabels,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr ConfigMap")
+ configMap := expectConfigMap(ctx, solrCloud, solrCloud.ConfigMapName(), map[string]string{"solr.xml": util.GenerateSolrXMLString("")})
+ Expect(configMap.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testConfigMapLabels)), "Incorrect configMap labels")
+ Expect(configMap.Annotations).To(Equal(testConfigMapAnnotations), "Incorrect configMap annotations")
+
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+ Expect(statefulSet.Spec.Replicas).To(Equal(solrCloud.Spec.Replicas), "Solr StatefulSet has incorrect number of replicas.")
+
+ Expect(statefulSet.Spec.Template.Annotations).To(HaveKeyWithValue(util.SolrXmlMd5Annotation, fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data[util.SolrXmlFile])))), "Wrong solr.xml MD5 annotation in the pod template!")
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME).foo-solrcloud-headless.default",
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "GC_TUNE": "gc Options",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "SOLR_STOP_WAIT": strconv.FormatInt(testTerminationGracePeriodSeconds-5, 10),
+ }
+ expectedStatefulSetLabels := util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), map[string]string{"technology": util.SolrCloudPVCTechnology})
+ expectedStatefulSetAnnotations := map[string]string{util.SolrZKConnectionStringAnnotation: "host:7271/"}
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedStatefulSetLabels, testSSLabels)), "Incorrect statefulSet labels")
+ Expect(statefulSet.Annotations).To(Equal(util.MergeLabelsOrAnnotations(expectedStatefulSetAnnotations, testSSAnnotations)), "Incorrect statefulSet annotations")
+ Expect(statefulSet.Spec.Template.ObjectMeta.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedStatefulSetLabels, testPodLabels)), "Incorrect pod labels")
+ Expect(statefulSet.Spec.Template.ObjectMeta.Annotations).To(HaveKey(util.SolrScheduledRestartAnnotation), "Pod Template does not have scheduled restart annotation when it should")
+ // Remove the annotation when we know that it exists, we don't know the exact value so we can't check it below.
+ delete(statefulSet.Spec.Template.Annotations, util.SolrScheduledRestartAnnotation)
+ Expect(statefulSet.Spec.Template.Annotations).To(Equal(util.MergeLabelsOrAnnotations(map[string]string{"solr.apache.org/solrXmlMd5": fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data["solr.xml"])))}, testPodAnnotations)), "Incorrect pod annotations")
+ Expect(statefulSet.Spec.Template.Spec.NodeSelector).To(Equal(testNodeSelectors), "Incorrect pod node selectors")
+
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].LivenessProbe, testProbeLivenessNonDefaults, "Incorrect Liveness Probe")
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].ReadinessProbe, testProbeReadinessNonDefaults, "Incorrect Readiness Probe")
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].StartupProbe, testProbeStartup, "Incorrect Startup Probe")
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle).To(Equal(testLifecycle), "Incorrect container lifecycle")
+ Expect(statefulSet.Spec.Template.Spec.Tolerations).To(Equal(testTolerations), "Incorrect Tolerations for Pod")
+ Expect(statefulSet.Spec.Template.Spec.PriorityClassName).To(Equal(testPriorityClass), "Incorrect Priority class name for Pod Spec")
+ Expect(statefulSet.Spec.Template.Spec.ImagePullSecrets).To(ConsistOf(append(testAdditionalImagePullSecrets, corev1.LocalObjectReference{Name: testImagePullSecretName})), "Incorrect imagePullSecrets")
+ Expect(statefulSet.Spec.Template.Spec.TerminationGracePeriodSeconds).To(Equal(&testTerminationGracePeriodSeconds), "Incorrect terminationGracePeriodSeconds")
+ Expect(statefulSet.Spec.Template.Spec.ServiceAccountName).To(Equal(testServiceAccountName), "Incorrect serviceAccountName")
+
+ // Check the update strategy
+ Expect(statefulSet.Spec.UpdateStrategy.Type).To(Equal(appsv1.RollingUpdateStatefulSetStrategyType), "Incorrect statefulset update strategy")
+ Expect(statefulSet.Spec.PodManagementPolicy).To(Equal(appsv1.OrderedReadyPodManagement), "Incorrect statefulset pod management policy")
+
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ expectedCommonServiceLabels := util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), map[string]string{"service-type": "common"})
+ Expect(commonService.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedCommonServiceLabels, testCommonServiceLabels)), "Incorrect common service labels")
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ expectedHeadlessServiceLabels := util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), map[string]string{"service-type": "headless"})
+ Expect(headlessService.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedHeadlessServiceLabels, testHeadlessServiceLabels)), "Incorrect headless service labels")
+ Expect(headlessService.Annotations).To(Equal(testHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ })
+ })
+
+ FContext("Solr Cloud with External Zookeeper & Chroot", func() {
+ connString := "host:7271,host2:7271"
+ BeforeEach(func() {
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ ChRoot: "a-ch/root",
+ ExternalConnectionString: &connString,
+ },
},
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- // Add an additional check for reconcile, so that the services will have IP addresses for the hostAliases to use
- // Otherwise the reconciler will have 'blockReconciliationOfStatefulSet' set to true, and the stateful set will not be created
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the configMap
- configMap := expectConfigMap(t, g, requests, expectedCloudRequest, cloudCMKey, map[string]string{})
- testMapsEqual(t, "configMap labels", util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), testConfigMapLabels), configMap.Labels)
- testMapsEqual(t, "configMap annotations", testConfigMapAnnotations, configMap.Annotations)
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
- assert.EqualValues(t, replicas, *statefulSet.Spec.Replicas, "Solr StatefulSet has incorrect number of replicas.")
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME).foo-clo-solrcloud-headless.default",
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "GC_TUNE": "gc Options",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- "SOLR_STOP_WAIT": strconv.FormatInt(testTerminationGracePeriodSeconds-5, 10),
- }
- expectedStatefulSetLabels := util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), map[string]string{"technology": "solr-cloud"})
- expectedStatefulSetAnnotations := map[string]string{util.SolrZKConnectionStringAnnotation: "host:7271/"}
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- testMapsEqual(t, "statefulSet labels", util.MergeLabelsOrAnnotations(expectedStatefulSetLabels, testSSLabels), statefulSet.Labels)
- testMapsEqual(t, "statefulSet annotations", util.MergeLabelsOrAnnotations(expectedStatefulSetAnnotations, testSSAnnotations), statefulSet.Annotations)
- testMapsEqual(t, "pod labels", util.MergeLabelsOrAnnotations(expectedStatefulSetLabels, testPodLabels), statefulSet.Spec.Template.ObjectMeta.Labels)
- if assert.True(t, metav1.HasAnnotation(statefulSet.Spec.Template.ObjectMeta, util.SolrScheduledRestartAnnotation), "Pod Template does not have scheduled restart annotation when it should") {
- // Remove the annotation when we know that it exists, we don't know the exact value so we can't check it below.
- delete(statefulSet.Spec.Template.Annotations, util.SolrScheduledRestartAnnotation)
- }
- testMapsEqual(t, "pod annotations", util.MergeLabelsOrAnnotations(map[string]string{"solr.apache.org/solrXmlMd5": fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data["solr.xml"])))}, testPodAnnotations), statefulSet.Spec.Template.Annotations)
- testMapsEqual(t, "pod node selectors", testNodeSelectors, statefulSet.Spec.Template.Spec.NodeSelector)
- testPodProbe(t, testProbeLivenessNonDefaults, statefulSet.Spec.Template.Spec.Containers[0].LivenessProbe, "liveness")
- testPodProbe(t, testProbeReadinessNonDefaults, statefulSet.Spec.Template.Spec.Containers[0].ReadinessProbe, "readiness")
- testPodProbe(t, testProbeStartup, statefulSet.Spec.Template.Spec.Containers[0].StartupProbe, "startup")
- testPodLifecycle(t, testLifecycle, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle)
- testPodTolerations(t, testTolerations, statefulSet.Spec.Template.Spec.Tolerations)
- assert.EqualValues(t, testPriorityClass, statefulSet.Spec.Template.Spec.PriorityClassName, "Incorrect Priority class name for Pod Spec")
- assert.ElementsMatch(t, append(testAdditionalImagePullSecrets, corev1.LocalObjectReference{Name: testImagePullSecretName}), statefulSet.Spec.Template.Spec.ImagePullSecrets, "Incorrect imagePullSecrets")
- assert.EqualValues(t, &testTerminationGracePeriodSeconds, statefulSet.Spec.Template.Spec.TerminationGracePeriodSeconds, "Incorrect terminationGracePeriodSeconds")
- assert.EqualValues(t, testServiceAccountName, statefulSet.Spec.Template.Spec.ServiceAccountName, "Incorrect serviceAccountName")
-
- // Check the update strategy
- assert.EqualValues(t, appsv1.RollingUpdateStatefulSetStrategyType, statefulSet.Spec.UpdateStrategy.Type, "Incorrect statefulset update strategy")
- assert.EqualValues(t, appsv1.OrderedReadyPodManagement, statefulSet.Spec.PodManagementPolicy, "Incorrect statefulset pod management policy")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Selector.MatchLabels)
- expectedCommonServiceLabels := util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), map[string]string{"service-type": "common"})
- testMapsEqual(t, "common service labels", util.MergeLabelsOrAnnotations(expectedCommonServiceLabels, testCommonServiceLabels), service.Labels)
- testMapsEqual(t, "common service annotations", testCommonServiceAnnotations, service.Annotations)
-
- // Check that the headless Service does not exist
- headlessService := expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Selector.MatchLabels)
- expectedHeadlessServiceLabels := util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), map[string]string{"service-type": "headless"})
- testMapsEqual(t, "common service labels", util.MergeLabelsOrAnnotations(expectedHeadlessServiceLabels, testHeadlessServiceLabels), headlessService.Labels)
- testMapsEqual(t, "common service annotations", testHeadlessServiceAnnotations, headlessService.Annotations)
-}
-
-func TestCloudWithExternalZookeeperChroot(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
- connString := "host:7271,host2:7271"
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- ChRoot: "a-ch/root",
- ExternalConnectionString: &connString,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr Status values")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.ZookeeperConnectionInfo.InternalConnectionString).To(Equal(connString), "Wrong internal zkConnectionString in status")
+ g.Expect(found.ZookeeperConnectionInfo.ExternalConnectionString).To(Not(BeNil()), "External zkConnectionString in status should not be nil")
+ g.Expect(*found.ZookeeperConnectionInfo.ExternalConnectionString).To(Equal(connString), "Wrong external zkConnectionString in status")
+ g.Expect(found.ZookeeperConnectionInfo.ChRoot).To(Equal("/a-ch/root"), "Wrong zk chRoot in status")
+ g.Expect(found.ZookeeperConnectionInfo.ZkConnectionString()).To(Equal(connString+"/a-ch/root"), "Wrong zkConnectionString()")
+ })
+
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+ expectedZKHost := "host:7271,host2:7271/a-ch/root"
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": expectedZKHost,
+ "ZK_SERVER": "host:7271,host2:7271",
+ "ZK_CHROOT": "/a-ch/root",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "8983",
+ "GC_TUNE": "",
+ }
+ expectedStatefulSetAnnotations := map[string]string{util.SolrZKConnectionStringAnnotation: expectedZKHost}
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ Expect(statefulSet.Annotations).To(Equal(expectedStatefulSetAnnotations), "Incorrect statefulSet annotations")
+ Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PostStart.Exec.Command).To(ConsistOf("sh", "-c", "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}"), "Incorrect post-start command")
+ Expect(statefulSet.Spec.Template.Spec.ServiceAccountName).To(BeEmpty(), "No custom serviceAccountName specified, so the field should be empty.")
+ })
+ })
+
+ FContext("Setting default values for the SolrCloud resource", func() {
+ BeforeEach(func() {
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ProvidedZookeeper: &solrv1beta1.ZookeeperSpec{
+ Persistence: &solrv1beta1.ZKPersistence{},
+ },
},
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- // Add an additional check for reconcile, so that the zkCluster will have been created
- // Otherwise the reconciler will have 'blockReconciliationOfStatefulSet' set to true, and the stateful set will not be created
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
-
- // Check that the ZkConnectionInformation is correct
- assert.Equal(t, "host:7271,host2:7271", instance.Status.ZookeeperConnectionInfo.InternalConnectionString, "Wrong internal zkConnectionString in status")
- assert.Equal(t, "host:7271,host2:7271", *instance.Status.ZookeeperConnectionInfo.ExternalConnectionString, "Wrong external zkConnectionString in status")
- assert.Equal(t, "/a-ch/root", instance.Status.ZookeeperConnectionInfo.ChRoot, "Wrong zk chRoot in status")
- assert.Equal(t, "host:7271,host2:7271/a-ch/root", instance.Status.ZookeeperConnectionInfo.ZkConnectionString(), "Wrong zkConnectionString())")
-
- // Check that the statefulSet has been created, using the given chRoot
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
- expectedZKHost := "host:7271,host2:7271/a-ch/root"
- expectedEnvVars := map[string]string{
- "ZK_HOST": expectedZKHost,
- "ZK_SERVER": "host:7271,host2:7271",
- "ZK_CHROOT": "/a-ch/root",
- "SOLR_HOST": "$(POD_HOSTNAME)." + cloudHsKey.Name + "." + cloudHsKey.Namespace,
- "SOLR_PORT": "8983",
- "GC_TUNE": "",
- }
- expectedStatefulSetAnnotations := map[string]string{util.SolrZKConnectionStringAnnotation: expectedZKHost}
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- testMapsEqual(t, "statefulSet annotations", expectedStatefulSetAnnotations, statefulSet.Annotations)
- assert.EqualValues(t, []string{"sh", "-c", "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}"}, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PostStart.Exec.Command, "Incorrect post-start command")
- assert.Empty(t, statefulSet.Spec.Template.Spec.ServiceAccountName, "No custom serviceAccountName specified, so the field should be empty.")
-}
-
-func TestDefaults(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ProvidedZookeeper: &solr.ZookeeperSpec{
- Persistence: &zookeeperv1beta1.Persistence{},
+ }
+ })
+ FIt("has the correct default values", func() {
+ expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+
+ // Solr defaults
+ g.Expect(found.Spec.Replicas).To(Not(BeNil()), "Bad Default - Spec.Replicas")
+ g.Expect(*found.Spec.Replicas).To(Equal(solrv1beta1.DefaultSolrReplicas), "Bad Default - Spec.Replicas")
+ g.Expect(found.Spec.SolrImage).To(Not(BeNil()), "Bad Default - instance.Spec.SolrImage")
+ g.Expect(found.Spec.SolrImage.Repository).To(Equal(solrv1beta1.DefaultSolrRepo), "Bad Default - instance.Spec.SolrImage.Repository")
+ g.Expect(found.Spec.SolrImage.Tag).To(Equal(solrv1beta1.DefaultSolrVersion), "Bad Default - instance.Spec.SolrImage.Tag")
+ g.Expect(found.Spec.BusyBoxImage).To(Not(BeNil()), "Bad Default - instance.Spec.BusyBoxImage")
+ g.Expect(found.Spec.BusyBoxImage.Repository).To(Equal(solrv1beta1.DefaultBusyBoxImageRepo), "Bad Default - instance.Spec.BusyBoxImage.Repository")
+ g.Expect(found.Spec.BusyBoxImage.Tag).To(Equal(solrv1beta1.DefaultBusyBoxImageVersion), "Bad Default - instance.Spec.BusyBoxImage.Tag")
+ g.Expect(found.Spec.SolrAddressability.CommonServicePort).To(Equal(80), "Bad Default - instance.Spec.SolrAddressability.CommonServicePort")
+ g.Expect(found.Spec.SolrAddressability.PodPort).To(Equal(8983), "Bad Default - instance.Spec.SolrAddressability.PodPort")
+ g.Expect(found.Spec.SolrAddressability.External).To(BeNil(), "Bad Default - instance.Spec.SolrAddressability.External")
+
+ // Check the default Zookeeper
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Replicas).To(Not(BeNil()), "Bad Default - Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Replicas")
+ g.Expect(*found.Spec.ZookeeperRef.ProvidedZookeeper.Replicas).To(Equal(solrv1beta1.DefaultZkReplicas), "Bad Default - Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Replicas")
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Image).To(Not(BeNil()), "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Image")
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Image.Repository).To(Equal(solrv1beta1.DefaultZkRepo), "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Image.Repository")
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Image.Tag).To(Equal(solrv1beta1.DefaultZkVersion), "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Image.Tag")
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Persistence).To(Not(BeNil()), "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence")
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.VolumeReclaimPolicy).To(Equal(solrv1beta1.DefaultZkVolumeReclaimPolicy), "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.VolumeReclaimPolicy")
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.VolumeReclaimPolicy).To(Equal(solrv1beta1.DefaultZkVolumeReclaimPolicy), "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.VolumeReclaimPolicy")
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.PersistentVolumeClaimSpec).To(Not(BeNil()), "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec")
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.PersistentVolumeClaimSpec.Resources.Requests).To(HaveLen(1), "Bad Default - Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec.Resources length")
+ g.Expect(found.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.PersistentVolumeClaimSpec.AccessModes).To(HaveLen(1), "Bad Default - Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec.AccesModes length")
+ })
+
+ })
+ })
+
+ FContext("Solr Cloud with an explicit kube domain", func() {
+ BeforeEach(func() {
+ replicas := int32(2)
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ Replicas: &replicas,
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
},
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
-
- // Solr defaults
- assert.EqualValues(t, solr.DefaultSolrReplicas, *instance.Spec.Replicas, "Bad Default - Spec.Replicas")
- assert.NotNil(t, instance.Spec.SolrImage, "Bad Default - instance.Spec.SolrImage")
- assert.Equal(t, solr.DefaultSolrRepo, instance.Spec.SolrImage.Repository, "Bad Default - instance.Spec.SolrImage.Repository")
- assert.Equal(t, solr.DefaultSolrVersion, instance.Spec.SolrImage.Tag, "Bad Default - instance.Spec.SolrImage.Tag")
- assert.NotNil(t, instance.Spec.BusyBoxImage, "Bad Default - instance.Spec.BusyBoxImage")
- assert.Equal(t, solr.DefaultBusyBoxImageRepo, instance.Spec.BusyBoxImage.Repository, "Bad Default - instance.Spec.BusyBoxImage.Repository")
- assert.Equal(t, solr.DefaultBusyBoxImageVersion, instance.Spec.BusyBoxImage.Tag, "Bad Default - instance.Spec.BusyBoxImage.Tag")
- assert.Equal(t, 80, instance.Spec.SolrAddressability.CommonServicePort, "Bad Default - instance.Spec.SolrAddressability.CommonServicePort")
- assert.Equal(t, 8983, instance.Spec.SolrAddressability.PodPort, "Bad Default - instance.Spec.SolrAddressability.PodPort")
- assert.Nil(t, instance.Spec.SolrAddressability.External, "Bad Default - instance.Spec.SolrAddressability.External")
-
- // Check the default Zookeeper
- assert.Equal(t, solr.DefaultZkReplicas, *instance.Spec.ZookeeperRef.ProvidedZookeeper.Replicas, "Bad Default - Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Replicas")
- assert.NotNil(t, instance.Spec.ZookeeperRef.ProvidedZookeeper.Image, "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Image")
- assert.Equal(t, solr.DefaultZkRepo, instance.Spec.ZookeeperRef.ProvidedZookeeper.Image.Repository, "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Image.Repository")
- assert.Equal(t, solr.DefaultZkVersion, instance.Spec.ZookeeperRef.ProvidedZookeeper.Image.Tag, "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Image.Tag")
- assert.NotNil(t, instance.Spec.ZookeeperRef.ProvidedZookeeper.Persistence, "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence")
- assert.Equal(t, solr.DefaultZkVolumeReclaimPolicy, instance.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.VolumeReclaimPolicy, "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.VolumeReclaimPolicy")
- assert.Equal(t, solr.DefaultZkVolumeReclaimPolicy, instance.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.VolumeReclaimPolicy, "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.VolumeReclaimPolicy")
- assert.NotNil(t, instance.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.PersistentVolumeClaimSpec, "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec")
- assert.Equal(t, 1, len(instance.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.PersistentVolumeClaimSpec.Resources.Requests), "Bad Default - Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec.Resources length")
- assert.Equal(t, 1, len(instance.Spec.ZookeeperRef.ProvidedZookeeper.Persistence.PersistentVolumeClaimSpec.AccessModes), "Bad Default - Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec.AccesModes length")
-
- // verify the ConfigMap created and the annotation with the checksum is set
- stateful := &appsv1.StatefulSet{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), cloudSsKey, stateful) }, timeout).Should(gomega.Succeed())
-
- assert.NotNil(t, stateful.Spec.Template.Spec.Volumes)
- var solrXmlVol *corev1.Volume = nil
- for _, vol := range stateful.Spec.Template.Spec.Volumes {
- if vol.Name == "solr-xml" {
- solrXmlVol = &vol
- break
- }
- }
- assert.True(t, solrXmlVol != nil, "Didn't find custom solr.xml volume in sts config!")
- assert.True(t, solrXmlVol.VolumeSource.ConfigMap != nil, "solr-xml Volume should have a ConfigMap source")
- assert.Equal(t, solrXmlVol.VolumeSource.ConfigMap.Name, instance.ConfigMapName(), "solr-xml Volume should have a ConfigMap source")
-
- foundConfigMap := &corev1.ConfigMap{}
- err = testClient.Get(context.TODO(), types.NamespacedName{Name: instance.ConfigMapName(), Namespace: instance.Namespace}, foundConfigMap)
- solrXmlMd5 := fmt.Sprintf("%x", md5.Sum([]byte(foundConfigMap.Data[util.SolrXmlFile])))
- assert.True(t, stateful.Spec.Template.Annotations[util.SolrXmlMd5Annotation] == solrXmlMd5,
- "solr.xml MD5 annotation should be set on the pod template!")
-}
-
-func TestExternalKubeDomainCloudReconcile(t *testing.T) {
- UseZkCRD(false)
- g := gomega.NewGomegaWithT(t)
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- ZookeeperRef: &solr.ZookeeperRef{
- ConnectionInfo: &solr.ZookeeperConnectionInfo{
- InternalConnectionString: "host:7271",
+ SolrAddressability: solrv1beta1.SolrAddressabilityOptions{
+ PodPort: 2000,
+ CommonServicePort: 5000,
+ KubeDomain: testKubeDomain,
},
- },
- SolrAddressability: solr.SolrAddressabilityOptions{
- PodPort: 2000,
- CommonServicePort: 5000,
- KubeDomain: testKubeDomain,
- },
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- CommonServiceOptions: &solr.ServiceOptions{
- Annotations: testCommonServiceAnnotations,
- Labels: testCommonServiceLabels,
+ CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
+ CommonServiceOptions: &solrv1beta1.ServiceOptions{
+ Annotations: testCommonServiceAnnotations,
+ Labels: testCommonServiceLabels,
+ },
+ HeadlessServiceOptions: &solrv1beta1.ServiceOptions{
+ Annotations: testHeadlessServiceAnnotations,
+ Labels: testHeadlessServiceLabels,
+ },
},
- HeadlessServiceOptions: &solr.ServiceOptions{
- Annotations: testHeadlessServiceAnnotations,
- Labels: testHeadlessServiceLabels,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires the solr container.")
+
+ // Env Variable Tests
+ expectedEnvVars := map[string]string{
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_HOSTNAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain,
+ "SOLR_PORT": "2000",
+ "SOLR_NODE_PORT": "2000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ }
+ testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+ By("testing the Solr Common Service")
+ commonService := expectService(ctx, solrCloud, solrCloud.CommonServiceName(), statefulSet.Spec.Selector.MatchLabels, false)
+ Expect(commonService.Annotations).To(Equal(testCommonServiceAnnotations), "Incorrect common service annotations")
+ Expect(commonService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(commonService.Spec.Ports[0].Port).To(Equal(int32(5000)), "Wrong port on common Service")
+ Expect(commonService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on common Service")
+
+ By("testing the Solr Headless Service")
+ headlessService := expectService(ctx, solrCloud, solrCloud.HeadlessServiceName(), statefulSet.Spec.Selector.MatchLabels, true)
+ Expect(headlessService.Annotations).To(Equal(testHeadlessServiceAnnotations), "Incorrect headless service annotations")
+ Expect(headlessService.Spec.Ports[0].Name).To(Equal("solr-client"), "Wrong port name on common Service")
+ Expect(headlessService.Spec.Ports[0].Port).To(Equal(int32(2000)), "Wrong port on headless Service")
+ Expect(headlessService.Spec.Ports[0].TargetPort.StrVal).To(Equal("solr-client"), "Wrong podPort name on headless Service")
+
+ By("making sure no individual Solr Node Services exist")
+ expectNoServices(ctx, solrCloud, "Node service shouldn't exist, but it does.", solrCloud.GetAllSolrNodeNames())
+
+ By("making sure no Ingress was created")
+ expectNoIngress(ctx, solrCloud, solrCloud.CommonIngressName())
+
+ By("making sure the node addresses in the Status are correct")
+ expectSolrCloudStatusWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloudStatus) {
+ g.Expect(found.InternalCommonAddress).To(Equal("http://"+solrCloud.CommonServiceName()+"."+solrCloud.Namespace+".svc."+testKubeDomain+":5000"), "Wrong internal common address in status")
+ g.Expect(found.ExternalCommonAddress).To(BeNil(), "External common address in status should be nil")
+ })
+ })
+ })
+
+ FContext("Solr Cloud with a custom Solr XML ConfigMap", func() {
+ testCustomSolrXmlConfigMap := "my-custom-solr-xml"
+ BeforeEach(func() {
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
},
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the statefulSet
- statefulSet := expectStatefulSet(t, g, requests, expectedCloudRequest, cloudSsKey)
-
- assert.Equal(t, 1, len(statefulSet.Spec.Template.Spec.Containers), "Solr StatefulSet requires a container.")
-
- // Env Variable Tests
- expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_HOSTNAME)." + cloudHsKey.Name + "." + instance.Namespace + ".svc." + testKubeDomain,
- "SOLR_PORT": "2000",
- "SOLR_NODE_PORT": "2000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
- }
- testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
- assert.Nil(t, statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PostStart, "Post-start command should be nil since there is no chRoot to ensure exists.")
-
- // Check the client Service
- service := expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "common service annotations", testCommonServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 5000, service.Spec.Ports[0].Port, "Wrong port on common Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on common Service")
-
- // Check the headless Service
- service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
- testMapsEqual(t, "headless service annotations", testHeadlessServiceAnnotations, service.Annotations)
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].Name, "Wrong port name on common Service")
- assert.EqualValues(t, 2000, service.Spec.Ports[0].Port, "Wrong port on headless Service")
- assert.EqualValues(t, "solr-client", service.Spec.Ports[0].TargetPort.StrVal, "Wrong podPort name on headless Service")
-
- // Make sure individual Node services don't exist
- nodeNames := instance.GetAllSolrNodeNames()
- for _, nodeName := range nodeNames {
- nodeSKey := types.NamespacedName{Name: nodeName, Namespace: "default"}
- expectNoService(g, nodeSKey, "Node service shouldn't exist, but it does.")
- }
-
- // Check the ingress
- expectNoIngress(g, cloudIKey)
-
- // Check that the Addresses in the status are correct
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- assert.Equal(t, "http://"+cloudCsKey.Name+"."+instance.Namespace+".svc."+testKubeDomain+":5000", instance.Status.InternalCommonAddress, "Wrong internal common address in status")
- assert.Nil(t, instance.Status.ExternalCommonAddress, "External common address in status should be nil")
-}
-
-func TestCloudWithCustomSolrXmlConfigMapReconcile(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
-
- testCustomSolrXmlConfigMap := "my-custom-solr-xml"
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- ConfigMapOptions: &solr.ConfigMapOptions{
- ProvidedConfigMap: testCustomSolrXmlConfigMap,
+ CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
+ ConfigMapOptions: &solrv1beta1.ConfigMapOptions{
+ ProvidedConfigMap: testCustomSolrXmlConfigMap,
+ },
},
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
-
- // reconcile will have failed b/c the provided ConfigMap doesn't exist ...
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // start with an invalid provided ConfigMap
- invalidConfigMap := &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testCustomSolrXmlConfigMap,
- Namespace: instance.Namespace,
- },
- Data: map[string]string{
- "foo": "bar",
- },
- }
- err = testClient.Create(context.TODO(), invalidConfigMap)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- invalidConfigMap.Data[util.SolrXmlFile] = "Invalid XML"
- err = testClient.Update(context.TODO(), invalidConfigMap)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // update with a valid custom config map so reconcile can proceed
- providedConfigMap := util.GenerateConfigMap(instance)
- providedConfigMap.Name = testCustomSolrXmlConfigMap
- err = testClient.Update(context.TODO(), providedConfigMap)
-
- defer testClient.Delete(context.TODO(), providedConfigMap)
-
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- emptyRequests(requests)
-
- stateful := &appsv1.StatefulSet{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), cloudSsKey, stateful) }, timeout).Should(gomega.Succeed())
-
- assert.NotEmpty(t, stateful.Spec.Template.Annotations[util.SolrXmlMd5Annotation], "Custom solr.xml MD5 annotation should be set on the pod template!")
-
- assert.NotNil(t, stateful.Spec.Template.Spec.Volumes)
- var solrXmlVol *corev1.Volume = nil
- for _, vol := range stateful.Spec.Template.Spec.Volumes {
- if vol.Name == "solr-xml" {
- solrXmlVol = &vol
- break
- }
- }
- assert.NotNil(t, solrXmlVol, "Didn't find custom solr.xml volume in sts config!")
- assert.NotNil(t, solrXmlVol.VolumeSource.ConfigMap, "solr-xml Volume should have a ConfigMap source")
- assert.Equal(t, solrXmlVol.VolumeSource.ConfigMap.Name, testCustomSolrXmlConfigMap, "solr-xml Volume should have a ConfigMap source")
-
- notExistName := fmt.Sprintf("%s-solrcloud-configmap", instance.GetName())
- foundConfigMap := &corev1.ConfigMap{}
- err = testClient.Get(context.TODO(), types.NamespacedName{Name: notExistName, Namespace: instance.Namespace}, foundConfigMap)
- g.Expect(err).To(gomega.HaveOccurred(), "Built-in solr.xml ConfigMap should not exist! "+notExistName)
-
- emptyRequests(requests)
- // update the embedded solr.xml to trigger a pod rolling restart
- foundConfigMap = &corev1.ConfigMap{}
- err = testClient.Get(context.TODO(), types.NamespacedName{Name: testCustomSolrXmlConfigMap, Namespace: instance.Namespace}, foundConfigMap)
- updateSolrXml := foundConfigMap.Data[util.SolrXmlFile]
- foundConfigMap.Data[util.SolrXmlFile] = strings.Replace(updateSolrXml, "${zkClientTimeout:30000}", "${zkClientTimeout:15000}", 1)
- err = testClient.Update(context.TODO(), foundConfigMap)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the annotation on the pod template to make sure a rolling restart will take place
- updateSolrXml = foundConfigMap.Data[util.SolrXmlFile]
- updateSolrXmlMd5 := fmt.Sprintf("%x", md5.Sum([]byte(updateSolrXml)))
- time.Sleep(time.Millisecond * 250)
- err = testClient.Get(context.TODO(), cloudSsKey, stateful)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- assert.Equal(t, updateSolrXmlMd5, stateful.Spec.Template.Annotations[util.SolrXmlMd5Annotation], "Custom solr.xml MD5 annotation should be updated on the pod template.")
-}
-
-func TestCloudWithUserProvidedLogConfigMapReconcile(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
-
- testCustomLogXmlConfigMap := "my-custom-log4j2-xml"
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- ConfigMapOptions: &solr.ConfigMapOptions{
- ProvidedConfigMap: testCustomLogXmlConfigMap,
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("ensuring no statefulSet exists when the configMap doesn't exist")
+ expectNoStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ By("ensuring no statefulSet exists when the configMap is invalid")
+ invalidConfigMap := &corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: testCustomSolrXmlConfigMap,
+ Namespace: solrCloud.Namespace,
},
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
-
- // reconcile will have failed b/c the provided ConfigMap doesn't exist ...
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- userProvidedConfigMap := &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testCustomLogXmlConfigMap,
- Namespace: instance.Namespace,
- },
- Data: map[string]string{
- util.LogXmlFile: "<Configuration/>",
- },
- }
- err = testClient.Create(context.TODO(), userProvidedConfigMap)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), userProvidedConfigMap)
-
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- emptyRequests(requests)
-
- stateful := &appsv1.StatefulSet{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), cloudSsKey, stateful) }, timeout).Should(gomega.Succeed())
- assert.NotEmpty(t, stateful.Spec.Template.Annotations[util.LogXmlMd5Annotation], "Custom log XML MD5 annotation should be set on the pod template!")
-
- assert.NotNil(t, stateful.Spec.Template.Spec.Volumes)
- var logXmlVol *corev1.Volume = nil
- for _, vol := range stateful.Spec.Template.Spec.Volumes {
- if vol.Name == "log4j2-xml" {
- logXmlVol = &vol
- break
- }
- }
- assert.NotNil(t, logXmlVol, "Didn't find custom log4j2-xml volume in sts config!")
- assert.NotNil(t, logXmlVol.VolumeSource.ConfigMap, "log4j2-xml Volume should have a ConfigMap source")
- assert.Equal(t, logXmlVol.VolumeSource.ConfigMap.Name, testCustomLogXmlConfigMap, "log4j2-xml Volume should have a ConfigMap source")
-
- var logXmlVolMount *corev1.VolumeMount = nil
- for _, mount := range stateful.Spec.Template.Spec.Containers[0].VolumeMounts {
- if mount.Name == "log4j2-xml" {
- logXmlVolMount = &mount
- break
- }
- }
- assert.NotNil(t, logXmlVolMount, "Didn't find the log4j2-xml Volume mount")
- expectedMountPath := fmt.Sprintf("/var/solr/%s", testCustomLogXmlConfigMap)
- assert.Equal(t, expectedMountPath, logXmlVolMount.MountPath, "Custom log4j2.xml mount path is incorrect.")
-
- solrXmlConfigName := fmt.Sprintf("%s-solrcloud-configmap", instance.GetName())
- foundConfigMap := &corev1.ConfigMap{}
- err = testClient.Get(context.TODO(), types.NamespacedName{Name: solrXmlConfigName, Namespace: instance.Namespace}, foundConfigMap)
- g.Expect(err).NotTo(gomega.HaveOccurred(), "Built-in solr.xml ConfigMap should still exist! ")
-
- emptyRequests(requests)
- // update the user-provided log XML to trigger a pod rolling restart
- updatedXml := "<Configuration>Updated!</Configuration>"
- foundConfigMap = &corev1.ConfigMap{}
- err = testClient.Get(context.TODO(), types.NamespacedName{Name: testCustomLogXmlConfigMap, Namespace: instance.Namespace}, foundConfigMap)
- foundConfigMap.Data[util.LogXmlFile] = updatedXml
- err = testClient.Update(context.TODO(), foundConfigMap)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- // capture all reconcile requests
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- // Check the annotation on the pod template to make sure a rolling restart will take place
- updateLogXmlMd5 := fmt.Sprintf("%x", md5.Sum([]byte(updatedXml)))
- time.Sleep(time.Millisecond * 250)
- err = testClient.Get(context.TODO(), cloudSsKey, stateful)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- assert.Equal(t, updateLogXmlMd5, stateful.Spec.Template.Annotations[util.LogXmlMd5Annotation], "Custom log XML MD5 annotation should be updated on the pod template.")
- expectedEnvVars := map[string]string{"LOG4J_PROPS": fmt.Sprintf("%s/%s", expectedMountPath, util.LogXmlFile)}
- testPodEnvVariables(t, expectedEnvVars, stateful.Spec.Template.Spec.Containers[0].Env)
-
- // switch the log4j config over to using monitorInterval, so no more rolling restart on change to config
- emptyRequests(requests)
- // update the user-provided log XML to trigger a pod rolling restart
- updatedXml = "<Configuration monitorInterval=\"30\">Updated!</Configuration>"
- foundConfigMap = &corev1.ConfigMap{}
- err = testClient.Get(context.TODO(), types.NamespacedName{Name: testCustomLogXmlConfigMap, Namespace: instance.Namespace}, foundConfigMap)
- foundConfigMap.Data[util.LogXmlFile] = updatedXml
- err = testClient.Update(context.TODO(), foundConfigMap)
- g.Expect(err).NotTo(gomega.HaveOccurred())
-
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
-
- time.Sleep(time.Millisecond * 250)
- err = testClient.Get(context.TODO(), cloudSsKey, stateful)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- // log xml md5 annotation should no longer be on the pod template
- assert.Equal(t, "", stateful.Spec.Template.Annotations[util.LogXmlMd5Annotation], "Custom log XML MD5 annotation should not be set on the pod template.")
-}
-
-func TestCloudWithUserProvidedSolrXmlAndLogConfigReconcile(t *testing.T) {
- UseZkCRD(true)
- g := gomega.NewGomegaWithT(t)
-
- testCustomConfigMap := "my-custom-config-xml"
-
- instance := &solr.SolrCloud{
- ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
- Spec: solr.SolrCloudSpec{
- CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
- ConfigMapOptions: &solr.ConfigMapOptions{
- ProvidedConfigMap: testCustomConfigMap,
+ Data: map[string]string{
+ "foo": "bar",
},
- },
- },
- }
-
- // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
- // channel when it is finished.
- mgr, err := manager.New(testCfg, manager.Options{})
- g.Expect(err).NotTo(gomega.HaveOccurred())
- testClient = mgr.GetClient()
-
- solrCloudReconciler := &SolrCloudReconciler{
- Client: testClient,
- Log: ctrl.Log.WithName("controllers").WithName("SolrCloud"),
- }
- newRec, requests := SetupTestReconcile(solrCloudReconciler)
- g.Expect(solrCloudReconciler.SetupWithManagerAndReconciler(mgr, newRec)).NotTo(gomega.HaveOccurred())
-
- stopMgr, mgrStopped := StartTestManager(mgr, g)
-
- defer func() {
- close(stopMgr)
- mgrStopped.Wait()
- }()
-
- cleanupTest(g, instance.Namespace)
-
- // Create the user-provided ConfigMap first to streamline reconcile,
- // other tests cover creating it after the reconcile loop starts on the SolrCloud
- userProvidedConfigMap := &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testCustomConfigMap,
- Namespace: instance.Namespace,
- },
- Data: map[string]string{
- util.LogXmlFile: "<Configuration/>",
- util.SolrXmlFile: "<solr> ${hostPort: </solr>", // the controller checks for ${hostPort: in the solr.xml
- },
- }
- err = testClient.Create(context.TODO(), userProvidedConfigMap)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), userProvidedConfigMap)
-
- // Create the SolrCloud object and expect the Reconcile and StatefulSet to be created
- err = testClient.Create(context.TODO(), instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- defer testClient.Delete(context.TODO(), instance)
-
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedCloudRequest)))
- g.Eventually(func() error { return testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance) }, timeout).Should(gomega.Succeed())
- emptyRequests(requests)
-
- stateful := &appsv1.StatefulSet{}
- g.Eventually(func() error { return testClient.Get(context.TODO(), cloudSsKey, stateful) }, timeout).Should(gomega.Succeed())
- assert.NotEmpty(t, stateful.Spec.Template.Annotations[util.LogXmlMd5Annotation], "Custom log XML MD5 annotation should be set on the pod template!")
-
- assert.NotNil(t, stateful.Spec.Template.Spec.Volumes)
- var customConfigVol *corev1.Volume = nil
- for _, vol := range stateful.Spec.Template.Spec.Volumes {
- if vol.Name == "solr-xml" {
- customConfigVol = &vol
- break
- }
- }
- assert.NotNil(t, customConfigVol, "Didn't find custom solr-xml volume in sts config!")
- assert.NotNil(t, customConfigVol.VolumeSource.ConfigMap, "solr-xml Volume should have a ConfigMap source")
- assert.Equal(t, customConfigVol.VolumeSource.ConfigMap.Name, testCustomConfigMap, "solr-xml Volume should have a ConfigMap source")
-
- var logXmlVolMount *corev1.VolumeMount = nil
- for _, mount := range stateful.Spec.Template.Spec.Containers[0].VolumeMounts {
- if mount.Name == "solr-xml" {
- logXmlVolMount = &mount
- break
- }
- }
- assert.NotNil(t, logXmlVolMount, "Didn't find the log4j2-xml Volume mount")
- expectedMountPath := fmt.Sprintf("/var/solr/%s", testCustomConfigMap)
- assert.Equal(t, expectedMountPath, logXmlVolMount.MountPath)
-
- solrXmlConfigName := fmt.Sprintf("%s-solrcloud-configmap", instance.GetName())
- foundConfigMap := &corev1.ConfigMap{}
- err = testClient.Get(context.TODO(), types.NamespacedName{Name: solrXmlConfigName, Namespace: instance.Namespace}, foundConfigMap)
- g.Expect(err).To(gomega.HaveOccurred(), "Built-in solr.xml ConfigMap should NOT exist! ")
-
- emptyRequests(requests)
-
- customSolrXml := userProvidedConfigMap.Data[util.SolrXmlFile]
- expectedSolrXmlMd5 := fmt.Sprintf("%x", md5.Sum([]byte(customSolrXml)))
- assert.Equal(t, expectedSolrXmlMd5, stateful.Spec.Template.Annotations[util.SolrXmlMd5Annotation], "Custom solr.xml MD5 annotation should be set on the pod template.")
-
- customLogXml := userProvidedConfigMap.Data[util.LogXmlFile]
- expectedLogXmlMd5 := fmt.Sprintf("%x", md5.Sum([]byte(customLogXml)))
- assert.Equal(t, expectedLogXmlMd5, stateful.Spec.Template.Annotations[util.LogXmlMd5Annotation], "Custom log4j2.xml MD5 annotation should be set on the pod template.")
- expectedEnvVars := map[string]string{"LOG4J_PROPS": fmt.Sprintf("%s/%s", expectedMountPath, util.LogXmlFile)}
- testPodEnvVariables(t, expectedEnvVars, stateful.Spec.Template.Spec.Containers[0].Env)
-}
+ }
+ Expect(k8sClient.Create(ctx, invalidConfigMap)).To(Succeed(), "Create the invalid configMap")
+ expectNoStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ invalidConfigMap.Data[util.SolrXmlFile] = "Invalid XML"
+ Expect(k8sClient.Update(ctx, invalidConfigMap)).To(Succeed(), "Update the invalid configMap")
+ expectNoStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ By("checking that a configured statefulSet exists when the configMap is valid")
+ validConfigMap := util.GenerateConfigMap(solrCloud)
+ validConfigMap.Name = testCustomSolrXmlConfigMap
+ Expect(k8sClient.Update(ctx, validConfigMap)).To(Succeed(), "Make the test configMap valid")
+
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+
+ Expect(statefulSet.Spec.Template.Annotations).To(HaveKey(util.SolrXmlMd5Annotation), "Custom solr.xml MD5 annotation should be set on the pod template!")
+ Expect(statefulSet.Spec.Template.Annotations).To(Not(HaveKey(util.LogXmlFile)), "Custom log4j2.xml MD5 annotation should not be set on the pod template.")
+
+ Expect(statefulSet.Spec.Template.Annotations).To(Not(BeNil()), "The Solr pod volumes should not be empty, because they must contain a solr-xml entry")
+ var solrXmlVol *corev1.Volume = nil
+ for _, vol := range statefulSet.Spec.Template.Spec.Volumes {
+ if vol.Name == "solr-xml" {
+ solrXmlVol = &vol
+ break
+ }
+ }
+ Expect(solrXmlVol).To(Not(BeNil()), "Didn't find custom solr.xml volume in sts config!")
+ Expect(solrXmlVol.VolumeSource.ConfigMap).To(Not(BeNil()), "solr-xml Volume should have a ConfigMap source")
+ Expect(solrXmlVol.VolumeSource.ConfigMap.Name).To(Equal(testCustomSolrXmlConfigMap), "solr-xml has the wrong configMap as its source")
+
+ expectNoConfigMap(ctx, solrCloud, fmt.Sprintf("%s-solrcloud-configmap", solrCloud.GetName()))
+
+ By("making sure a rolling restart happens when the solr.xml is changed")
+ validConfigMap.Data[util.SolrXmlFile] = strings.Replace(validConfigMap.Data[util.SolrXmlFile], "${zkClientTimeout:30000}", "${zkClientTimeout:15000}", 1)
+ Expect(k8sClient.Update(ctx, validConfigMap)).To(Succeed(), "Change the valid test configMap")
+
+ updateSolrXmlMd5 := fmt.Sprintf("%x", md5.Sum([]byte(validConfigMap.Data[util.SolrXmlFile])))
+ statefulSet = expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Annotations).To(HaveKeyWithValue(util.SolrXmlMd5Annotation, updateSolrXmlMd5), "Custom solr.xml MD5 annotation should be updated on the pod template.")
+ })
+ })
+ })
+
+ FContext("Solr Cloud with a custom Solr Log4J ConfigMap", func() {
+ testCustomConfigMap := "my-custom-config-xml"
+ BeforeEach(func() {
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
+ },
+ CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
+ ConfigMapOptions: &solrv1beta1.ConfigMapOptions{
+ ProvidedConfigMap: testCustomConfigMap,
+ },
+ },
+ }
+ })
+ FIt("has the correct resources", func() {
+ configMap := &corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: testCustomConfigMap,
+ Namespace: solrCloud.Namespace,
+ },
+ Data: map[string]string{
+ util.LogXmlFile: "<Configuration/>",
+ },
+ }
+ Expect(k8sClient.Create(ctx, configMap)).To(Succeed(), "Create the valid configMap")
+
+ expectedMountPath := fmt.Sprintf("/var/solr/%s", testCustomConfigMap)
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Annotations).To(HaveKey(util.LogXmlMd5Annotation), "Custom log XML MD5 annotation should be set on the pod template!")
+
+ g.Expect(found.Spec.Template.Spec.Volumes).To(Not(BeNil()), "Volumes are required for the Solr Pod")
+ var customConfigVol *corev1.Volume = nil
+ for _, vol := range found.Spec.Template.Spec.Volumes {
+ if vol.Name == "log4j2-xml" {
+ customConfigVol = &vol
+ break
+ }
+ }
+ g.Expect(customConfigVol).To(Not(BeNil()), "Didn't find custom solr-xml volume in pod template!")
+ g.Expect(customConfigVol.VolumeSource.ConfigMap, "solr-xml Volume should have a ConfigMap source")
+ g.Expect(customConfigVol.VolumeSource.ConfigMap.Name).To(Equal(testCustomConfigMap), "solr-xml has the wrong configMap as its source")
+
+ var logXmlVolMount *corev1.VolumeMount = nil
+ for _, mount := range found.Spec.Template.Spec.Containers[0].VolumeMounts {
+ if mount.Name == "log4j2-xml" {
+ logXmlVolMount = &mount
+ break
+ }
+ }
+ g.Expect(logXmlVolMount).To(Not(BeNil()), "Didn't find the log4j2-xml Volume mount")
+ g.Expect(logXmlVolMount.MountPath).To(Equal(expectedMountPath), "log4j2-xml Volume mount has the wrong path")
+
+ g.Expect(found.Spec.Template.Annotations).To(HaveKeyWithValue(util.SolrXmlMd5Annotation, fmt.Sprintf("%x", md5.Sum([]byte(util.GenerateSolrXMLString(""))))), "Custom solr.xml MD5 annotation should be set on the pod template.")
+
+ g.Expect(found.Spec.Template.Annotations).To(HaveKeyWithValue(util.LogXmlMd5Annotation, fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data[util.LogXmlFile])))), "Custom log4j2.xml MD5 annotation should be set on the pod template.")
+ expectedEnvVars := map[string]string{"LOG4J_PROPS": fmt.Sprintf("%s/%s", expectedMountPath, util.LogXmlFile)}
+ testPodEnvVariablesWithGomega(g, expectedEnvVars, found.Spec.Template.Spec.Containers[0].Env)
+ })
+
+ expectConfigMap(ctx, solrCloud, fmt.Sprintf("%s-solrcloud-configmap", solrCloud.GetName()), map[string]string{util.SolrXmlFile: util.GenerateSolrXMLString("")})
+
+ By("updating the user-provided log XML to trigger a pod rolling restart")
+ configMap.Data[util.LogXmlFile] = "<Configuration>Updated!</Configuration>"
+ Expect(k8sClient.Update(ctx, configMap)).To(Succeed(), "Change the test log4j configMap")
+
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Annotations).To(HaveKeyWithValue(util.LogXmlMd5Annotation, fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data[util.LogXmlFile])))), "Custom log4j2.xml MD5 annotation should be set on the pod template.")
+
+ expectedEnvVars := map[string]string{"LOG4J_PROPS": fmt.Sprintf("%s/%s", expectedMountPath, util.LogXmlFile)}
+ testPodEnvVariablesWithGomega(g, expectedEnvVars, found.Spec.Template.Spec.Containers[0].Env)
+ })
+
+ By("updating the user-provided log XML again to trigger another pod rolling restart")
+ configMap.Data[util.LogXmlFile] = "<Configuration monitorInterval=\\\"30\\\">Updated!</Configuration>"
+ Expect(k8sClient.Update(ctx, configMap)).To(Succeed(), "Change the test log4j configMap")
+
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Annotations).To(Not(HaveKey(util.LogXmlMd5Annotation)), "Custom log XML MD5 annotation should not be set on the pod template, when monitorInterval is used")
+
+ expectedEnvVars := map[string]string{"LOG4J_PROPS": fmt.Sprintf("%s/%s", expectedMountPath, util.LogXmlFile)}
+ testPodEnvVariablesWithGomega(g, expectedEnvVars, found.Spec.Template.Spec.Containers[0].Env)
+ })
+ })
+ })
+
+ FContext("Solr Cloud with a custom Solr Log4J and solr.xml ConfigMap", func() {
+ testCustomConfigMap := "my-custom-config-xml"
+ BeforeEach(func() {
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
+ },
+ CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
+ ConfigMapOptions: &solrv1beta1.ConfigMapOptions{
+ ProvidedConfigMap: testCustomConfigMap,
+ },
+ },
+ }
+ })
+ FIt("has the correct resources", func() {
+ configMap := &corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: testCustomConfigMap,
+ Namespace: solrCloud.Namespace,
+ },
+ Data: map[string]string{
+ util.LogXmlFile: "<Configuration/>",
+ util.SolrXmlFile: "<solr> ${hostPort:} </solr>", // the controller checks for ${hostPort: in the solr.xml
+ },
+ }
+ Expect(k8sClient.Create(ctx, configMap)).To(Succeed(), "Create the valid configMap")
+
+ expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
+ g.Expect(found.Spec.Template.Annotations).To(HaveKey(util.LogXmlMd5Annotation), "Custom log XML MD5 annotation should be set on the pod template!")
+
+ g.Expect(found.Spec.Template.Spec.Volumes).To(Not(BeNil()), "Volumes are required for the Solr Pod")
+ var customConfigVol *corev1.Volume = nil
+ for _, vol := range found.Spec.Template.Spec.Volumes {
+ if vol.Name == "solr-xml" {
+ customConfigVol = &vol
+ break
+ }
+ }
+ g.Expect(customConfigVol).To(Not(BeNil()), "Didn't find custom solr-xml volume in pod template!")
+ g.Expect(customConfigVol.VolumeSource.ConfigMap, "solr-xml Volume should have a ConfigMap source")
+ g.Expect(customConfigVol.VolumeSource.ConfigMap.Name).To(Equal(testCustomConfigMap), "solr-xml has the wrong configMap as its source")
+
+ var logXmlVolMount *corev1.VolumeMount = nil
+ for _, mount := range found.Spec.Template.Spec.Containers[0].VolumeMounts {
+ if mount.Name == "solr-xml" {
+ logXmlVolMount = &mount
+ break
+ }
+ }
+ g.Expect(logXmlVolMount).To(Not(BeNil()), "Didn't find the log4j2-xml Volume mount")
+ expectedMountPath := fmt.Sprintf("/var/solr/%s", testCustomConfigMap)
+ g.Expect(logXmlVolMount.MountPath).To(Equal(expectedMountPath), "log4j2-xml Volume mount has the wrong path")
+
+ g.Expect(found.Spec.Template.Annotations).To(HaveKeyWithValue(util.SolrXmlMd5Annotation, fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data[util.SolrXmlFile])))), "Custom solr.xml MD5 annotation should be set on the pod template.")
+
+ g.Expect(found.Spec.Template.Annotations).To(HaveKeyWithValue(util.LogXmlMd5Annotation, fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data[util.LogXmlFile])))), "Custom log4j2.xml MD5 annotation should be set on the pod template.")
+ expectedEnvVars := map[string]string{"LOG4J_PROPS": fmt.Sprintf("%s/%s", expectedMountPath, util.LogXmlFile)}
+ testPodEnvVariablesWithGomega(g, expectedEnvVars, found.Spec.Template.Spec.Containers[0].Env)
+ })
+
+ expectNoConfigMap(ctx, solrCloud, fmt.Sprintf("%s-solrcloud-configmap", solrCloud.GetName()))
+ })
+ })
+})
diff --git a/controllers/solrcloud_controller_tls_test.go b/controllers/solrcloud_controller_tls_test.go
index 8f9e214..5c2b2bd 100644
--- a/controllers/solrcloud_controller_tls_test.go
+++ b/controllers/solrcloud_controller_tls_test.go
@@ -18,874 +18,383 @@
package controllers
import (
+ "context"
"crypto/md5"
b64 "encoding/base64"
"fmt"
- solr "github.com/apache/solr-operator/api/v1beta1"
+ solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
"github.com/apache/solr-operator/controllers/util"
- "github.com/onsi/gomega"
- "github.com/stretchr/testify/assert"
- "golang.org/x/net/context"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
- netv1 "k8s.io/api/networking/v1beta1"
- "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
- ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/manager"
- "sigs.k8s.io/controller-runtime/pkg/reconcile"
"strings"
- "sync"
- "testing"
- "time"
)
-var _ reconcile.Reconciler = &SolrCloudReconciler{}
+var _ = FDescribe("SolrCloud controller - TLS", func() {
+ var (
+ ctx context.Context
-var (
- expectedCloudWithTLSRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: "foo-tls", Namespace: "default"}}
- expectedIngressWithTLS = types.NamespacedName{Name: "foo-tls-solrcloud-common", Namespace: "default"}
- expectedStatefulSetName = types.NamespacedName{Name: "foo-tls-solrcloud", Namespace: "default"}
-)
+ solrCloud *solrv1beta1.SolrCloud
+ )
-func TestBasicAuthBootstrapSecurityJson(t *testing.T) {
- instance := buildTestSolrCloud()
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic, ProbesRequireAuth: true}
- verifyReconcileWithSecurity(t, instance, false, false)
-}
+ BeforeEach(func() {
+ ctx = context.Background()
-func TestBasicAuthBootstrapSecurityJsonWithZkACLs(t *testing.T) {
- instance := buildTestSolrCloud()
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic, ProbesRequireAuth: true}
- zkReplicas := int32(1)
- instance.Spec.ZookeeperRef = &solr.ZookeeperRef{
- ProvidedZookeeper: &solr.ZookeeperSpec{
- Replicas: &zkReplicas,
- AllACL: &solr.ZookeeperACL{
- SecretRef: "secret-name",
- UsernameKey: "user",
- PasswordKey: "pass",
+ replicas := int32(1)
+ solrCloud = &solrv1beta1.SolrCloud{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ Namespace: "default",
},
- ReadOnlyACL: &solr.ZookeeperACL{
- SecretRef: "read-secret-name",
- UsernameKey: "read-only-user",
- PasswordKey: "read-only-pass",
- },
- },
- }
- verifyReconcileWithSecurity(t, instance, false, true)
-}
-
-func TestBasicAuthBootstrapSecurityJsonDeleteSecret(t *testing.T) {
- instance := buildTestSolrCloud()
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic}
- verifyReconcileWithSecurity(t, instance, true, false)
-}
-
-func TestBasicAuthWithUserProvidedCreds(t *testing.T) {
- instance := buildTestSolrCloud()
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic, BasicAuthSecret: "my-basic-auth-secret"}
- verifyReconcileWithSecurity(t, instance, false, false)
-}
-
-func TestBasicAuthBootstrapWithTLS(t *testing.T) {
- instance := buildTestSolrCloud()
-
- // custom probe endpoint too
- instance.Spec.CustomSolrKubeOptions.PodOptions = &solr.PodOptions{
- ReadinessProbe: &corev1.Probe{
- Handler: corev1.Handler{
- HTTPGet: &corev1.HTTPGetAction{
- Scheme: corev1.URISchemeHTTPS,
- Path: "/solr/admin/info/health",
- Port: intstr.FromInt(8983),
+ Spec: solrv1beta1.SolrCloudSpec{
+ Replicas: &replicas,
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
+ },
+ SolrAddressability: solrv1beta1.SolrAddressabilityOptions{
+ External: &solrv1beta1.ExternalAddressability{
+ Method: solrv1beta1.Ingress,
+ UseExternalAddress: true,
+ DomainName: testDomain,
+ },
},
},
- },
- }
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic}
-
- probePaths := util.GetCustomProbePaths(instance)
- assert.EqualValues(t, []string{"/solr/admin/info/health"}, probePaths)
-
- tlsSecretName := "tls-cert-secret-from-user"
- keystorePassKey := "some-password-key-thingy"
- instance.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, false)
- verifyUserSuppliedTLSConfig(t, instance.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName)
- verifyReconcileUserSuppliedTLS(t, instance, false, false)
-}
-
-func mountedTLSDir(path string) *solr.MountedTLSDirectory {
- return &solr.MountedTLSDirectory{
- Path: path,
- KeystoreFile: util.DefaultPkcs12KeystoreFile,
- TruststoreFile: util.DefaultPkcs12TruststoreFile,
- KeystorePasswordFile: util.DefaultKeystorePasswordFile,
- }
-}
-
-func TestMountedTLSDir(t *testing.T) {
- instance := buildTestSolrCloud()
- mountedDir := mountedTLSDir("/mounted-tls-dir")
- instance.Spec.SolrTLS = &solr.SolrTLSOptions{MountedTLSDir: mountedDir, CheckPeerName: true, ClientAuth: "Need", VerifyClientHostname: true}
- verifyReconcileMountedTLSDir(t, instance)
-}
-
-func TestMountedTLSDirNonDefaultFileNames(t *testing.T) {
- instance := buildTestSolrCloud()
- mountedDir := &solr.MountedTLSDirectory{
- Path: "/mounted-non-default",
- KeystoreFile: "ks.p12",
- TruststoreFile: "ts.p12",
- KeystorePasswordFile: "ks-password",
- TruststorePasswordFile: "ts-password",
- }
- instance.Spec.SolrTLS = &solr.SolrTLSOptions{MountedTLSDir: mountedDir, CheckPeerName: true, ClientAuth: "Need", VerifyClientHostname: true}
- verifyReconcileMountedTLSDir(t, instance)
-}
-
-func TestMountedTLSDirWithBasicAuth(t *testing.T) {
- instance := buildTestSolrCloud()
- mountedDir := mountedTLSDir("/mounted-tls-dir")
- instance.Spec.SolrTLS = &solr.SolrTLSOptions{MountedTLSDir: mountedDir, CheckPeerName: true, ClientAuth: "Need", VerifyClientHostname: true}
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic} // with basic-auth too
- verifyReconcileMountedTLSDir(t, instance)
-}
-
-func TestMountedTLSServerAndClientDirs(t *testing.T) {
- instance := buildTestSolrCloud()
- mountedSrvrTLSDir := mountedTLSDir("/mounted-tls-dir")
- mountedClientTLSDir := mountedTLSDir("/mounted-client-tls-dir")
- instance.Spec.SolrTLS = &solr.SolrTLSOptions{MountedTLSDir: mountedSrvrTLSDir, CheckPeerName: true, ClientAuth: "Need", VerifyClientHostname: true}
- instance.Spec.SolrClientTLS = &solr.SolrTLSOptions{MountedTLSDir: mountedClientTLSDir, CheckPeerName: true}
- verifyReconcileMountedTLSDir(t, instance)
-}
-
-// For TLS, all we really need is a secret holding the keystore password and a secret holding the pkcs12 keystore,
-// which can come from anywhere really, so this method tests handling of user-supplied secrets
-func TestUserSuppliedTLSSecretWithPkcs12Keystore(t *testing.T) {
- tlsSecretName := "tls-cert-secret-from-user"
- keystorePassKey := "some-password-key-thingy"
- instance := buildTestSolrCloud()
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic} // with basic-auth too
- instance.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, false)
- verifyUserSuppliedTLSConfig(t, instance.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName)
- verifyReconcileUserSuppliedTLS(t, instance, false, false)
-}
-
-// User wants a different trust store than the keystore
-func TestUserSuppliedTLSSecretWithSeparateTrustStore(t *testing.T) {
- tlsSecretName := "tls-cert-secret-from-user"
- keystorePassKey := "some-password-key-thingy"
- instance := buildTestSolrCloud()
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic} // with basic-auth too
- instance.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, false)
-
- trustStoreSecretName := "custom-truststore-secret"
- trustStoreFile := "truststore.p12"
- instance.Spec.SolrTLS.TrustStoreSecret = &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: trustStoreSecretName},
- Key: trustStoreFile,
- }
-
- instance.Spec.SolrTLS.TrustStorePasswordSecret = &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: trustStoreSecretName},
- Key: "truststore-pass",
- }
-
- instance.Spec.SolrTLS.ClientAuth = solr.Need // require client auth too (mTLS between the pods)
-
- verifyUserSuppliedTLSConfig(t, instance.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName)
- verifyReconcileUserSuppliedTLS(t, instance, false, false)
-}
-
-// Test upgrade from non-TLS cluster to TLS enabled cluster
-func TestEnableTLSOnExistingCluster(t *testing.T) {
- instance := buildTestSolrCloud()
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic}
-
- changed := instance.WithDefaults()
- assert.True(t, changed, "WithDefaults should have changed the test SolrCloud instance")
-
- g := gomega.NewGomegaWithT(t)
- helper := NewTLSTestHelper(g)
- defer func() {
- helper.StopTest()
- }()
-
- ctx := context.TODO()
- cleanupTest(g, instance.Namespace)
- helper.ReconcileSolrCloud(ctx, instance, 2, util.DefaultPkcs12KeystoreFile)
- defer testClient.Delete(ctx, instance)
-
- // now, update the config to enable TLS
- tlsSecretName := "tls-cert-secret-from-user"
- keystorePassKey := "some-password-key-thingy"
-
- err := testClient.Get(ctx, types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
- instance.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, true)
- foundTLSSecret := &corev1.Secret{}
- lookupErr := testClient.Get(ctx, types.NamespacedName{Name: instance.Spec.SolrTLS.PKCS12Secret.Name, Namespace: instance.Namespace}, foundTLSSecret)
- // TLS secret should not exist
- assert.True(t, errors.IsNotFound(lookupErr))
-
- // apply the update to trigger the upgrade to https
- err = testClient.Update(ctx, instance)
- g.Expect(err).NotTo(gomega.HaveOccurred())
-
- // Create a mock secret in the background so the isCert ready function returns
- var mockTLSSecret *corev1.Secret
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- secret, _ := createMockTLSSecret(ctx, testClient, instance.Spec.SolrTLS.PKCS12Secret.Name, "keystore.p12", instance.Namespace, instance.Spec.SolrTLS.KeyStorePasswordSecret.Key, "")
- mockTLSSecret = &secret
- wg.Done()
- }()
- helper.WaitForReconcile(3)
- wg.Wait()
-
- expectStatefulSetTLSConfig(t, g, instance, false)
- expectPassthroughIngressTLSConfig(t, g, tlsSecretName, instance.Spec.SolrTLS)
-
- defer testClient.Delete(ctx, mockTLSSecret)
-}
-
-func TestUserSuppliedTLSSecretWithPkcs12Conversion(t *testing.T) {
- tlsSecretName := "tls-cert-secret-from-user-no-pkcs12"
- keystorePassKey := "some-password-key-thingy"
- instance := buildTestSolrCloud()
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic}
- instance.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, false)
- verifyUserSuppliedTLSConfig(t, instance.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName)
- verifyReconcileUserSuppliedTLS(t, instance, true, false)
-}
-
-func TestTLSSecretUpdate(t *testing.T) {
- tlsSecretName := "tls-cert-secret-update"
- keystorePassKey := "some-password-key-thingy"
- instance := buildTestSolrCloud()
- instance.Spec.SolrSecurity = &solr.SolrSecurityOptions{AuthenticationType: solr.Basic}
- instance.Spec.SolrTLS = createTLSOptions(tlsSecretName, keystorePassKey, true)
- instance.Spec.SolrTLS.ClientAuth = solr.Need
- verifyUserSuppliedTLSConfig(t, instance.Spec.SolrTLS, tlsSecretName, keystorePassKey, tlsSecretName)
- verifyReconcileUserSuppliedTLS(t, instance, false, true)
-}
-
-func TestTLSServerAndClientFromSecret(t *testing.T) {
- serverCertSecret := "tls-server-cert"
- clientCertSecret := "tls-client-cert"
- keystorePassKey := "some-password-key-thingy"
- instance := buildTestSolrCloud()
- instance.Spec.SolrTLS = createTLSOptions(serverCertSecret, keystorePassKey, true)
- instance.Spec.SolrTLS.ClientAuth = solr.Need
-
- // Additional client cert
- instance.Spec.SolrClientTLS = &solr.SolrTLSOptions{
- KeyStorePasswordSecret: &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: clientCertSecret},
- Key: keystorePassKey,
- },
- PKCS12Secret: &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: clientCertSecret},
- Key: util.DefaultPkcs12KeystoreFile,
- },
- TrustStoreSecret: &corev1.SecretKeySelector{
- LocalObjectReference: corev1.LocalObjectReference{Name: clientCertSecret},
- Key: util.DefaultPkcs12TruststoreFile,
- },
- RestartOnTLSSecretUpdate: true,
- }
-
- verifyReconcileUserSuppliedTLS(t, instance, false, true)
... 9984 lines suppressed ...