blob: 36699fc223bdf7b616b75b1fdfdd78a6ad2b51b0 [file] [log] [blame]
/*
* 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 (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"testing"
"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{}
func TestZKACLsCloudReconcile(t *testing.T) {
UseZkCRD(false)
g := gomega.NewGomegaWithT(t)
replicas := int32(3)
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",
AllACL: &solr.ZookeeperACL{
SecretRef: "secret-name",
UsernameKey: "user",
PasswordKey: "pass",
},
},
},
CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
PodOptions: &solr.PodOptions{
EnvVariables: extraVars,
},
},
SolrOpts: "-Dextra -Dopts",
},
}
// 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 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,
"SOLR_PORT": "8983",
"SOLR_NODE_PORT": "8983",
"SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
}
foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
f := false
zkAclEnvVars := []corev1.EnvVar{
{
Name: "ZK_ALL_ACL_USERNAME",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "secret-name"},
Key: "user",
Optional: &f,
},
},
},
{
Name: "ZK_ALL_ACL_PASSWORD",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "secret-name"},
Key: "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)",
ValueFrom: nil,
},
}
assert.Equal(t, zkAclEnvVars, foundEnv[len(foundEnv)-6:len(foundEnv)-3], "ZK ACL Env Vars are not correct")
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)-6], foundEnv[len(foundEnv)-1]))
assert.Equal(t, []string{"solr", "stop", "-p", "8983"}, 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)
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 the headless Service
service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
assert.EqualValues(t, 8983, 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)
}
func TestBothZKACLsCloudReconcile(t *testing.T) {
replicas := int32(3)
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",
AllACL: &solr.ZookeeperACL{
SecretRef: "secret-name",
UsernameKey: "user",
PasswordKey: "pass",
},
ReadOnlyACL: &solr.ZookeeperACL{
SecretRef: "read-secret-name",
UsernameKey: "read-only-user",
PasswordKey: "read-only-pass",
},
},
},
CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
PodOptions: &solr.PodOptions{
EnvVariables: extraVars,
},
},
SolrOpts: "-Dextra -Dopts",
},
}
testZkACLsReconcile(t, false, instance, "host:7271/")
}
func TestZKACLsForProvidedReconcile(t *testing.T) {
replicas := int32(3)
zkReplicas := int32(1)
instance := &solr.SolrCloud{
ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
Spec: solr.SolrCloudSpec{
Replicas: &replicas,
ZookeeperRef: &solr.ZookeeperRef{
ProvidedZookeeper: &solr.ZookeeperSpec{
Replicas: &zkReplicas,
AllACL: &solr.ZookeeperACL{
SecretRef: "secret-name",
UsernameKey: "user",
PasswordKey: "pass",
},
ReadOnlyACL: &solr.ZookeeperACL{
SecretRef: "read-secret-name",
UsernameKey: "read-only-user",
PasswordKey: "read-only-pass",
},
},
},
CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
PodOptions: &solr.PodOptions{
EnvVariables: extraVars,
},
},
SolrOpts: "-Dextra -Dopts",
},
}
expectedZkHost := fmt.Sprintf("%s-zookeeper-0.%s-zookeeper-headless.%s.svc.cluster.local:2181/",
cloudSsKey.Name, cloudSsKey.Name, cloudSsKey.Namespace)
testZkACLsReconcile(t, true, instance, expectedZkHost)
}
func testZkACLsReconcile(t *testing.T, useZkCRD bool, instance *solr.SolrCloud, expectedZkHost string) {
UseZkCRD(useZkCRD)
g := gomega.NewGomegaWithT(t)
// 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 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": expectedZkHost,
"SOLR_HOST": "$(POD_HOSTNAME)." + cloudHsKey.Name + "." + instance.Namespace,
"SOLR_PORT": "8983",
"SOLR_NODE_PORT": "8983",
"SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
}
foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
testACLEnvVars(t, foundEnv[len(foundEnv)-8:len(foundEnv)-3])
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)-8], foundEnv[len(foundEnv)-1]))
assert.Equal(t, []string{"solr", "stop", "-p", "8983"}, 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)
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 the headless Service
service = expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
assert.EqualValues(t, 8983, 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)
// now update the env vars on the zk spec
if instance.Spec.ZookeeperRef.ProvidedZookeeper != nil {
err = testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, instance)
g.Expect(err).NotTo(gomega.HaveOccurred())
updateEnvVars := []corev1.EnvVar{
{
Name: "VAR_1",
Value: "VAL_1",
},
}
instance.Spec.ZookeeperRef.ProvidedZookeeper.ZookeeperPod = solr.ZookeeperPodPolicy{Env: updateEnvVars}
err = testClient.Update(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)))
lookup := &solr.SolrCloud{}
err = testClient.Get(context.TODO(), expectedCloudRequest.NamespacedName, lookup)
g.Expect(err).NotTo(gomega.HaveOccurred())
assert.EqualValues(t, updateEnvVars, lookup.Spec.ZookeeperRef.ProvidedZookeeper.ZookeeperPod.Env, "Updated ZK Pod env not reconciled!")
}
}