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


##########
controllers/solrcloud_controller.go:
##########
@@ -436,6 +441,273 @@ func (r *SolrCloudReconciler) Reconcile(ctx 
context.Context, req ctrl.Request) (
                }
        }
 
+       // Reconcile Gateway API HTTPRoutes
+       if extAddressabilityOpts != nil && extAddressabilityOpts.Method == 
solrv1beta1.Gateway {
+               // Validate that gateway config is provided
+               if extAddressabilityOpts.Gateway == nil || 
len(extAddressabilityOpts.Gateway.ParentRefs) == 0 {
+                       return requeueOrNot, fmt.Errorf("gateway.parentRefs is 
required when using method=Gateway")
+               }
+
+               // Reconcile Common HTTPRoute (if not hidden)
+               if !extAddressabilityOpts.HideCommon {
+                       commonHTTPRoute := 
util.GenerateCommonHTTPRoute(instance, solrNodeNames)
+                       commonHTTPRouteLogger := logger.WithValues("httproute", 
commonHTTPRoute.Name)
+                       foundCommonHTTPRoute := &gatewayv1.HTTPRoute{}
+                       err = r.Get(ctx, types.NamespacedName{Name: 
commonHTTPRoute.Name, Namespace: commonHTTPRoute.Namespace}, 
foundCommonHTTPRoute)
+                       if err != nil && errors.IsNotFound(err) {
+                               commonHTTPRouteLogger.Info("Creating common 
HTTPRoute")
+                               if err = 
controllerutil.SetControllerReference(instance, commonHTTPRoute, r.Scheme); err 
== nil {
+                                       err = r.Create(ctx, commonHTTPRoute)
+                               }
+                       } else if err == nil {
+                               var needsUpdate bool
+                               needsUpdate, err = 
util.OvertakeControllerRef(instance, foundCommonHTTPRoute, r.Scheme)
+                               needsUpdate = 
util.CopyHTTPRouteFields(commonHTTPRoute, foundCommonHTTPRoute, 
commonHTTPRouteLogger) || needsUpdate
+
+                               if needsUpdate && err == nil {
+                                       commonHTTPRouteLogger.Info("Updating 
common HTTPRoute")
+                                       err = r.Update(ctx, 
foundCommonHTTPRoute)
+                               }
+                       }
+                       if err != nil {
+                               return requeueOrNot, err
+                       }
+               } else {
+                       // Delete common HTTPRoute if it exists but should be 
hidden
+                       foundCommonHTTPRoute := &gatewayv1.HTTPRoute{}
+                       err := r.Get(ctx, types.NamespacedName{Name: 
instance.CommonHTTPRouteName(), Namespace: instance.GetNamespace()}, 
foundCommonHTTPRoute)
+                       if err == nil {
+                               logger.Info("Deleting common HTTPRoute 
(hideCommon=true)")
+                               err = r.Delete(ctx, foundCommonHTTPRoute)
+                               if err != nil && !errors.IsNotFound(err) {
+                                       return requeueOrNot, err
+                               }
+                       }
+               }
+
+               // Reconcile Node HTTPRoutes (if not hidden)
+               if !extAddressabilityOpts.HideNodes {
+                       for _, nodeName := range solrNodeNames {
+                               nodeHTTPRoute := 
util.GenerateNodeHTTPRoute(instance, nodeName)
+                               nodeHTTPRouteLogger := 
logger.WithValues("httproute", nodeHTTPRoute.Name)
+                               foundNodeHTTPRoute := &gatewayv1.HTTPRoute{}
+                               err = r.Get(ctx, types.NamespacedName{Name: 
nodeHTTPRoute.Name, Namespace: nodeHTTPRoute.Namespace}, foundNodeHTTPRoute)
+                               if err != nil && errors.IsNotFound(err) {
+                                       nodeHTTPRouteLogger.Info("Creating node 
HTTPRoute")
+                                       if err = 
controllerutil.SetControllerReference(instance, nodeHTTPRoute, r.Scheme); err 
== nil {
+                                               err = r.Create(ctx, 
nodeHTTPRoute)
+                                       }
+                               } else if err == nil {
+                                       var needsUpdate bool
+                                       needsUpdate, err = 
util.OvertakeControllerRef(instance, foundNodeHTTPRoute, r.Scheme)
+                                       needsUpdate = 
util.CopyHTTPRouteFields(nodeHTTPRoute, foundNodeHTTPRoute, 
nodeHTTPRouteLogger) || needsUpdate
+
+                                       if needsUpdate && err == nil {
+                                               
nodeHTTPRouteLogger.Info("Updating node HTTPRoute")
+                                               err = r.Update(ctx, 
foundNodeHTTPRoute)
+                                       }
+                               }
+                               if err != nil {
+                                       return requeueOrNot, err
+                               }
+                       }
+               }
+
+               // Cleanup orphaned node HTTPRoutes (when scaling down)
+               httpRouteList := &gatewayv1.HTTPRouteList{}
+               labelSelector := labels.SelectorFromSet(instance.SharedLabels())
+               listOps := &client.ListOptions{
+                       Namespace:     instance.Namespace,
+                       LabelSelector: labelSelector,
+               }
+               err = r.List(ctx, httpRouteList, listOps)
+               if err == nil {
+                       for _, httpRoute := range httpRouteList.Items {
+                               // Skip the common HTTPRoute
+                               if httpRoute.Name == 
instance.CommonHTTPRouteName() {
+                                       continue
+                               }
+                               // Check if this node still exists
+                               nodeExists := false
+                               for _, nodeName := range solrNodeNames {
+                                       if httpRoute.Name == 
instance.NodeHTTPRouteName(nodeName) {
+                                               nodeExists = true
+                                               break
+                                       }
+                               }
+                               // Delete orphaned HTTPRoute
+                               if !nodeExists {
+                                       logger.Info("Deleting orphaned node 
HTTPRoute", "httproute", httpRoute.Name)
+                                       err = r.Delete(ctx, &httpRoute)
+                                       if err != nil && 
!errors.IsNotFound(err) {
+                                               return requeueOrNot, err
+                                       }
+                               }
+                       }
+               }
+
+               // Reconcile BackendTLSPolicy resources if configured
+               if extAddressabilityOpts.HasBackendTLSPolicy() {
+                       // Reconcile Common BackendTLSPolicy (if not hidden)

Review Comment:
   `BackendTLSPolicy` creation is triggered purely by 
`external.gateway.backendTLSPolicy` being set. If `spec.solrTLS` is not 
configured, Solr backends will be plain HTTP but the Gateway will be instructed 
to use TLS to the backend, which is guaranteed to break traffic. Since the PR 
description/docs say BackendTLSPolicy support is for TLS-enabled Solr, consider 
validating this and either rejecting the config (preferred) or ignoring 
BackendTLSPolicy unless `spec.solrTLS` is set.



##########
controllers/solrcloud_controller.go:
##########
@@ -436,6 +441,273 @@ func (r *SolrCloudReconciler) Reconcile(ctx 
context.Context, req ctrl.Request) (
                }
        }
 
+       // Reconcile Gateway API HTTPRoutes
+       if extAddressabilityOpts != nil && extAddressabilityOpts.Method == 
solrv1beta1.Gateway {
+               // Validate that gateway config is provided
+               if extAddressabilityOpts.Gateway == nil || 
len(extAddressabilityOpts.Gateway.ParentRefs) == 0 {
+                       return requeueOrNot, fmt.Errorf("gateway.parentRefs is 
required when using method=Gateway")
+               }
+
+               // Reconcile Common HTTPRoute (if not hidden)
+               if !extAddressabilityOpts.HideCommon {
+                       commonHTTPRoute := 
util.GenerateCommonHTTPRoute(instance, solrNodeNames)
+                       commonHTTPRouteLogger := logger.WithValues("httproute", 
commonHTTPRoute.Name)
+                       foundCommonHTTPRoute := &gatewayv1.HTTPRoute{}
+                       err = r.Get(ctx, types.NamespacedName{Name: 
commonHTTPRoute.Name, Namespace: commonHTTPRoute.Namespace}, 
foundCommonHTTPRoute)
+                       if err != nil && errors.IsNotFound(err) {
+                               commonHTTPRouteLogger.Info("Creating common 
HTTPRoute")
+                               if err = 
controllerutil.SetControllerReference(instance, commonHTTPRoute, r.Scheme); err 
== nil {
+                                       err = r.Create(ctx, commonHTTPRoute)
+                               }
+                       } else if err == nil {
+                               var needsUpdate bool
+                               needsUpdate, err = 
util.OvertakeControllerRef(instance, foundCommonHTTPRoute, r.Scheme)
+                               needsUpdate = 
util.CopyHTTPRouteFields(commonHTTPRoute, foundCommonHTTPRoute, 
commonHTTPRouteLogger) || needsUpdate
+
+                               if needsUpdate && err == nil {
+                                       commonHTTPRouteLogger.Info("Updating 
common HTTPRoute")
+                                       err = r.Update(ctx, 
foundCommonHTTPRoute)
+                               }
+                       }
+                       if err != nil {
+                               return requeueOrNot, err
+                       }
+               } else {
+                       // Delete common HTTPRoute if it exists but should be 
hidden
+                       foundCommonHTTPRoute := &gatewayv1.HTTPRoute{}
+                       err := r.Get(ctx, types.NamespacedName{Name: 
instance.CommonHTTPRouteName(), Namespace: instance.GetNamespace()}, 
foundCommonHTTPRoute)
+                       if err == nil {
+                               logger.Info("Deleting common HTTPRoute 
(hideCommon=true)")
+                               err = r.Delete(ctx, foundCommonHTTPRoute)
+                               if err != nil && !errors.IsNotFound(err) {
+                                       return requeueOrNot, err
+                               }
+                       }
+               }
+
+               // Reconcile Node HTTPRoutes (if not hidden)
+               if !extAddressabilityOpts.HideNodes {
+                       for _, nodeName := range solrNodeNames {
+                               nodeHTTPRoute := 
util.GenerateNodeHTTPRoute(instance, nodeName)
+                               nodeHTTPRouteLogger := 
logger.WithValues("httproute", nodeHTTPRoute.Name)
+                               foundNodeHTTPRoute := &gatewayv1.HTTPRoute{}
+                               err = r.Get(ctx, types.NamespacedName{Name: 
nodeHTTPRoute.Name, Namespace: nodeHTTPRoute.Namespace}, foundNodeHTTPRoute)
+                               if err != nil && errors.IsNotFound(err) {
+                                       nodeHTTPRouteLogger.Info("Creating node 
HTTPRoute")
+                                       if err = 
controllerutil.SetControllerReference(instance, nodeHTTPRoute, r.Scheme); err 
== nil {
+                                               err = r.Create(ctx, 
nodeHTTPRoute)
+                                       }
+                               } else if err == nil {
+                                       var needsUpdate bool
+                                       needsUpdate, err = 
util.OvertakeControllerRef(instance, foundNodeHTTPRoute, r.Scheme)
+                                       needsUpdate = 
util.CopyHTTPRouteFields(nodeHTTPRoute, foundNodeHTTPRoute, 
nodeHTTPRouteLogger) || needsUpdate
+
+                                       if needsUpdate && err == nil {
+                                               
nodeHTTPRouteLogger.Info("Updating node HTTPRoute")
+                                               err = r.Update(ctx, 
foundNodeHTTPRoute)
+                                       }
+                               }
+                               if err != nil {
+                                       return requeueOrNot, err
+                               }
+                       }
+               }
+
+               // Cleanup orphaned node HTTPRoutes (when scaling down)
+               httpRouteList := &gatewayv1.HTTPRouteList{}
+               labelSelector := labels.SelectorFromSet(instance.SharedLabels())
+               listOps := &client.ListOptions{
+                       Namespace:     instance.Namespace,
+                       LabelSelector: labelSelector,
+               }
+               err = r.List(ctx, httpRouteList, listOps)
+               if err == nil {
+                       for _, httpRoute := range httpRouteList.Items {
+                               // Skip the common HTTPRoute
+                               if httpRoute.Name == 
instance.CommonHTTPRouteName() {
+                                       continue
+                               }
+                               // Check if this node still exists
+                               nodeExists := false
+                               for _, nodeName := range solrNodeNames {
+                                       if httpRoute.Name == 
instance.NodeHTTPRouteName(nodeName) {
+                                               nodeExists = true
+                                               break
+                                       }
+                               }
+                               // Delete orphaned HTTPRoute
+                               if !nodeExists {
+                                       logger.Info("Deleting orphaned node 
HTTPRoute", "httproute", httpRoute.Name)
+                                       err = r.Delete(ctx, &httpRoute)
+                                       if err != nil && 
!errors.IsNotFound(err) {
+                                               return requeueOrNot, err
+                                       }
+                               }

Review Comment:
   Node HTTPRoutes are only deleted when the node no longer exists 
(scale-down). If the user switches `hideNodes` from false -> true, existing 
per-node HTTPRoutes will not be removed because `nodeExists` stays true, 
leaving stale routes behind (and the new e2e test expects them to be deleted). 
Consider deleting all non-common HTTPRoutes when `hideNodes=true`, similar to 
the BackendTLSPolicy cleanup logic below.



##########
controllers/solrcloud_controller.go:
##########
@@ -1191,6 +1463,7 @@ func (r *SolrCloudReconciler) SetupWithManager(mgr 
ctrl.Manager) error {
                Owns(&corev1.Service{}).
                Owns(&corev1.Secret{}). /* for authentication */
                Owns(&netv1.Ingress{}).
+               Owns(&gatewayv1.HTTPRoute{}).
                Owns(&policyv1.PodDisruptionBudget{})

Review Comment:
   The controller declares ownership of HTTPRoute objects but not 
BackendTLSPolicy. Without `Owns(&gatewayv1.BackendTLSPolicy{})`, 
deletions/updates to BackendTLSPolicy resources will not enqueue reconciles, 
which can leave the cluster in a drifted state until some other event triggers 
reconciliation.



##########
docs/solr-cloud/gateway-api.md:
##########
@@ -0,0 +1,264 @@
+<!--
+    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.
+ -->
+
+# Gateway API
+
+## Overview
+
+The Solr Operator supports using the [Kubernetes Gateway 
API](https://gateway-api.sigs.k8s.io/) for external addressability of 
SolrClouds.
+Gateway API is a vendor-neutral, Kubernetes-native API for managing ingress 
traffic and is the successor to the Ingress API.
+
+When you configure `spec.solrAddressability.external.method: Gateway`, the 
Solr Operator creates and manages HTTPRoute resources
+that route external traffic to your Solr nodes through an existing Gateway 
resource in your cluster.
+
+## What Gets Created
+
+When Gateway mode is enabled, the Solr Operator automatically creates the 
following Kubernetes resources:
+
+### HTTPRoute Resources
+
+The operator creates HTTPRoute resources to route traffic to Solr:
+
+- **Common HTTPRoute**: Routes traffic to the common Solr service 
(load-balanced across all nodes)
+  - Named: `<solrcloud-name>-solrcloud-common`
+  - Hostname: `<namespace>-<solrcloud-name>-solrcloud.<domainName>`
+  
+- **Per-Node HTTPRoutes**: Routes traffic directly to individual Solr nodes 
(when `hideNodes: false`)
+  - Named: `<solrcloud-name>-solrcloud-<node-index>`
+  - Hostname: 
`<namespace>-<solrcloud-name>-solrcloud-<node-index>.<domainName>`
+
+All HTTPRoutes are owned by the SolrCloud resource and will be automatically 
cleaned up when the SolrCloud is deleted.
+
+### Services
+
+The same services are created as with other external addressability methods:
+- Common service (load-balanced)
+- Headless service (for internal cluster communication)
+- Per-node services (when individual node access is enabled)

Review Comment:
   This section implies the headless Service is always created in Gateway mode. 
In the operator logic, the headless Service is only created when *not* using 
per-node Services (see `UsesHeadlessService()`), and the new Gateway e2e test 
in this PR explicitly expects the headless Service to not exist when per-node 
Services are used. Please clarify this to reflect the actual behavior.



##########
controllers/util/gateway_util_backendtls.go:
##########
@@ -0,0 +1,192 @@
+/*
+ * 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 (
+       solr "github.com/apache/solr-operator/api/v1beta1"
+       "github.com/go-logr/logr"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+// GenerateCommonBackendTLSPolicy creates a BackendTLSPolicy for the common 
Solr service
+func GenerateCommonBackendTLSPolicy(solrCloud *solr.SolrCloud) 
*gatewayv1.BackendTLSPolicy {
+       if solrCloud.Spec.SolrAddressability.External.Gateway == nil ||
+               
solrCloud.Spec.SolrAddressability.External.Gateway.BackendTLSPolicy == nil {
+               return nil
+       }
+
+       // Get the full FQDN for hostname validation
+       domainName := solrCloud.Spec.SolrAddressability.External.DomainName
+       fqdn := solrCloud.ExternalCommonUrl(domainName, false)
+
+       labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
+       backendTLSConfig := 
solrCloud.Spec.SolrAddressability.External.Gateway.BackendTLSPolicy
+
+       // Convert CA certificate refs
+       var caCertRefs []gatewayv1.LocalObjectReference
+       if backendTLSConfig.CACertificateRefs != nil {
+               caCertRefs = make([]gatewayv1.LocalObjectReference, 
len(backendTLSConfig.CACertificateRefs))
+               for i, ref := range backendTLSConfig.CACertificateRefs {
+                       // Default to ConfigMap if kind is not specified
+                       kind := gatewayv1.Kind("ConfigMap")
+                       if ref.Kind != nil {
+                               kind = gatewayv1.Kind(*ref.Kind)
+                       }
+                       // Default to empty group (core API)
+                       group := gatewayv1.Group("")
+                       if ref.Group != nil {
+                               group = gatewayv1.Group(*ref.Group)
+                       }
+                       certRef := gatewayv1.LocalObjectReference{
+                               Group: group,
+                               Kind:  kind,
+                               Name:  gatewayv1.ObjectName(ref.Name),
+                       }
+                       caCertRefs[i] = certRef
+               }
+       }
+
+       policy := &gatewayv1.BackendTLSPolicy{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      solrCloud.CommonBackendTLSPolicyName(),
+                       Namespace: solrCloud.GetNamespace(),
+                       Labels:    labels,
+               },
+               Spec: gatewayv1.BackendTLSPolicySpec{
+                       TargetRefs: 
[]gatewayv1.LocalPolicyTargetReferenceWithSectionName{
+                               {
+                                       LocalPolicyTargetReference: 
gatewayv1.LocalPolicyTargetReference{
+                                               Group: "",
+                                               Kind:  "Service",
+                                               Name:  
gatewayv1.ObjectName(solrCloud.CommonServiceName()),
+                                       },
+                               },
+                       },
+                       Validation: gatewayv1.BackendTLSPolicyValidation{
+                               Hostname: gatewayv1.PreciseHostname(fqdn),
+                       },
+               },
+       }
+
+       // Set CA certificates or well-known CAs
+       if caCertRefs != nil {
+               policy.Spec.Validation.CACertificateRefs = caCertRefs
+       } else if backendTLSConfig.WellKnownCACertificates != nil {
+               wellKnown := 
gatewayv1.WellKnownCACertificatesType(*backendTLSConfig.WellKnownCACertificates)
+               policy.Spec.Validation.WellKnownCACertificates = &wellKnown
+       }
+
+       return policy
+}
+
+// GenerateNodeBackendTLSPolicy creates a BackendTLSPolicy for individual Solr 
node service
+func GenerateNodeBackendTLSPolicy(solrCloud *solr.SolrCloud, nodeName string) 
*gatewayv1.BackendTLSPolicy {
+       if solrCloud.Spec.SolrAddressability.External.Gateway == nil ||
+               
solrCloud.Spec.SolrAddressability.External.Gateway.BackendTLSPolicy == nil {
+               return nil
+       }
+
+       // Get the full FQDN for hostname validation
+       domainName := solrCloud.Spec.SolrAddressability.External.DomainName
+       fqdn := solrCloud.ExternalNodeUrl(nodeName, domainName, false)
+

Review Comment:
   BackendTLSPolicy `spec.validation.hostname` for node services is currently 
set to the *external* Gateway hostname (`ExternalNodeUrl(...)`). The 
BackendTLSPolicy targets the per-node Service; the hostname used for 
validation/SNI should match that Service (and the e2e test in this PR expects 
`nodeName`).



##########
controllers/util/gateway_util_backendtls.go:
##########
@@ -0,0 +1,192 @@
+/*
+ * 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 (
+       solr "github.com/apache/solr-operator/api/v1beta1"
+       "github.com/go-logr/logr"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+// GenerateCommonBackendTLSPolicy creates a BackendTLSPolicy for the common 
Solr service
+func GenerateCommonBackendTLSPolicy(solrCloud *solr.SolrCloud) 
*gatewayv1.BackendTLSPolicy {
+       if solrCloud.Spec.SolrAddressability.External.Gateway == nil ||
+               
solrCloud.Spec.SolrAddressability.External.Gateway.BackendTLSPolicy == nil {
+               return nil
+       }
+
+       // Get the full FQDN for hostname validation
+       domainName := solrCloud.Spec.SolrAddressability.External.DomainName
+       fqdn := solrCloud.ExternalCommonUrl(domainName, false)
+

Review Comment:
   BackendTLSPolicy `spec.validation.hostname` is currently set to the 
*external* Gateway hostname (`ExternalCommonUrl(...)`). BackendTLS is between 
the Gateway and the in-cluster Service, and the docs/tests in this PR expect 
the Service name to be used for SNI/hostname validation. Using the external 
hostname here will cause the new e2e assertions to fail and can break 
validation if Solr certs are issued for Service DNS names.



##########
docs/solr-cloud/gateway-api.md:
##########
@@ -0,0 +1,264 @@
+<!--
+    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.
+ -->
+
+# Gateway API
+
+## Overview
+
+The Solr Operator supports using the [Kubernetes Gateway 
API](https://gateway-api.sigs.k8s.io/) for external addressability of 
SolrClouds.
+Gateway API is a vendor-neutral, Kubernetes-native API for managing ingress 
traffic and is the successor to the Ingress API.
+
+When you configure `spec.solrAddressability.external.method: Gateway`, the 
Solr Operator creates and manages HTTPRoute resources
+that route external traffic to your Solr nodes through an existing Gateway 
resource in your cluster.
+
+## What Gets Created
+
+When Gateway mode is enabled, the Solr Operator automatically creates the 
following Kubernetes resources:
+
+### HTTPRoute Resources
+
+The operator creates HTTPRoute resources to route traffic to Solr:
+
+- **Common HTTPRoute**: Routes traffic to the common Solr service 
(load-balanced across all nodes)
+  - Named: `<solrcloud-name>-solrcloud-common`
+  - Hostname: `<namespace>-<solrcloud-name>-solrcloud.<domainName>`
+  
+- **Per-Node HTTPRoutes**: Routes traffic directly to individual Solr nodes 
(when `hideNodes: false`)
+  - Named: `<solrcloud-name>-solrcloud-<node-index>`
+  - Hostname: 
`<namespace>-<solrcloud-name>-solrcloud-<node-index>.<domainName>`
+
+All HTTPRoutes are owned by the SolrCloud resource and will be automatically 
cleaned up when the SolrCloud is deleted.
+
+### Services
+
+The same services are created as with other external addressability methods:
+- Common service (load-balanced)
+- Headless service (for internal cluster communication)
+- Per-node services (when individual node access is enabled)
+
+## What the Operator Assumes
+
+The Gateway mode assumes the following resources already exist in your cluster:
+
+1. **Gateway API CRDs**: The Gateway API CRDs must be installed in your cluster
+   - Minimum version: v1.4.0 (required for `BackendTLSPolicy` support)
+   - Required CRDs: `Gateway`, `GatewayClass`, `HTTPRoute`, `BackendTLSPolicy` 
(optional, for TLS backends)
+   
+2. **Gateway Resource**: A Gateway resource must already exist and be managed 
by your platform team
+   - The operator only manages HTTPRoute resources, not the Gateway itself
+   - The Gateway must be configured with appropriate listeners and TLS 
termination (if needed)
+   
+3. **Gateway Controller**: A Gateway controller implementation must be running 
(e.g., NGINX Gateway Fabric, Istio, Envoy Gateway)
+
+The Solr Operator does **not** create or manage Gateway or GatewayClass 
resources - these are infrastructure-level resources
+typically managed by platform administrators and are specific to the Gateway 
implementation deployed in your cluster (e.g., NGINX Gateway Fabric, Istio, 
Envoy Gateway).
+
+## Configuration
+
+Configure Gateway mode in your SolrCloud spec:
+
+```yaml
+apiVersion: solr.apache.org/v1beta1
+kind: SolrCloud
+metadata:
+  name: example
+  namespace: solr-ns
+spec:
+  replicas: 3
+  solrImage:
+    tag: "9.7.0"
+  solrAddressability:
+    external:
+      method: Gateway
+      domainName: solr.example.com
+      useExternalAddress: true
+      gateway:
+        # Reference to existing Gateway resource(s)
+        parentRefs:
+        - name: my-gateway
+          namespace: gateway-ns
+          sectionName: https  # Optional: specific listener name
+        
+        # Optional: annotations to add to HTTPRoute resources
+        annotations:
+          example.com/custom-annotation: "value"
+        
+        # Optional: labels to add to HTTPRoute resources
+        labels:
+          app: solr
+          environment: production
+```
+
+### Configuration Options
+
+- **`parentRefs`** (required): List of Gateway resources to attach HTTPRoutes 
to
+  - `name`: Name of the Gateway resource
+  - `namespace`: Namespace of the Gateway (can be different from SolrCloud 
namespace)
+  - `sectionName`: Optional listener name within the Gateway
+  
+- **`annotations`**: Optional annotations to add to all created HTTPRoute 
resources
+
+- **`labels`**: Optional labels to add to all created HTTPRoute resources
+
+- **`domainName`**: Base domain for constructing hostnames
+
+- **`useExternalAddress`**: When true, Solr nodes use external addresses for 
inter-node communication
+
+- **`hideCommon`**: Set to true to skip creating the common HTTPRoute 
(default: false)
+
+- **`hideNodes`**: Set to true to skip creating per-node HTTPRoutes (default: 
false)
+
+## Backend TLS Policy
+
+When using TLS-enabled Solr (`spec.solrTLS` is configured), the Solr Operator 
automatically sets `appProtocol: https` on all Services.
+The operator also supports creating `BackendTLSPolicy` resources to configure 
secure connections between the Gateway and Solr backend services.
+
+### Configuring BackendTLSPolicy
+
+The Solr Operator can automatically create and manage `BackendTLSPolicy` 
resources (Gateway API v1alpha3) when configured in the SolrCloud spec:

Review Comment:
   The doc says BackendTLSPolicy is "Gateway API v1alpha3", but this PR 
requires Gateway API v1.4.0+ and the examples below use `apiVersion: 
gateway.networking.k8s.io/v1`. Please update this reference to avoid confusion.



##########
docs/solr-cloud/gateway-api.md:
##########
@@ -0,0 +1,264 @@
+<!--
+    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

Review Comment:
   The Apache license header is missing the opening parenthesis before `the 
"License"` (it should be `Version 2.0 (the "License"); ...`).



-- 
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