| /* |
| * 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 v1beta1 |
| |
| import ( |
| "fmt" |
| "github.com/go-logr/logr" |
| "strconv" |
| "strings" |
| |
| "k8s.io/apimachinery/pkg/util/intstr" |
| |
| corev1 "k8s.io/api/core/v1" |
| "k8s.io/apimachinery/pkg/api/resource" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| ) |
| |
| const ( |
| DefaultPullPolicy = "" // This will use the default pullPolicy of Always when the tag is "latest" and IfNotPresent for all other tags. |
| |
| DefaultSolrReplicas = int32(3) |
| DefaultSolrRepo = "library/solr" |
| DefaultSolrVersion = "8.11" |
| DefaultSolrJavaMem = "-Xms1g -Xmx2g" |
| DefaultSolrOpts = "" |
| DefaultSolrLogLevel = "INFO" |
| DefaultSolrGCTune = "" |
| |
| DefaultBusyBoxImageRepo = "library/busybox" |
| DefaultBusyBoxImageVersion = "1.28.0-glibc" |
| |
| DefaultZkReplicas = int32(3) |
| DefaultZkStorage = "5Gi" |
| DefaultZkRepo = "pravega/zookeeper" |
| DefaultZkVersion = "" |
| DefaultZkVolumeReclaimPolicy VolumeReclaimPolicy = "Retain" |
| |
| SolrTechnologyLabel = "solr-cloud" |
| ZookeeperTechnologyLabel = "zookeeper" |
| |
| DefaultBasicAuthUsername = "k8s-oper" |
| |
| LegacyBackupRepositoryName = "legacy_volume_repository" |
| ) |
| |
| // 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"` |
| |
| // Customize how the cloud data is stored. |
| // If neither "persistent" or "ephemeral" is provided, then ephemeral storage will be used by default. |
| // |
| // +optional |
| StorageOptions SolrDataStorageOptions `json:"dataStorage,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"` |
| |
| // Define how Solr rolling updates are executed. |
| // +optional |
| UpdateStrategy SolrUpdateStrategy `json:"updateStrategy,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"` |
| |
| // This will add java system properties for connecting to Zookeeper. |
| // SolrZkOpts is the string interface for these optional settings |
| // +optional |
| SolrZkOpts string `json:"solrZkOpts,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"` |
| |
| // Options to enable the server TLS certificate for Solr pods |
| // +optional |
| SolrTLS *SolrTLSOptions `json:"solrTLS,omitempty"` |
| |
| // Options to configure client TLS certificate for Solr pods |
| // +optional |
| SolrClientTLS *SolrTLSOptions `json:"solrClientTLS,omitempty"` |
| |
| // Options to enable Solr security |
| // +optional |
| SolrSecurity *SolrSecurityOptions `json:"solrSecurity,omitempty"` |
| |
| // Allows specification of multiple different "repositories" for Solr to use when backing up data. |
| //+optional |
| //+listType:=map |
| //+listMapKey:=name |
| BackupRepositories []SolrBackupRepository `json:"backupRepositories,omitempty"` |
| |
| // List of Solr Modules to be loaded when starting Solr |
| // Note: You do not need to specify a module if it is required by another property (e.g. backupRepositories[].gcs) |
| // |
| //+optional |
| SolrModules []string `json:"solrModules,omitempty"` |
| |
| // List of paths in the Solr Docker image to load in the classpath. |
| // Note: Solr Modules will be auto-loaded if specified in the "solrModules" property. There is no need to specify them here as well. |
| // |
| //+optional |
| AdditionalLibs []string `json:"additionalLibs,omitempty"` |
| } |
| |
| func (spec *SolrCloudSpec) withDefaults(logger logr.Logger) (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(spec.SolrTLS != nil, logger) || changed |
| |
| changed = spec.UpdateStrategy.withDefaults() || 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 |
| |
| changed = spec.StorageOptions.withDefaults() || changed |
| |
| 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 SolrDataStorageOptions struct { |
| |
| // PersistentStorage is the specification for how the persistent Solr data storage should be configured. |
| // |
| // This option cannot be used with the "ephemeral" option. |
| // |
| // +optional |
| PersistentStorage *SolrPersistentDataStorageOptions `json:"persistent,omitempty"` |
| |
| // EphemeralStorage is the specification for how the ephemeral Solr data storage should be configured. |
| // |
| // This option cannot be used with the "persistent" option. |
| // Ephemeral storage is used by default if neither "persistent" or "ephemeral" is provided. |
| // |
| // +optional |
| EphemeralStorage *SolrEphemeralDataStorageOptions `json:"ephemeral,omitempty"` |
| } |
| |
| func (opts *SolrDataStorageOptions) withDefaults() (changed bool) { |
| if opts.PersistentStorage != nil { |
| changed = changed || opts.PersistentStorage.withDefaults() |
| } |
| |
| return changed |
| } |
| |
| type SolrPersistentDataStorageOptions struct { |
| |
| // VolumeReclaimPolicy determines how the Solr Cloud's PVCs will be treated after the cloud is deleted. |
| // - Retain: This is the default Kubernetes policy, where PVCs created for StatefulSets are not deleted when the StatefulSet is deleted. |
| // - Delete: The PVCs will be deleted by the Solr Operator after the SolrCloud object is deleted. |
| // The default value is Retain, so no data will be deleted unless explicitly configured. |
| // +optional |
| VolumeReclaimPolicy VolumeReclaimPolicy `json:"reclaimPolicy,omitempty"` |
| |
| // PersistentVolumeClaimTemplate is the PVC object for the solr node to store its data. |
| // Within metadata, the Name, Labels and Annotations are able to be specified, but defaults will be provided if necessary. |
| // The entire Spec is customizable, however there will be defaults provided if necessary. |
| // This field is optional. If no PVC spec is provided, then a default will be provided. |
| // +optional |
| PersistentVolumeClaimTemplate PersistentVolumeClaimTemplate `json:"pvcTemplate,omitempty"` |
| } |
| |
| func (opts *SolrPersistentDataStorageOptions) withDefaults() (changed bool) { |
| if opts.VolumeReclaimPolicy == "" { |
| changed = true |
| opts.VolumeReclaimPolicy = VolumeReclaimPolicyRetain |
| } |
| |
| return changed |
| } |
| |
| // VolumeReclaimPolicy is a string enumeration type that enumerates |
| // all possible ways that a SolrCloud can treat it's PVCs after its death |
| // +kubebuilder:validation:Enum=Retain;Delete |
| type VolumeReclaimPolicy string |
| |
| const ( |
| // All pod PVCs are retained after the SolrCloud is deleted. |
| VolumeReclaimPolicyRetain VolumeReclaimPolicy = "Retain" |
| |
| // All pod PVCs are deleted after the SolrCloud is deleted. |
| VolumeReclaimPolicyDelete VolumeReclaimPolicy = "Delete" |
| ) |
| |
| // PersistentVolumeClaimTemplate is used to produce |
| // PersistentVolumeClaim objects as part of an EphemeralVolumeSource. |
| type PersistentVolumeClaimTemplate struct { |
| // May contain labels and annotations that will be copied into the PVC |
| // when creating it. No other fields are allowed and will be rejected during |
| // validation. |
| // |
| // +optional |
| ObjectMeta TemplateMeta `json:"metadata,omitempty"` |
| |
| // The specification for the PersistentVolumeClaim. The entire content is |
| // copied unchanged into the PVC that gets created from this |
| // template. The same fields as in a PersistentVolumeClaim |
| // are also valid here. |
| // |
| // +optional |
| Spec corev1.PersistentVolumeClaimSpec `json:"spec,omitempty"` |
| } |
| |
| // TemplateMeta is metadata for templated resources. |
| type TemplateMeta struct { |
| // Name must be unique within a namespace. Is required when creating resources, although |
| // some resources may allow a client to request the generation of an appropriate name |
| // automatically. Name is primarily intended for creation idempotence and configuration |
| // definition. |
| // Cannot be updated. |
| // More info: http://kubernetes.io/docs/user-guide/identifiers#names |
| // +optional |
| Name string `json:"name,omitempty"` |
| |
| // Map of string keys and values that can be used to organize and categorize |
| // (scope and select) objects. May match selectors of replication controllers |
| // and services. |
| // More info: http://kubernetes.io/docs/user-guide/labels |
| // +optional |
| Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"` |
| |
| // Annotations is an unstructured key value map stored with a resource that may be |
| // set by external tools to store and retrieve arbitrary metadata. They are not |
| // queryable and should be preserved when modifying objects. |
| // More info: http://kubernetes.io/docs/user-guide/annotations |
| // +optional |
| Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"` |
| } |
| |
| type SolrEphemeralDataStorageOptions struct { |
| // HostPathVolumeSource is an optional config to specify a path on the host machine to store Solr data. |
| // |
| // If hostPath is omitted, then the default EmptyDir is used, otherwise hostPath takes precedence over EmptyDir. |
| // +optional |
| HostPath *corev1.HostPathVolumeSource `json:"hostPath,omitempty"` |
| //EmptyDirVolumeSource is an optional config for the emptydir volume that will store Solr data. |
| // +optional |
| EmptyDir *corev1.EmptyDirVolumeSource `json:"emptyDir,omitempty"` |
| } |
| |
| //+kubebuilder:validation:MinProperties:=2 |
| //+kubebuilder:validation:MaxProperties:=2 |
| type SolrBackupRepository struct { |
| // A name used to identify this local storage profile. Values should follow RFC-1123. (See here for more details: |
| // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names) |
| // |
| // +kubebuilder:validation:Pattern:=[a-zA-Z0-9]([-_a-zA-Z0-9]*[a-zA-Z0-9])? |
| // +kubebuilder:validation:MinLength:=1 |
| // +kubebuilder:validation:MaxLength:=100 |
| Name string `json:"name"` |
| |
| // A GCSRepository for Solr to use when backing up and restoring collections. |
| //+optional |
| GCS *GcsRepository `json:"gcs,omitempty"` |
| |
| // An S3Repository for Solr to use when backing up and restoring collections. |
| //+optional |
| S3 *S3Repository `json:"s3,omitempty"` |
| |
| // Allows specification of a "repository" for Solr to use when backing up data "locally". |
| //+optional |
| Volume *VolumeRepository `json:"volume,omitempty"` |
| } |
| |
| type GcsRepository struct { |
| // The name of the GCS bucket that all backup data will be stored in |
| Bucket string `json:"bucket"` |
| |
| // The name & key of a Kubernetes secret holding a Google cloud service account key. Must be set unless deployed in |
| // GKE and making use of Google's "Workplace Identity" feature. |
| //+optional |
| GcsCredentialSecret *corev1.SecretKeySelector `json:"gcsCredentialSecret,omitempty"` |
| |
| // An already-created chroot within the bucket to store data in. Defaults to the root path "/" if not specified. |
| // +optional |
| BaseLocation string `json:"baseLocation,omitempty"` |
| } |
| |
| type S3Repository struct { |
| // The S3 region to store the backup data in |
| Region string `json:"region"` |
| |
| // The name of the S3 bucket that all backup data will be stored in |
| Bucket string `json:"bucket"` |
| |
| // Options for specifying S3Credentials. This is optional in case you want to mount this information yourself. |
| // However, if you do not include these credentials, and you do not load them yourself via a mount or EnvVars, |
| // you will likely see errors when taking s3 backups. |
| // |
| // If running in EKS, you can create an IAMServiceAccount that uses a role permissioned for this S3 bucket. |
| // Then use that serviceAccountName for your SolrCloud, and the credentials should be auto-populated. |
| // |
| // +optional |
| Credentials *S3Credentials `json:"credentials,omitempty"` |
| |
| // An already-created chroot within the bucket to store data in. Defaults to the root path "/" if not specified. |
| // +optional |
| BaseLocation string `json:"baseLocation,omitempty"` |
| |
| // The full endpoint URL to use when connecting with S3 (or a supported S3 compatible interface) |
| // +optional |
| Endpoint string `json:"endpoint,omitempty"` |
| |
| // The full proxy URL to use when connecting with S3 |
| // +optional |
| ProxyUrl string `json:"proxyUrl,omitempty"` |
| } |
| |
| type S3Credentials struct { |
| // The name & key of a Kubernetes secret holding an AWS Access Key ID |
| // +optional |
| AccessKeyIdSecret *corev1.SecretKeySelector `json:"accessKeyIdSecret,omitempty"` |
| |
| // The name & key of a Kubernetes secret holding an AWS Secret Access Key |
| // +optional |
| SecretAccessKeySecret *corev1.SecretKeySelector `json:"secretAccessKeySecret,omitempty"` |
| |
| // The name & key of a Kubernetes secret holding an AWS Session Token |
| // +optional |
| SessionTokenSecret *corev1.SecretKeySelector `json:"sessionTokenSecret,omitempty"` |
| |
| // The name & key of a Kubernetes secret holding an AWS credentials file |
| // +optional |
| CredentialsFileSecret *corev1.SecretKeySelector `json:"credentialsFileSecret,omitempty"` |
| } |
| |
| type VolumeRepository struct { |
| // 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 namespaced 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. |
| Source corev1.VolumeSource `json:"source"` |
| |
| // Select a custom directory name to mount the backup/restore data in the given volume. |
| // If not specified, then the name of the solrcloud will be used by default. |
| // +optional |
| Directory string `json:"directory,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 (when not using TLS) or 443 (when using TLS) |
| // +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(usesTLS bool, logger logr.Logger) (changed bool) { |
| if opts.External != nil { |
| changed = opts.External.withDefaults(usesTLS, logger) |
| } |
| if opts.PodPort == 0 { |
| changed = true |
| opts.PodPort = 8983 |
| } |
| if opts.CommonServicePort == 0 { |
| changed = true |
| if usesTLS { |
| opts.CommonServicePort = 443 |
| } else { |
| 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. |
| // |
| // +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. given.domain.name.com -> default-example-solrcloud.given.domain.name.com |
| // |
| // 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. |
| DomainName string `json:"domainName"` |
| |
| // Provide additional domainNames that the Ingress or ExternalDNS should listen on. |
| // This option is ignored with the LoadBalancer method. |
| // +optional |
| AdditionalDomainNames []string `json:"additionalDomainNames,omitempty"` |
| |
| // Provide additional domainNames that the Ingress or ExternalDNS should listen on. |
| // This option is ignored with the LoadBalancer method. |
| // |
| // DEPRECATED: Please use additionalDomainNames instead. This will be removed in a future version. |
| // |
| // +optional |
| AdditionalDomains []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 (without TLS) or 443 (with TLS) if HideNodes=false and method=Ingress, otherwise this is optional. |
| // +optional |
| NodePortOverride int `json:"nodePortOverride,omitempty"` |
| |
| // IngressTLSTerminationSecret defines a TLS Secret to use for TLS termination of all exposed addresses in the ingress. |
| // |
| // This is option is only available when Method=Ingress, because ExternalDNS and LoadBalancer Services do not support TLS termination. |
| // This option is also unavailable when the SolrCloud has TLS enabled via `spec.solrTLS`, in this case the Ingress cannot terminate TLS before reaching Solr. |
| // |
| // When using this option, the UseExternalAddress option will be disabled, since Solr cannot be running in HTTP mode and making internal requests in HTTPS. |
| // |
| // DEPRECATED: Use ingressTLSTermination.tlsSecret instead |
| // |
| // +optional |
| IngressTLSTerminationSecret string `json:"ingressTLSTerminationSecret,omitempty"` |
| |
| // IngressTLSTermination tells the SolrCloud Ingress to terminate TLS on incoming connections. |
| // |
| // This is option is only available when Method=Ingress, because ExternalDNS and LoadBalancer Services do not support TLS termination. |
| // This option is also unavailable when the SolrCloud has TLS enabled via `spec.solrTLS`, in this case the Ingress cannot terminate TLS before reaching Solr. |
| // |
| // When using this option, the UseExternalAddress option will be disabled, since Solr cannot be running in HTTP mode and making internal requests in HTTPS. |
| // |
| // +optional |
| IngressTLSTermination *SolrIngressTLSTermination `json:"ingressTLSTermination,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 |
| 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(usesTLS bool, logger logr.Logger) (changed bool) { |
| // TODO: Remove in v0.7.0 |
| // If the deprecated IngressTLSTerminationSecret exists, use it to default the new location of the value. |
| // If that location already exists, then merely remove the deprecated option. |
| if opts.IngressTLSTerminationSecret != "" { |
| terminationSecretLogger := logger.WithValues("option", "spec.solrAddressability.external.ingressTLSTerminationSecret").WithValues("newLocation", "spec.solrAddressability.external.ingressTLSTermination.tlsSecret") |
| var loggingAction string |
| if !opts.HasIngressTLSTermination() { |
| opts.IngressTLSTermination = &SolrIngressTLSTermination{ |
| TLSSecret: opts.IngressTLSTerminationSecret, |
| } |
| loggingAction = "Moving" |
| } else { |
| terminationSecretLogger = terminationSecretLogger.WithValues("reason", "Cannot move deprecated option because ingressTLSTermination is already defined") |
| loggingAction = "Removing" |
| } |
| opts.IngressTLSTerminationSecret = "" |
| terminationSecretLogger.Info(loggingAction + " deprecated CRD option") |
| changed = true |
| } |
| |
| // You can't use an externalAddress for Solr Nodes if the Nodes are hidden externally |
| if opts.UseExternalAddress && (opts.HideNodes || opts.IngressTLSTermination != nil) { |
| changed = true |
| opts.UseExternalAddress = false |
| } |
| |
| // Add the values from the deprecated "additionalDomains" to the new "additionalDomainNames" field |
| // But make sure you aren't creating duplicates |
| // TODO: Remove in v0.7.0 |
| if opts.AdditionalDomains != nil { |
| // Only modify AdditionalDomainNames if AdditionalDomains is empty |
| // But if it is non-nil and empty, still set AdditionalDomains to nil |
| if len(opts.AdditionalDomains) > 0 { |
| if len(opts.AdditionalDomainNames) == 0 { |
| opts.AdditionalDomainNames = opts.AdditionalDomains |
| } else { |
| for _, domain := range opts.AdditionalDomains { |
| hasDomain := false |
| for _, containsDomain := range opts.AdditionalDomainNames { |
| if domain == containsDomain { |
| hasDomain = true |
| break |
| } |
| } |
| if !hasDomain { |
| opts.AdditionalDomainNames = append(opts.AdditionalDomainNames, domain) |
| } |
| } |
| } |
| } |
| logger.Info("Moving deprecated CRD option", "option", "spec.solrAddressability.external.additionalDomains", "newLocation", "spec.solrAddressability.external.additionalDomainNames") |
| changed = true |
| opts.AdditionalDomains = nil |
| } |
| |
| // If the Ingress method is used, default the nodePortOverride to 80 or 443, since that is the port that most ingress controllers listen on. |
| if !opts.HideNodes && opts.Method == Ingress && opts.NodePortOverride == 0 { |
| changed = true |
| if usesTLS { |
| opts.NodePortOverride = 443 |
| } else { |
| 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 |
| } |
| |
| // SolrIngressTLSTermination defines how a SolrCloud should have TLS Termination enabled. |
| // Only one option can be provided. |
| // |
| // +kubebuilder:validation:MaxProperties=1 |
| type SolrIngressTLSTermination struct { |
| |
| // UseDefaultTLSSecret determines whether the ingress should use the default TLS secret provided by the Ingress implementation. |
| // |
| // For example, using nginx: https://kubernetes.github.io/ingress-nginx/user-guide/tls/#default-ssl-certificate |
| // |
| // +optional |
| UseDefaultTLSSecret bool `json:"useDefaultTLSSecret,omitempty"` |
| |
| // TLSSecret defines a TLS Secret to use for TLS termination of all exposed addresses for this SolrCloud in the Ingress. |
| // |
| // +optional |
| TLSSecret string `json:"tlsSecret,omitempty"` |
| } |
| |
| type SolrUpdateStrategy struct { |
| // Method defines the way in which SolrClouds should be updated when the podSpec changes. |
| // +optional |
| Method SolrUpdateMethod `json:"method,omitempty"` |
| |
| // Options for Solr Operator Managed rolling updates. |
| // +optional |
| ManagedUpdateOptions ManagedUpdateOptions `json:"managed,omitempty"` |
| |
| // Perform a scheduled restart on the given schedule, in CRON format. |
| // |
| // Multiple CRON syntaxes are supported |
| // - Standard CRON (e.g. "CRON_TZ=Asia/Seoul 0 6 * * ?") |
| // - Predefined Schedules (e.g. "@yearly", "@weekly", etc.) |
| // - Intervals (e.g. "@every 10h30m") |
| // |
| // For more information please check this reference: |
| // https://pkg.go.dev/github.com/robfig/cron/v3?utm_source=godoc#hdr-CRON_Expression_Format |
| // |
| // +optional |
| RestartSchedule string `json:"restartSchedule,omitempty"` |
| } |
| |
| // SolrUpdateMethod is a string enumeration type that enumerates |
| // all possible ways that a SolrCloud can having rolling updates managed. |
| // +kubebuilder:validation:Enum=Managed;StatefulSet;Manual |
| type SolrUpdateMethod string |
| |
| const ( |
| // Let the Solr Operator manage rolling updates to keep collections/shards available while updating pods in parallel. |
| // This is the default option. |
| ManagedUpdate SolrUpdateMethod = "Managed" |
| |
| // Use the default StatefulSet rolling updates logic. One pod at a time, starting with the highest ordinal. |
| StatefulSetUpdate SolrUpdateMethod = "StatefulSet" |
| |
| // The Solr Operator and Kubernetes will not delete pods for updates. The user will be responsible for this. |
| ManualUpdate SolrUpdateMethod = "Manual" |
| ) |
| |
| func (opts *SolrUpdateStrategy) withDefaults() (changed bool) { |
| // You can't use an externalAddress for Solr Nodes if the Nodes are hidden externally |
| if opts.Method == "" { |
| changed = true |
| opts.Method = ManagedUpdate |
| } |
| |
| return changed |
| } |
| |
| // Spec to control the desired behavior of managed rolling update. |
| type ManagedUpdateOptions struct { |
| |
| // The maximum number of pods that can be unavailable during the update. |
| // Value can be an absolute number (ex: 5) or a percentage of the desired number of pods (ex: 10%). |
| // Absolute number is calculated from percentage by rounding down. |
| // If the provided number is 0 or negative, then all pods will be allowed to be updated in unison. |
| // |
| // Defaults to 25%. |
| // |
| // +optional |
| MaxPodsUnavailable *intstr.IntOrString `json:"maxPodsUnavailable,omitempty"` |
| |
| // The maximum number of replicas for each shard that can be unavailable during the update. |
| // Value can be an absolute number (ex: 5) or a percentage of replicas in a shard (ex: 25%). |
| // Absolute number is calculated from percentage by rounding down. |
| // If the provided number is 0 or negative, then all replicas will be allowed to be updated in unison. |
| // |
| // Defaults to 1. |
| // |
| // +optional |
| MaxShardReplicasUnavailable *intstr.IntOrString `json:"maxShardReplicasUnavailable,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"` |
| |
| // Create a new Zookeeper Ensemble with the following spec |
| // Note: This option will not allow the SolrCloud to run across kube-clusters. |
| // Note: Requires |
| // - The zookeeperOperator flag to be provided to the Solr Operator |
| // - A zookeeper operator to be running |
| // +optional |
| ProvidedZookeeper *ZookeeperSpec `json:"provided,omitempty"` |
| } |
| |
| func (ref *ZookeeperRef) withDefaults() (changed bool) { |
| if ref.ProvidedZookeeper == nil && ref.ConnectionInfo == nil { |
| changed = true |
| ref.ProvidedZookeeper = &ZookeeperSpec{} |
| } 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 (ref *ZookeeperRef) GetACLs() (allACL *ZookeeperACL, readOnlyACL *ZookeeperACL) { |
| if ref.ConnectionInfo != nil { |
| allACL = ref.ConnectionInfo.AllACL |
| readOnlyACL = ref.ConnectionInfo.ReadOnlyACL |
| } else if ref.ProvidedZookeeper != nil { |
| allACL = ref.ProvidedZookeeper.AllACL |
| readOnlyACL = ref.ProvidedZookeeper.ReadOnlyACL |
| } |
| return |
| } |
| |
| // ZookeeperSpec defines the internal zookeeper ensemble to run with the given spec |
| 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"` |
| |
| // Persistence is the configuration for zookeeper persistent layer. |
| // PersistentVolumeClaimSpec and VolumeReclaimPolicy can be specified in here. |
| // At anypoint only one of Persistence or Ephemeral should be present in the manifest |
| // +optional |
| Persistence *ZKPersistence `json:"persistence,omitempty"` |
| |
| // Ephemeral is the configuration which helps create ephemeral storage |
| // At anypoint only one of Persistence or Ephemeral should be present in the manifest |
| // +optional |
| Ephemeral *ZKEphemeral `json:"ephemeral,omitempty"` |
| |
| // Customization options for the Zookeeper Pod |
| // +optional |
| ZookeeperPod ZookeeperPodPolicy `json:"zookeeperPodPolicy,omitempty"` |
| |
| // The ChRoot to connect solr at |
| // +optional |
| ChRoot string `json:"chroot,omitempty"` |
| |
| // ZooKeeper ACL to use when connecting with ZK. |
| // This ACL should have ALL permission in the given chRoot. |
| // +optional |
| AllACL *ZookeeperACL `json:"acl,omitempty"` |
| |
| // ZooKeeper ACL to use when connecting with ZK for reading operations. |
| // This ACL should have READ permission in the given chRoot. |
| // +optional |
| ReadOnlyACL *ZookeeperACL `json:"readOnlyAcl,omitempty"` |
| |
| // Additional Zookeeper Configuration settings |
| // +optional |
| Config ZookeeperConfig `json:"config,omitempty"` |
| } |
| |
| type ZKPersistence struct { |
| // VolumeReclaimPolicy is a zookeeper operator configuration. If it's set to Delete, |
| // the corresponding PVCs will be deleted by the operator when zookeeper cluster is deleted. |
| // The default value is Retain. |
| VolumeReclaimPolicy VolumeReclaimPolicy `json:"reclaimPolicy,omitempty"` |
| // PersistentVolumeClaimSpec is the spec to describe PVC for the container |
| // This field is optional. If no PVC is specified default persistentvolume |
| // will get created. |
| PersistentVolumeClaimSpec corev1.PersistentVolumeClaimSpec `json:"spec,omitempty"` |
| // Annotations specifies the annotations to attach to pvc the operator |
| // creates. |
| Annotations map[string]string `json:"annotations,omitempty"` |
| } |
| |
| type ZKEphemeral struct { |
| //EmptyDirVolumeSource is optional and this will create the emptydir volume |
| //It has two parameters Medium and SizeLimit which are optional as well |
| //Medium specifies What type of storage medium should back this directory. |
| //SizeLimit specifies Total amount of local storage required for this EmptyDir volume. |
| EmptyDirVolumeSource corev1.EmptyDirVolumeSource `json:"emptydirvolumesource,omitempty"` |
| } |
| |
| // ZookeeperConfig is the current configuration of each Zookeeper node, which |
| // sets these values in the config-map |
| type ZookeeperConfig struct { |
| // InitLimit is the amount of time, in ticks, to allow followers to connect |
| // and sync to a leader. |
| // |
| // Default value is 10. |
| InitLimit int `json:"initLimit,omitempty"` |
| |
| // TickTime is the length of a single tick, which is the basic time unit used |
| // by Zookeeper, as measured in milliseconds |
| // |
| // The default value is 2000. |
| TickTime int `json:"tickTime,omitempty"` |
| |
| // SyncLimit is the amount of time, in ticks, to allow followers to sync with |
| // Zookeeper. |
| // |
| // The default value is 2. |
| SyncLimit int `json:"syncLimit,omitempty"` |
| |
| // Clients can submit requests faster than ZooKeeper can process them, especially |
| // if there are a lot of clients. Zookeeper will throttle Clients so that requests |
| // won't exceed global outstanding limit. |
| // |
| // The default value is 1000 |
| GlobalOutstandingLimit int `json:"globalOutstandingLimit,omitempty"` |
| |
| // To avoid seeks ZooKeeper allocates space in the transaction log file in |
| // blocks of preAllocSize kilobytes |
| // |
| // The default value is 64M |
| PreAllocSize int `json:"preAllocSize,omitempty"` |
| |
| // ZooKeeper records its transactions using snapshots and a transaction log |
| // The number of transactions recorded in the transaction log before a snapshot |
| // can be taken is determined by snapCount |
| // |
| // The default value is 100,000 |
| SnapCount int `json:"snapCount,omitempty"` |
| |
| // Zookeeper maintains an in-memory list of last committed requests for fast |
| // synchronization with followers |
| // |
| // The default value is 500 |
| CommitLogCount int `json:"commitLogCount,omitempty"` |
| |
| // Snapshot size limit in Kb |
| // |
| // The defult value is 4GB |
| SnapSizeLimitInKb int `json:"snapSizeLimitInKb,omitempty"` |
| |
| // Limits the total number of concurrent connections that can be made to a |
| //zookeeper server |
| // |
| // The defult value is 0, indicating no limit |
| MaxCnxns int `json:"maxCnxns,omitempty"` |
| |
| // Limits the number of concurrent connections that a single client, identified |
| // by IP address, may make to a single member of the ZooKeeper ensemble. |
| // |
| // The default value is 60 |
| MaxClientCnxns int `json:"maxClientCnxns,omitempty"` |
| |
| // The minimum session timeout in milliseconds that the server will allow the |
| // client to negotiate |
| // |
| // The default value is 4000 |
| MinSessionTimeout int `json:"minSessionTimeout,omitempty"` |
| |
| // The maximum session timeout in milliseconds that the server will allow the |
| // client to negotiate. |
| // |
| // The default value is 40000 |
| MaxSessionTimeout int `json:"maxSessionTimeout,omitempty"` |
| |
| // Retain the snapshots according to retain count |
| // |
| // The default value is 3 |
| AutoPurgeSnapRetainCount int `json:"autoPurgeSnapRetainCount,omitempty"` |
| |
| // The time interval in hours for which the purge task has to be triggered |
| // |
| // Disabled by default |
| AutoPurgePurgeInterval int `json:"autoPurgePurgeInterval,omitempty"` |
| |
| // QuorumListenOnAllIPs when set to true the ZooKeeper server will listen for |
| // connections from its peers on all available IP addresses, and not only the |
| // address configured in the server list of the configuration file. It affects |
| // the connections handling the ZAB protocol and the Fast Leader Election protocol. |
| // |
| // The default value is false. |
| QuorumListenOnAllIPs bool `json:"quorumListenOnAllIPs,omitempty"` |
| |
| // key-value map of additional zookeeper configuration parameters |
| // +kubebuilder:pruning:PreserveUnknownFields |
| // +optional |
| AdditionalConfig map[string]string `json:"additionalConfig,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, corev1.PullIfNotPresent) || changed |
| |
| 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 |
| } |
| } |
| |
| if z.ChRoot == "" { |
| changed = true |
| z.ChRoot = "/" |
| } else if !strings.HasPrefix(z.ChRoot, "/") { |
| changed = true |
| z.ChRoot = "/" + z.ChRoot |
| } |
| return changed |
| } |
| |
| // 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"` |
| |
| // List of environment variables to set in the main ZK container. |
| // +optional |
| Env []corev1.EnvVar `json:"env,omitempty"` |
| |
| // Resources is the resource requirements for the Zookeeper container. |
| // +optional |
| Resources corev1.ResourceRequirements `json:"resources,omitempty"` |
| |
| // Optional Service Account to run the zookeeper pods under. |
| // +optional |
| ServiceAccountName string `json:"serviceAccountName,omitempty"` |
| |
| // Labels specifies the labels to attach to pods the operator creates for |
| // the zookeeper cluster. |
| // +optional |
| Labels map[string]string `json:"labels,omitempty"` |
| |
| // Annotations specifies the annotations to attach to zookeeper pods |
| // creates. |
| // +optional |
| Annotations map[string]string `json:"annotations,omitempty"` |
| |
| // SecurityContext specifies the security context for the entire zookeeper pod |
| // More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context |
| // +optional |
| SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` |
| |
| // TerminationGracePeriodSeconds is the amount of time that kubernetes will |
| // give for a zookeeper pod instance to shutdown normally. |
| // The default value is 30. |
| // +optional |
| // +kubebuilder:validation:Minimum=0 |
| TerminationGracePeriodSeconds int64 `json:"terminationGracePeriodSeconds,omitempty"` |
| |
| // ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images |
| // +optional |
| ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` |
| } |
| |
| // 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 desired replicas in the cluster |
| Replicas int32 `json:"replicas"` |
| |
| // PodSelector for SolrCloud pods, required by the HPA |
| PodSelector string `json:"podSelector"` |
| |
| // ReadyReplicas is the number of ready replicas in the cluster |
| ReadyReplicas int32 `json:"readyReplicas"` |
| |
| // UpToDateNodes is the number of Solr Node pods that are running the latest pod spec |
| UpToDateNodes int32 `json:"upToDateNodes"` |
| |
| // 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"` |
| |
| // BackupRepositoriesAvailable lists the backupRepositories specified in the SolrCloud and whether they are available across all Pods. |
| // +optional |
| BackupRepositoriesAvailable map[string]bool `json:"backupRepositoriesAvailable,omitempty"` |
| } |
| |
| // 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"` |
| |
| // This Solr Node pod is using the latest version of solrcloud pod spec. |
| SpecUpToDate bool `json:"specUpToDate"` |
| } |
| |
| //+kubebuilder:object:root=true |
| //+kubebuilder:resource:scope=Namespaced |
| //+kubebuilder:resource:shortName=solr |
| //+kubebuilder:categories=all |
| //+kubebuilder:subresource:status |
| //+kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.readyReplicas,selectorpath=.status.podSelector |
| //+kubebuilder:storageversion |
| //+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="UpToDateNodes",type="integer",JSONPath=".status.upToDateNodes",description="Number of solr nodes running the latest SolrCloud pod spec" |
| //+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" |
| |
| // SolrCloud is the Schema for the solrclouds API |
| 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(logger logr.Logger) bool { |
| return sc.Spec.withDefaults(logger) |
| } |
| |
| func (sc *SolrCloud) GetAllSolrPodNames() []string { |
| replicas := 1 |
| if sc.Spec.Replicas != nil { |
| replicas = int(*sc.Spec.Replicas) |
| } |
| podNames := make([]string, replicas) |
| statefulSetName := sc.StatefulSetName() |
| for i := range podNames { |
| podNames[i] = fmt.Sprintf("%s-%d", statefulSetName, i) |
| } |
| return podNames |
| } |
| |
| func (sc *SolrCloud) BasicAuthSecretName() string { |
| if sc.Spec.SolrSecurity != nil && sc.Spec.SolrSecurity.BasicAuthSecret != "" { |
| return sc.Spec.SolrSecurity.BasicAuthSecret |
| } else { |
| return fmt.Sprintf("%s-solrcloud-basic-auth", sc.Name) |
| } |
| } |
| |
| func (sc *SolrCloud) SecurityBootstrapSecretName() string { |
| return fmt.Sprintf("%s-solrcloud-security-bootstrap", sc.Name) |
| } |
| |
| // 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(sc *SolrCloud) string { |
| return fmt.Sprintf("%s://%s-solrcloud-common.%s%s", sc.UrlScheme(false), sc.Name, sc.Namespace, sc.CommonPortSuffix(false)) |
| } |
| |
| // 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()) |
| } |
| |
| // 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 (scs SolrCloudStatus) DissectZkInfo() (zkConnectionString string, zkServer string, zkChRoot string) { |
| zkConnectionString = scs.ZookeeperConnectionInfo.ZkConnectionString() |
| zkParts := strings.SplitN(zkConnectionString, "/", 2) |
| zkServer = zkParts[0] |
| zkChRoot = "/" + zkParts[1] |
| |
| return zkConnectionString, zkServer, zkChRoot |
| } |
| |
| 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) 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(false) |
| } |
| 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(false) |
| } |
| return url |
| } |
| |
| func (sc *SolrCloud) CommonPortSuffix(external bool) string { |
| return sc.PortToSuffix(sc.Spec.SolrAddressability.CommonServicePort, external) |
| } |
| |
| func (sc *SolrCloud) NodePortSuffix(external bool) string { |
| return sc.PortToSuffix(sc.NodePort(), external) |
| } |
| |
| 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 |
| } |
| |
| func (sc *SolrCloud) PortToSuffix(port int, external bool) string { |
| suffix := "" |
| if sc.UrlScheme(external) == "https" { |
| if port != 443 { |
| suffix = ":" + strconv.Itoa(port) |
| } |
| } else { |
| if port != 80 { |
| suffix = ":" + strconv.Itoa(port) |
| } |
| } |
| return suffix |
| } |
| |
| 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.CommonPortSuffix(false) |
| } |
| 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 && 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) |
| url += sc.NodePortSuffix(true) |
| } |
| 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)) |
| } |
| // 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) |
| url += sc.CommonPortSuffix(true) |
| } |
| return url |
| } |
| |
| func (ea *ExternalAddressability) HasIngressTLSTermination() bool { |
| if ea != nil && ea.Method == Ingress && ea.IngressTLSTermination != nil { |
| return ea.IngressTLSTermination.UseDefaultTLSSecret || ea.IngressTLSTermination.TLSSecret != "" |
| } |
| return false |
| } |
| |
| func (sc *SolrCloud) UrlScheme(external bool) string { |
| urlScheme := "http" |
| if sc.Spec.SolrTLS != nil { |
| urlScheme = "https" |
| } else if external && sc.Spec.SolrAddressability.External.HasIngressTLSTermination() { |
| urlScheme = "https" |
| } |
| return urlScheme |
| } |
| |
| 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) UsesPersistentStorage() bool { |
| return sc.Spec.StorageOptions.PersistentStorage != nil |
| } |
| |
| func (sc *SolrCloud) DataVolumeName() string { |
| // Set the default name of the pvc |
| if sc.Spec.StorageOptions.PersistentStorage != nil && |
| sc.Spec.StorageOptions.PersistentStorage.PersistentVolumeClaimTemplate.ObjectMeta.Name != "" { |
| return sc.Spec.StorageOptions.PersistentStorage.PersistentVolumeClaimTemplate.ObjectMeta.Name |
| } else { |
| return "data" |
| } |
| } |
| |
| 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{}) |
| } |
| |
| // +kubebuilder:validation:Enum=None;Want;Need |
| type ClientAuthType string |
| |
| const ( |
| None ClientAuthType = "None" |
| Want ClientAuthType = "Want" |
| Need ClientAuthType = "Need" |
| ) |
| |
| type MountedTLSDirectory struct { |
| // The path on the main Solr container where the TLS files are mounted by some external agent or CSI Driver |
| Path string `json:"path"` |
| |
| // Override the name of the keystore file; no default, if you don't supply this setting, then the corresponding |
| // env vars and Java system properties will not be configured for the pod template |
| // +optional |
| KeystoreFile string `json:"keystoreFile,omitempty"` |
| |
| // Override the name of the keystore password file; defaults to keystore-password |
| // +optional |
| KeystorePasswordFile string `json:"keystorePasswordFile,omitempty"` |
| |
| // Override the name of the truststore file; no default, if you don't supply this setting, then the corresponding |
| // env vars and Java system properties will not be configured for the pod template |
| // +optional |
| TruststoreFile string `json:"truststoreFile,omitempty"` |
| |
| // Override the name of the truststore password file; defaults to the same value as the KeystorePasswordFile |
| // +optional |
| TruststorePasswordFile string `json:"truststorePasswordFile,omitempty"` |
| } |
| |
| type SolrTLSOptions struct { |
| // TLS Secret containing a pkcs12 keystore; required for Solr pods unless mountedTLSDir is used |
| // +optional |
| PKCS12Secret *corev1.SecretKeySelector `json:"pkcs12Secret,omitempty"` |
| |
| // Secret containing the key store password; this field is required unless mountedTLSDir is used, as most JVMs do not support pkcs12 keystores without a password |
| // +optional |
| KeyStorePasswordSecret *corev1.SecretKeySelector `json:"keyStorePasswordSecret,omitempty"` |
| |
| // TLS Secret containing a pkcs12 truststore; if not provided, then the keystore and password are used for the truststore |
| // The specified key is used as the truststore file name when mounted into Solr pods |
| // +optional |
| TrustStoreSecret *corev1.SecretKeySelector `json:"trustStoreSecret,omitempty"` |
| |
| // Secret containing the trust store password; if not provided the keyStorePassword will be used |
| // +optional |
| TrustStorePasswordSecret *corev1.SecretKeySelector `json:"trustStorePasswordSecret,omitempty"` |
| |
| // Determines the client authentication method, either None, Want, or Need; |
| // this affects K8s ability to call liveness / readiness probes so use cautiously. |
| // Only applies for server certificates, has no effect on client certificates |
| // +kubebuilder:default=None |
| ClientAuth ClientAuthType `json:"clientAuth,omitempty"` |
| |
| // Verify client's hostname during SSL handshake |
| // Only applies for server configuration |
| // +optional |
| VerifyClientHostname bool `json:"verifyClientHostname,omitempty"` |
| |
| // TLS certificates contain host/ip "peer name" information that is validated by default. |
| // +optional |
| CheckPeerName bool `json:"checkPeerName,omitempty"` |
| |
| // Opt-in flag to restart Solr pods after TLS secret updates, such as if the cert is renewed; default is false. |
| // This option only applies when using the `spec.solrTLS.pkcs12Secret` option; when using the `spec.solrTLS.mountedTLSDir` option, |
| // you need to ensure pods get restarted before the certs expire, see `spec.updateStrategy.restartSchedule` for scheduling restarts. |
| // +optional |
| RestartOnTLSSecretUpdate bool `json:"restartOnTLSSecretUpdate,omitempty"` |
| |
| // Used to specify a path where the keystore, truststore, and password files for the TLS certificate are mounted by an external agent or CSI driver. |
| // This option is typically used with `spec.updateStrategy.restartSchedule` to restart Solr pods before the mounted TLS cert expires. |
| // +optional |
| MountedTLSDir *MountedTLSDirectory `json:"mountedTLSDir,omitempty"` |
| } |
| |
| // +kubebuilder:validation:Enum=Basic |
| type AuthenticationType string |
| |
| const ( |
| Basic AuthenticationType = "Basic" |
| ) |
| |
| type SolrSecurityOptions struct { |
| // Indicates the authentication plugin type that is being used by Solr; for now only "Basic" is supported by the |
| // Solr operator but support for other authentication plugins may be added in the future. |
| AuthenticationType AuthenticationType `json:"authenticationType,omitempty"` |
| |
| // Secret (kubernetes.io/basic-auth) containing credentials the operator should use for API requests to secure Solr pods. |
| // If you provide this secret, then the operator assumes you've also configured your own security.json file and |
| // uploaded it to Solr. If you change the password for this user using the Solr security API, then you *must* update |
| // the secret with the new password or the operator will be locked out of Solr and API requests will fail, |
| // ultimately causing a CrashBackoffLoop for all pods if probe endpoints are secured (see 'probesRequireAuth' setting). |
| // |
| // If you don't supply this secret, then the operator creates a kubernetes.io/basic-auth secret containing the password |
| // for the "k8s-oper" user. All API requests from the operator are made as the "k8s-oper" user, which is configured |
| // with read-only access to a minimal set of endpoints. In addition, the operator bootstraps a default security.json |
| // file and credentials for two additional users: admin and solr. The 'solr' user has basic read access to Solr |
| // resources. Once the security.json is bootstrapped, the operator will not update it! You're expected to use the |
| // 'admin' user to access the Security API to make further changes. It's strictly a bootstrapping operation. |
| // +optional |
| BasicAuthSecret string `json:"basicAuthSecret,omitempty"` |
| |
| // Flag to indicate if the configured HTTP endpoint(s) used for the probes require authentication; defaults |
| // to false. If you set to true, then probes will use a local command on the main container to hit the secured |
| // endpoints with credentials sourced from an env var instead of HTTP directly. |
| // +optional |
| ProbesRequireAuth bool `json:"probesRequireAuth,omitempty"` |
| |
| // Configure a user-provided security.json from a secret to allow for advanced security config. |
| // If not specified, the operator bootstraps a security.json with basic auth enabled. |
| // This is a bootstrapping config only; once Solr is initialized, the security config should be managed by the security API. |
| // +optional |
| BootstrapSecurityJson *corev1.SecretKeySelector `json:"bootstrapSecurityJson,omitempty"` |
| } |