| /* |
| Copyright 2019 Bloomberg Finance LP. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package controllers |
| |
| import ( |
| extv1 "k8s.io/api/extensions/v1beta1" |
| "k8s.io/apimachinery/pkg/types" |
| "strconv" |
| "testing" |
| |
| "github.com/bloomberg/solr-operator/controllers/util" |
| "github.com/stretchr/testify/assert" |
| |
| solr "github.com/bloomberg/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 TestIngressCloudReconcile(t *testing.T) { |
| SetIngressBaseUrl("") |
| UseEtcdCRD(false) |
| 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, |
| 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))) |
| |
| // 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", |
| } |
| testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env) |
| assert.ElementsMatch(t, []string{"-DhostPort=100"}, statefulSet.Spec.Template.Spec.Containers[0].Args, "Wrong Solr container arguments (Solr advertising port)") |
| |
| // 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, 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, 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", testIngressAnnotations, ingress.Annotations) |
| testIngressRules(t, ingress, true, int(replicas), []string{testDomain}) |
| } |
| |
| func TestIngressNoNodesCloudReconcile(t *testing.T) { |
| SetIngressBaseUrl("") |
| UseEtcdCRD(false) |
| 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, |
| UseExternalAddress: true, |
| HideNodes: 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))) |
| |
| // 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", |
| } |
| testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env) |
| assert.ElementsMatch(t, []string{"-DhostPort=3000"}, statefulSet.Spec.Template.Spec.Containers[0].Args, "Wrong Solr container arguments (Solr advertising port)") |
| |
| // 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, 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, 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", testIngressAnnotations, ingress.Annotations) |
| testIngressRules(t, ingress, true, 0, []string{testDomain}) |
| } |
| |
| func TestIngressNoCommonCloudReconcile(t *testing.T) { |
| SetIngressBaseUrl("") |
| UseEtcdCRD(false) |
| 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, |
| UseExternalAddress: true, |
| HideCommon: 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))) |
| |
| // 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", |
| } |
| testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env) |
| assert.ElementsMatch(t, []string{"-DhostPort=100"}, statefulSet.Spec.Template.Spec.Containers[0].Args, "Wrong Solr container arguments (Solr advertising port)") |
| |
| // 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, 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, 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", testIngressAnnotations, ingress.Annotations) |
| testIngressRules(t, ingress, false, int(replicas), []string{testDomain}) |
| } |
| |
| func TestIngressUseInternalAddressCloudReconcile(t *testing.T) { |
| SetIngressBaseUrl("") |
| UseEtcdCRD(false) |
| 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, |
| 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))) |
| // 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.") |
| |
| // 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", |
| } |
| testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env) |
| assert.ElementsMatch(t, []string{"-DhostPort=100"}, statefulSet.Spec.Template.Spec.Containers[0].Args, "Wrong Solr container arguments (Solr advertising port)") |
| |
| // 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, 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, 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", testIngressAnnotations, ingress.Annotations) |
| testIngressRules(t, ingress, true, int(replicas), []string{testDomain}) |
| } |
| |
| func TestIngressExtraDomainsCloudReconcile(t *testing.T) { |
| SetIngressBaseUrl("") |
| UseEtcdCRD(false) |
| 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, |
| UseExternalAddress: true, |
| DomainName: testDomain, |
| AdditionalDomainNames: testAdditionalDomains, |
| 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))) |
| |
| // 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", |
| } |
| testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env) |
| assert.ElementsMatch(t, []string{"-DhostPort=100"}, statefulSet.Spec.Template.Spec.Containers[0].Args, "Wrong Solr container arguments (Solr advertising port)") |
| |
| // 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, 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, 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", testIngressAnnotations, ingress.Annotations) |
| testIngressRules(t, ingress, true, int(replicas), append([]string{testDomain}, testAdditionalDomains...)) |
| } |
| |
| func TestIngressKubeDomainCloudReconcile(t *testing.T) { |
| SetIngressBaseUrl("") |
| UseEtcdCRD(false) |
| 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, |
| UseExternalAddress: false, |
| DomainName: testDomain, |
| NodePortOverride: 100, |
| }, |
| PodPort: 3000, |
| CommonServicePort: 4000, |
| 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, |
| }, |
| }, |
| }, |
| } |
| |
| // 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.") |
| |
| // 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 + ".svc." + testKubeDomain, |
| "SOLR_PORT": "3000", |
| } |
| testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env) |
| assert.ElementsMatch(t, []string{"-DhostPort=100"}, statefulSet.Spec.Template.Spec.Containers[0].Args, "Wrong Solr container arguments (Solr advertising port)") |
| |
| // 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, 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, 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", testIngressAnnotations, ingress.Annotations) |
| testIngressRules(t, ingress, true, int(replicas), []string{testDomain}) |
| } |
| |
| func testIngressRules(t *testing.T, ingress *extv1.Ingress, withCommon bool, withNodes int, domainNames []string) { |
| expected := 0 |
| if withCommon { |
| expected += 1 |
| } |
| if withNodes > 0 { |
| expected += withNodes |
| } |
| perDomain := expected |
| numDomains := len(domainNames) |
| expected *= numDomains |
| assert.EqualValues(t, expected, len(ingress.Spec.Rules), "Wrong number of ingress rules.") |
| for i := 0; i < perDomain; i++ { |
| // Common Rules |
| ruleName := "common" |
| hostAppend := "" |
| port := 4000 |
| serviceSuffix := "common" |
| |
| // Node Rules |
| if i > 0 || !withCommon { |
| nodeNum := strconv.Itoa(i - 1) |
| if !withCommon { |
| nodeNum = strconv.Itoa(i) |
| } |
| ruleName = "node-" + nodeNum |
| hostAppend = "-" + nodeNum |
| serviceSuffix = nodeNum |
| port = 100 |
| } |
| 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) |
| 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) |
| } |
| } |
| } |