Copilot commented on code in PR #880:
URL: https://github.com/apache/solr-operator/pull/880#discussion_r4473998200


##########
controllers/solrcloud_controller.go:
##########
@@ -212,6 +234,10 @@ func (r *SolrCloudReconciler) Reconcile(ctx 
context.Context, req ctrl.Request) (
                }
        }
 
+       if err = r.reconcilePodMonitor(ctx, instance, logger); err != nil {
+               return requeueOrNot, err
+       }

Review Comment:
   A PodMonitor error here stops reconciliation before the ConfigMap, security 
configuration and StatefulSet are managed. For example, an invalid value in 
`podMonitor.labels` passes the SolrCloud schema but is rejected when creating 
the PodMonitor, blocking cloud creation or scaling. Report monitoring failures 
as warning events and schedule retries while continuing the rest of the cloud 
reconciliation.



##########
controllers/solrcloud_controller.go:
##########
@@ -1001,6 +1027,82 @@ func (r *SolrCloudReconciler) reconcileNodeService(ctx 
context.Context, logger l
 
        return nil, ip
 }
+
+// reconcilePodMonitor creates, updates or deletes the PodMonitor for the 
cloud.
+func (r *SolrCloudReconciler) reconcilePodMonitor(ctx context.Context, 
instance *solrv1beta1.SolrCloud, logger logr.Logger) error {
+       if !instance.UsesPodMonitor() {
+               return r.deletePodMonitor(ctx, instance, logger)
+       }
+
+       // Keep managing the rest of the SolrCloud: metrics are not worth 
stalling a Solr cluster over.
+       if !usePrometheusCRD {
+               msg := "Cannot create a PodMonitor, as the Solr Operator is not 
configured to use the Prometheus Operator CRDs. Install the Solr Operator with 
`--prometheus-operator=true` to enable this."
+               logger.Error(nil, msg)
+               r.Recorder.Event(instance, corev1.EventTypeWarning, 
"MissingPrometheusCRD", msg)
+               return nil
+       }
+
+       // TODO: Gate this on Solr 10, once #827 has landed and the operator 
can parse the Solr version.
+       // Solr 9 serves no prometheus-format metrics, so scraping it only 
produces errors. Deleting rather
+       // than skipping also covers a SolrCloud downgraded from Solr 10 back 
to Solr 9.
+       //
+       //      if major, err := instance.SolrMajorVersion(); err != nil {
+       //              return err
+       //      } else if major < 10 {
+       //              msg := fmt.Sprintf("Not creating a PodMonitor: Solr %d 
does not serve prometheus-format metrics at %s. Use a SolrPrometheusExporter 
instead.", major, util.SolrMetricsPath)
+       //              logger.Error(nil, msg)
+       //              r.Recorder.Event(instance, corev1.EventTypeWarning, 
"UnsupportedSolrVersion", msg)
+       //              return r.deletePodMonitor(ctx, instance, logger)
+       //      }
+
+       podMonitor := util.GeneratePodMonitor(instance)
+       podMonitorLogger := logger.WithValues("podMonitor", podMonitor.Name)
+
+       foundPodMonitor := &monitoringv1.PodMonitor{}
+       err := r.Get(ctx, types.NamespacedName{Name: podMonitor.Name, 
Namespace: podMonitor.Namespace}, foundPodMonitor)
+       if err != nil && errors.IsNotFound(err) {
+               podMonitorLogger.Info("Creating PodMonitor")
+               if err = controllerutil.SetControllerReference(instance, 
podMonitor, r.Scheme); err == nil {
+                       err = r.Create(ctx, podMonitor)
+               }
+       } else if err == nil {
+               var needsUpdate bool
+               needsUpdate, err = util.OvertakeControllerRef(instance, 
foundPodMonitor, r.Scheme)
+               needsUpdate = util.CopyPodMonitorFields(podMonitor, 
foundPodMonitor, podMonitorLogger) || needsUpdate
+
+               // Update the found PodMonitor and write the result back if 
there are any changes
+               if needsUpdate && err == nil {
+                       podMonitorLogger.Info("Updating PodMonitor")
+                       err = r.Update(ctx, foundPodMonitor)
+               }
+       }
+       return err
+}
+
+// deletePodMonitor removes a PodMonitor that the cloud no longer wants. The 
operator cannot have
+// created one without the Prometheus Operator CRDs, so there is nothing to 
look for in that case.
+func (r *SolrCloudReconciler) deletePodMonitor(ctx context.Context, instance 
*solrv1beta1.SolrCloud, logger logr.Logger) error {
+       if !usePrometheusCRD {
+               return nil
+       }
+
+       podMonitor := &monitoringv1.PodMonitor{}
+       err := r.Get(ctx, types.NamespacedName{Name: instance.PodMonitorName(), 
Namespace: instance.Namespace}, podMonitor)
+       if err != nil {
+               if errors.IsNotFound(err) {
+                       return nil
+               }
+               return err
+       }
+
+       logger.Info("Deleting PodMonitor, as it is no longer enabled for the 
SolrCloud", "podMonitor", podMonitor.Name)
+       err = r.Delete(ctx, podMonitor)

Review Comment:
   This deletes any PodMonitor with the generated name, even if the SolrCloud 
has never enabled PodMonitor management. Since Helm enables this integration 
automatically when the CRDs exist, upgrading can delete a hand-managed 
`<cloud>-solrcloud-metrics` PodMonitor without opting in. Check that the 
PodMonitor is controlled by this SolrCloud before deleting it.



##########
controllers/util/solr_metrics_util.go:
##########
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package util
+
+import (
+       "strconv"
+
+       monitoringv1 
"github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/utils/ptr"
+
+       solr "github.com/apache/solr-operator/api/v1beta1"
+)
+
+const (
+       // SolrMetricsPath is the path of Solr's metrics API
+       SolrMetricsPath = "/solr/admin/metrics"
+
+       // Solr 10 answers in the Prometheus exposition format even without 
this parameter, but asking for
+       // it explicitly means a change of that default cannot silently break 
scraping.
+       SolrMetricsWtParam      = "wt"
+       SolrMetricsWtPrometheus = "prometheus"
+
+       PrometheusScrapeAnnotation = "prometheus.io/scrape"
+       PrometheusSchemeAnnotation = "prometheus.io/scheme"
+       PrometheusPathAnnotation   = "prometheus.io/path"
+       PrometheusPortAnnotation   = "prometheus.io/port"
+       // The wt parameter needs an annotation of its own: Prometheus puts the 
path annotation in
+       // __metrics_path__, where a "?" is escaped. A scrape config maps this 
one to the __param_wt label.
+       PrometheusWtParamAnnotation = "prometheus.io/param-" + 
SolrMetricsWtParam
+)
+
+// PrometheusScrapeAnnotations returns the prometheus.io/* annotations 
describing how to scrape the
+// metrics API of a Solr pod. They are a convention, so only a scrape config 
acting on them has effect.
+func PrometheusScrapeAnnotations(solrCloud *solr.SolrCloud) map[string]string {
+       return map[string]string{
+               PrometheusScrapeAnnotation:  "true",
+               PrometheusSchemeAnnotation:  solrCloud.UrlScheme(false),
+               PrometheusPathAnnotation:    SolrMetricsPath,
+               PrometheusPortAnnotation:    
strconv.Itoa(solrCloud.Spec.SolrAddressability.PodPort),
+               PrometheusWtParamAnnotation: SolrMetricsWtPrometheus,
+       }
+}
+
+// GeneratePodMonitor returns a new monitoringv1.PodMonitor for the metrics 
API of the SolrCloud's
+// pods. One PodMonitor covers the whole cloud: the Prometheus Operator turns 
it into a single scrape
+// job that discovers every Solr pod as its own target, following the cloud as 
it scales.
+func GeneratePodMonitor(solrCloud *solr.SolrCloud) *monitoringv1.PodMonitor {
+       options := solrCloud.Spec.Prometheus.PodMonitor
+
+       endpoint := monitoringv1.PodMetricsEndpoint{
+               Port:          ptr.To(SolrClientPortName),
+               Path:          SolrMetricsPath,
+               Params:        map[string][]string{SolrMetricsWtParam: 
{SolrMetricsWtPrometheus}},
+               Scheme:        
ptr.To(monitoringv1.Scheme(solrCloud.UrlScheme(false))),
+               Interval:      monitoringv1.Duration(options.Interval),
+               ScrapeTimeout: monitoringv1.Duration(options.ScrapeTimeout),
+       }
+
+       // The secret of the SolrCloud is used unless another one is given, as 
the bootstrapped
+       // security.json grants its user access to /admin/metrics.
+       if solrCloud.Spec.SolrSecurity != nil && 
solrCloud.Spec.SolrSecurity.AuthenticationType == solr.Basic {
+               basicAuthSecret := options.BasicAuthSecret
+               if basicAuthSecret == "" {
+                       basicAuthSecret = solrCloud.BasicAuthSecretName()
+               }
+               endpoint.BasicAuth = &monitoringv1.BasicAuth{
+                       Username: corev1.SecretKeySelector{
+                               LocalObjectReference: 
corev1.LocalObjectReference{Name: basicAuthSecret},
+                               Key:                  
corev1.BasicAuthUsernameKey,
+                       },
+                       Password: corev1.SecretKeySelector{
+                               LocalObjectReference: 
corev1.LocalObjectReference{Name: basicAuthSecret},
+                               Key:                  
corev1.BasicAuthPasswordKey,
+                       },
+               }
+       }
+
+       if tlsConfig := options.TLSConfig; tlsConfig != nil {
+               endpoint.TLSConfig = &monitoringv1.SafeTLSConfig{
+                       InsecureSkipVerify: 
ptr.To(tlsConfig.InsecureSkipVerify),
+               }

Review Comment:
   For a SolrCloud with `spec.solrTLS.clientAuth: Need`, this produces an HTTPS 
scrape configuration without a client certificate or key, so scrapes fail 
during the TLS handshake even when `caSecret` is correct. The new TLS options 
cannot supply those credentials, and manually adding them to the PodMonitor is 
undone by drift correction. Expose PEM client-certificate/key secret selectors 
and populate `SafeTLSConfig.Cert` and `KeySecret` so PodMonitors can scrape 
this supported Solr TLS configuration.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to