diff --git a/controllers/solrcloud_controller.go b/controllers/solrcloud_controller.go
index 6bd81e8d..4a0338c1 100644
--- a/controllers/solrcloud_controller.go
+++ b/controllers/solrcloud_controller.go
@@ -225,9 +225,9 @@ func (r *SolrCloudReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
if hasSolrXml {
// make sure the user-provided solr.xml is valid
- if !strings.Contains(solrXml, "${hostPort:") {
+ if !(strings.Contains(solrXml, "${solr.port.advertise:") || strings.Contains(solrXml, "${hostPort:")) {
return requeueOrNot,
- fmt.Errorf("custom solr.xml in ConfigMap %s must contain a placeholder for the 'hostPort' variable, such as ${hostPort:80}",
+ fmt.Errorf("custom solr.xml in ConfigMap %s must contain a placeholder for either 'solr.port.advertise', or its deprecated alternative 'hostPort', e.g. ${solr.port.advertise:80}",
providedConfigMapName)
}
// stored in the pod spec annotations on the statefulset so that we get a restart when solr.xml changes
diff --git a/controllers/solrcloud_controller_backup_test.go b/controllers/solrcloud_controller_backup_test.go
index 0e8cafda..566b7c7e 100644
--- a/controllers/solrcloud_controller_backup_test.go
+++ b/controllers/solrcloud_controller_backup_test.go
@@ -102,11 +102,12 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "8983",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
}
foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
@@ -150,12 +151,13 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + foundSolrCloud.HeadlessServiceName() + "." + foundSolrCloud.Namespace,
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "SOLR_LOG_LEVEL": "INFO",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + foundSolrCloud.HeadlessServiceName() + "." + foundSolrCloud.Namespace,
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "8983",
+ "SOLR_LOG_LEVEL": "INFO",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
}
foundEnv := found.Spec.Template.Spec.Containers[0].Env
@@ -204,12 +206,13 @@ var _ = FDescribe("SolrCloud controller - Backup Repositories", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + foundSolrCloud.HeadlessServiceName() + "." + foundSolrCloud.Namespace,
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "SOLR_LOG_LEVEL": "INFO",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + foundSolrCloud.HeadlessServiceName() + "." + foundSolrCloud.Namespace,
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "8983",
+ "SOLR_LOG_LEVEL": "INFO",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
}
foundEnv := found.Spec.Template.Spec.Containers[0].Env
diff --git a/controllers/solrcloud_controller_externaldns_test.go b/controllers/solrcloud_controller_externaldns_test.go
index b63a1ffc..92d7e7ea 100644
--- a/controllers/solrcloud_controller_externaldns_test.go
+++ b/controllers/solrcloud_controller_externaldns_test.go
@@ -97,11 +97,12 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -177,11 +178,12 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
- "SOLR_PORT": "2000",
- "SOLR_NODE_PORT": "2000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "2000",
+ "SOLR_NODE_PORT": "2000",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -248,11 +250,12 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -318,11 +321,12 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -386,11 +390,12 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -466,11 +471,12 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain,
- "SOLR_PORT": "2000",
- "SOLR_NODE_PORT": "2000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain,
+ "SOLR_PORT": "2000",
+ "SOLR_NODE_PORT": "2000",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -531,11 +537,12 @@ var _ = FDescribe("SolrCloud controller - External DNS", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain,
- "SOLR_PORT": "2000",
- "SOLR_NODE_PORT": "2000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + "." + testDomain,
+ "SOLR_PORT": "2000",
+ "SOLR_NODE_PORT": "2000",
+ "SOLR_PORT_ADVERTISE": "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")
diff --git a/controllers/solrcloud_controller_ingress_test.go b/controllers/solrcloud_controller_ingress_test.go
index f9c61ba6..d9ad9bf0 100644
--- a/controllers/solrcloud_controller_ingress_test.go
+++ b/controllers/solrcloud_controller_ingress_test.go
@@ -113,11 +113,12 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -213,11 +214,12 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "3000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "3000",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -292,11 +294,12 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -369,11 +372,12 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -446,11 +450,12 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -519,11 +524,12 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + ".svc." + testKubeDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.Namespace + ".svc." + testKubeDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_PORT_ADVERTISE": "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")
@@ -595,11 +601,12 @@ var _ = FDescribe("SolrCloud controller - Ingress", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain,
- "SOLR_PORT": "3000",
- "SOLR_NODE_PORT": "100",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": solrCloud.Namespace + "-$(POD_NAME)." + testDomain,
+ "SOLR_PORT": "3000",
+ "SOLR_NODE_PORT": "100",
+ "SOLR_PORT_ADVERTISE": "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")
diff --git a/controllers/solrcloud_controller_test.go b/controllers/solrcloud_controller_test.go
index 16460139..2c568585 100644
--- a/controllers/solrcloud_controller_test.go
+++ b/controllers/solrcloud_controller_test.go
@@ -117,13 +117,14 @@ var _ = FDescribe("SolrCloud controller - General", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + 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",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_JAVA_MEM": "-Xmx4G",
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "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.
@@ -247,13 +248,14 @@ var _ = FDescribe("SolrCloud controller - General", func() {
Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/test",
- "SOLR_HOST": "$(POD_NAME).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),
+ "ZK_HOST": "host:7271/test",
+ "SOLR_HOST": "$(POD_NAME).foo-solrcloud-headless.default",
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "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/test"}
@@ -438,11 +440,12 @@ var _ = FDescribe("SolrCloud controller - General", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain,
- "SOLR_PORT": "2000",
- "SOLR_NODE_PORT": "2000",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace + ".svc." + testKubeDomain,
+ "SOLR_PORT": "2000",
+ "SOLR_NODE_PORT": "2000",
+ "SOLR_PORT_ADVERTISE": "2000",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT)",
}
testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
By("testing the Solr Common Service")
diff --git a/controllers/solrcloud_controller_zk_test.go b/controllers/solrcloud_controller_zk_test.go
index 8fb8a745..57917fb4 100644
--- a/controllers/solrcloud_controller_zk_test.go
+++ b/controllers/solrcloud_controller_zk_test.go
@@ -104,11 +104,12 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
+ "ZK_HOST": "host:7271/",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "8983",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
}
insertExpectedAclEnvVars(expectedEnvVars, false)
for _, envVar := range extraVars {
@@ -169,11 +170,12 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
// Env Variable Tests
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/a-ch/root",
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
+ "ZK_HOST": "host:7271/a-ch/root",
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "8983",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
}
insertExpectedAclEnvVars(expectedEnvVars, true)
for _, envVar := range extraVars {
@@ -449,12 +451,13 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
// Env Variable Tests
expectedZKHost := expectedZkConnStr + "/a-ch/root"
expectedEnvVars := map[string]string{
- "ZK_HOST": expectedZKHost,
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "ZK_CHROOT": "/a-ch/root",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
+ "ZK_HOST": expectedZKHost,
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "8983",
+ "ZK_CHROOT": "/a-ch/root",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
}
insertExpectedAclEnvVars(expectedEnvVars, false)
for _, envVar := range extraVars {
@@ -525,11 +528,12 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
// Env Variable Tests
expectedZKHost := expectedZkConnStr + "/"
expectedEnvVars := map[string]string{
- "ZK_HOST": expectedZKHost,
- "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
+ "ZK_HOST": expectedZKHost,
+ "SOLR_HOST": "$(POD_NAME)." + solrCloud.HeadlessServiceName() + "." + solrCloud.Namespace,
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "8983",
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
}
insertExpectedAclEnvVars(expectedEnvVars, true)
for _, envVar := range extraVars {
@@ -565,13 +569,14 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
expectedEnvVars := map[string]string{
- "ZK_HOST": "host:7271/test",
- "SOLR_HOST": "$(POD_NAME).foo-solrcloud-headless.default",
- "SOLR_PORT": "8983",
- "SOLR_NODE_PORT": "8983",
- "SOLR_ZK_OPTS": testSolrZKOpts,
- "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_OPTS) " + testSolrOpts,
- "SOLR_STOP_WAIT": strconv.FormatInt(60-5, 10),
+ "ZK_HOST": "host:7271/test",
+ "SOLR_HOST": "$(POD_NAME).foo-solrcloud-headless.default",
+ "SOLR_PORT": "8983",
+ "SOLR_NODE_PORT": "8983",
+ "SOLR_PORT_ADVERTISE": "8983",
+ "SOLR_ZK_OPTS": testSolrZKOpts,
+ "SOLR_OPTS": "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_OPTS) " + testSolrOpts,
+ "SOLR_STOP_WAIT": strconv.FormatInt(60-5, 10),
}
testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go
index 5c5b6142..f73eb974 100644
--- a/controllers/util/solr_util.go
+++ b/controllers/util/solr_util.go
@@ -61,8 +61,9 @@ const (
DefaultStatefulSetPodManagementPolicy = appsv1.ParallelPodManagement
- DistLibs = "/opt/solr/dist"
- ContribLibs = "/opt/solr/contrib/%s/lib"
+ DistLibs = "/opt/solr/dist"
+ ContribLibs = "/opt/solr/contrib/%s/lib"
+ SysPropLibPlaceholder = "${solr.sharedLib:}"
)
var (
@@ -305,9 +306,15 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl
},
{
// This is the port that the Solr Node will advertise itself as listening on in live_nodes
+ // TODO Remove in 0.9.0 once users have had a chance to switch any custom solr.xml files over to using the `solr.port.advertise` placeholder
Name: "SOLR_NODE_PORT",
Value: strconv.Itoa(solrAdressingPort),
},
+ {
+ // Supercedes SOLR_NODE_PORT above. 'bin/solr' converts to 'solr.port.advertise' sysprop automatically.
+ Name: "SOLR_PORT_ADVERTISE",
+ Value: strconv.Itoa(solrAdressingPort),
+ },
// POD_HOSTNAME is deprecated and will be removed in a future version. Use POD_NAME instead
{
Name: "POD_HOSTNAME",
@@ -771,7 +778,7 @@ const DefaultSolrXML = `
%s
${host:}
- ${hostPort:80}
+ ${solr.port.advertise:80}
${hostContext:solr}
${genericCoreNodeNames:true}
${zkClientTimeout:30000}
@@ -786,6 +793,8 @@ const DefaultSolrXML = `
${connTimeout:60000}
${solr.max.booleanClauses:1024}
+ ${solr.allowPaths:}
+
%s
`
@@ -831,6 +840,9 @@ func GenerateSolrXMLString(backupSection string, solrModules []string, additiona
func GenerateAdditionalLibXMLPart(solrModules []string, additionalLibs []string) string {
libs := make(map[string]bool, 0)
+ // Placeholder for users to specify libs via sysprop
+ libs[SysPropLibPlaceholder] = true
+
// Add all module library locations
if len(solrModules) > 0 {
libs[DistLibs] = true
@@ -844,16 +856,12 @@ func GenerateAdditionalLibXMLPart(solrModules []string, additionalLibs []string)
libs[libPath] = true
}
- libXml := ""
- if len(libs) > 0 {
- libList := make([]string, 0)
- for lib := range libs {
- libList = append(libList, lib)
- }
- sort.Strings(libList)
- libXml = fmt.Sprintf("%s", strings.Join(libList, ","))
+ libList := make([]string, 0)
+ for lib := range libs {
+ libList = append(libList, lib)
}
- return libXml
+ sort.Strings(libList)
+ return fmt.Sprintf("%s", strings.Join(libList, ","))
}
func getAppProtocol(solrCloud *solr.SolrCloud) *string {
diff --git a/controllers/util/solr_util_test.go b/controllers/util/solr_util_test.go
index 0e3f6352..b6d6bb43 100644
--- a/controllers/util/solr_util_test.go
+++ b/controllers/util/solr_util_test.go
@@ -116,25 +116,29 @@ func TestGeneratedGcsRepositoryXmlSkipsCredentialIfUnset(t *testing.T) {
}
func TestGenerateAdditionalLibXMLPart(t *testing.T) {
+ // No specified libs
+ xmlString := GenerateAdditionalLibXMLPart([]string{}, []string{})
+ assert.EqualValuesf(t, xmlString, "${solr.sharedLib:}", "Wrong sharedLib xml for no specified libs")
+
// Just 1 repeated solr module
- xmlString := GenerateAdditionalLibXMLPart([]string{"gcs-repository", "gcs-repository"}, []string{})
- assert.EqualValuesf(t, xmlString, "/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for just 1 repeated solr module")
+ xmlString = GenerateAdditionalLibXMLPart([]string{"gcs-repository", "gcs-repository"}, []string{})
+ assert.EqualValuesf(t, xmlString, "${solr.sharedLib:},/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for just 1 repeated solr module")
// Just 2 different solr modules
xmlString = GenerateAdditionalLibXMLPart([]string{"gcs-repository", "analytics"}, []string{})
- assert.EqualValuesf(t, xmlString, "/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for just 2 different solr modules")
+ assert.EqualValuesf(t, xmlString, "${solr.sharedLib:},/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for just 2 different solr modules")
// Just 2 repeated libs
xmlString = GenerateAdditionalLibXMLPart([]string{}, []string{"/ext/lib", "/ext/lib"})
- assert.EqualValuesf(t, xmlString, "/ext/lib", "Wrong sharedLib xml for just 1 repeated additional lib")
+ assert.EqualValuesf(t, xmlString, "${solr.sharedLib:},/ext/lib", "Wrong sharedLib xml for just 1 repeated additional lib")
// Just 2 different libs
xmlString = GenerateAdditionalLibXMLPart([]string{}, []string{"/ext/lib2", "/ext/lib1"})
- assert.EqualValuesf(t, xmlString, "/ext/lib1,/ext/lib2", "Wrong sharedLib xml for just 2 different additional libs")
+ assert.EqualValuesf(t, xmlString, "${solr.sharedLib:},/ext/lib1,/ext/lib2", "Wrong sharedLib xml for just 2 different additional libs")
// Combination of everything
xmlString = GenerateAdditionalLibXMLPart([]string{"gcs-repository", "analytics", "analytics"}, []string{"/ext/lib2", "/ext/lib2", "/ext/lib1"})
- assert.EqualValuesf(t, xmlString, "/ext/lib1,/ext/lib2,/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for mix of additional libs and solr modules")
+ assert.EqualValuesf(t, xmlString, "${solr.sharedLib:},/ext/lib1,/ext/lib2,/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for mix of additional libs and solr modules")
}
func TestGenerateSolrXMLStringForCloud(t *testing.T) {
@@ -151,7 +155,7 @@ func TestGenerateSolrXMLStringForCloud(t *testing.T) {
SolrModules: []string{"ltr", "analytics"},
},
}
- assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "/ext/lib1,/ext/lib2,/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with a backupRepo, additionalLibs and solrModules")
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "${solr.sharedLib:},/ext/lib1,/ext/lib2,/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with a backupRepo, additionalLibs and solrModules")
// Just SolrModules and AdditionalLibs
solrCloud = &solr.SolrCloud{
@@ -160,7 +164,7 @@ func TestGenerateSolrXMLStringForCloud(t *testing.T) {
SolrModules: []string{"ltr", "analytics"},
},
}
- assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "/ext/lib1,/ext/lib2,/opt/solr/contrib/analytics/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with additionalLibs and solrModules")
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "${solr.sharedLib:},/ext/lib1,/ext/lib2,/opt/solr/contrib/analytics/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with additionalLibs and solrModules")
// Just SolrModules and Backups
solrCloud = &solr.SolrCloud{
@@ -174,7 +178,7 @@ func TestGenerateSolrXMLStringForCloud(t *testing.T) {
SolrModules: []string{"ltr", "analytics"},
},
}
- assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with a backupRepo and solrModules")
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "${solr.sharedLib:},/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with a backupRepo and solrModules")
// Just AdditionalLibs and Backups
solrCloud = &solr.SolrCloud{
@@ -188,7 +192,7 @@ func TestGenerateSolrXMLStringForCloud(t *testing.T) {
AdditionalLibs: []string{"/ext/lib2", "/ext/lib1"},
},
}
- assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "/ext/lib1,/ext/lib2,/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with a backupRepo and additionalLibs")
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "${solr.sharedLib:},/ext/lib1,/ext/lib2,/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with a backupRepo and additionalLibs")
// Just SolrModules
solrCloud = &solr.SolrCloud{
@@ -196,7 +200,7 @@ func TestGenerateSolrXMLStringForCloud(t *testing.T) {
SolrModules: []string{"ltr", "analytics"},
},
}
- assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "/opt/solr/contrib/analytics/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with just solrModules")
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "${solr.sharedLib:},/opt/solr/contrib/analytics/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with just solrModules")
// Just Backups
solrCloud = &solr.SolrCloud{
@@ -209,7 +213,7 @@ func TestGenerateSolrXMLStringForCloud(t *testing.T) {
},
},
}
- assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with just a backupRepo")
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "${solr.sharedLib:},/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist", "Wrong sharedLib xml for a cloud with just a backupRepo")
// Just AdditionalLibs
solrCloud = &solr.SolrCloud{
@@ -217,5 +221,5 @@ func TestGenerateSolrXMLStringForCloud(t *testing.T) {
AdditionalLibs: []string{"/ext/lib2", "/ext/lib1"},
},
}
- assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "/ext/lib1,/ext/lib2", "Wrong sharedLib xml for a cloud with a just additionalLibs")
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "${solr.sharedLib:},/ext/lib1,/ext/lib2", "Wrong sharedLib xml for a cloud with a just additionalLibs")
}
diff --git a/docs/solr-cloud/solr-cloud-crd.md b/docs/solr-cloud/solr-cloud-crd.md
index d2080d96..b30ea670 100644
--- a/docs/solr-cloud/solr-cloud-crd.md
+++ b/docs/solr-cloud/solr-cloud-crd.md
@@ -338,7 +338,7 @@ data:
... CUSTOM CONFIG HERE ...
```
-**Important: Your custom `solr.xml` must include `${hostPort:0}` as the operator relies on this element to set the port Solr pods advertise to ZooKeeper. If this element is missing, then your Solr pods will not be created.**
+**Important: Your custom `solr.xml` must include `${solr.port.advertise:0}` as the operator relies on this element to set the port Solr pods advertise to ZooKeeper. If this element is missing, then your Solr pods will not be created.**
You can get the default `solr.xml` from a Solr pod as a starting point for creating a custom config using `kubectl cp` as shown in the example below:
```bash
diff --git a/docs/upgrade-notes.md b/docs/upgrade-notes.md
index 6f5ad4aa..128be56b 100644
--- a/docs/upgrade-notes.md
+++ b/docs/upgrade-notes.md
@@ -130,6 +130,8 @@ _Note that the Helm chart version does not contain a `v` prefix, which the downl
- The `POD_HOSTNAME` envVar in SolrCloud Pods has been deprecated. Use `POD_NAME` instead.
+- Use of the `hostPort` system property placeholder in custom solr.xml files has been deprecated. Use `${solr.port.advertise:80}`, the default value used by Solr, instead.
+
### v0.7.0
- **Kubernetes support is now limited to 1.21+.**
If you are unable to use a newer version of Kubernetes, please install the `v0.6.0` version of the Solr Operator for use with Kubernetes `1.20` and below.
diff --git a/helm/solr/Chart.yaml b/helm/solr/Chart.yaml
index 44070f56..f265d4b8 100644
--- a/helm/solr/Chart.yaml
+++ b/helm/solr/Chart.yaml
@@ -55,6 +55,13 @@ annotations:
url: https://github.com/apache/solr-operator/issues/630
- name: Github PR
url: https://github.com/apache/solr-operator/pull/631
+ - kind: changed
+ description: The default solr.xml now includes the necessary boilerplate to specify additional `sharedLib` and `allowPath` values (via the `solr.sharedLib` and `solr.allowPaths` system properties respectively). The `metricsEnabled` system property can also be used to toggle Solr's metrics processing (defaults to `true`). Lastly, the `solr.port.advertise` property can be used to control the port Solr is advertised under in `/live_nodes` and other cluster state. Users providing their own `solr.xml` should replace any references to the `hostPort` system property with `solr.port.advertise`, as support for the `hostPort` placeholder will be removed in a future release.
+ links:
+ - name: Github Issue
+ url: https://github.com/apache/solr-operator/issues/635
+ - name: Github PR
+ url: https://github.com/apache/solr-operator/pull/636
artifacthub.io/containsSecurityUpdates: "false"
artifacthub.io/recommendations: |
- url: https://artifacthub.io/packages/helm/apache-solr/solr-operator