Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b21b317
Add Gateway API support for SolrCloud external addressability
chinmoysahu Jan 19, 2026
c21078e
Added permissions for backendtls policy
chinmoysahu Jan 19, 2026
7521091
Added backendtlpolicy support
chinmoysahu Jan 26, 2026
199eb0b
Updated doc for backendtlspolicy
chinmoysahu Jan 26, 2026
a965519
Updated doc for backendtlspolicy for gateway implementations
chinmoysahu Jan 26, 2026
885392f
fix: Address PR review comments for BackendTLSPolicy implementation
chinmoysahu Jan 26, 2026
dbd4372
Make add crd files
chinmoysahu Jan 26, 2026
9f543b3
Make add crd files
chinmoysahu Jan 26, 2026
b261342
Add additionalHostnames field to SolrGatewayOptions
chinmoysahu Jun 3, 2026
e289f34
Fix main.go import ordering (gofmt)
chinmoysahu Jun 4, 2026
3423883
Address PR review: conditional Gateway API watch, hideNodes cleanup, …
chinmoysahu Jun 4, 2026
2599c60
Merge remote-tracking branch 'apache/main' into pr/815
HoustonPutman Jun 18, 2026
906f287
Migrate new docs to antora
HoustonPutman Jun 18, 2026
dc6bdae
Merge remote-tracking branch 'apache/main' into pr/843
HoustonPutman Aug 4, 2026
fe8a307
Add dependency info
HoustonPutman Aug 4, 2026
14d58ef
Separate gateway logic into its own file
HoustonPutman Aug 4, 2026
a01ddb5
Split up method, fix TLS owning error
HoustonPutman Aug 4, 2026
a1945d0
Small fix
HoustonPutman Aug 4, 2026
96e600f
Some more fixes
HoustonPutman Aug 4, 2026
dbfa8a5
Remove flag, automatically detect CRDs
HoustonPutman Aug 4, 2026
20169f5
Add gateway API support in e2e-tests
HoustonPutman Aug 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ GO_LICENSES_VERSION=v2.0.1
GO_LICENSES_ENV=GOOS=linux GOARCH=amd64
GINKGO_VERSION = $(shell cat go.mod | grep 'github.com/onsi/ginkgo' | sed 's/.*\(v.*\)$$/\1/g')
KIND_VERSION=v0.30.0
CLOUD_PROVIDER_KIND_VERSION=v0.11.1
YQ_VERSION=v4.53.3
CONTROLLER_RUNTIME_VERSION = $(shell cat go.mod | grep 'sigs.k8s.io/controller-runtime' | sed 's/.*\(v\(.*\)\.[^.]*\)$$/\2/g')
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
Expand Down Expand Up @@ -327,7 +328,7 @@ e2e-tests: export OPERATOR_IMAGE=$(IMG):$(TAG)
e2e-tests: export TEST_PARALLELISM?=4
# Use path for subcommands so that we use the correct dev-dependencies rather than those installed globally
e2e-tests: export PATH:=$(LOCALBIN):${PATH}
e2e-tests: ginkgo kind manifests generate helm-dependency-build docker-build ## Run e2e/integration tests. For help, refer to: dev-docs/e2e-testing.md
e2e-tests: ginkgo kind cloud-provider-kind manifests generate helm-dependency-build docker-build ## Run e2e/integration tests. For help, refer to: dev-docs/e2e-testing.md
./tests/scripts/manage_e2e_tests.sh run-tests

##@ Helm
Expand Down Expand Up @@ -387,7 +388,7 @@ LOCALBIN ?= $(PROJECT_DIR)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)

install-dependencies: controller-gen kustomize go-licenses setup-envtest kind ginkgo ## Install necessary dependencies for building and testing the Solr Operator
install-dependencies: controller-gen kustomize go-licenses setup-envtest kind cloud-provider-kind ginkgo ## Install necessary dependencies for building and testing the Solr Operator

CONTROLLER_GEN = $(LOCALBIN)/controller-gen
.PHONY: controller-gen
Expand Down Expand Up @@ -419,6 +420,12 @@ kind: $(KIND) ## Download kind locally if necessary.
$(KIND): $(LOCALBIN)
$(call go-get-tool,$(KIND),sigs.k8s.io/kind@$(KIND_VERSION))

CLOUD_PROVIDER_KIND = $(LOCALBIN)/cloud-provider-kind
.PHONY: cloud-provider-kind
cloud-provider-kind: $(CLOUD_PROVIDER_KIND) ## Download cloud-provider-kin locally if necessary.
$(CLOUD_PROVIDER_KIND): $(LOCALBIN)
$(call go-get-tool,$(CLOUD_PROVIDER_KIND),sigs.k8s.io/cloud-provider-kind@$(CLOUD_PROVIDER_KIND_VERSION))

YQ = $(LOCALBIN)/yq
.PHONY: yq
yq: $(YQ) ## Download yq locally if necessary.
Expand Down
164 changes: 155 additions & 9 deletions api/v1beta1/solrcloud_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ package v1beta1

import (
"fmt"
"github.com/go-logr/logr"
zkApi "github.com/pravega/zookeeper-operator/api/v1beta1"
"math/rand"
"strconv"
"strings"

"github.com/go-logr/logr"
zkApi "github.com/pravega/zookeeper-operator/api/v1beta1"

"k8s.io/apimachinery/pkg/util/intstr"

corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -563,11 +564,20 @@ type ExternalAddressability struct {
//
// +optional
IngressTLSTermination *SolrIngressTLSTermination `json:"ingressTLSTermination,omitempty"`

// Gateway defines settings for Kubernetes Gateway API routing.
//
// This option is only available when Method=Gateway.
// The referenced Gateway must already exist and be managed by your platform team.
// The Solr Operator only manages the HTTPRoute resources.
//
// +optional
Gateway *SolrGatewayOptions `json:"gateway,omitempty"`
}

// ExternalAddressabilityMethod is a string enumeration type that enumerates
// all possible ways that a SolrCloud can be made addressable external to the kubernetes cluster.
// +kubebuilder:validation:Enum=Ingress;ExternalDNS
// +kubebuilder:validation:Enum=Ingress;ExternalDNS;Gateway
type ExternalAddressabilityMethod string

const (
Expand All @@ -577,6 +587,9 @@ const (
// Use ExternalDNS to make the Solr service(s) externally addressable
ExternalDNS ExternalAddressabilityMethod = "ExternalDNS"

// Use Gateway API to make the Solr service(s) externally addressable
Gateway ExternalAddressabilityMethod = "Gateway"

// Make Solr service(s) type:LoadBalancer to make them externally addressable
// NOTE: This option is not currently supported.
LoadBalancer ExternalAddressabilityMethod = "LoadBalancer"
Expand Down Expand Up @@ -626,6 +639,107 @@ type SolrIngressTLSTermination struct {
TLSSecret string `json:"tlsSecret,omitempty"`
}

// SolrGatewayOptions defines how a SolrCloud should be exposed via Kubernetes Gateway API
type SolrGatewayOptions struct {
// ParentRefs specifies the Gateway(s) to attach HTTPRoutes to.
// This is required when using method=Gateway.
//
// The referenced Gateway must already exist and be managed by your platform team.
// The Solr Operator only manages the HTTPRoute resources.
//
// +kubebuilder:validation:MinItems=1
ParentRefs []GatewayParentReference `json:"parentRefs"`

// AdditionalHostnames specifies extra hostnames to include in the common HTTPRoute.
// These are appended to the auto-generated hostnames derived from DomainName and AdditionalDomainNames.
// This is useful for adding alias hostnames that should also route to the common Solr service.
//
// +optional
// +kubebuilder:validation:MaxItems=16
AdditionalHostnames []string `json:"additionalHostnames,omitempty"`

// Annotations to add to HTTPRoute resources
// +optional
Annotations map[string]string `json:"annotations,omitempty"`

// Labels to add to HTTPRoute resources
// +optional
Labels map[string]string `json:"labels,omitempty"`

// BackendTLSPolicy defines TLS configuration for backend connections from Gateway to Solr pods.
//
// This is used when Solr pods are running with TLS enabled (spec.solrTLS) and the Gateway
// needs to establish secure connections to the backend services.
//
// The Solr Operator will create BackendTLSPolicy resources for each HTTPRoute.
//
// +optional
BackendTLSPolicy *SolrBackendTLSPolicy `json:"backendTLSPolicy,omitempty"`
}

// GatewayParentReference identifies a parent Gateway resource to attach HTTPRoutes to
type GatewayParentReference struct {
// Name of the Gateway resource
Name string `json:"name"`

// Namespace of the Gateway resource.
// If not specified, defaults to the HTTPRoute's namespace.
// +optional
Namespace *string `json:"namespace,omitempty"`

// SectionName refers to a specific listener on the Gateway.
// For example, "https" or "http".
// +optional
SectionName *string `json:"sectionName,omitempty"`
}

// SolrBackendTLSPolicy defines backend TLS configuration for Gateway API
//
// For a valid BackendTLSPolicy configuration, exactly one of CACertificateRefs or
// WellKnownCACertificates must be specified. The operator validates this constraint
// via the HasBackendTLSPolicy() function before creating BackendTLSPolicy resources.
//
// +kubebuilder:validation:MaxProperties=1
// +kubebuilder:validation:MinProperties=1
type SolrBackendTLSPolicy struct {
// CACertificateRefs contains one or more references to Kubernetes objects that contain
// TLS certificates of the Certificate Authorities that can be used as a trust anchor
// to validate the certificates presented by the backend.
//
// If specified, WellKnownCACertificates must not be set.
//
// +optional
// +kubebuilder:validation:MaxItems=8
CACertificateRefs []GatewayCertificateReference `json:"caCertificateRefs,omitempty"`

// WellKnownCACertificates specifies whether system CA certificates may be used in the
// TLS handshake between the gateway and backend pod.
//
// If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs must be
// specified with at least one entry for a valid configuration.
//
// Only one of CACertificateRefs or WellKnownCACertificates may be specified, not both.
// If specified, CACertificateRefs must not be set.
//
// +optional
WellKnownCACertificates *string `json:"wellKnownCACertificates,omitempty"`
}

// GatewayCertificateReference identifies a certificate object in Kubernetes
type GatewayCertificateReference struct {
// Name of the Kubernetes resource (e.g., ConfigMap or Secret)
Name string `json:"name"`

// Kind of the resource (e.g., "ConfigMap" or "Secret")
// +optional
// +kubebuilder:default="ConfigMap"
Kind *string `json:"kind,omitempty"`

// Group of the resource
// +optional
Group *string `json:"group,omitempty"`
}

type SolrUpdateStrategy struct {
// Method defines the way in which SolrClouds should be updated when the podSpec changes.
// +optional
Expand Down Expand Up @@ -1297,6 +1411,26 @@ func (sc *SolrCloud) CommonIngressName() string {
return fmt.Sprintf("%s-solrcloud-common", sc.GetName())
}

// CommonHTTPRouteName returns the name of the common HTTPRoute for the cloud
func (sc *SolrCloud) CommonHTTPRouteName() string {
return fmt.Sprintf("%s-solrcloud-common", sc.GetName())
}

// NodeHTTPRouteName returns the name of the HTTPRoute for a specific node
func (sc *SolrCloud) NodeHTTPRouteName(nodeName string) string {
return nodeName
}

// CommonBackendTLSPolicyName returns the name of the common BackendTLSPolicy for the cloud
func (sc *SolrCloud) CommonBackendTLSPolicyName() string {
return fmt.Sprintf("%s-solrcloud-common", sc.GetName())
}

// NodeBackendTLSPolicyName returns the name of the BackendTLSPolicy for a specific node
func (sc *SolrCloud) NodeBackendTLSPolicyName(nodeName string) string {
return nodeName
}

// ProvidedZookeeperName returns the provided zk cluster
func (sc *SolrCloud) ProvidedZookeeperName() string {
return fmt.Sprintf("%s-solrcloud-zookeeper", sc.GetName())
Expand Down Expand Up @@ -1340,8 +1474,8 @@ func (sc *SolrCloud) UsesIndividualNodeServices() bool {
}

func (extOpts *ExternalAddressability) UsesIndividualNodeServices() bool {
// LoadBalancer and Ingress will not work with headless services if each pod needs to be exposed externally.
return extOpts != nil && !extOpts.HideNodes && (extOpts.Method == Ingress || extOpts.Method == LoadBalancer)
// LoadBalancer, Ingress, and Gateway will not work with headless services if each pod needs to be exposed externally.
return extOpts != nil && !extOpts.HideNodes && (extOpts.Method == Ingress || extOpts.Method == LoadBalancer || extOpts.Method == Gateway)
}

func (sc *SolrCloud) CommonExternalPrefix() string {
Expand Down Expand Up @@ -1435,11 +1569,13 @@ func (sc *SolrCloud) ExternalNodeUrl(nodeName string, domainName string, withPor
url = fmt.Sprintf("%s.%s", sc.NodeIngressPrefix(nodeName), domainName)
} else if sc.Spec.SolrAddressability.External.Method == ExternalDNS {
url = fmt.Sprintf("%s.%s", nodeName, sc.ExternalDnsDomain(domainName))
} else if sc.Spec.SolrAddressability.External.Method == Gateway {
url = fmt.Sprintf("%s.%s", sc.NodeIngressPrefix(nodeName), domainName)
}
// TODO: Add LoadBalancer stuff here

if withPort && sc.Spec.SolrAddressability.External.Method != Ingress {
// Ingress does not require a port, since the port is whatever the ingress is listening on (80 and 443)
if withPort && sc.Spec.SolrAddressability.External.Method != Ingress && sc.Spec.SolrAddressability.External.Method != Gateway {
// Ingress and Gateway do not require a port, since the port is whatever the ingress/gateway is listening on (80 and 443)
url += sc.NodePortSuffix(true)
}
return url
Expand All @@ -1450,11 +1586,13 @@ func (sc *SolrCloud) ExternalCommonUrl(domainName string, withPort bool) (url st
url = fmt.Sprintf("%s.%s", sc.CommonExternalPrefix(), domainName)
} else if sc.Spec.SolrAddressability.External.Method == ExternalDNS {
url = fmt.Sprintf("%s.%s", sc.CommonServiceName(), sc.ExternalDnsDomain(domainName))
} else if sc.Spec.SolrAddressability.External.Method == Gateway {
url = fmt.Sprintf("%s.%s", sc.CommonExternalPrefix(), domainName)
}
// TODO: Add LoadBalancer stuff here

if withPort && sc.Spec.SolrAddressability.External.Method != Ingress {
// Ingress does not require a port, since the port is whatever the ingress is listening on (80 and 443)
if withPort && sc.Spec.SolrAddressability.External.Method != Ingress && sc.Spec.SolrAddressability.External.Method != Gateway {
// Ingress and Gateway do not require a port, since the port is whatever the ingress/gateway is listening on (80 and 443)
url += sc.CommonPortSuffix(true)
}
return url
Expand All @@ -1467,6 +1605,14 @@ func (ea *ExternalAddressability) HasIngressTLSTermination() bool {
return false
}

func (ea *ExternalAddressability) HasBackendTLSPolicy() bool {
if ea != nil && ea.Method == Gateway && ea.Gateway != nil && ea.Gateway.BackendTLSPolicy != nil {
return (ea.Gateway.BackendTLSPolicy.CACertificateRefs != nil && len(ea.Gateway.BackendTLSPolicy.CACertificateRefs) > 0) ||
(ea.Gateway.BackendTLSPolicy.WellKnownCACertificates != nil && *ea.Gateway.BackendTLSPolicy.WellKnownCACertificates != "")
}
return false
}

func (sc *SolrCloud) UrlScheme(external bool) string {
urlScheme := "http"
if sc.Spec.SolrTLS != nil {
Expand Down
Loading