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/10/13 15:34:26 UTC
[solr-operator] branch main updated: Add support for Solr Modules
and additional libs (#332)
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 44e7375 Add support for Solr Modules and additional libs (#332)
44e7375 is described below
commit 44e73756c56c9f42c58448d43ffbd6009d87cc7c
Author: Houston Putman <ho...@apache.org>
AuthorDate: Wed Oct 13 11:34:18 2021 -0400
Add support for Solr Modules and additional libs (#332)
---
api/v1beta1/solrcloud_types.go | 12 +++
api/v1beta1/zz_generated.deepcopy.go | 10 ++
config/crd/bases/solr.apache.org_solrclouds.yaml | 10 ++
controllers/solrcloud_controller_test.go | 46 ++++++++-
controllers/util/solr_backup_repo_util.go | 34 ++++++-
controllers/util/solr_backup_repo_util_test.go | 26 ++++-
controllers/util/solr_util.go | 79 +++++++--------
controllers/util/solr_util_test.go | 116 ++++++++++++++++++++++-
docs/solr-cloud/solr-cloud-crd.md | 14 +++
example/test_solrcloud.yaml | 3 +
helm/solr-operator/Chart.yaml | 9 ++
helm/solr-operator/crds/crds.yaml | 10 ++
helm/solr/README.md | 2 +
helm/solr/templates/solrcloud.yaml | 10 ++
helm/solr/values.yaml | 2 +
15 files changed, 333 insertions(+), 50 deletions(-)
diff --git a/api/v1beta1/solrcloud_types.go b/api/v1beta1/solrcloud_types.go
index 1522401..def3a62 100644
--- a/api/v1beta1/solrcloud_types.go
+++ b/api/v1beta1/solrcloud_types.go
@@ -126,6 +126,18 @@ type SolrCloudSpec struct {
//+listType:=map
//+listMapKey:=name
BackupRepositories []SolrBackupRepository `json:"backupRepositories,omitempty"`
+
+ // List of Solr Modules to be loaded when starting Solr
+ // Note: You do not need to specify a module if it is required by another property (e.g. backupRepositories[].gcs)
+ //
+ //+optional
+ SolrModules []string `json:"solrModules,omitempty"`
+
+ // List of paths in the Solr Docker image to load in the classpath.
+ // Note: Solr Modules will be auto-loaded if specified in the "solrModules" property. There is no need to specify them here as well.
+ //
+ //+optional
+ AdditionalLibs []string `json:"additionalLibs,omitempty"`
}
func (spec *SolrCloudSpec) withDefaults() (changed bool) {
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index c8f06df..aacf29b 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -908,6 +908,16 @@ func (in *SolrCloudSpec) DeepCopyInto(out *SolrCloudSpec) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
+ if in.SolrModules != nil {
+ in, out := &in.SolrModules, &out.SolrModules
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.AdditionalLibs != nil {
+ in, out := &in.AdditionalLibs, &out.AdditionalLibs
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SolrCloudSpec.
diff --git a/config/crd/bases/solr.apache.org_solrclouds.yaml b/config/crd/bases/solr.apache.org_solrclouds.yaml
index 2f80d77..edfb8f3 100644
--- a/config/crd/bases/solr.apache.org_solrclouds.yaml
+++ b/config/crd/bases/solr.apache.org_solrclouds.yaml
@@ -76,6 +76,11 @@ spec:
spec:
description: SolrCloudSpec defines the desired state of SolrCloud
properties:
+ additionalLibs:
+ description: 'List of paths in the Solr Docker image to load in the classpath. Note: Solr Modules will be auto-loaded if specified in the "solrModules" property. There is no need to specify them here as well.'
+ items:
+ type: string
+ type: array
backupRepositories:
description: Allows specification of multiple different "repositories" for Solr to use when backing up data.
items:
@@ -5730,6 +5735,11 @@ spec:
solrLogLevel:
description: Set the Solr Log level, defaults to INFO
type: string
+ solrModules:
+ description: 'List of Solr Modules to be loaded when starting Solr Note: You do not need to specify a module if it is required by another property (e.g. backupRepositories[].gcs)'
+ items:
+ type: string
+ type: array
solrOpts:
description: You can add common system properties to the SOLR_OPTS environment variable SolrOpts is the string interface for these optional settings
type: string
diff --git a/controllers/solrcloud_controller_test.go b/controllers/solrcloud_controller_test.go
index 17a410d..1c6dce4 100644
--- a/controllers/solrcloud_controller_test.go
+++ b/controllers/solrcloud_controller_test.go
@@ -203,7 +203,7 @@ var _ = FDescribe("SolrCloud controller - General", func() {
})
FIt("has the correct resources", func() {
By("testing the Solr ConfigMap")
- configMap := expectConfigMap(ctx, solrCloud, solrCloud.ConfigMapName(), map[string]string{"solr.xml": util.GenerateSolrXMLString("")})
+ configMap := expectConfigMap(ctx, solrCloud, solrCloud.ConfigMapName(), map[string]string{"solr.xml": util.GenerateSolrXMLString("", []string{}, []string{})})
Expect(configMap.Labels).To(Equal(util.MergeLabelsOrAnnotations(solrCloud.SharedLabelsWith(solrCloud.Labels), testConfigMapLabels)), "Incorrect configMap labels")
Expect(configMap.Annotations).To(Equal(testConfigMapAnnotations), "Incorrect configMap annotations")
@@ -419,6 +419,46 @@ var _ = FDescribe("SolrCloud controller - General", func() {
})
})
+ FContext("Solr Cloud with changing generated SolrXML", func() {
+ BeforeEach(func() {
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString: "host:7271",
+ },
+ },
+ SolrModules: []string{"analytics", "ltr"},
+ BackupRepositories: []solrv1beta1.SolrBackupRepository{
+ {
+ Name: "test1",
+ GCS: &solrv1beta1.GcsRepository{},
+ },
+ },
+ }
+ })
+ FIt("has the correct resources", func() {
+ By("testing the Solr ConfigMap")
+ configMap := expectConfigMap(ctx, solrCloud, solrCloud.ConfigMapName(), map[string]string{"solr.xml": util.GenerateSolrXMLStringForCloud(solrCloud)})
+
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+ 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!")
+
+ By("making sure the solr.xml is updated and a rolling restart happens when libs change")
+ foundSolrCloud := expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
+ found.Spec.AdditionalLibs = []string{"/ext/lib2", "/ext/lib1"}
+ g.Expect(k8sClient.Update(ctx, found)).To(Succeed(), "Change the additionalLibs for the SolrCloud")
+ })
+
+ newConfigMap := expectConfigMap(ctx, solrCloud, solrCloud.ConfigMapName(), map[string]string{"solr.xml": util.GenerateSolrXMLStringForCloud(foundSolrCloud)})
+
+ updateSolrXmlMd5 := fmt.Sprintf("%x", md5.Sum([]byte(newConfigMap.Data[util.SolrXmlFile])))
+ 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 XML ConfigMap", func() {
testCustomSolrXmlConfigMap := "my-custom-solr-xml"
BeforeEach(func() {
@@ -545,14 +585,14 @@ var _ = FDescribe("SolrCloud controller - General", func() {
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.SolrXmlMd5Annotation, fmt.Sprintf("%x", md5.Sum([]byte(util.GenerateSolrXMLString("", []string{}, []string{}))))), "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("")})
+ expectConfigMap(ctx, solrCloud, fmt.Sprintf("%s-solrcloud-configmap", solrCloud.GetName()), map[string]string{util.SolrXmlFile: util.GenerateSolrXMLString("", []string{}, []string{})})
By("updating the user-provided log XML to trigger a pod rolling restart")
configMap.Data[util.LogXmlFile] = "<Configuration>Updated!</Configuration>"
diff --git a/controllers/util/solr_backup_repo_util.go b/controllers/util/solr_backup_repo_util.go
index 306455f..f7b9b93 100644
--- a/controllers/util/solr_backup_repo_util.go
+++ b/controllers/util/solr_backup_repo_util.go
@@ -21,15 +21,14 @@ import (
"fmt"
solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
corev1 "k8s.io/api/core/v1"
+ "sort"
+ "strings"
)
const (
BaseBackupRestorePath = "/var/solr/data/backup-restore"
GCSCredentialSecretKey = "service-account-key.json"
-
- DistLibs = "/opt/solr/dist"
- ContribLibs = "/opt/solr/contrib/%s/lib"
)
func RepoVolumeName(repo *solrv1beta1.SolrBackupRepository) string {
@@ -85,13 +84,17 @@ func RepoVolumeSourceAndMount(repo *solrv1beta1.SolrBackupRepository, solrCloudN
return
}
-func AdditionalRepoLibs(repo *solrv1beta1.SolrBackupRepository) (libs []string) {
+func RepoSolrModules(repo *solrv1beta1.SolrBackupRepository) (libs []string) {
if repo.GCS != nil {
- libs = []string{DistLibs, fmt.Sprintf(ContribLibs, "gcs-repository")}
+ libs = []string{"gcs-repository"}
}
return
}
+func AdditionalRepoLibs(repo *solrv1beta1.SolrBackupRepository) (libs []string) {
+ return
+}
+
func RepoXML(repo *solrv1beta1.SolrBackupRepository) (xml string) {
if repo.Managed != nil {
xml = fmt.Sprintf(`<repository name="%s" class="org.apache.solr.core.backup.repository.LocalFileSystemRepository"/>`, repo.Name)
@@ -109,6 +112,27 @@ func RepoEnvVars(repo *solrv1beta1.SolrBackupRepository) (envVars []corev1.EnvVa
return envVars
}
+func GenerateBackupRepositoriesForSolrXml(backupRepos []solrv1beta1.SolrBackupRepository) (repoXML string, solrModules []string, additionalLibs []string) {
+ if len(backupRepos) == 0 {
+ return
+ }
+ repoXMLs := make([]string, len(backupRepos))
+
+ for i, repo := range backupRepos {
+ solrModules = append(solrModules, RepoSolrModules(&repo)...)
+ additionalLibs = append(additionalLibs, AdditionalRepoLibs(&repo)...)
+ repoXMLs[i] = RepoXML(&repo)
+ }
+ sort.Strings(repoXMLs)
+
+ repoXML = fmt.Sprintf(
+ `<backup>
+ %s
+ </backup>`, strings.Join(repoXMLs, `
+`))
+ return
+}
+
func IsBackupVolumePresent(repo *solrv1beta1.SolrBackupRepository, pod *corev1.Pod) bool {
expectedVolumeName := RepoVolumeName(repo)
for _, volume := range pod.Spec.Volumes {
diff --git a/controllers/util/solr_backup_repo_util_test.go b/controllers/util/solr_backup_repo_util_test.go
index 19f2925..b294ce9 100644
--- a/controllers/util/solr_backup_repo_util_test.go
+++ b/controllers/util/solr_backup_repo_util_test.go
@@ -71,7 +71,21 @@ func TestGCSRepoAdditionalLibs(t *testing.T) {
},
},
}
- assert.EqualValues(t, []string{"/opt/solr/dist", "/opt/solr/contrib/gcs-repository/lib"}, AdditionalRepoLibs(repo), "GCS Repos require no additional libraries for Solr")
+ assert.Empty(t, AdditionalRepoLibs(repo), "GCS Repos require no additional libraries for Solr")
+}
+
+func TestGCSRepoSolrModules(t *testing.T) {
+ repo := &solr.SolrBackupRepository{
+ Name: "gcsrepository1",
+ GCS: &solr.GcsRepository{
+ Bucket: "some-bucket-name1",
+ GcsCredentialSecret: corev1.SecretKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{Name: "some-secret-name1"},
+ Key: "some-secret-key",
+ },
+ },
+ }
+ assert.EqualValues(t, []string{"gcs-repository"}, RepoSolrModules(repo), "GCS Repos require the gcs-repository solr module")
}
func TestManagedRepoAdditionalLibs(t *testing.T) {
@@ -83,3 +97,13 @@ func TestManagedRepoAdditionalLibs(t *testing.T) {
}
assert.Empty(t, AdditionalRepoLibs(repo), "Managed Repos require no additional libraries for Solr")
}
+
+func TestManagedRepoSolrModules(t *testing.T) {
+ repo := &solr.SolrBackupRepository{
+ Name: "managedrepository2",
+ Managed: &solr.ManagedRepository{
+ Volume: corev1.VolumeSource{},
+ },
+ }
+ assert.Empty(t, RepoSolrModules(repo), "Managed Repos require no solr modules")
+}
diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go
index e2d9c8a..db4455b 100644
--- a/controllers/util/solr_util.go
+++ b/controllers/util/solr_util.go
@@ -51,6 +51,9 @@ const (
LogXmlFile = "log4j2.xml"
DefaultStatefulSetPodManagementPolicy = appsv1.ParallelPodManagement
+
+ DistLibs = "/opt/solr/dist"
+ ContribLibs = "/opt/solr/contrib/%s/lib"
)
// GenerateStatefulSet returns a new appsv1.StatefulSet pointer generated for the SolrCloud instance
@@ -594,41 +597,9 @@ func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus
return containers
}
-func GenerateBackupRepositoriesForSolrXml(backupRepos []solr.SolrBackupRepository) string {
- if len(backupRepos) == 0 {
- return ""
- }
- libs := make(map[string]bool, 0)
- repoXMLs := make([]string, len(backupRepos))
-
- for i, repo := range backupRepos {
- for _, lib := range AdditionalRepoLibs(&repo) {
- libs[lib] = true
- }
- repoXMLs[i] = RepoXML(&repo)
- }
- sort.Strings(repoXMLs)
-
- libXml := ""
- if len(libs) > 0 {
- libList := make([]string, 0)
- for lib := range libs {
- libList = append(libList, lib)
- }
- sort.Strings(libList)
- libXml = fmt.Sprintf("<str name=\"sharedLib\">%s</str>", strings.Join(libList, ","))
- }
-
- return fmt.Sprintf(
- `%s
- <backup>
- %s
- </backup>`, libXml, strings.Join(repoXMLs, `
-`))
-}
-
const DefaultSolrXML = `<?xml version="1.0" encoding="UTF-8" ?>
<solr>
+ %s
<solrcloud>
<str name="host">${host:}</str>
<int name="hostPort">${hostPort:80}</int>
@@ -661,7 +632,6 @@ func GenerateConfigMap(solrCloud *solr.SolrCloud) *corev1.ConfigMap {
annotations = MergeLabelsOrAnnotations(annotations, customOptions.Annotations)
}
- backupSection := GenerateBackupRepositoriesForSolrXml(solrCloud.Spec.BackupRepositories)
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.ConfigMapName(),
@@ -670,15 +640,50 @@ func GenerateConfigMap(solrCloud *solr.SolrCloud) *corev1.ConfigMap {
Annotations: annotations,
},
Data: map[string]string{
- "solr.xml": GenerateSolrXMLString(backupSection),
+ "solr.xml": GenerateSolrXMLStringForCloud(solrCloud),
},
}
return configMap
}
-func GenerateSolrXMLString(backupSection string) string {
- return fmt.Sprintf(DefaultSolrXML, backupSection)
+func GenerateSolrXMLStringForCloud(solrCloud *solr.SolrCloud) string {
+ backupSection, solrModules, additionalLibs := GenerateBackupRepositoriesForSolrXml(solrCloud.Spec.BackupRepositories)
+ solrModules = append(solrModules, solrCloud.Spec.SolrModules...)
+ additionalLibs = append(additionalLibs, solrCloud.Spec.AdditionalLibs...)
+ return GenerateSolrXMLString(backupSection, solrModules, additionalLibs)
+}
+
+func GenerateSolrXMLString(backupSection string, solrModules []string, additionalLibs []string) string {
+ return fmt.Sprintf(DefaultSolrXML, GenerateAdditionalLibXMLPart(solrModules, additionalLibs), backupSection)
+}
+
+func GenerateAdditionalLibXMLPart(solrModules []string, additionalLibs []string) string {
+ libs := make(map[string]bool, 0)
+
+ // Add all module library locations
+ if len(solrModules) > 0 {
+ libs[DistLibs] = true
+ }
+ for _, module := range solrModules {
+ libs[fmt.Sprintf(ContribLibs, module)] = true
+ }
+
+ // Add all custom library locations
+ for _, libPath := range additionalLibs {
+ 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("<str name=\"sharedLib\">%s</str>", strings.Join(libList, ","))
+ }
+ return libXml
}
// GenerateCommonService returns a new corev1.Service pointer generated for the entire SolrCloud instance
diff --git a/controllers/util/solr_util_test.go b/controllers/util/solr_util_test.go
index 5ebb728..5ffa8a9 100644
--- a/controllers/util/solr_util_test.go
+++ b/controllers/util/solr_util_test.go
@@ -25,7 +25,10 @@ import (
)
func TestNoRepositoryXmlGeneratedWhenNoRepositoriesExist(t *testing.T) {
- assert.Equal(t, "", GenerateBackupRepositoriesForSolrXml(make([]solr.SolrBackupRepository, 0)), "There should be no backup XML when no backupRepos are specified")
+ xmlString, modules, libs := GenerateBackupRepositoriesForSolrXml(make([]solr.SolrBackupRepository, 0))
+ assert.Equal(t, "", xmlString, "There should be no backup XML when no backupRepos are specified")
+ assert.Empty(t, modules, "There should be no modules for the backupRepos when no backupRepos are specified")
+ assert.Empty(t, libs, "There should be no libs for the backupRepos when no backupRepos are specified")
}
func TestGeneratedSolrXmlContainsEntryForEachRepository(t *testing.T) {
@@ -64,7 +67,7 @@ func TestGeneratedSolrXmlContainsEntryForEachRepository(t *testing.T) {
},
},
}
- xmlString := GenerateBackupRepositoriesForSolrXml(repos)
+ xmlString, modules, libs := GenerateBackupRepositoriesForSolrXml(repos)
// These assertions don't fully guarantee valid XML, but they at least make sure each repo is defined and uses the correct class.
// If we wanted to bring in an xpath library for assertions we could be a lot more comprehensive here.
@@ -73,6 +76,111 @@ func TestGeneratedSolrXmlContainsEntryForEachRepository(t *testing.T) {
assert.Containsf(t, xmlString, "<repository name=\"gcsrepository1\" class=\"org.apache.solr.gcs.GCSBackupRepository\">", "Did not find '%s' in the list of backup repositories", "gcsrepository1")
assert.Containsf(t, xmlString, "<repository name=\"gcsrepository2\" class=\"org.apache.solr.gcs.GCSBackupRepository\">", "Did not find '%s' in the list of backup repositories", "gcsrepository2")
- // Since GCS repositories are defined, make sure the contrib is on the classpath
- assert.Contains(t, xmlString, "<str name=\"sharedLib\">/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist</str>")
+ assert.Contains(t, modules, "gcs-repository", "The modules for the backupRepos should contain gcs-repository")
+ assert.Empty(t, libs, "There should be no libs for the backupRepos")
+}
+
+func TestGenerateAdditionalLibXMLPart(t *testing.T) {
+ // Just 1 repeated solr module
+ xmlString := GenerateAdditionalLibXMLPart([]string{"gcs-repository", "gcs-repository"}, []string{})
+ assert.EqualValuesf(t, xmlString, "<str name=\"sharedLib\">/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist</str>", "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, "<str name=\"sharedLib\">/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist</str>", "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, "<str name=\"sharedLib\">/ext/lib</str>", "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, "<str name=\"sharedLib\">/ext/lib1,/ext/lib2</str>", "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, "<str name=\"sharedLib\">/ext/lib1,/ext/lib2,/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist</str>", "Wrong sharedLib xml for mix of additional libs and solr modules")
+}
+
+func TestGenerateSolrXMLStringForCloud(t *testing.T) {
+ // All 3 options that factor into the sharedLib
+ solrCloud := &solr.SolrCloud{
+ Spec: solr.SolrCloudSpec{
+ BackupRepositories: []solr.SolrBackupRepository{
+ {
+ Name: "test",
+ GCS: &solr.GcsRepository{},
+ },
+ },
+ AdditionalLibs: []string{"/ext/lib2", "/ext/lib1"},
+ SolrModules: []string{"ltr", "analytics"},
+ },
+ }
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "<str name=\"sharedLib\">/ext/lib1,/ext/lib2,/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist</str>", "Wrong sharedLib xml for a cloud with a backupRepo, additionalLibs and solrModules")
+
+ // Just SolrModules and AdditionalLibs
+ solrCloud = &solr.SolrCloud{
+ Spec: solr.SolrCloudSpec{
+ AdditionalLibs: []string{"/ext/lib2", "/ext/lib1"},
+ SolrModules: []string{"ltr", "analytics"},
+ },
+ }
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "<str name=\"sharedLib\">/ext/lib1,/ext/lib2,/opt/solr/contrib/analytics/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist</str>", "Wrong sharedLib xml for a cloud with additionalLibs and solrModules")
+
+ // Just SolrModules and Backups
+ solrCloud = &solr.SolrCloud{
+ Spec: solr.SolrCloudSpec{
+ BackupRepositories: []solr.SolrBackupRepository{
+ {
+ Name: "test",
+ GCS: &solr.GcsRepository{},
+ },
+ },
+ SolrModules: []string{"ltr", "analytics"},
+ },
+ }
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "<str name=\"sharedLib\">/opt/solr/contrib/analytics/lib,/opt/solr/contrib/gcs-repository/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist</str>", "Wrong sharedLib xml for a cloud with a backupRepo and solrModules")
+
+ // Just AdditionalLibs and Backups
+ solrCloud = &solr.SolrCloud{
+ Spec: solr.SolrCloudSpec{
+ BackupRepositories: []solr.SolrBackupRepository{
+ {
+ Name: "test",
+ GCS: &solr.GcsRepository{},
+ },
+ },
+ AdditionalLibs: []string{"/ext/lib2", "/ext/lib1"},
+ },
+ }
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "<str name=\"sharedLib\">/ext/lib1,/ext/lib2,/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist</str>", "Wrong sharedLib xml for a cloud with a backupRepo and additionalLibs")
+
+ // Just SolrModules
+ solrCloud = &solr.SolrCloud{
+ Spec: solr.SolrCloudSpec{
+ SolrModules: []string{"ltr", "analytics"},
+ },
+ }
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "<str name=\"sharedLib\">/opt/solr/contrib/analytics/lib,/opt/solr/contrib/ltr/lib,/opt/solr/dist</str>", "Wrong sharedLib xml for a cloud with just solrModules")
+
+ // Just Backups
+ solrCloud = &solr.SolrCloud{
+ Spec: solr.SolrCloudSpec{
+ BackupRepositories: []solr.SolrBackupRepository{
+ {
+ Name: "test",
+ GCS: &solr.GcsRepository{},
+ },
+ },
+ },
+ }
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "<str name=\"sharedLib\">/opt/solr/contrib/gcs-repository/lib,/opt/solr/dist</str>", "Wrong sharedLib xml for a cloud with just a backupRepo")
+
+ // Just AdditionalLibs
+ solrCloud = &solr.SolrCloud{
+ Spec: solr.SolrCloudSpec{
+ AdditionalLibs: []string{"/ext/lib2", "/ext/lib1"},
+ },
+ }
+ assert.Containsf(t, GenerateSolrXMLStringForCloud(solrCloud), "<str name=\"sharedLib\">/ext/lib1,/ext/lib2</str>", "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 783d5ed..fb3bea9 100644
--- a/docs/solr-cloud/solr-cloud-crd.md
+++ b/docs/solr-cloud/solr-cloud-crd.md
@@ -20,6 +20,20 @@
The SolrCloud CRD allows users to spin up a Solr cloud in a very configurable way.
Those configuration options are laid out on this page.
+## Solr Options
+
+The SolrCloud CRD gives users the ability to customize how Solr is run.
+
+### Solr Modules and Additional Libraries
+_Since v0.5.0_
+
+Solr comes packaged with modules that can be loaded optionally, known as either Solr Modules or Solr Contrib Modules.
+By default they are not included in the classpath of Solr, so they have to be explicitly enabled.
+Use the **`SolrCloud.spec.solrModules`** property to add a list of module names, not paths, and they will automatically be enabled for the solrCloud.
+
+However, users might want to include custom code that is not an official Solr Module.
+In order to facilitate this, the **`SolrCloud.spec.additionalLibs`** property takes a list of paths to folders, containing jars to load in the classpath of the SolrCloud.
+
## Data Storage
The SolrCloud CRD gives the option for users to use either
diff --git a/example/test_solrcloud.yaml b/example/test_solrcloud.yaml
index 258ab9d..1757bb0 100644
--- a/example/test_solrcloud.yaml
+++ b/example/test_solrcloud.yaml
@@ -36,6 +36,9 @@ spec:
solrImage:
tag: 8.7.0
solrJavaMem: "-Xms1g -Xmx3g"
+ solrModules:
+ - jaegertracer-configurator
+ - ltr
customSolrKubeOptions:
podOptions:
resources:
diff --git a/helm/solr-operator/Chart.yaml b/helm/solr-operator/Chart.yaml
index 47d35fa..4d97f4f 100644
--- a/helm/solr-operator/Chart.yaml
+++ b/helm/solr-operator/Chart.yaml
@@ -100,6 +100,15 @@ annotations:
url: https://github.com/apache/solr-operator/issues/322
- name: Github PR
url: https://github.com/apache/solr-operator/pull/324
+ - kind: added
+ description: Add support for using Solr Modules (contrib) and additional libraries
+ links:
+ - name: Github Issue
+ url: https://github.com/apache/solr-operator/issues/329
+ - name: Github PR
+ url: https://github.com/apache/solr-operator/pull/332
+ - name: Solr Modules
+ url: https://github.com/apache/solr/tree/main/solr/contrib
artifacthub.io/images: |
- name: solr-operator
image: apache/solr-operator:v0.5.0-prerelease
diff --git a/helm/solr-operator/crds/crds.yaml b/helm/solr-operator/crds/crds.yaml
index 91dc769..d364816 100644
--- a/helm/solr-operator/crds/crds.yaml
+++ b/helm/solr-operator/crds/crds.yaml
@@ -1205,6 +1205,11 @@ spec:
spec:
description: SolrCloudSpec defines the desired state of SolrCloud
properties:
+ additionalLibs:
+ description: 'List of paths in the Solr Docker image to load in the classpath. Note: Solr Modules will be auto-loaded if specified in the "solrModules" property. There is no need to specify them here as well.'
+ items:
+ type: string
+ type: array
backupRepositories:
description: Allows specification of multiple different "repositories" for Solr to use when backing up data.
items:
@@ -6859,6 +6864,11 @@ spec:
solrLogLevel:
description: Set the Solr Log level, defaults to INFO
type: string
+ solrModules:
+ description: 'List of Solr Modules to be loaded when starting Solr Note: You do not need to specify a module if it is required by another property (e.g. backupRepositories[].gcs)'
+ items:
+ type: string
+ type: array
solrOpts:
description: You can add common system properties to the SOLR_OPTS environment variable SolrOpts is the string interface for these optional settings
type: string
diff --git a/helm/solr/README.md b/helm/solr/README.md
index 687a4ee..b001c2b 100644
--- a/helm/solr/README.md
+++ b/helm/solr/README.md
@@ -90,6 +90,8 @@ The command removes the SolrCloud resource, and then Kubernetes will garbage col
| solrOptions.javaOpts | string | `""` | Additional java arguments to pass via the command line |
| solrOptions.logLevel | string | `"INFO"` | Log level to run Solr under |
| solrOptions.gcTune | string | `""` | GC Tuning parameters for Solr |
+| solrOptions.solrModules | []string | | List of packaged Solr Modules to load when running Solr. Note: There is no need to specify solr modules necessary for other parts of the Spec (i.e. `backupRepositories[].gcs`), those will be added automatically. |
+| solrOptions.additionalLibs | []string | | List of paths in the Solr Image to add to the classPath when running Solr. Note: There is no need to include paths for solrModules here if already listed in `solrModules`, those paths will be added automatically. |
| solrOptions.security.authenticationType | string | `""` | Type of authentication to use for Solr |
| solrOptions.security.basicAuthSecret | string | `""` | Name of Secret in the same namespace that stores the basicAuth information for the Solr user |
| solrOptions.security.probesRequireAuth | boolean | | Whether the probes for the SolrCloud pod require auth |
diff --git a/helm/solr/templates/solrcloud.yaml b/helm/solr/templates/solrcloud.yaml
index 3916713..d05c08b 100644
--- a/helm/solr/templates/solrcloud.yaml
+++ b/helm/solr/templates/solrcloud.yaml
@@ -152,6 +152,16 @@ spec:
{{- end }}
{{- end }}
+ {{- if .Values.solrOptions.solrModules }}
+ solrModules:
+ {{- toYaml .Values.solrOptions.solrModules | nindent 4 }}
+ {{- end }}
+
+ {{- if .Values.solrOptions.additionalLibs }}
+ additionalLibs:
+ {{- toYaml .Values.solrOptions.additionalLibs | nindent 4 }}
+ {{- end }}
+
{{- if .Values.backupRepositories }}
backupRepositories:
{{- toYaml .Values.backupRepositories | nindent 4 }}
diff --git a/helm/solr/values.yaml b/helm/solr/values.yaml
index cdb7354..773a5a5 100644
--- a/helm/solr/values.yaml
+++ b/helm/solr/values.yaml
@@ -54,6 +54,8 @@ solrOptions:
javaOpts: ""
logLevel: ""
gcTune: ""
+ solrModules: []
+ additionalLibs: []
# Enable authentication for the Solr Cloud
# More information can be found at: