blob: eae9f30b126768014fa222f44215f65c83d2164b [file] [log] [blame]
/*
Copyright 2019 Bloomberg Finance LP.
Licensed 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 v1beta1
import (
"fmt"
"strconv"
"strings"
zk "github.com/pravega/zookeeper-operator/pkg/apis/zookeeper/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
DefaultPullPolicy = corev1.PullIfNotPresent
DefaultSolrReplicas = int32(3)
DefaultSolrRepo = "library/solr"
DefaultSolrVersion = "7.7.0"
DefaultSolrStorage = "5Gi"
DefaultSolrJavaMem = "-Xms1g -Xmx2g"
DefaultSolrOpts = ""
DefaultSolrLogLevel = "INFO"
DefaultSolrGCTune = ""
DefaultBusyBoxImageRepo = "library/busybox"
DefaultBusyBoxImageVersion = "1.28.0-glibc"
DefaultZkReplicas = int32(3)
DefaultZkStorage = "5Gi"
DefaultZkRepo = "pravega/zookeeper"
DefaultZkVersion = "0.2.6"
DefaultZkVolumeReclaimPolicy = zk.VolumeReclaimPolicyRetain
DefaultEtcdReplicas = 3
DefaultEtcdRepo = "quay.io/coreos/etcd"
DefaultEtcdVersion = "3.2.13"
DefaultZetcdReplicas = int32(1)
DefaultZetcdRepo = "quay.io/etcd-io/zetcd"
DefaultZetcdVersion = "0.0.5"
SolrTechnologyLabel = "solr-cloud"
ZookeeperTechnologyLabel = "zookeeper"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// SolrCloudSpec defines the desired state of SolrCloud
type SolrCloudSpec struct {
// The number of solr nodes to run
// +optional
Replicas *int32 `json:"replicas,omitempty"`
// The information for the Zookeeper this SolrCloud should connect to
// Can be a zookeeper that is running, or one that is created by the solr operator
// +optional
ZookeeperRef *ZookeeperRef `json:"zookeeperRef,omitempty"`
// +optional
SolrImage *ContainerImage `json:"solrImage,omitempty"`
// DEPRECATED: Please use the options provided in customSolrKubeOptions.podOptions
//
// Pod defines the policy to create pod for the SolrCloud.
// Updating the Pod does not take effect on any existing pods.
// +optional
SolrPod SolrPodPolicy `json:"solrPodPolicy,omitempty"`
// DataPvcSpec is the spec to describe PVC for the solr node to store its data.
// This field is optional. If no PVC spec is provided, each solr node will use emptyDir as the data volume
// +optional
DataPvcSpec *corev1.PersistentVolumeClaimSpec `json:"dataPvcSpec,omitempty"`
// Required for backups & restores to be enabled.
// This is a volumeSource for a volume that will be mounted to all solrNodes to store backups and load restores.
// The data within the volume will be namespaces for this instance, so feel free to use the same volume for multiple clouds.
// Since the volume will be mounted to all solrNodes, it must be able to be written from multiple pods.
// If a PVC reference is given, the PVC must have `accessModes: - ReadWriteMany`.
// Other options are to use a NFS volume.
// +optional
BackupRestoreVolume *corev1.VolumeSource `json:"backupRestoreVolume,omitempty"`
// Provide custom options for kubernetes objects created for the Solr Cloud.
// +optional
CustomSolrKubeOptions CustomSolrKubeOptions `json:"customSolrKubeOptions,omitempty"`
// Customize how Solr is addressed both internally and externally in Kubernetes.
// +optional
SolrAddressability SolrAddressabilityOptions `json:"solrAddressability,omitempty"`
// +optional
BusyBoxImage *ContainerImage `json:"busyBoxImage,omitempty"`
// +optional
SolrJavaMem string `json:"solrJavaMem,omitempty"`
// You can add common system properties to the SOLR_OPTS environment variable
// SolrOpts is the string interface for these optional settings
// +optional
SolrOpts string `json:"solrOpts,omitempty"`
// Set the Solr Log level, defaults to INFO
// +optional
SolrLogLevel string `json:"solrLogLevel,omitempty"`
// Set GC Tuning configuration through GC_TUNE environment variable
// +optional
SolrGCTune string `json:"solrGCTune,omitempty"`
}
func (spec *SolrCloudSpec) withDefaults(ingressBaseDomain string) (changed bool) {
if spec.Replicas == nil {
changed = true
r := DefaultSolrReplicas
spec.Replicas = &r
}
if spec.SolrJavaMem == "" && DefaultSolrJavaMem != "" {
changed = true
spec.SolrJavaMem = DefaultSolrJavaMem
}
if spec.SolrOpts == "" && DefaultSolrOpts != "" {
changed = true
spec.SolrOpts = DefaultSolrOpts
}
if spec.SolrLogLevel == "" && DefaultSolrLogLevel != "" {
changed = true
spec.SolrLogLevel = DefaultSolrLogLevel
}
if spec.SolrGCTune == "" && DefaultSolrGCTune != "" {
changed = true
spec.SolrGCTune = DefaultSolrGCTune
}
changed = spec.SolrAddressability.withDefaults(ingressBaseDomain) || changed
if spec.ZookeeperRef == nil {
spec.ZookeeperRef = &ZookeeperRef{}
}
changed = spec.ZookeeperRef.withDefaults() || changed
if spec.SolrImage == nil {
spec.SolrImage = &ContainerImage{}
}
changed = spec.SolrImage.withDefaults(DefaultSolrRepo, DefaultSolrVersion, DefaultPullPolicy) || changed
if spec.DataPvcSpec != nil {
spec.DataPvcSpec.AccessModes = []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
}
if len(spec.DataPvcSpec.Resources.Requests) == 0 {
spec.DataPvcSpec.Resources.Requests = corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse(DefaultSolrStorage),
}
changed = true
}
if spec.DataPvcSpec.VolumeMode == nil {
temp := corev1.PersistentVolumeFilesystem
spec.DataPvcSpec.VolumeMode = &temp
}
}
if spec.BusyBoxImage == nil {
c := ContainerImage{}
spec.BusyBoxImage = &c
}
changed = spec.BusyBoxImage.withDefaults(DefaultBusyBoxImageRepo, DefaultBusyBoxImageVersion, DefaultPullPolicy) || changed
return changed
}
type CustomSolrKubeOptions struct {
// SolrPodOptions defines the custom options for solrCloud pods.
// +optional
PodOptions *PodOptions `json:"podOptions,omitempty"`
// StatefulSetOptions defines the custom options for the solrCloud StatefulSet.
// +optional
StatefulSetOptions *StatefulSetOptions `json:"statefulSetOptions,omitempty"`
// CommonServiceOptions defines the custom options for the common solrCloud Service.
// +optional
CommonServiceOptions *ServiceOptions `json:"commonServiceOptions,omitempty"`
// HeadlessServiceOptions defines the custom options for the headless solrCloud Service.
// +optional
HeadlessServiceOptions *ServiceOptions `json:"headlessServiceOptions,omitempty"`
// NodeServiceOptions defines the custom options for the individual solrCloud Node services, if they are created.
// These services will only be created when exposing SolrNodes externally via an Ingress in the AddressabilityOptions.
// +optional
NodeServiceOptions *ServiceOptions `json:"nodeServiceOptions,omitempty"`
// ServiceOptions defines the custom options for the solrCloud ConfigMap.
// +optional
ConfigMapOptions *ConfigMapOptions `json:"configMapOptions,omitempty"`
// IngressOptions defines the custom options for the solrCloud Ingress.
// +optional
IngressOptions *IngressOptions `json:"ingressOptions,omitempty"`
}
type SolrAddressabilityOptions struct {
// External defines the way in which this SolrCloud nodes should be made addressable externally, from outside the Kubernetes cluster.
// If none is provided, the Solr Cloud will not be made addressable externally.
// +optional
External *ExternalAddressability `json:"external,omitempty"`
// PodPort defines the port to have the Solr Pod listen on.
// Defaults to 8983
// +optional
PodPort int `json:"podPort,omitempty"`
// CommonServicePort defines the port to have the common Solr service listen on.
// Defaults to 80
// +optional
CommonServicePort int `json:"commonServicePort,omitempty"`
// KubeDomain allows for the specification of an override of the default "cluster.local" Kubernetes cluster domain.
// Only use this option if the Kubernetes cluster has been setup with a custom domain.
// +optional
KubeDomain string `json:"kubeDomain,omitempty"`
}
func (opts *SolrAddressabilityOptions) withDefaults(ingressBaseDomain string) (changed bool) {
// DEPRECATED: ingressBaseDomain will be removed in v0.3.0
if opts.External == nil && ingressBaseDomain != "" {
changed = true
opts.External = &ExternalAddressability{
Method: Ingress,
DomainName: ingressBaseDomain,
UseExternalAddress: true,
NodePortOverride: 80,
}
} else if opts.External != nil {
changed = opts.External.withDefaults()
}
if opts.PodPort == 0 {
changed = true
opts.PodPort = 8983
}
if opts.CommonServicePort == 0 {
changed = true
opts.CommonServicePort = 80
}
return changed
}
// ExternalAddressability defines the config for making Solr services available externally to kubernetes.
// Be careful when using LoadBalanced and includeNodes, as many IP addresses could be created if you are running many large solrClouds.
type ExternalAddressability struct {
// The way in which this SolrCloud's service(s) should be made addressable externally.
Method ExternalAddressabilityMethod `json:"method"`
// Use the external address to advertise the SolrNode, defaults to false.
//
// 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.
// If true, Solr will startup with the hostname of the external address.
//
// 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.
//
// Deprecation warning: When an ingress-base-domain is passed in to the operator, this value defaults to true.
// +optional
UseExternalAddress bool `json:"useExternalAddress"`
// Do not expose the common Solr service externally. This affects a single service.
// Defaults to false.
// +optional
HideCommon bool `json:"hideCommon,omitempty"`
// Do not expose each of the Solr Node services externally.
// 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).
// Defaults to false.
// +optional
HideNodes bool `json:"hideNodes,omitempty"`
// Override the domainName provided as startup parameters to the operator, used by ingresses and externalDNS.
// The common and/or node services will be addressable by unique names under the given domain.
// e.g. default-example-solrcloud.given.domain.name.com
//
// This options will be required for the Ingress and ExternalDNS methods once the ingressBaseDomain startup parameter is removed.
//
// For the LoadBalancer method, this field is optional and will only be used when useExternalAddress=true.
// If used with the LoadBalancer method, you will need DNS routing to the LoadBalancer IP address through the url template given above.
// +optional
DomainName string `json:"domainName,omitempty"`
// Provide additional domainNames that the Ingress or ExternalDNS should listen on.
// This option is ignored with the LoadBalancer method.
// +optional
AdditionalDomainNames []string `json:"additionalDomains,omitempty"`
// NodePortOverride defines the port to have all Solr node service(s) listen on and advertise itself as if advertising through an Ingress or LoadBalancer.
// This overrides the default usage of the podPort.
//
// This is option is only used when HideNodes=false, otherwise the the port each Solr Node will advertise itself with the podPort.
// This option is also unavailable with the ExternalDNS method.
//
// If using method=Ingress, your ingress controller is required to listen on this port.
// If your ingress controller is not listening on the podPort, then this option is required for solr to be addressable via an Ingress.
//
// Defaults to 80 if HideNodes=false and method=Ingress, otherwise this is optional.
// +optional
NodePortOverride int `json:"nodePortOverride,omitempty"`
}
// ExternalAddressability 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
type ExternalAddressabilityMethod string
const (
// Use an ingress to make the Solr service(s) externally addressable
Ingress ExternalAddressabilityMethod = "Ingress"
// Use ExternalDNS to make the Solr service(s) externally addressable
ExternalDNS ExternalAddressabilityMethod = "ExternalDNS"
// Make Solr service(s) type:LoadBalancer to make them externally addressable
// NOTE: This option is not currently supported.
LoadBalancer ExternalAddressabilityMethod = "LoadBalancer"
)
func (opts *ExternalAddressability) withDefaults() (changed bool) {
// You can't use an externalAddress for Solr Nodes if the Nodes are hidden externally
if opts.UseExternalAddress && opts.HideNodes {
changed = true
opts.UseExternalAddress = false
}
// If the Ingress method is used, default the nodePortOverride to 80, since that is the port that most ingress controllers listen on.
if !opts.HideNodes && opts.Method == Ingress && opts.NodePortOverride == 0 {
changed = true
opts.NodePortOverride = 80
}
// If a headless service is used, aka not using individual node services, then a nodePortOverride is not allowed.
if !opts.UsesIndividualNodeServices() && opts.NodePortOverride > 0 {
changed = true
opts.NodePortOverride = 0
}
return changed
}
// DEPRECATED: Please use the options provided in SolrCloud.Spec.customSolrKubeOptions.podOptions
//
// SolrPodPolicy defines the common pod configuration for Pods, including when used
// in deployments, stateful-sets, etc.
type SolrPodPolicy struct {
// The scheduling constraints on pods.
// +optional
Affinity *corev1.Affinity `json:"affinity,omitempty"`
// Resources is the resource requirements for the container.
// This field cannot be updated once the cluster is created.
// +optional
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}
// ZookeeperRef defines the zookeeper ensemble for solr to connect to
// If no ConnectionString is provided, the solr-cloud controller will create and manage an internal ensemble
type ZookeeperRef struct {
// A zookeeper ensemble that is run independently of the solr operator
// If an externalConnectionString is provided, but no internalConnectionString is, the external will be used as the internal
// +optional
ConnectionInfo *ZookeeperConnectionInfo `json:"connectionInfo,omitempty"`
// A zookeeper that is created by the solr operator
// Note: This option will not allow the SolrCloud to run across kube-clusters.
// +optional
ProvidedZookeeper *ProvidedZookeeper `json:"provided,omitempty"`
}
func (ref *ZookeeperRef) withDefaults() (changed bool) {
if ref.ProvidedZookeeper == nil && ref.ConnectionInfo == nil {
changed = true
ref.ProvidedZookeeper = &ProvidedZookeeper{}
} else if ref.ConnectionInfo != nil {
if ref.ProvidedZookeeper != nil {
ref.ProvidedZookeeper = nil
changed = true
}
changed = ref.ConnectionInfo.withDefaults() || changed
}
if ref.ProvidedZookeeper != nil {
changed = ref.ProvidedZookeeper.withDefaults() || changed
}
return changed
}
func (ci *ZookeeperConnectionInfo) withDefaults() (changed bool) {
if ci.InternalConnectionString == "" {
if ci.ExternalConnectionString != nil {
changed = true
ci.InternalConnectionString = *ci.ExternalConnectionString
}
}
if ci.ChRoot == "" {
changed = true
ci.ChRoot = "/"
} else if !strings.HasPrefix(ci.ChRoot, "/") {
changed = true
ci.ChRoot = "/" + ci.ChRoot
}
return changed
}
// ProvidedZookeeper defines the internal zookeeper ensemble to run
type ProvidedZookeeper struct {
// Create a new Zookeeper Ensemble with the following spec
// Note: Requires
// - The zookeeperOperator flag to be provided to the Solr Operator
// - A zookeeper operator to be running
// +optional
Zookeeper *ZookeeperSpec `json:"zookeeper,omitempty"`
// Create a new Etcd Cluster and a Zetcd proxy to connect the cluster to solr
// Note: Requires
// - The etcdOperator flag to be provided to the Solr Operator
// - An etcd operator to be running
// +optional
Zetcd *FullZetcdSpec `json:"zetcd,inline"`
// The ChRoot to connect solr at
// +optional
ChRoot string `json:"chroot,omitempty"`
}
func (z *ProvidedZookeeper) withDefaults() (changed bool) {
if z.Zookeeper == nil && z.Zetcd == nil {
changed = true
z.Zookeeper = &ZookeeperSpec{}
}
if z.Zookeeper != nil {
changed = z.Zookeeper.withDefaults() || changed
}
if z.Zetcd != nil {
changed = z.Zetcd.withDefaults() || changed
}
if z.ChRoot == "" {
changed = true
z.ChRoot = "/"
} else if !strings.HasPrefix(z.ChRoot, "/") {
changed = true
z.ChRoot = "/" + z.ChRoot
}
return changed
}
// ZookeeperSpec defines the internal zookeeper ensemble to run for solr
type ZookeeperSpec struct {
// Number of members to create up for the ZK ensemble
// Defaults to 3
// +optional
Replicas *int32 `json:"replicas,omitempty"`
// Image of Zookeeper to run
// +optional
Image *ContainerImage `json:"image,omitempty"`
// PersistentVolumeClaimSpec is the spec to describe PVC for the zk container
// This field is optional. If no PVC spec is provided, etcd container will use emptyDir as volume.
// WARNING: This field is DEPRECATED, please use the Persistence option
// +optional
PersistentVolumeClaimSpec *corev1.PersistentVolumeClaimSpec `json:"persistentVolumeClaimSpec,omitempty"`
// Persistence is the configuration for zookeeper persistent layer.
// PersistentVolumeClaimSpec and VolumeReclaimPolicy can be specified in here.
// +optional
Persistence *zk.Persistence `json:"persistence,omitempty"`
// Pod resources for zookeeper pod
// +optional
ZookeeperPod ZookeeperPodPolicy `json:"zookeeperPodPolicy,omitempty"`
}
// ZookeeperPodPolicy defines the common pod configuration for Pods, including when used
// in deployments, stateful-sets, etc.
type ZookeeperPodPolicy struct {
// The scheduling constraints on pods.
// +optional
Affinity *corev1.Affinity `json:"affinity,omitempty"`
// Node Selector to be added on pods.
// +optional
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
// Tolerations to be added on pods.
// +optional
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
// Resources is the resource requirements for the container.
// This field cannot be updated once the cluster is created.
// +optional
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}
func (z *ZookeeperSpec) withDefaults() (changed bool) {
if z.Replicas == nil {
changed = true
r := DefaultZkReplicas
z.Replicas = &r
}
if z.Image == nil {
z.Image = &ContainerImage{}
}
changed = z.Image.withDefaults(DefaultZkRepo, DefaultZkVersion, DefaultPullPolicy) || changed
// Backwards compatibility with old ZK Persistence options.
// This will be removed eventually
if z.Persistence == nil && z.PersistentVolumeClaimSpec != nil {
z.Persistence = &zk.Persistence{
PersistentVolumeClaimSpec: *z.PersistentVolumeClaimSpec,
}
changed = true
}
if z.Persistence != nil {
if z.Persistence.VolumeReclaimPolicy == "" {
z.Persistence.VolumeReclaimPolicy = DefaultZkVolumeReclaimPolicy
changed = true
}
if len(z.Persistence.PersistentVolumeClaimSpec.AccessModes) == 0 {
z.Persistence.PersistentVolumeClaimSpec.AccessModes = []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
}
changed = true
}
if len(z.Persistence.PersistentVolumeClaimSpec.Resources.Requests) == 0 {
z.Persistence.PersistentVolumeClaimSpec.Resources.Requests = corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse(DefaultZkStorage),
}
changed = true
}
}
return changed
}
// FullZetcdSpec defines the internal etcd ensemble and zetcd server to run for solr (spoofing zookeeper)
type FullZetcdSpec struct {
// +optional
EtcdSpec *EtcdSpec `json:"etcdSpec,omitempty"`
// +optional
ZetcdSpec *ZetcdSpec `json:"zetcdSpec,omitempty"`
}
func (z *FullZetcdSpec) withDefaults() (changed bool) {
if z.EtcdSpec == nil {
z.EtcdSpec = &EtcdSpec{}
}
changed = z.EtcdSpec.withDefaults() || changed
if z.ZetcdSpec == nil {
z.ZetcdSpec = &ZetcdSpec{}
}
changed = z.ZetcdSpec.withDefaults() || changed
return changed
}
// EtcdSpec defines the internal etcd ensemble to run for solr (spoofing zookeeper)
type EtcdSpec struct {
// The number of EtcdReplicas to create
// +optional
Replicas *int `json:"replicas,omitempty"`
// +optional
Image *ContainerImage `json:"image,omitempty"`
// PersistentVolumeClaimSpec is the spec to describe PVC for the zk container
// This field is optional. If no PVC spec, etcd container will use emptyDir as volume
PersistentVolumeClaimSpec *corev1.PersistentVolumeClaimSpec `json:"persistentVolumeClaimSpec,omitempty"`
// Pod resources for etcd pods
// +optional
EtcdPod EtcdPodPolicy `json:"etcdPodPolicy,omitempty"`
}
// EtcdPodPolicy defines the common pod configuration for Pods, including when used
// in deployments, stateful-sets, etc.
type EtcdPodPolicy struct {
// The scheduling constraints on pods.
// +optional
Affinity *corev1.Affinity `json:"affinity,omitempty"`
// Resources is the resource requirements for the container.
// This field cannot be updated once the cluster is created.
// +optional
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}
func (s *EtcdSpec) withDefaults() (changed bool) {
if s.Replicas == nil {
changed = true
r := DefaultEtcdReplicas
s.Replicas = &r
}
if s.Image == nil {
s.Image = &ContainerImage{}
}
changed = s.Image.withDefaults(DefaultEtcdRepo, DefaultEtcdVersion, DefaultPullPolicy) || changed
return changed
}
// ZetcdSpec defines the zetcd proxy to run connection solr and etcd
type ZetcdSpec struct {
// +optional
Replicas *int32 `json:"replicas,omitempty"`
// +optional
Image *ContainerImage `json:"image,omitempty"`
// Pod resources for zetcd pods
// +optional
ZetcdPod ZetcdPodPolicy `json:"zetcdPodPolicy,omitempty"`
}
// EtcdPodPolicy defines the common pod configuration for Pods, including when used
// in deployments, stateful-sets, etc.
type ZetcdPodPolicy struct {
// The scheduling constraints on pods.
// +optional
Affinity *corev1.Affinity `json:"affinity,omitempty"`
// Resources is the resource requirements for the container.
// This field cannot be updated once the cluster is created.
// +optional
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}
func (s *ZetcdSpec) withDefaults() (changed bool) {
if s.Replicas == nil {
changed = true
r := DefaultZetcdReplicas
s.Replicas = &r
}
if s.Image == nil {
s.Image = &ContainerImage{}
}
changed = s.Image.withDefaults(DefaultZetcdRepo, DefaultZetcdVersion, DefaultPullPolicy) || changed
return changed
}
// SolrCloudStatus defines the observed state of SolrCloud
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 int32 `json:"replicas"`
// ReadyReplicas is the number of number of ready replicas in the cluster
ReadyReplicas int32 `json:"readyReplicas"`
// The version of solr that the cloud is running
Version string `json:"version"`
// The version of solr that the cloud is meant to be running.
// Will only be provided when the cloud is migrating between versions
// +optional
TargetVersion string `json:"targetVersion,omitempty"`
// InternalCommonAddress is the internal common http address for all solr nodes
InternalCommonAddress string `json:"internalCommonAddress"`
// ExternalCommonAddress is the external common http address for all solr nodes.
// Will only be provided when an ingressUrl is provided for the cloud
// +optional
ExternalCommonAddress *string `json:"externalCommonAddress,omitempty"`
// ZookeeperConnectionInfo is the information on how to connect to the used Zookeeper
ZookeeperConnectionInfo ZookeeperConnectionInfo `json:"zookeeperConnectionInfo"`
// BackupRestoreReady announces whether the solrCloud has the backupRestorePVC mounted to all pods
// and therefore is ready for backups and restores.
BackupRestoreReady bool `json:"backupRestoreReady"`
}
// SolrNodeStatus is the status of a solrNode in the cloud, with readiness status
// and internal and external addresses
type SolrNodeStatus struct {
// The name of the pod running the node
Name string `json:"name"`
// The name of the Kubernetes Node which the pod is running on
NodeName string `json:"nodeName"`
// An address the node can be connected to from within the Kube cluster
InternalAddress string `json:"internalAddress"`
// An address the node can be connected to from outside of the Kube cluster
// Will only be provided when an ingressUrl is provided for the cloud
// +optional
ExternalAddress string `json:"externalAddress,omitempty"`
// Is the node up and running
Ready bool `json:"ready"`
// The version of solr that the node is running
Version string `json:"version"`
}
// SolrNodeStatus is the status of a solrNode in the cloud, with readiness status
// and internal and external addresses
type ZookeeperConnectionInfo struct {
// The connection string to connect to the ensemble from within the Kubernetes cluster
// +optional
InternalConnectionString string `json:"internalConnectionString,omitempty"`
// The connection string to connect to the ensemble from outside of the Kubernetes cluster
// If external and no internal connection string is provided, the external cnx string will be used as the internal cnx string
// +optional
ExternalConnectionString *string `json:"externalConnectionString,omitempty"`
// The ChRoot to connect solr at
// +optional
ChRoot string `json:"chroot,omitempty"`
}
// +kubebuilder:object:root=true
// SolrCloud is the Schema for the solrclouds API
// +kubebuilder:resource:shortName=solr
// +kubebuilder:categories=all
// +kubebuilder:subresource:status
// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.readyReplicas
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".status.version",description="Solr Version of the cloud"
// +kubebuilder:printcolumn:name="TargetVersion",type="string",JSONPath=".status.targetVersion",description="Target Solr Version of the cloud"
// +kubebuilder:printcolumn:name="DesiredNodes",type="integer",JSONPath=".spec.replicas",description="Number of solr nodes configured to run in the cloud"
// +kubebuilder:printcolumn:name="Nodes",type="integer",JSONPath=".status.replicas",description="Number of solr nodes running"
// +kubebuilder:printcolumn:name="ReadyNodes",type="integer",JSONPath=".status.readyReplicas",description="Number of solr nodes connected to the cloud"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type SolrCloud struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SolrCloudSpec `json:"spec,omitempty"`
Status SolrCloudStatus `json:"status,omitempty"`
}
// WithDefaults set default values when not defined in the spec.
func (sc *SolrCloud) WithDefaults(ingressBaseDomain string) bool {
return sc.Spec.withDefaults(ingressBaseDomain)
}
func (sc *SolrCloud) GetAllSolrNodeNames() []string {
replicas := 1
if sc.Spec.Replicas != nil {
replicas = int(*sc.Spec.Replicas)
}
nodeNames := make([]string, replicas)
statefulSetName := sc.StatefulSetName()
for i := range nodeNames {
nodeNames[i] = fmt.Sprintf("%s-%d", statefulSetName, i)
}
return nodeNames
}
// ConfigMapName returns the name of the cloud config-map
func (sc *SolrCloud) ConfigMapName() string {
return fmt.Sprintf("%s-solrcloud-configmap", sc.GetName())
}
// StatefulSetName returns the name of the statefulset for the cloud
func (sc *SolrCloud) StatefulSetName() string {
return fmt.Sprintf("%s-solrcloud", sc.GetName())
}
// CommonServiceName returns the name of the common service for the cloud
func (sc *SolrCloud) CommonServiceName() string {
return fmt.Sprintf("%s-solrcloud-common", sc.GetName())
}
// InternalURLForCloud returns the name of the common service for the cloud
func InternalURLForCloud(cloudName string, namespace string) string {
return fmt.Sprintf("http://%s-solrcloud-common.%s", cloudName, namespace)
}
// HeadlessServiceName returns the name of the headless service for the cloud
func (sc *SolrCloud) HeadlessServiceName() string {
return fmt.Sprintf("%s-solrcloud-headless", sc.GetName())
}
// CommonIngressName returns the name of the common ingress for the cloud
func (sc *SolrCloud) CommonIngressName() string {
return fmt.Sprintf("%s-solrcloud-common", sc.GetName())
}
// ProvidedZookeeperName returns the provided zk cluster
func (sc *SolrCloud) ProvidedZookeeperName() string {
return fmt.Sprintf("%s-solrcloud-zookeeper", sc.GetName())
}
// ProvidedZookeeperAddress returns the client address of the provided zk cluster
func (sc *SolrCloud) ProvidedZookeeperAddress() string {
return fmt.Sprintf("%s-solrcloud-zookeeper-client:2181", sc.GetName())
}
// ProvidedZetcdName returns the name of the zetcd cluster
func (sc *SolrCloud) ProvidedZetcdName() string {
return fmt.Sprintf("%s-solrcloud-zetcd", sc.GetName())
}
// IngressName returns the name of the ingress for the cloud
func (sc *SolrCloud) ProvidedZetcdAddress() string {
return fmt.Sprintf("%s-solrcloud-zetcd:2181", sc.GetName())
}
// ZkConnectionString returns the zkConnectionString for the cloud
func (sc *SolrCloud) ZkConnectionString() string {
return sc.Status.ZkConnectionString()
}
func (scs SolrCloudStatus) ZkConnectionString() string {
return scs.ZookeeperConnectionInfo.ZkConnectionString()
}
func (zkInfo ZookeeperConnectionInfo) ZkConnectionString() string {
return zkInfo.InternalConnectionString + zkInfo.ChRoot
}
// UsesHeadlessService returns whether the given solrCloud requires a headless service to be created for it.
// solrCloud: SolrCloud instance
func (sc *SolrCloud) UsesHeadlessService() bool {
return !sc.Spec.SolrAddressability.External.UsesIndividualNodeServices()
}
// UsesIndividualNodeServices returns whether the given solrCloud requires a individual node services to be created for it.
// solrCloud: SolrCloud instance
func (sc *SolrCloud) UsesIndividualNodeServices() bool {
return sc.Spec.SolrAddressability.External.UsesIndividualNodeServices()
}
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)
}
func (sc *SolrCloud) CommonExternalPrefix() string {
return fmt.Sprintf("%s-%s-solrcloud", sc.Namespace, sc.Name)
}
func (sc *SolrCloud) CommonExternalUrl(domainName string) string {
return fmt.Sprintf("%s.%s", sc.CommonExternalPrefix(), domainName)
}
func (sc *SolrCloud) NodeIngressPrefix(nodeName string) string {
return fmt.Sprintf("%s-%s", sc.Namespace, nodeName)
}
func (sc *SolrCloud) ExternalDnsDomain(domainName string) string {
return fmt.Sprintf("%s.%s", sc.Namespace, domainName)
}
func (sc *SolrCloud) customKubeDomain() string {
if sc.Spec.SolrAddressability.KubeDomain != "" {
return ".svc." + sc.Spec.SolrAddressability.KubeDomain
} else {
return ""
}
}
func (sc *SolrCloud) NodeHeadlessUrl(nodeName string, withPort bool) (url string) {
url = fmt.Sprintf("%s.%s.%s", nodeName, sc.HeadlessServiceName(), sc.Namespace) + sc.customKubeDomain()
if withPort {
url += sc.NodePortSuffix()
}
return url
}
func (sc *SolrCloud) NodeServiceUrl(nodeName string, withPort bool) (url string) {
url = fmt.Sprintf("%s.%s", nodeName, sc.Namespace) + sc.customKubeDomain()
if withPort {
url += sc.NodePortSuffix()
}
return url
}
func (sc *SolrCloud) CommonPortSuffix() string {
return PortToSuffix(sc.Spec.SolrAddressability.CommonServicePort)
}
func (sc *SolrCloud) NodePortSuffix() string {
return PortToSuffix(sc.NodePort())
}
func (sc *SolrCloud) NodePort() int {
port := sc.Spec.SolrAddressability.PodPort
external := sc.Spec.SolrAddressability.External
// The nodePort is different than the podPort ONLY if the nodes are exposed externally and a nodePortOverride has been set.
if external.UsesIndividualNodeServices() && external.NodePortOverride > 0 {
port = sc.Spec.SolrAddressability.External.NodePortOverride
}
return port
}
// PortToSuffix returns the url suffix for a port.
// Port 80 does not require a suffix, as it is the default port for HTTP.
func PortToSuffix(port int) string {
if port == 80 {
return ""
}
return ":" + strconv.Itoa(port)
}
func (sc *SolrCloud) InternalNodeUrl(nodeName string, withPort bool) string {
if sc.UsesHeadlessService() {
return sc.NodeHeadlessUrl(nodeName, withPort)
} else if sc.UsesIndividualNodeServices() {
return sc.NodeServiceUrl(nodeName, withPort)
} else {
return ""
}
}
func (sc *SolrCloud) InternalCommonUrl(withPort bool) (url string) {
url = fmt.Sprintf("%s.%s", sc.CommonServiceName(), sc.Namespace) + sc.customKubeDomain()
if withPort {
url += sc.NodePortSuffix()
}
return url
}
func (sc *SolrCloud) ExternalNodeUrl(nodeName string, domainName string, withPort bool) (url string) {
if sc.Spec.SolrAddressability.External.Method == Ingress {
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))
}
// TODO: Add LoadBalancer stuff here
if withPort {
url += sc.NodePortSuffix()
}
return url
}
func (sc *SolrCloud) ExternalCommonUrl(domainName string, withPort bool) (url string) {
if sc.Spec.SolrAddressability.External.Method == Ingress {
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))
}
if withPort {
url += sc.CommonPortSuffix()
}
return url
}
func (sc *SolrCloud) AdvertisedNodeHost(nodeName string) string {
external := sc.Spec.SolrAddressability.External
if external != nil && external.UseExternalAddress {
return sc.ExternalNodeUrl(nodeName, sc.Spec.SolrAddressability.External.DomainName, false)
} else {
return sc.InternalNodeUrl(nodeName, false)
}
}
func (sc *SolrCloud) SharedLabels() map[string]string {
return sc.SharedLabelsWith(map[string]string{})
}
func (sc *SolrCloud) SharedLabelsWith(labels map[string]string) map[string]string {
newLabels := map[string]string{}
if labels != nil {
for k, v := range labels {
newLabels[k] = v
}
}
newLabels["solr-cloud"] = sc.Name
return newLabels
}
// +kubebuilder:object:root=true
// SolrCloudList contains a list of SolrCloud
type SolrCloudList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []SolrCloud `json:"items"`
}
func init() {
SchemeBuilder.Register(&SolrCloud{}, &SolrCloudList{})
}