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 9860911  Specify individual backupRepo availability in SolrCloud 
Status (#358)
9860911 is described below

commit 9860911ac24479bc23d447a73115d74ae96b4eb2
Author: Houston Putman <[email protected]>
AuthorDate: Thu Nov 4 13:14:39 2021 -0400

    Specify individual backupRepo availability in SolrCloud Status (#358)
---
 api/v1beta1/solrbackup_types.go                    |  8 ++
 api/v1beta1/solrcloud_types.go                     | 14 +++-
 api/v1beta1/zz_generated.deepcopy.go               |  7 ++
 config/crd/bases/solr.apache.org_solrbackups.yaml  |  6 ++
 config/crd/bases/solr.apache.org_solrclouds.yaml   | 14 +++-
 controllers/solrbackup_controller.go               | 86 ++++++++++++++++++----
 controllers/solrcloud_controller.go                | 44 ++++++-----
 controllers/solrcloud_controller_backup_test.go    | 54 +++++++++++++-
 .../solrprometheusexporter_controller_test.go      |  5 +-
 controllers/util/solr_api/cluster_status.go        | 48 ++++++------
 controllers/util/solr_backup_repo_util.go          | 28 +++++++
 controllers/util/solr_util.go                      |  2 +
 example/test_backup_gcs.yaml                       |  4 +-
 example/test_backup_managed.yaml                   |  2 +-
 example/test_solrcloud_backuprepos.yaml            | 27 ++++---
 helm/solr-operator/Chart.yaml                      |  7 ++
 helm/solr-operator/crds/crds.yaml                  | 20 ++++-
 17 files changed, 288 insertions(+), 88 deletions(-)

diff --git a/api/v1beta1/solrbackup_types.go b/api/v1beta1/solrbackup_types.go
index e81ee9e..ec0d151 100644
--- a/api/v1beta1/solrbackup_types.go
+++ b/api/v1beta1/solrbackup_types.go
@@ -27,10 +27,18 @@ import (
 // SolrBackupSpec defines the desired state of SolrBackup
 type SolrBackupSpec struct {
        // A reference to the SolrCloud to create a backup for
+       //
+       // +kubebuilder:validation:Pattern:=[a-z0-9]([-a-z0-9]*[a-z0-9])?
+       // +kubebuilder:validation:MinLength:=1
+       // +kubebuilder:validation:MaxLength:=63
        SolrCloud string `json:"solrCloud"`
 
        // The name of the repository to use for the backup.  Defaults to 
"legacy_local_repository" if not specified (the
        // auto-configured repository for legacy singleton volumes).
+       //
+       // 
+kubebuilder:validation:Pattern:=[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])?
+       // +kubebuilder:validation:MinLength:=1
+       // +kubebuilder:validation:MaxLength:=100
        // +optional
        RepositoryName string `json:"repositoryName,omitempty"`
 
diff --git a/api/v1beta1/solrcloud_types.go b/api/v1beta1/solrcloud_types.go
index 692afd4..ff265ec 100644
--- a/api/v1beta1/solrcloud_types.go
+++ b/api/v1beta1/solrcloud_types.go
@@ -385,6 +385,10 @@ type SolrBackupRestoreOptions struct {
 type SolrBackupRepository struct {
        // A name used to identify this local storage profile.  Values should 
follow RFC-1123.  (See here for more details:
        // 
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names)
+       //
+       // 
+kubebuilder:validation:Pattern:=[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])?
+       // +kubebuilder:validation:MinLength:=1
+       // +kubebuilder:validation:MaxLength:=100
        Name string `json:"name"`
 
        // A GCSRepository for Solr to use when backing up and restoring 
collections.
@@ -1013,16 +1017,16 @@ type SolrCloudStatus struct {
        // SolrNodes contain the statuses of each solr node running in this 
solr cloud.
        SolrNodes []SolrNodeStatus `json:"solrNodes"`
 
-       // Replicas is the number of number of desired replicas in the cluster
+       // Replicas is the number of desired replicas in the cluster
        Replicas int32 `json:"replicas"`
 
        // PodSelector for SolrCloud pods, required by the HPA
        PodSelector string `json:"podSelector"`
 
-       // ReadyReplicas is the number of number of ready replicas in the 
cluster
+       // ReadyReplicas is the number of ready replicas in the cluster
        ReadyReplicas int32 `json:"readyReplicas"`
 
-       // UpToDateNodes is the number of number of Solr Node pods that are 
running the latest pod spec
+       // UpToDateNodes is the number of Solr Node pods that are running the 
latest pod spec
        UpToDateNodes int32 `json:"upToDateNodes"`
 
        // The version of solr that the cloud is running
@@ -1047,6 +1051,10 @@ type SolrCloudStatus struct {
        // BackupRestoreReady announces whether the solrCloud has the 
backupRestorePVC mounted to all pods
        // and therefore is ready for backups and restores.
        BackupRestoreReady bool `json:"backupRestoreReady"`
+
+       // BackupRepositoriesAvailable lists the backupRepositories specified 
in the SolrCloud and whether they are available across all Pods.
+       // +optional
+       BackupRepositoriesAvailable map[string]bool 
`json:"backupRepositoriesAvailable,omitempty"`
 }
 
 // SolrNodeStatus is the status of a solrNode in the cloud, with readiness 
status
diff --git a/api/v1beta1/zz_generated.deepcopy.go 
b/api/v1beta1/zz_generated.deepcopy.go
index 79d356a..31031ee 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -1020,6 +1020,13 @@ func (in *SolrCloudStatus) DeepCopyInto(out 
*SolrCloudStatus) {
                **out = **in
        }
        in.ZookeeperConnectionInfo.DeepCopyInto(&out.ZookeeperConnectionInfo)
+       if in.BackupRepositoriesAvailable != nil {
+               in, out := &in.BackupRepositoriesAvailable, 
&out.BackupRepositoriesAvailable
+               *out = make(map[string]bool, len(*in))
+               for key, val := range *in {
+                       (*out)[key] = val
+               }
+       }
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new SolrCloudStatus.
diff --git a/config/crd/bases/solr.apache.org_solrbackups.yaml 
b/config/crd/bases/solr.apache.org_solrbackups.yaml
index ddb9ffe..248515a 100644
--- a/config/crd/bases/solr.apache.org_solrbackups.yaml
+++ b/config/crd/bases/solr.apache.org_solrbackups.yaml
@@ -1053,9 +1053,15 @@ spec:
                 type: object
               repositoryName:
                 description: The name of the repository to use for the backup. 
 Defaults to "legacy_local_repository" if not specified (the auto-configured 
repository for legacy singleton volumes).
+                maxLength: 100
+                minLength: 1
+                pattern: '[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])?'
                 type: string
               solrCloud:
                 description: A reference to the SolrCloud to create a backup 
for
+                maxLength: 63
+                minLength: 1
+                pattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?'
                 type: string
             required:
             - solrCloud
diff --git a/config/crd/bases/solr.apache.org_solrclouds.yaml 
b/config/crd/bases/solr.apache.org_solrclouds.yaml
index 4af3461..29ce391 100644
--- a/config/crd/bases/solr.apache.org_solrclouds.yaml
+++ b/config/crd/bases/solr.apache.org_solrclouds.yaml
@@ -1021,6 +1021,9 @@ spec:
                       type: object
                     name:
                       description: 'A name used to identify this local storage 
profile.  Values should follow RFC-1123.  (See here for more details: 
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names)'
+                      maxLength: 100
+                      minLength: 1
+                      pattern: '[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])?'
                       type: string
                     s3:
                       description: An S3Repository for Solr to use when 
backing up and restoring collections.
@@ -6927,6 +6930,11 @@ spec:
           status:
             description: SolrCloudStatus defines the observed state of 
SolrCloud
             properties:
+              backupRepositoriesAvailable:
+                additionalProperties:
+                  type: boolean
+                description: BackupRepositoriesAvailable lists the 
backupRepositories specified in the SolrCloud and whether they are available 
across all Pods.
+                type: object
               backupRestoreReady:
                 description: BackupRestoreReady announces whether the 
solrCloud has the backupRestorePVC mounted to all pods and therefore is ready 
for backups and restores.
                 type: boolean
@@ -6940,11 +6948,11 @@ spec:
                 description: PodSelector for SolrCloud pods, required by the 
HPA
                 type: string
               readyReplicas:
-                description: ReadyReplicas is the number of number of ready 
replicas in the cluster
+                description: ReadyReplicas is the number of ready replicas in 
the cluster
                 format: int32
                 type: integer
               replicas:
-                description: Replicas is the number of number of desired 
replicas in the cluster
+                description: Replicas is the number of desired replicas in the 
cluster
                 format: int32
                 type: integer
               solrNodes:
@@ -6986,7 +6994,7 @@ spec:
                 description: The version of solr that the cloud is meant to be 
running. Will only be provided when the cloud is migrating between versions
                 type: string
               upToDateNodes:
-                description: UpToDateNodes is the number of number of Solr 
Node pods that are running the latest pod spec
+                description: UpToDateNodes is the number of Solr Node pods 
that are running the latest pod spec
                 format: int32
                 type: integer
               version:
diff --git a/controllers/solrbackup_controller.go 
b/controllers/solrbackup_controller.go
index cfee5d4..4e8d41b 100644
--- a/controllers/solrbackup_controller.go
+++ b/controllers/solrbackup_controller.go
@@ -20,7 +20,12 @@ package controllers
 import (
        "context"
        "fmt"
+       "k8s.io/apimachinery/pkg/fields"
        "reflect"
+       "sigs.k8s.io/controller-runtime/pkg/builder"
+       "sigs.k8s.io/controller-runtime/pkg/handler"
+       "sigs.k8s.io/controller-runtime/pkg/predicate"
+       "sigs.k8s.io/controller-runtime/pkg/source"
        "time"
 
        "github.com/apache/solr-operator/controllers/util"
@@ -158,12 +163,10 @@ func (r *SolrBackupReconciler) 
reconcileSolrCloudBackup(ctx context.Context, bac
                        return solrCloud, actionTaken, err
                }
 
-               // Make sure that all solr nodes are active and have the 
backupRestore shared volume mounted
-               // TODO: we do not need all replicas to be healthy. We should 
just check that leaders exist for all shards. (or just let Solr do that)
-               cloudReady := solrCloud.Status.BackupRestoreReady && 
(solrCloud.Status.Replicas == solrCloud.Status.ReadyReplicas)
-               if !cloudReady {
-                       logger.Info("Cloud not ready for backup backup", 
"solrCloud", solrCloud.Name)
-                       return solrCloud, actionTaken, 
errors.NewServiceUnavailable("Cloud is not ready for backups or restores")
+               // Make sure that all solr living Solr pods have the backupRepo 
configured
+               if 
!solrCloud.Status.BackupRepositoriesAvailable[backupRepository.Name] {
+                       logger.Info("Cloud not ready for backup", "solrCloud", 
solrCloud.Name, "repository", backupRepository.Name)
+                       return solrCloud, actionTaken, 
errors.NewServiceUnavailable(fmt.Sprintf("Cloud is not ready for backups in the 
%s repository", backupRepository.Name))
                }
 
                // Only set the solr version at the start of the backup. This 
shouldn't change throughout the backup.
@@ -173,7 +176,9 @@ func (r *SolrBackupReconciler) reconcileSolrCloudBackup(ctx 
context.Context, bac
        // Go through each collection specified and reconcile the backup.
        for _, collection := range backup.Spec.Collections {
                // This will in-place update the CollectionBackupStatus in the 
backup object
-               _, err = reconcileSolrCollectionBackup(ctx, backup, solrCloud, 
backupRepository, collection, logger)
+               if _, err = reconcileSolrCollectionBackup(ctx, backup, 
solrCloud, backupRepository, collection, logger); err != nil {
+                       break
+               }
        }
 
        // First check if the collection backups have been completed
@@ -198,7 +203,8 @@ func reconcileSolrCollectionBackup(ctx context.Context, 
backup *solrv1beta1.Solr
        // If the collection backup hasn't started, start it
        if !collectionBackupStatus.InProgress && 
!collectionBackupStatus.Finished {
                // Start the backup by calling solr
-               started, err := util.StartBackupForCollection(ctx, solrCloud, 
backupRepository, backup, collection, logger)
+               var started bool
+               started, err = util.StartBackupForCollection(ctx, solrCloud, 
backupRepository, backup, collection, logger)
                if err != nil {
                        return true, err
                }
@@ -208,8 +214,10 @@ func reconcileSolrCollectionBackup(ctx context.Context, 
backup *solrv1beta1.Solr
                }
                collectionBackupStatus.BackupName = 
util.FullCollectionBackupName(collection, backup.Name)
        } else if collectionBackupStatus.InProgress {
+               var successful bool
+               var asyncStatus string
                // Check the state of the backup, when it is in progress, and 
update the state accordingly
-               finished, successful, asyncStatus, err := 
util.CheckBackupForCollection(ctx, solrCloud, collection, backup.Name, logger)
+               finished, successful, asyncStatus, err = 
util.CheckBackupForCollection(ctx, solrCloud, collection, backup.Name, logger)
                if err != nil {
                        return false, err
                }
@@ -240,11 +248,63 @@ func reconcileSolrCollectionBackup(ctx context.Context, 
backup *solrv1beta1.Solr
 }
 
 // SetupWithManager sets up the controller with the Manager.
-func (r *SolrBackupReconciler) SetupWithManager(mgr ctrl.Manager) error {
+func (r *SolrBackupReconciler) SetupWithManager(mgr ctrl.Manager) (err error) {
        r.config = mgr.GetConfig()
 
-       return ctrl.NewControllerManagedBy(mgr).
+       ctrlBuilder := ctrl.NewControllerManagedBy(mgr).
                For(&solrv1beta1.SolrBackup{}).
-               Owns(&batchv1.Job{}).
-               Complete(r)
+               Owns(&batchv1.Job{})
+
+       ctrlBuilder, err = r.indexAndWatchForSolrClouds(mgr, ctrlBuilder)
+       if err != nil {
+               return err
+       }
+
+       return ctrlBuilder.Complete(r)
+}
+
+func (r *SolrBackupReconciler) indexAndWatchForSolrClouds(mgr ctrl.Manager, 
ctrlBuilder *builder.Builder) (*builder.Builder, error) {
+       solrCloudField := ".spec.solrCloud"
+
+       if err := mgr.GetFieldIndexer().IndexField(context.Background(), 
&solrv1beta1.SolrBackup{}, solrCloudField, func(rawObj client.Object) []string {
+               // grab the SolrBackup object, extract the used SolrCloud...
+               return []string{rawObj.(*solrv1beta1.SolrBackup).Spec.SolrCloud}
+       }); err != nil {
+               return ctrlBuilder, err
+       }
+
+       return ctrlBuilder.Watches(
+               &source.Kind{Type: &solrv1beta1.SolrCloud{}},
+               handler.EnqueueRequestsFromMapFunc(func(obj client.Object) 
[]reconcile.Request {
+                       solrCloud := obj.(*solrv1beta1.SolrCloud)
+                       foundBackups := &solrv1beta1.SolrBackupList{}
+                       listOps := &client.ListOptions{
+                               FieldSelector: 
fields.OneTermEqualSelector(solrCloudField, obj.GetName()),
+                               Namespace:     obj.GetNamespace(),
+                       }
+                       err := r.List(context.Background(), foundBackups, 
listOps)
+                       if err != nil {
+                               // if no exporters found, just no-op this
+                               return []reconcile.Request{}
+                       }
+
+                       requests := make([]reconcile.Request, 0)
+                       for _, item := range foundBackups.Items {
+                               // Only queue the request if the Cloud is ready.
+                               cloudIsReady := 
solrCloud.Status.BackupRestoreReady
+                               if item.Spec.RepositoryName != "" {
+                                       cloudIsReady = 
solrCloud.Status.BackupRepositoriesAvailable[item.Spec.RepositoryName]
+                               }
+                               if cloudIsReady {
+                                       requests = append(requests, 
reconcile.Request{
+                                               NamespacedName: 
types.NamespacedName{
+                                                       Name:      
item.GetName(),
+                                                       Namespace: 
item.GetNamespace(),
+                                               },
+                                       })
+                               }
+                       }
+                       return requests
+               }),
+               
builder.WithPredicates(predicate.GenerationChangedPredicate{})), nil
 }
diff --git a/controllers/solrcloud_controller.go 
b/controllers/solrcloud_controller.go
index b854855..8d0153c 100644
--- a/controllers/solrcloud_controller.go
+++ b/controllers/solrcloud_controller.go
@@ -115,7 +115,7 @@ func (r *SolrCloudReconciler) Reconcile(ctx 
context.Context, req ctrl.Request) (
        newStatus := solrv1beta1.SolrCloudStatus{}
 
        blockReconciliationOfStatefulSet := false
-       if err := r.reconcileZk(ctx, logger, instance, &newStatus); err != nil {
+       if err = r.reconcileZk(ctx, logger, instance, &newStatus); err != nil {
                return requeueOrNot, err
        }
 
@@ -511,9 +511,12 @@ func (r *SolrCloudReconciler) reconcileCloudStatus(ctx 
context.Context, solrClou
                return outOfDatePods, outOfDatePodsNotStarted, 
availableUpdatedPodCount, err
        }
        newStatus.PodSelector = selector.String()
-       allPodsBackupReady := true
-       for idx, p := range foundPods.Items {
-               nodeNames[idx] = p.Name
+       backupReposAvailable := make(map[string]bool, 
len(solrCloud.Spec.BackupRepositories))
+       for _, repo := range solrCloud.Spec.BackupRepositories {
+               backupReposAvailable[repo.Name] = false
+       }
+       for podIdx, p := range foundPods.Items {
+               nodeNames[podIdx] = p.Name
                nodeStatus := solrv1beta1.SolrNodeStatus{}
                nodeStatus.Name = p.Name
                nodeStatus.NodeName = p.Spec.NodeName
@@ -540,9 +543,10 @@ func (r *SolrCloudReconciler) reconcileCloudStatus(ctx 
context.Context, solrClou
                        newStatus.ReadyReplicas += 1
                }
 
-               // Skip "backup-readiness" check for pod if we've already found 
a pod that's not ready
-               if allPodsBackupReady {
-                       allPodsBackupReady = allPodsBackupReady && 
isPodReadyForBackup(&p, solrCloud)
+               // Merge BackupRepository availability for this pod
+               backupReposAvailableForPod := util.GetAvailableBackupRepos(&p)
+               for repo, availableSoFar := range backupReposAvailable {
+                       backupReposAvailable[repo] = (availableSoFar || podIdx 
== 0) && backupReposAvailableForPod[repo]
                }
 
                // A pod is out of date if it's revision label is not equal to 
the statefulSetStatus' updateRevision.
@@ -581,10 +585,16 @@ func (r *SolrCloudReconciler) reconcileCloudStatus(ctx 
context.Context, solrClou
        for idx, nodeName := range nodeNames {
                newStatus.SolrNodes[idx] = nodeStatusMap[nodeName]
        }
-       if allPodsBackupReady && len(foundPods.Items) > 0 {
-               newStatus.BackupRestoreReady = true
-       } else {
-               newStatus.BackupRestoreReady = false
+       if len(backupReposAvailable) > 0 {
+               newStatus.BackupRepositoriesAvailable = backupReposAvailable
+               allPodsBackupReady := len(backupReposAvailable) > 0
+               for _, backupRepo := range solrCloud.Spec.BackupRepositories {
+                       allPodsBackupReady = allPodsBackupReady && 
backupReposAvailable[backupRepo.Name]
+                       if !allPodsBackupReady {
+                               break
+                       }
+               }
+               newStatus.BackupRestoreReady = allPodsBackupReady
        }
 
        // If there are multiple versions of solr running, use the first 
otherVersion as the current running solr version of the cloud
@@ -605,18 +615,6 @@ func (r *SolrCloudReconciler) reconcileCloudStatus(ctx 
context.Context, solrClou
        return outOfDatePods, outOfDatePodsNotStarted, 
availableUpdatedPodCount, nil
 }
 
-func isPodReadyForBackup(pod *corev1.Pod, solrCloud *solrv1beta1.SolrCloud) 
bool {
-       // If solrcloud doesn't request backup support then everything is 
'ready' implicitly
-       if len(solrCloud.Spec.BackupRepositories) == 0 {
-               return false
-       }
-
-       // TODO: There is no way to possibly do this with the new S3 option.
-       // This is wrong, but not the end of the world.
-       // Replace with new functionality in 
https://github.com/apache/solr-operator/issues/326
-       return true
-}
-
 func (r *SolrCloudReconciler) reconcileNodeService(ctx context.Context, logger 
logr.Logger, instance *solrv1beta1.SolrCloud, nodeName string) (err error, ip 
string) {
        // Generate Node Service
        service := util.GenerateNodeService(instance, nodeName)
diff --git a/controllers/solrcloud_controller_backup_test.go 
b/controllers/solrcloud_controller_backup_test.go
index 55eef65..e70fe5c 100644
--- a/controllers/solrcloud_controller_backup_test.go
+++ b/controllers/solrcloud_controller_backup_test.go
@@ -75,6 +75,7 @@ var _ = FDescribe("SolrCloud controller - Backup 
Repositories", func() {
                                        PodOptions: &solrv1beta1.PodOptions{
                                                EnvVariables: extraVars,
                                                Volumes:      extraVolumes,
+                                               Annotations:  
testPodAnnotations,
                                        },
                                },
                                BackupRepositories: 
[]solrv1beta1.SolrBackupRepository{
@@ -98,7 +99,10 @@ var _ = FDescribe("SolrCloud controller - Backup 
Repositories", func() {
 
                        // Annotations for the solrxml configMap
                        solrXmlMd5 := fmt.Sprintf("%x", 
md5.Sum([]byte(configMap.Data[util.SolrXmlFile])))
-                       
Expect(statefulSet.Spec.Template.Annotations).To(HaveKeyWithValue(util.SolrXmlMd5Annotation,
 solrXmlMd5), "Wrong solr.xml MD5 annotation in the pod template!")
+                       
Expect(statefulSet.Spec.Template.Annotations).To(Equal(util.MergeLabelsOrAnnotations(testPodAnnotations,
 map[string]string{
+                               "solr.apache.org/solrXmlMd5":          
solrXmlMd5,
+                               util.SolrBackupRepositoriesAnnotation: 
"test-repo",
+                       })), "Incorrect pod annotations")
 
                        // Env Variable Tests
                        expectedEnvVars := map[string]string{
@@ -273,4 +277,52 @@ var _ = FDescribe("SolrCloud controller - Backup 
Repositories", func() {
                        })
                })
        })
+
+       FContext("Multiple Repositories - Annotations", func() {
+               BeforeEach(func() {
+                       solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+                               ZookeeperRef: &solrv1beta1.ZookeeperRef{
+                                       ConnectionInfo: 
&solrv1beta1.ZookeeperConnectionInfo{
+                                               InternalConnectionString: 
"host:7271",
+                                       },
+                               },
+                               CustomSolrKubeOptions: 
solrv1beta1.CustomSolrKubeOptions{
+                                       PodOptions: &solrv1beta1.PodOptions{
+                                               EnvVariables: extraVars,
+                                               Volumes:      extraVolumes,
+                                       },
+                               },
+                               BackupRepositories: 
[]solrv1beta1.SolrBackupRepository{
+                                       {
+                                               Name: "test-repo",
+                                               S3: &solrv1beta1.S3Repository{
+                                                       Region: "test-region",
+                                                       Bucket: "test-bucket",
+                                               },
+                                       },
+                                       {
+                                               Name: "another",
+                                               S3: &solrv1beta1.S3Repository{
+                                                       Region: "test-region-2",
+                                                       Bucket: "test-bucket-2",
+                                               },
+                                       },
+                               },
+                       }
+               })
+               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 with explicit volumes 
and envVars before adding S3Repo credentials")
+                       // Make sure envVars and Volumes are correct be
+                       statefulSet := expectStatefulSet(ctx, solrCloud, 
solrCloud.StatefulSetName())
+
+                       // Annotations for the solrxml configMap
+                       
Expect(statefulSet.Spec.Template.Annotations).To(Equal(map[string]string{
+                               "solr.apache.org/solrXmlMd5":          
fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data["solr.xml"]))),
+                               util.SolrBackupRepositoriesAnnotation: 
"another,test-repo",
+                       }), "Incorrect pod annotations")
+               })
+       })
 })
diff --git a/controllers/solrprometheusexporter_controller_test.go 
b/controllers/solrprometheusexporter_controller_test.go
index 7a6ae59..c9c8541 100644
--- a/controllers/solrprometheusexporter_controller_test.go
+++ b/controllers/solrprometheusexporter_controller_test.go
@@ -190,8 +190,9 @@ var _ = FDescribe("SolrPrometheusExporter controller - 
General", func() {
                        
Expect(deployment.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedDeploymentLabels,
 testDeploymentLabels)), "Incorrect deployment labels")
                        
Expect(deployment.Annotations).To(Equal(testDeploymentAnnotations), "Incorrect 
deployment annotations")
                        
Expect(deployment.Spec.Template.ObjectMeta.Labels).To(Equal(util.MergeLabelsOrAnnotations(expectedDeploymentLabels,
 testPodLabels)), "Incorrect pod labels")
-                       
testPodAnnotations[util.PrometheusExporterConfigXmlMd5Annotation] = 
fmt.Sprintf("%x", md5.Sum([]byte(testExporterConfig)))
-                       
Expect(deployment.Spec.Template.ObjectMeta.Annotations).To(Equal(testPodAnnotations),
 "Incorrect pod annotations")
+                       
Expect(deployment.Spec.Template.ObjectMeta.Annotations).To(Equal(util.MergeLabelsOrAnnotations(testPodAnnotations,
 map[string]string{
+                               util.PrometheusExporterConfigXmlMd5Annotation: 
fmt.Sprintf("%x", md5.Sum([]byte(testExporterConfig))),
+                       })), "Incorrect pod annotations")
 
                        
Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(len(extraContainers1)+1),
 "Wrong number of containers for the Deployment")
                        
Expect(deployment.Spec.Template.Spec.Containers[1:]).To(Equal(extraContainers1),
 "Incorrect sidecar containers")
diff --git a/controllers/util/solr_api/cluster_status.go 
b/controllers/util/solr_api/cluster_status.go
index a1768e5..ac39bc7 100644
--- a/controllers/util/solr_api/cluster_status.go
+++ b/controllers/util/solr_api/cluster_status.go
@@ -17,73 +17,75 @@
 
 package solr_api
 
+import "k8s.io/apimachinery/pkg/util/intstr"
+
 type SolrOverseerStatusResponse struct {
        ResponseHeader SolrResponseHeader `json:"responseHeader"`
 
        // +optional
-       Leader string `json:"leader"`
+       Leader string `json:"leader,omitempty"`
 
        // +optional
-       QueueSize int `json:"overseer_queue_size"`
+       QueueSize int `json:"overseer_queue_size,omitempty"`
 
        // +optional
-       WorkQueueSize int `json:"overseer_work_queue_size"`
+       WorkQueueSize int `json:"overseer_work_queue_size,omitempty"`
 
        // +optional
-       CollectionQueueSize int `json:"overseer_collection_queue_size"`
+       CollectionQueueSize int 
`json:"overseer_collection_queue_size,omitempty"`
 }
 
 type SolrClusterStatusResponse struct {
        ResponseHeader SolrResponseHeader `json:"responseHeader"`
 
        // +optional
-       ClusterStatus SolrClusterStatus `json:"cluster"`
+       ClusterStatus SolrClusterStatus `json:"cluster,omitempty"`
 }
 
 type SolrClusterStatus struct {
        // +optional
-       Collections map[string]SolrCollectionStatus `json:"collections"`
+       Collections map[string]SolrCollectionStatus 
`json:"collections,omitempty"`
 
        // +optional
-       Aliases map[string]string `json:"aliases"`
+       Aliases map[string]string `json:"aliases,omitempty"`
 
        // +optional
-       Roles map[string][]string `json:"roles"`
+       Roles map[string][]string `json:"roles,omitempty"`
 
        // +optional
-       LiveNodes []string `json:"live_nodes"`
+       LiveNodes []string `json:"live_nodes,omitempty"`
 }
 
 type SolrCollectionStatus struct {
        // +optional
-       Shards map[string]SolrShardStatus `json:"shards"`
+       Shards map[string]SolrShardStatus `json:"shards,omitempty"`
 
        // +optional
-       ConfigName string `json:"configName"`
+       ConfigName string `json:"configName,omitempty"`
 
        // +optional
-       ZnodeVersion string `json:"znodeVersion"`
+       ZnodeVersion intstr.IntOrString `json:"znodeVersion,omitempty"`
 
        // +optional
-       AutoAddReplicas string `json:"autoAddReplicas"`
+       AutoAddReplicas string `json:"autoAddReplicas,omitempty"`
 
        // +optional
-       NrtReplicas int `json:"nrtReplicas"`
+       NrtReplicas intstr.IntOrString `json:"nrtReplicas,omitempty"`
 
        // +optional
-       TLogReplicas int `json:"tlogReplicas"`
+       TLogReplicas intstr.IntOrString `json:"tlogReplicas,omitempty"`
 
        // +optional
-       PullReplicas int `json:"pullReplicas"`
+       PullReplicas intstr.IntOrString `json:"pullReplicas,omitempty"`
 
        // +optional
-       MaxShardsPerNode string `json:"maxShardsPerNode"`
+       MaxShardsPerNode intstr.IntOrString `json:"maxShardsPerNode,omitempty"`
 
        // +optional
-       ReplicationFactor string `json:"replicationFactor"`
+       ReplicationFactor intstr.IntOrString 
`json:"replicationFactor,omitempty"`
 
        // +optional
-       Router SolrCollectionRouter `json:"router"`
+       Router SolrCollectionRouter `json:"router,omitempty"`
 }
 
 type SolrCollectionRouter struct {
@@ -92,13 +94,13 @@ type SolrCollectionRouter struct {
 
 type SolrShardStatus struct {
        // +optional
-       Replicas map[string]SolrReplicaStatus `json:"replicas"`
+       Replicas map[string]SolrReplicaStatus `json:"replicas,omitempty"`
 
        // +optional
-       Range string `json:"range"`
+       Range string `json:"range,omitempty"`
 
        // +optional
-       State SolrShardState `json:"state"`
+       State SolrShardState `json:"state,omitempty"`
 }
 
 type SolrShardState string
@@ -120,7 +122,7 @@ type SolrReplicaStatus struct {
        Leader bool `json:"leader,string"`
 
        // +optional
-       Type SolrReplicaType `json:"type"`
+       Type SolrReplicaType `json:"type,omitempty"`
 }
 
 type SolrReplicaState string
diff --git a/controllers/util/solr_backup_repo_util.go 
b/controllers/util/solr_backup_repo_util.go
index ced61cf..55f34f6 100644
--- a/controllers/util/solr_backup_repo_util.go
+++ b/controllers/util/solr_backup_repo_util.go
@@ -30,6 +30,8 @@ const (
 
        GCSCredentialSecretKey = "service-account-key.json"
        S3CredentialFileName   = "credentials"
+
+       SolrBackupRepositoriesAnnotation = "solr.apache.org/backupRepositories"
 )
 
 func RepoVolumeName(repo *solrv1beta1.SolrBackupRepository) string {
@@ -229,3 +231,29 @@ func BackupLocationPath(repo 
*solrv1beta1.SolrBackupRepository, backupLocation s
        }
        return backupLocation
 }
+
+func GetAvailableBackupRepos(pod *corev1.Pod) (repos map[string]bool) {
+       if availableRepos, hasAny := 
pod.Annotations[SolrBackupRepositoriesAnnotation]; hasAny {
+               repoNames := strings.Split(availableRepos, ",")
+               repos = make(map[string]bool, len(repoNames))
+               for _, repoName := range repoNames {
+                       repos[repoName] = true
+               }
+       }
+       return
+}
+
+func SetAvailableBackupRepos(solrCloud *solrv1beta1.SolrCloud, podAnnotations 
map[string]string) map[string]string {
+       if len(solrCloud.Spec.BackupRepositories) > 0 {
+               if podAnnotations == nil {
+                       podAnnotations = make(map[string]string, 1)
+               }
+               repoNames := make([]string, 
len(solrCloud.Spec.BackupRepositories))
+               for idx, repo := range solrCloud.Spec.BackupRepositories {
+                       repoNames[idx] = repo.Name
+               }
+               sort.Strings(repoNames)
+               podAnnotations[SolrBackupRepositoriesAnnotation] = 
strings.Join(repoNames, ",")
+       }
+       return podAnnotations
+}
diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go
index 5b6b41b..9b122bc 100644
--- a/controllers/util/solr_util.go
+++ b/controllers/util/solr_util.go
@@ -211,6 +211,8 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, 
solrCloudStatus *solr.SolrCl
                        backupEnvVars = append(backupEnvVars, repoEnvVars...)
                }
        }
+       // Add annotation specifying the backupRepositories available with this 
version of the Pod.
+       podAnnotations = SetAvailableBackupRepos(solrCloud, podAnnotations)
 
        if nil != customPodOptions {
                // Add Custom Volumes to pod
diff --git a/example/test_backup_gcs.yaml b/example/test_backup_gcs.yaml
index f3e4ee3..778fabc 100644
--- a/example/test_backup_gcs.yaml
+++ b/example/test_backup_gcs.yaml
@@ -21,7 +21,7 @@ metadata:
   name: main-collection-gcs-backups
   namespace: default
 spec:
-  repositoryName: main_collection_backup_repository
-  solrCloud: multiple-backup-repositories-solr
+  repositoryName: "main_collection_backup_repository"
+  solrCloud: multiple-backup-repos
   collections:
     - example
diff --git a/example/test_backup_managed.yaml b/example/test_backup_managed.yaml
index 3364c49..f66bd10 100644
--- a/example/test_backup_managed.yaml
+++ b/example/test_backup_managed.yaml
@@ -21,6 +21,6 @@ metadata:
   namespace: default
 spec:
   repositoryName: "managed_repository_1"
-  solrCloud: multiple-backup-repositories-solr
+  solrCloud: multiple-backup-repos
   collections:
     - example
diff --git a/example/test_solrcloud_backuprepos.yaml 
b/example/test_solrcloud_backuprepos.yaml
index 8de031c..6cb2922 100644
--- a/example/test_solrcloud_backuprepos.yaml
+++ b/example/test_solrcloud_backuprepos.yaml
@@ -16,7 +16,7 @@
 apiVersion: solr.apache.org/v1beta1
 kind: SolrCloud
 metadata:
-  name: multiple-backup-repositories-solr
+  name: multiple-backup-repos
 spec:
   replicas: 1
   solrImage:
@@ -54,19 +54,18 @@ spec:
         region: "us-west-2"
         bucket: "product-catalog"
         credentials:
-          credentials:
-            accessKeyIdSecret: # Optional
-              name: aws-secrets
-              key: access-key-id
-            secretAccessKeySecret: # Optional
-              name: aws-secrets
-              key: secret-access-key
-            sessionTokenSecret: # Optional
-              name: aws-secrets
-              key: session-token
-            credentialsFileSecret: # Optional
-              name: aws-credentials
-              key: credentials
+          accessKeyIdSecret: # Optional
+            name: aws-secrets
+            key: access-key-id
+          secretAccessKeySecret: # Optional
+            name: aws-secrets
+            key: secret-access-key
+          sessionTokenSecret: # Optional
+            name: aws-secrets
+            key: session-token
+          credentialsFileSecret: # Optional
+            name: aws-credentials
+            key: credentials
 
     - name: "main_collection_backup_repository_log"
       gcs:
diff --git a/helm/solr-operator/Chart.yaml b/helm/solr-operator/Chart.yaml
index 4f9bd1b..257cb62 100644
--- a/helm/solr-operator/Chart.yaml
+++ b/helm/solr-operator/Chart.yaml
@@ -176,6 +176,13 @@ annotations:
           url: https://github.com/apache/solr-operator/issues/352
         - name: Github PR
           url: https://github.com/apache/solr-operator/pull/361
+    - kind: added
+      description: Separate SolrCloud backup ready status by backup repository
+      links:
+        - name: Github Issue
+          url: https://github.com/apache/solr-operator/issues/326
+        - name: Github PR
+          url: https://github.com/apache/solr-operator/pull/358
   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 a2d4ccd..6ff1bd1 100644
--- a/helm/solr-operator/crds/crds.yaml
+++ b/helm/solr-operator/crds/crds.yaml
@@ -1053,9 +1053,15 @@ spec:
                 type: object
               repositoryName:
                 description: The name of the repository to use for the backup. 
 Defaults to "legacy_local_repository" if not specified (the auto-configured 
repository for legacy singleton volumes).
+                maxLength: 100
+                minLength: 1
+                pattern: '[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])?'
                 type: string
               solrCloud:
                 description: A reference to the SolrCloud to create a backup 
for
+                maxLength: 63
+                minLength: 1
+                pattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?'
                 type: string
             required:
             - solrCloud
@@ -2155,6 +2161,9 @@ spec:
                       type: object
                     name:
                       description: 'A name used to identify this local storage 
profile.  Values should follow RFC-1123.  (See here for more details: 
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names)'
+                      maxLength: 100
+                      minLength: 1
+                      pattern: '[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])?'
                       type: string
                     s3:
                       description: An S3Repository for Solr to use when 
backing up and restoring collections.
@@ -8061,6 +8070,11 @@ spec:
           status:
             description: SolrCloudStatus defines the observed state of 
SolrCloud
             properties:
+              backupRepositoriesAvailable:
+                additionalProperties:
+                  type: boolean
+                description: BackupRepositoriesAvailable lists the 
backupRepositories specified in the SolrCloud and whether they are available 
across all Pods.
+                type: object
               backupRestoreReady:
                 description: BackupRestoreReady announces whether the 
solrCloud has the backupRestorePVC mounted to all pods and therefore is ready 
for backups and restores.
                 type: boolean
@@ -8074,11 +8088,11 @@ spec:
                 description: PodSelector for SolrCloud pods, required by the 
HPA
                 type: string
               readyReplicas:
-                description: ReadyReplicas is the number of number of ready 
replicas in the cluster
+                description: ReadyReplicas is the number of ready replicas in 
the cluster
                 format: int32
                 type: integer
               replicas:
-                description: Replicas is the number of number of desired 
replicas in the cluster
+                description: Replicas is the number of desired replicas in the 
cluster
                 format: int32
                 type: integer
               solrNodes:
@@ -8120,7 +8134,7 @@ spec:
                 description: The version of solr that the cloud is meant to be 
running. Will only be provided when the cloud is migrating between versions
                 type: string
               upToDateNodes:
-                description: UpToDateNodes is the number of number of Solr 
Node pods that are running the latest pod spec
+                description: UpToDateNodes is the number of Solr Node pods 
that are running the latest pod spec
                 format: int32
                 type: integer
               version:

Reply via email to