Skip to content

Commit 4d05a34

Browse files
authored
Merge pull request #95 from HoustonPutman/addressability
Adding custom addressability options to the SolrCloud spec.
2 parents 2610a35 + 045abaa commit 4d05a34

18 files changed

+2447
-150
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ Please visit the following pages for documentation on using and developing the S
4343

4444
## Version Compatibility & Upgrade Notes
4545

46+
#### v0.2.6
47+
- The solr-operator argument `--ingressBaseDomain` has been **DEPRECATED**.
48+
In order to set the external baseDomain of your clouds, please begin to use `SolrCloud.spec.solrAddressability.external.domainName` instead.
49+
You will also need to set `SolrCloud.spec.solrAddressability.external.method` to `Ingress`.
50+
The `--ingressBaseDomain` argument is backwards compatible, and all existing SolrCloud objects will be auto-updated once your operator is upgraded to `v0.2.6`.
51+
The argument will be removed in a future version (`v0.3.0`).
52+
4653
#### v0.2.4
4754
- The default supported version of the Zookeeper Operator has been upgraded to `v0.2.6`.
4855
If you are using the provided zookeeper option for your SolrClouds, then you will want to upgrade your zookeeper operator version as well as the version and image of the zookeeper that you are running.

api/v1beta1/solrcloud_types.go

+265-21
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package v1beta1
1818

1919
import (
2020
"fmt"
21+
"strconv"
2122
"strings"
2223

2324
zk "github.com/pravega/zookeeper-operator/pkg/apis/zookeeper/v1beta1"
@@ -101,6 +102,10 @@ type SolrCloudSpec struct {
101102
// +optional
102103
CustomSolrKubeOptions CustomSolrKubeOptions `json:"customSolrKubeOptions,omitempty"`
103104

105+
// Customize how Solr is addressed both internally and externally in Kubernetes.
106+
// +optional
107+
SolrAddressability SolrAddressabilityOptions `json:"solrAddressability,omitempty"`
108+
104109
// +optional
105110
BusyBoxImage *ContainerImage `json:"busyBoxImage,omitempty"`
106111

@@ -121,7 +126,7 @@ type SolrCloudSpec struct {
121126
SolrGCTune string `json:"solrGCTune,omitempty"`
122127
}
123128

124-
func (spec *SolrCloudSpec) withDefaults() (changed bool) {
129+
func (spec *SolrCloudSpec) withDefaults(ingressBaseDomain string) (changed bool) {
125130
if spec.Replicas == nil {
126131
changed = true
127132
r := DefaultSolrReplicas
@@ -148,6 +153,8 @@ func (spec *SolrCloudSpec) withDefaults() (changed bool) {
148153
spec.SolrGCTune = DefaultSolrGCTune
149154
}
150155

156+
changed = spec.SolrAddressability.withDefaults(ingressBaseDomain) || changed
157+
151158
if spec.ZookeeperRef == nil {
152159
spec.ZookeeperRef = &ZookeeperRef{}
153160
}
@@ -214,6 +221,147 @@ type CustomSolrKubeOptions struct {
214221
IngressOptions *IngressOptions `json:"ingressOptions,omitempty"`
215222
}
216223

224+
type SolrAddressabilityOptions struct {
225+
// External defines the way in which this SolrCloud nodes should be made addressable externally, from outside the Kubernetes cluster.
226+
// If none is provided, the Solr Cloud will not be made addressable externally.
227+
// +optional
228+
External *ExternalAddressability `json:"external,omitempty"`
229+
230+
// PodPort defines the port to have the Solr Pod listen on.
231+
// Defaults to 8983
232+
// +optional
233+
PodPort int `json:"podPort,omitempty"`
234+
235+
// CommonServicePort defines the port to have the common Solr service listen on.
236+
// Defaults to 80
237+
// +optional
238+
CommonServicePort int `json:"commonServicePort,omitempty"`
239+
240+
// KubeDomain allows for the specification of an override of the default "cluster.local" Kubernetes cluster domain.
241+
// Only use this option if the Kubernetes cluster has been setup with a custom domain.
242+
// +optional
243+
KubeDomain string `json:"kubeDomain,omitempty"`
244+
}
245+
246+
func (opts *SolrAddressabilityOptions) withDefaults(ingressBaseDomain string) (changed bool) {
247+
// DEPRECATED: ingressBaseDomain will be removed in v0.3.0
248+
if opts.External == nil && ingressBaseDomain != "" {
249+
changed = true
250+
opts.External = &ExternalAddressability{
251+
Method: Ingress,
252+
DomainName: ingressBaseDomain,
253+
UseExternalAddress: true,
254+
NodePortOverride: 80,
255+
}
256+
} else if opts.External != nil {
257+
changed = opts.External.withDefaults()
258+
}
259+
if opts.PodPort == 0 {
260+
changed = true
261+
opts.PodPort = 8983
262+
}
263+
if opts.CommonServicePort == 0 {
264+
changed = true
265+
opts.CommonServicePort = 80
266+
}
267+
return changed
268+
}
269+
270+
// ExternalAddressability defines the config for making Solr services available externally to kubernetes.
271+
// Be careful when using LoadBalanced and includeNodes, as many IP addresses could be created if you are running many large solrClouds.
272+
type ExternalAddressability struct {
273+
// The way in which this SolrCloud's service(s) should be made addressable externally.
274+
Method ExternalAddressabilityMethod `json:"method"`
275+
276+
// Use the external address to advertise the SolrNode, defaults to false.
277+
//
278+
// If false, the external address will be available, however Solr (and clients using the CloudSolrClient in SolrJ) will only be aware of the internal URLs.
279+
// If true, Solr will startup with the hostname of the external address.
280+
//
281+
// NOTE: This option cannot be true when hideNodes is set to true. So it will be auto-set to false if that is the case.
282+
//
283+
// Deprecation warning: When an ingress-base-domain is passed in to the operator, this value defaults to true.
284+
// +optional
285+
UseExternalAddress bool `json:"useExternalAddress"`
286+
287+
// Do not expose the common Solr service externally. This affects a single service.
288+
// Defaults to false.
289+
// +optional
290+
HideCommon bool `json:"hideCommon,omitempty"`
291+
292+
// Do not expose each of the Solr Node services externally.
293+
// The number of services this affects could range from 1 (a headless service for ExternalDNS) to the number of Solr pods your cloud contains (individual node services for Ingress/LoadBalancer).
294+
// Defaults to false.
295+
// +optional
296+
HideNodes bool `json:"hideNodes,omitempty"`
297+
298+
// Override the domainName provided as startup parameters to the operator, used by ingresses and externalDNS.
299+
// The common and/or node services will be addressable by unique names under the given domain.
300+
// e.g. default-example-solrcloud.given.domain.name.com
301+
//
302+
// This options will be required for the Ingress and ExternalDNS methods once the ingressBaseDomain startup parameter is removed.
303+
//
304+
// For the LoadBalancer method, this field is optional and will only be used when useExternalAddress=true.
305+
// If used with the LoadBalancer method, you will need DNS routing to the LoadBalancer IP address through the url template given above.
306+
// +optional
307+
DomainName string `json:"domainName,omitempty"`
308+
309+
// Provide additional domainNames that the Ingress or ExternalDNS should listen on.
310+
// This option is ignored with the LoadBalancer method.
311+
// +optional
312+
AdditionalDomainNames []string `json:"additionalDomains,omitempty"`
313+
314+
// NodePortOverride defines the port to have all Solr node service(s) listen on and advertise itself as if advertising through an Ingress or LoadBalancer.
315+
// This overrides the default usage of the podPort.
316+
//
317+
// This is option is only used when HideNodes=false, otherwise the the port each Solr Node will advertise itself with the podPort.
318+
// This option is also unavailable with the ExternalDNS method.
319+
//
320+
// If using method=Ingress, your ingress controller is required to listen on this port.
321+
// If your ingress controller is not listening on the podPort, then this option is required for solr to be addressable via an Ingress.
322+
//
323+
// Defaults to 80 if HideNodes=false and method=Ingress, otherwise this is optional.
324+
// +optional
325+
NodePortOverride int `json:"nodePortOverride,omitempty"`
326+
}
327+
328+
// ExternalAddressability is a string enumeration type that enumerates
329+
// all possible ways that a SolrCloud can be made addressable external to the kubernetes cluster.
330+
// +kubebuilder:validation:Enum=Ingress;ExternalDNS
331+
type ExternalAddressabilityMethod string
332+
333+
const (
334+
// Use an ingress to make the Solr service(s) externally addressable
335+
Ingress ExternalAddressabilityMethod = "Ingress"
336+
337+
// Use ExternalDNS to make the Solr service(s) externally addressable
338+
ExternalDNS ExternalAddressabilityMethod = "ExternalDNS"
339+
340+
// Make Solr service(s) type:LoadBalancer to make them externally addressable
341+
// NOTE: This option is not currently supported.
342+
LoadBalancer ExternalAddressabilityMethod = "LoadBalancer"
343+
)
344+
345+
func (opts *ExternalAddressability) withDefaults() (changed bool) {
346+
// You can't use an externalAddress for Solr Nodes if the Nodes are hidden externally
347+
if opts.UseExternalAddress && opts.HideNodes {
348+
changed = true
349+
opts.UseExternalAddress = false
350+
}
351+
// If the Ingress method is used, default the nodePortOverride to 80, since that is the port that most ingress controllers listen on.
352+
if !opts.HideNodes && opts.Method == Ingress && opts.NodePortOverride == 0 {
353+
changed = true
354+
opts.NodePortOverride = 80
355+
}
356+
// If a headless service is used, aka not using individual node services, then a nodePortOverride is not allowed.
357+
if !opts.UsesIndividualNodeServices() && opts.NodePortOverride > 0 {
358+
changed = true
359+
opts.NodePortOverride = 0
360+
}
361+
362+
return changed
363+
}
364+
217365
// DEPRECATED: Please use the options provided in SolrCloud.Spec.customSolrKubeOptions.podOptions
218366
//
219367
// SolrPodPolicy defines the common pod configuration for Pods, including when used
@@ -620,12 +768,16 @@ type SolrCloud struct {
620768
}
621769

622770
// WithDefaults set default values when not defined in the spec.
623-
func (sc *SolrCloud) WithDefaults() bool {
624-
return sc.Spec.withDefaults()
771+
func (sc *SolrCloud) WithDefaults(ingressBaseDomain string) bool {
772+
return sc.Spec.withDefaults(ingressBaseDomain)
625773
}
626774

627775
func (sc *SolrCloud) GetAllSolrNodeNames() []string {
628-
nodeNames := make([]string, *sc.Spec.Replicas)
776+
replicas := 1
777+
if sc.Spec.Replicas != nil {
778+
replicas = int(*sc.Spec.Replicas)
779+
}
780+
nodeNames := make([]string, replicas)
629781
statefulSetName := sc.StatefulSetName()
630782
for i := range nodeNames {
631783
nodeNames[i] = fmt.Sprintf("%s-%d", statefulSetName, i)
@@ -695,47 +847,139 @@ func (zkInfo ZookeeperConnectionInfo) ZkConnectionString() string {
695847
return zkInfo.InternalConnectionString + zkInfo.ChRoot
696848
}
697849

698-
func (sc *SolrCloud) CommonIngressPrefix() string {
850+
// UsesHeadlessService returns whether the given solrCloud requires a headless service to be created for it.
851+
// solrCloud: SolrCloud instance
852+
func (sc *SolrCloud) UsesHeadlessService() bool {
853+
return !sc.Spec.SolrAddressability.External.UsesIndividualNodeServices()
854+
}
855+
856+
// UsesIndividualNodeServices returns whether the given solrCloud requires a individual node services to be created for it.
857+
// solrCloud: SolrCloud instance
858+
func (sc *SolrCloud) UsesIndividualNodeServices() bool {
859+
return sc.Spec.SolrAddressability.External.UsesIndividualNodeServices()
860+
}
861+
862+
func (extOpts *ExternalAddressability) UsesIndividualNodeServices() bool {
863+
// LoadBalancer and Ingress will not work with headless services if each pod needs to be exposed externally.
864+
return extOpts != nil && !extOpts.HideNodes && (extOpts.Method == Ingress || extOpts.Method == LoadBalancer)
865+
}
866+
867+
func (sc *SolrCloud) CommonExternalPrefix() string {
699868
return fmt.Sprintf("%s-%s-solrcloud", sc.Namespace, sc.Name)
700869
}
701870

702-
func (sc *SolrCloud) CommonIngressUrl(ingressBaseUrl string) string {
703-
return fmt.Sprintf("%s.%s", sc.CommonIngressPrefix(), ingressBaseUrl)
871+
func (sc *SolrCloud) CommonExternalUrl(domainName string) string {
872+
return fmt.Sprintf("%s.%s", sc.CommonExternalPrefix(), domainName)
704873
}
705874

706875
func (sc *SolrCloud) NodeIngressPrefix(nodeName string) string {
707876
return fmt.Sprintf("%s-%s", sc.Namespace, nodeName)
708877
}
709878

710-
func (sc *SolrCloud) NodeIngressUrl(nodeName string, ingressBaseUrl string) string {
711-
return fmt.Sprintf("%s.%s", sc.NodeIngressPrefix(nodeName), ingressBaseUrl)
879+
func (sc *SolrCloud) ExternalDnsDomain(domainName string) string {
880+
return fmt.Sprintf("%s.%s", sc.Namespace, domainName)
881+
}
882+
883+
func (sc *SolrCloud) customKubeDomain() string {
884+
if sc.Spec.SolrAddressability.KubeDomain != "" {
885+
return ".svc." + sc.Spec.SolrAddressability.KubeDomain
886+
} else {
887+
return ""
888+
}
712889
}
713890

714-
func (sc *SolrCloud) NodeHeadlessUrl(nodeName string, withPort bool) string {
715-
url := fmt.Sprintf("%s.%s.%s", nodeName, sc.HeadlessServiceName(), sc.Namespace)
891+
func (sc *SolrCloud) NodeHeadlessUrl(nodeName string, withPort bool) (url string) {
892+
url = fmt.Sprintf("%s.%s.%s", nodeName, sc.HeadlessServiceName(), sc.Namespace) + sc.customKubeDomain()
716893
if withPort {
717-
url += ":8983"
894+
url += sc.NodePortSuffix()
718895
}
719896
return url
720897
}
721898

722-
func (sc *SolrCloud) NodeServiceUrl(nodeName string) string {
723-
return fmt.Sprintf("%s.%s", nodeName, sc.Namespace)
899+
func (sc *SolrCloud) NodeServiceUrl(nodeName string, withPort bool) (url string) {
900+
url = fmt.Sprintf("%s.%s", nodeName, sc.Namespace) + sc.customKubeDomain()
901+
if withPort {
902+
url += sc.NodePortSuffix()
903+
}
904+
return url
724905
}
725906

726-
func (sc *SolrCloud) InternalNodeUrl(nodeName string, useHeadlessService bool, withPort bool) string {
727-
if useHeadlessService {
907+
func (sc *SolrCloud) CommonPortSuffix() string {
908+
return PortToSuffix(sc.Spec.SolrAddressability.CommonServicePort)
909+
}
910+
911+
func (sc *SolrCloud) NodePortSuffix() string {
912+
return PortToSuffix(sc.NodePort())
913+
}
914+
915+
func (sc *SolrCloud) NodePort() int {
916+
port := sc.Spec.SolrAddressability.PodPort
917+
external := sc.Spec.SolrAddressability.External
918+
// The nodePort is different than the podPort ONLY if the nodes are exposed externally and a nodePortOverride has been set.
919+
if external.UsesIndividualNodeServices() && external.NodePortOverride > 0 {
920+
port = sc.Spec.SolrAddressability.External.NodePortOverride
921+
}
922+
return port
923+
}
924+
925+
// PortToSuffix returns the url suffix for a port.
926+
// Port 80 does not require a suffix, as it is the default port for HTTP.
927+
func PortToSuffix(port int) string {
928+
if port == 80 {
929+
return ""
930+
}
931+
return ":" + strconv.Itoa(port)
932+
}
933+
934+
func (sc *SolrCloud) InternalNodeUrl(nodeName string, withPort bool) string {
935+
if sc.UsesHeadlessService() {
728936
return sc.NodeHeadlessUrl(nodeName, withPort)
937+
} else if sc.UsesIndividualNodeServices() {
938+
return sc.NodeServiceUrl(nodeName, withPort)
729939
} else {
730-
return sc.NodeServiceUrl(nodeName)
940+
return ""
731941
}
732942
}
733943

734-
func (sc *SolrCloud) ExternalNodeUrl(nodeName string, ingressBaseDomain string, withPort bool) string {
735-
if ingressBaseDomain == "" {
736-
return sc.NodeHeadlessUrl(nodeName, withPort)
944+
func (sc *SolrCloud) InternalCommonUrl(withPort bool) (url string) {
945+
url = fmt.Sprintf("%s.%s", sc.CommonServiceName(), sc.Namespace) + sc.customKubeDomain()
946+
if withPort {
947+
url += sc.NodePortSuffix()
948+
}
949+
return url
950+
}
951+
952+
func (sc *SolrCloud) ExternalNodeUrl(nodeName string, domainName string, withPort bool) (url string) {
953+
if sc.Spec.SolrAddressability.External.Method == Ingress {
954+
url = fmt.Sprintf("%s.%s", sc.NodeIngressPrefix(nodeName), domainName)
955+
} else if sc.Spec.SolrAddressability.External.Method == ExternalDNS {
956+
url = fmt.Sprintf("%s.%s", nodeName, sc.ExternalDnsDomain(domainName))
957+
}
958+
// TODO: Add LoadBalancer stuff here
959+
if withPort {
960+
url += sc.NodePortSuffix()
961+
}
962+
return url
963+
}
964+
965+
func (sc *SolrCloud) ExternalCommonUrl(domainName string, withPort bool) (url string) {
966+
if sc.Spec.SolrAddressability.External.Method == Ingress {
967+
url = fmt.Sprintf("%s.%s", sc.CommonExternalPrefix(), domainName)
968+
} else if sc.Spec.SolrAddressability.External.Method == ExternalDNS {
969+
url = fmt.Sprintf("%s.%s", sc.CommonServiceName(), sc.ExternalDnsDomain(domainName))
970+
}
971+
if withPort {
972+
url += sc.CommonPortSuffix()
973+
}
974+
return url
975+
}
976+
977+
func (sc *SolrCloud) AdvertisedNodeHost(nodeName string) string {
978+
external := sc.Spec.SolrAddressability.External
979+
if external != nil && external.UseExternalAddress {
980+
return sc.ExternalNodeUrl(nodeName, sc.Spec.SolrAddressability.External.DomainName, false)
737981
} else {
738-
return sc.NodeIngressUrl(nodeName, ingressBaseDomain)
982+
return sc.InternalNodeUrl(nodeName, false)
739983
}
740984
}
741985

0 commit comments

Comments
 (0)