blob: 346ce5dfb371a5f81df44df158cc00a57973a032 [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 util
import (
"sort"
"strconv"
"strings"
solr "github.com/bloomberg/solr-operator/api/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
extv1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
const (
SolrClientPortName = "solr-client"
BackupRestoreVolume = "backup-restore"
SolrNodeContainer = "solrcloud-node"
SolrStorageFinalizer = "storage.finalizers.solr.apache.org"
SolrZKConnectionStringAnnotation = "solr.apache.org/zkConnectionString"
SolrPVCTechnologyLabel = "solr.apache.org/technology"
SolrCloudPVCTechnology = "solr-cloud"
SolrPVCStorageLabel = "solr.apache.org/storage"
SolrCloudPVCDataStorage = "data"
SolrPVCInstanceLabel = "solr.apache.org/instance"
SolrXmlMd5Annotation = "solr.apache.org/solrXmlMd5"
DefaultStatefulSetPodManagementPolicy = appsv1.ParallelPodManagement
DefaultLivenessProbeInitialDelaySeconds = 20
DefaultLivenessProbeTimeoutSeconds = 1
DefaultLivenessProbeSuccessThreshold = 1
DefaultLivenessProbeFailureThreshold = 3
DefaultLivenessProbePeriodSeconds = 10
DefaultReadinessProbeInitialDelaySeconds = 15
DefaultReadinessProbeTimeoutSeconds = 1
DefaultReadinessProbeSuccessThreshold = 1
DefaultReadinessProbeFailureThreshold = 3
DefaultReadinessProbePeriodSeconds = 5
DefaultStartupProbeInitialDelaySeconds = 20
DefaultStartupProbeTimeoutSeconds = 30
DefaultStartupProbeSuccessThreshold = 1
DefaultStartupProbeFailureThreshold = 15
DefaultStartupProbePeriodSeconds = 10
)
// GenerateStatefulSet returns a new appsv1.StatefulSet pointer generated for the SolrCloud instance
// object: SolrCloud instance
// replicas: the number of replicas for the SolrCloud instance
// storage: the size of the storage for the SolrCloud instance (e.g. 100Gi)
// zkConnectionString: the connectionString of the ZK instance to connect to
func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, hostNameIPs map[string]string, solrXmlConfigMapName string, solrXmlMd5 string) *appsv1.StatefulSet {
gracePeriodTerm := int64(10)
solrPodPort := solrCloud.Spec.SolrAddressability.PodPort
fsGroup := int64(solrPodPort)
defaultMode := int32(420)
defaultHandler := corev1.Handler{
HTTPGet: &corev1.HTTPGetAction{
Scheme: corev1.URISchemeHTTP,
Path: "/solr/admin/info/system",
Port: intstr.FromInt(solrPodPort),
},
}
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
selectorLabels := solrCloud.SharedLabels()
labels["technology"] = solr.SolrTechnologyLabel
selectorLabels["technology"] = solr.SolrTechnologyLabel
annotations := map[string]string{
SolrZKConnectionStringAnnotation: solrCloudStatus.ZkConnectionString(),
}
podLabels := labels
customSSOptions := solrCloud.Spec.CustomSolrKubeOptions.StatefulSetOptions
if nil != customSSOptions {
labels = MergeLabelsOrAnnotations(labels, customSSOptions.Labels)
annotations = MergeLabelsOrAnnotations(annotations, customSSOptions.Annotations)
}
customPodOptions := solrCloud.Spec.CustomSolrKubeOptions.PodOptions
var podAnnotations map[string]string
if nil != customPodOptions {
podLabels = MergeLabelsOrAnnotations(podLabels, customPodOptions.Labels)
podAnnotations = customPodOptions.Annotations
}
// Volumes & Mounts
solrVolumes := []corev1.Volume{
{
Name: "solr-xml",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: solrXmlConfigMapName,
},
Items: []corev1.KeyToPath{
{
Key: "solr.xml",
Path: "solr.xml",
},
},
DefaultMode: &defaultMode,
},
},
},
}
solrDataVolumeName := "data"
volumeMounts := []corev1.VolumeMount{{Name: solrDataVolumeName, MountPath: "/var/solr/data"}}
var pvcs []corev1.PersistentVolumeClaim
if solrCloud.UsesPersistentStorage() {
pvc := solrCloud.Spec.StorageOptions.PersistentStorage.PersistentVolumeClaimTemplate.DeepCopy()
// Set the default name of the pvc
if pvc.ObjectMeta.Name == "" {
pvc.ObjectMeta.Name = solrDataVolumeName
}
// Set some defaults in the PVC Spec
if len(pvc.Spec.AccessModes) == 0 {
pvc.Spec.AccessModes = []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
}
}
if pvc.Spec.VolumeMode == nil {
temp := corev1.PersistentVolumeFilesystem
pvc.Spec.VolumeMode = &temp
}
// Add internally-used labels.
internalLabels := map[string]string{
SolrPVCTechnologyLabel: SolrCloudPVCTechnology,
SolrPVCStorageLabel: SolrCloudPVCDataStorage,
SolrPVCInstanceLabel: solrCloud.Name,
}
pvc.ObjectMeta.Labels = MergeLabelsOrAnnotations(internalLabels, pvc.ObjectMeta.Labels)
pvcs = []corev1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{
Name: pvc.ObjectMeta.Name,
Labels: pvc.ObjectMeta.Labels,
Annotations: pvc.ObjectMeta.Annotations,
},
Spec: pvc.Spec,
},
}
} else {
emptyDirVolume := corev1.Volume{
Name: solrDataVolumeName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
}
if solrCloud.Spec.StorageOptions.EphemeralStorage != nil {
emptyDirVolume.VolumeSource.EmptyDir = &solrCloud.Spec.StorageOptions.EphemeralStorage.EmptyDir
}
solrVolumes = append(solrVolumes, emptyDirVolume)
}
// Add backup volumes
if solrCloud.Spec.StorageOptions.BackupRestoreOptions != nil {
solrVolumes = append(solrVolumes, corev1.Volume{
Name: BackupRestoreVolume,
VolumeSource: solrCloud.Spec.StorageOptions.BackupRestoreOptions.Volume,
})
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: BackupRestoreVolume,
MountPath: BaseBackupRestorePath,
SubPath: BackupRestoreSubPathForCloud(solrCloud.Spec.StorageOptions.BackupRestoreOptions.Directory, solrCloud.Name),
})
}
if nil != customPodOptions {
// Add Custom Volumes to pod
for _, volume := range customPodOptions.Volumes {
volume.DefaultContainerMount.Name = volume.Name
// Only add the container mount if one has been provided.
if volume.DefaultContainerMount != nil {
volumeMounts = append(volumeMounts, *volume.DefaultContainerMount)
}
solrVolumes = append(solrVolumes, corev1.Volume{
Name: volume.Name,
VolumeSource: volume.Source,
})
}
}
// Host Aliases
hostAliases := make([]corev1.HostAlias, len(hostNameIPs))
if len(hostAliases) == 0 {
hostAliases = nil
} else {
hostNames := make([]string, len(hostNameIPs))
index := 0
for hostName := range hostNameIPs {
hostNames[index] = hostName
index += 1
}
sort.Strings(hostNames)
for index, hostName := range hostNames {
hostAliases[index] = corev1.HostAlias{
IP: hostNameIPs[hostName],
Hostnames: []string{hostName},
}
index++
}
}
// if an ingressBaseDomain is provided, the node should be addressable outside of the cluster
solrHostName := solrCloud.AdvertisedNodeHost("$(POD_HOSTNAME)")
solrAdressingPort := solrCloud.NodePort()
zkConnectionStr, zkServer, zkChroot := solrCloudStatus.DissectZkInfo()
// Only have a postStart command to create the chRoot, if it is not '/' (which does not need to be created)
var postStart *corev1.Handler
if len(zkChroot) > 1 {
postStart = &corev1.Handler{
Exec: &corev1.ExecAction{
Command: []string{"sh", "-c", "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}"},
},
}
}
// Keep track of the SolrOpts that the Solr Operator needs to set
// These will be added to the SolrOpts given by the user.
allSolrOpts := []string{"-DhostPort=$(SOLR_NODE_PORT)"}
// Environment Variables
envVars := []corev1.EnvVar{
{
Name: "SOLR_JAVA_MEM",
Value: solrCloud.Spec.SolrJavaMem,
},
{
Name: "SOLR_HOME",
Value: "/var/solr/data",
},
{
// This is the port that jetty will listen on
Name: "SOLR_PORT",
Value: strconv.Itoa(solrPodPort),
},
{
// This is the port that the Solr Node will advertise itself as listening on in live_nodes
Name: "SOLR_NODE_PORT",
Value: strconv.Itoa(solrAdressingPort),
},
{
Name: "POD_HOSTNAME",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.name",
APIVersion: "v1",
},
},
},
{
Name: "SOLR_HOST",
Value: solrHostName,
},
{
Name: "ZK_HOST",
Value: zkConnectionStr,
},
{
Name: "ZK_SERVER",
Value: zkServer,
},
{
Name: "ZK_CHROOT",
Value: zkChroot,
},
{
Name: "SOLR_LOG_LEVEL",
Value: solrCloud.Spec.SolrLogLevel,
},
{
Name: "GC_TUNE",
Value: solrCloud.Spec.SolrGCTune,
},
}
// Add ACL information, if given, through Env Vars
if hasACLs, aclEnvs := AddACLsToEnv(solrCloud.Spec.ZookeeperRef.ConnectionInfo); hasACLs {
envVars = append(envVars, aclEnvs...)
// The $SOLR_ZK_CREDS_AND_ACLS parameter does not get picked up when running solr, it must be added to the SOLR_OPTS.
allSolrOpts = append(allSolrOpts, "$(SOLR_ZK_CREDS_AND_ACLS)")
}
// Add Custom EnvironmentVariables to the solr container
if nil != customPodOptions {
envVars = append(envVars, customPodOptions.EnvVariables...)
}
// track the MD5 of the custom solr.xml in the pod spec annotations,
// so we get a rolling restart when the configMap changes
if solrXmlMd5 != "" {
if podAnnotations == nil {
podAnnotations = make(map[string]string, 1)
}
podAnnotations[SolrXmlMd5Annotation] = solrXmlMd5
}
if solrCloud.Spec.SolrOpts != "" {
allSolrOpts = append(allSolrOpts, solrCloud.Spec.SolrOpts)
}
// Add SOLR_OPTS last, so that it can use values from all of the other ENV_VARS
envVars = append(envVars, corev1.EnvVar{
Name: "SOLR_OPTS",
Value: strings.Join(allSolrOpts, " "),
})
initContainers := []corev1.Container{
{
Name: "cp-solr-xml",
Image: solrCloud.Spec.BusyBoxImage.ToImageName(),
ImagePullPolicy: solrCloud.Spec.BusyBoxImage.PullPolicy,
Command: []string{"sh", "-c", "cp /tmp/solr.xml /tmp-config/solr.xml"},
VolumeMounts: []corev1.VolumeMount{
{
Name: "solr-xml",
MountPath: "/tmp",
},
{
Name: solrDataVolumeName,
MountPath: "/tmp-config",
},
},
},
}
// Add user defined additional init containers
if customPodOptions != nil && len(customPodOptions.InitContainers) > 0 {
initContainers = append(initContainers, customPodOptions.InitContainers...)
}
containers := []corev1.Container{
{
Name: SolrNodeContainer,
Image: solrCloud.Spec.SolrImage.ToImageName(),
ImagePullPolicy: solrCloud.Spec.SolrImage.PullPolicy,
Ports: []corev1.ContainerPort{
{
ContainerPort: int32(solrPodPort),
Name: SolrClientPortName,
Protocol: "TCP",
},
},
LivenessProbe: &corev1.Probe{
InitialDelaySeconds: DefaultLivenessProbeInitialDelaySeconds,
TimeoutSeconds: DefaultLivenessProbeTimeoutSeconds,
SuccessThreshold: DefaultLivenessProbeSuccessThreshold,
FailureThreshold: DefaultLivenessProbeFailureThreshold,
PeriodSeconds: DefaultLivenessProbePeriodSeconds,
Handler: defaultHandler,
},
ReadinessProbe: &corev1.Probe{
InitialDelaySeconds: DefaultReadinessProbeInitialDelaySeconds,
TimeoutSeconds: DefaultReadinessProbeTimeoutSeconds,
SuccessThreshold: DefaultReadinessProbeSuccessThreshold,
FailureThreshold: DefaultReadinessProbeFailureThreshold,
PeriodSeconds: DefaultReadinessProbePeriodSeconds,
Handler: defaultHandler,
},
VolumeMounts: volumeMounts,
Env: envVars,
Lifecycle: &corev1.Lifecycle{
PostStart: postStart,
PreStop: &corev1.Handler{
Exec: &corev1.ExecAction{
Command: []string{"solr", "stop", "-p", strconv.Itoa(solrPodPort)},
},
},
},
},
}
// Add user defined additional sidecar containers
if customPodOptions != nil && len(customPodOptions.SidecarContainers) > 0 {
containers = append(containers, customPodOptions.SidecarContainers...)
}
// Decide which update strategy to use
updateStrategy := appsv1.OnDeleteStatefulSetStrategyType
if solrCloud.Spec.UpdateStrategy.Method == solr.StatefulSetUpdate {
// Only use the rolling update strategy if the StatefulSetUpdate method is specified.
updateStrategy = appsv1.RollingUpdateStatefulSetStrategyType
}
// Determine which podManagementPolicy to use for the statefulSet
podManagementPolicy := DefaultStatefulSetPodManagementPolicy
if solrCloud.Spec.CustomSolrKubeOptions.StatefulSetOptions != nil && solrCloud.Spec.CustomSolrKubeOptions.StatefulSetOptions.PodManagementPolicy != "" {
podManagementPolicy = solrCloud.Spec.CustomSolrKubeOptions.StatefulSetOptions.PodManagementPolicy
}
// Create the Stateful Set
stateful := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.StatefulSetName(),
Namespace: solrCloud.GetNamespace(),
Labels: labels,
Annotations: annotations,
},
Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: selectorLabels,
},
ServiceName: solrCloud.HeadlessServiceName(),
Replicas: solrCloud.Spec.Replicas,
PodManagementPolicy: podManagementPolicy,
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
Type: updateStrategy,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: podLabels,
Annotations: podAnnotations,
},
Spec: corev1.PodSpec{
TerminationGracePeriodSeconds: &gracePeriodTerm,
SecurityContext: &corev1.PodSecurityContext{
FSGroup: &fsGroup,
},
Volumes: solrVolumes,
InitContainers: initContainers,
HostAliases: hostAliases,
Containers: containers,
},
},
VolumeClaimTemplates: pvcs,
},
}
if solrCloud.Spec.SolrImage.ImagePullSecret != "" {
stateful.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{
{Name: solrCloud.Spec.SolrImage.ImagePullSecret},
}
}
if nil != customPodOptions {
if customPodOptions.Affinity != nil {
stateful.Spec.Template.Spec.Affinity = customPodOptions.Affinity
}
if customPodOptions.Resources.Limits != nil || customPodOptions.Resources.Requests != nil {
stateful.Spec.Template.Spec.Containers[0].Resources = customPodOptions.Resources
}
if customPodOptions.PodSecurityContext != nil {
stateful.Spec.Template.Spec.SecurityContext = customPodOptions.PodSecurityContext
}
if customPodOptions.Tolerations != nil {
stateful.Spec.Template.Spec.Tolerations = customPodOptions.Tolerations
}
if customPodOptions.NodeSelector != nil {
stateful.Spec.Template.Spec.NodeSelector = customPodOptions.NodeSelector
}
if customPodOptions.LivenessProbe != nil {
stateful.Spec.Template.Spec.Containers[0].LivenessProbe = fillProbe(*customPodOptions.LivenessProbe, DefaultLivenessProbeInitialDelaySeconds, DefaultLivenessProbeTimeoutSeconds, DefaultLivenessProbeSuccessThreshold, DefaultLivenessProbeFailureThreshold, DefaultLivenessProbePeriodSeconds, &defaultHandler)
}
if customPodOptions.ReadinessProbe != nil {
stateful.Spec.Template.Spec.Containers[0].ReadinessProbe = fillProbe(*customPodOptions.ReadinessProbe, DefaultReadinessProbeInitialDelaySeconds, DefaultReadinessProbeTimeoutSeconds, DefaultReadinessProbeSuccessThreshold, DefaultReadinessProbeFailureThreshold, DefaultReadinessProbePeriodSeconds, &defaultHandler)
}
if customPodOptions.StartupProbe != nil {
stateful.Spec.Template.Spec.Containers[0].StartupProbe = fillProbe(*customPodOptions.StartupProbe, DefaultStartupProbeInitialDelaySeconds, DefaultStartupProbeTimeoutSeconds, DefaultStartupProbeSuccessThreshold, DefaultStartupProbeFailureThreshold, DefaultStartupProbePeriodSeconds, &defaultHandler)
}
if customPodOptions.PriorityClassName != "" {
stateful.Spec.Template.Spec.PriorityClassName = customPodOptions.PriorityClassName
}
}
return stateful
}
// GenerateConfigMap returns a new corev1.ConfigMap pointer generated for the SolrCloud instance solr.xml
// solrCloud: SolrCloud instance
func GenerateConfigMap(solrCloud *solr.SolrCloud) *corev1.ConfigMap {
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
var annotations map[string]string
customOptions := solrCloud.Spec.CustomSolrKubeOptions.ConfigMapOptions
if nil != customOptions {
labels = MergeLabelsOrAnnotations(labels, customOptions.Labels)
annotations = MergeLabelsOrAnnotations(annotations, customOptions.Annotations)
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.ConfigMapName(),
Namespace: solrCloud.GetNamespace(),
Labels: labels,
Annotations: annotations,
},
Data: map[string]string{
"solr.xml": `<?xml version="1.0" encoding="UTF-8" ?>
<solr>
<solrcloud>
<str name="host">${host:}</str>
<int name="hostPort">${hostPort:80}</int>
<str name="hostContext">${hostContext:solr}</str>
<bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>
<int name="zkClientTimeout">${zkClientTimeout:30000}</int>
<int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:600000}</int>
<int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
<str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
<str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
</solrcloud>
<shardHandlerFactory name="shardHandlerFactory"
class="HttpShardHandlerFactory">
<int name="socketTimeout">${socketTimeout:600000}</int>
<int name="connTimeout">${connTimeout:60000}</int>
</shardHandlerFactory>
</solr>
`,
},
}
return configMap
}
// fillProbe builds the probe logic used for pod liveness, readiness, startup checks
func fillProbe(customSolrKubeOptions corev1.Probe, defaultInitialDelaySeconds int32, defaultTimeoutSeconds int32, defaultSuccessThreshold int32, defaultFailureThreshold int32, defaultPeriodSeconds int32, defaultHandler *corev1.Handler) *corev1.Probe {
probe := &corev1.Probe{
InitialDelaySeconds: defaultInitialDelaySeconds,
TimeoutSeconds: defaultTimeoutSeconds,
SuccessThreshold: defaultSuccessThreshold,
FailureThreshold: defaultFailureThreshold,
PeriodSeconds: defaultPeriodSeconds,
Handler: *defaultHandler,
}
if customSolrKubeOptions.InitialDelaySeconds != 0 {
probe.InitialDelaySeconds = customSolrKubeOptions.InitialDelaySeconds
}
if customSolrKubeOptions.TimeoutSeconds != 0 {
probe.TimeoutSeconds = customSolrKubeOptions.TimeoutSeconds
}
if customSolrKubeOptions.SuccessThreshold != 0 {
probe.SuccessThreshold = customSolrKubeOptions.SuccessThreshold
}
if customSolrKubeOptions.FailureThreshold != 0 {
probe.FailureThreshold = customSolrKubeOptions.FailureThreshold
}
if customSolrKubeOptions.PeriodSeconds != 0 {
probe.PeriodSeconds = customSolrKubeOptions.PeriodSeconds
}
if customSolrKubeOptions.Handler.Exec != nil || customSolrKubeOptions.Handler.HTTPGet != nil {
probe.Handler = customSolrKubeOptions.Handler
}
return probe
}
// GenerateCommonService returns a new corev1.Service pointer generated for the entire SolrCloud instance
// solrCloud: SolrCloud instance
func GenerateCommonService(solrCloud *solr.SolrCloud) *corev1.Service {
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
labels["service-type"] = "common"
selectorLabels := solrCloud.SharedLabels()
selectorLabels["technology"] = solr.SolrTechnologyLabel
var annotations map[string]string
// Add externalDNS annotation if necessary
extOpts := solrCloud.Spec.SolrAddressability.External
if extOpts != nil && extOpts.Method == solr.ExternalDNS && !extOpts.HideCommon {
annotations = make(map[string]string, 1)
urls := []string{solrCloud.ExternalDnsDomain(extOpts.DomainName)}
for _, domain := range extOpts.AdditionalDomainNames {
urls = append(urls, solrCloud.ExternalDnsDomain(domain))
}
annotations["external-dns.alpha.kubernetes.io/hostname"] = strings.Join(urls, ",")
}
customOptions := solrCloud.Spec.CustomSolrKubeOptions.CommonServiceOptions
if nil != customOptions {
labels = MergeLabelsOrAnnotations(labels, customOptions.Labels)
annotations = MergeLabelsOrAnnotations(annotations, customOptions.Annotations)
}
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.CommonServiceName(),
Namespace: solrCloud.GetNamespace(),
Labels: labels,
Annotations: annotations,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Name: SolrClientPortName, Port: int32(solrCloud.Spec.SolrAddressability.CommonServicePort), Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromString(SolrClientPortName)},
},
Selector: selectorLabels,
},
}
return service
}
// GenerateHeadlessService returns a new Headless corev1.Service pointer generated for the SolrCloud instance
// The PublishNotReadyAddresses option is set as true, because we want each pod to be reachable no matter the readiness of the pod.
// solrCloud: SolrCloud instance
func GenerateHeadlessService(solrCloud *solr.SolrCloud) *corev1.Service {
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
labels["service-type"] = "headless"
selectorLabels := solrCloud.SharedLabels()
selectorLabels["technology"] = solr.SolrTechnologyLabel
var annotations map[string]string
// Add externalDNS annotation if necessary
extOpts := solrCloud.Spec.SolrAddressability.External
if extOpts != nil && extOpts.Method == solr.ExternalDNS && !extOpts.HideNodes {
annotations = make(map[string]string, 1)
urls := []string{solrCloud.ExternalDnsDomain(extOpts.DomainName)}
for _, domain := range extOpts.AdditionalDomainNames {
urls = append(urls, solrCloud.ExternalDnsDomain(domain))
}
annotations["external-dns.alpha.kubernetes.io/hostname"] = strings.Join(urls, ",")
}
customOptions := solrCloud.Spec.CustomSolrKubeOptions.HeadlessServiceOptions
if nil != customOptions {
labels = MergeLabelsOrAnnotations(labels, customOptions.Labels)
annotations = MergeLabelsOrAnnotations(annotations, customOptions.Annotations)
}
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.HeadlessServiceName(),
Namespace: solrCloud.GetNamespace(),
Labels: labels,
Annotations: annotations,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Name: SolrClientPortName, Port: int32(solrCloud.NodePort()), Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromString(SolrClientPortName)},
},
Selector: selectorLabels,
ClusterIP: corev1.ClusterIPNone,
PublishNotReadyAddresses: true,
},
}
return service
}
// GenerateNodeService returns a new External corev1.Service pointer generated for the given Solr Node.
// The PublishNotReadyAddresses option is set as true, because we want each pod to be reachable no matter the readiness of the pod.
// solrCloud: SolrCloud instance
// nodeName: string node
func GenerateNodeService(solrCloud *solr.SolrCloud, nodeName string) *corev1.Service {
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
labels["service-type"] = "external"
selectorLabels := solrCloud.SharedLabels()
selectorLabels["technology"] = solr.SolrTechnologyLabel
selectorLabels["statefulset.kubernetes.io/pod-name"] = nodeName
var annotations map[string]string
customOptions := solrCloud.Spec.CustomSolrKubeOptions.NodeServiceOptions
if nil != customOptions {
labels = MergeLabelsOrAnnotations(labels, customOptions.Labels)
annotations = MergeLabelsOrAnnotations(annotations, customOptions.Annotations)
}
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: nodeName,
Namespace: solrCloud.GetNamespace(),
Labels: labels,
Annotations: annotations,
},
Spec: corev1.ServiceSpec{
Selector: selectorLabels,
Ports: []corev1.ServicePort{
{Name: SolrClientPortName, Port: int32(solrCloud.NodePort()), Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromString(SolrClientPortName)},
},
PublishNotReadyAddresses: true,
},
}
return service
}
// GenerateIngress returns a new Ingress pointer generated for the entire SolrCloud, pointing to all instances
// solrCloud: SolrCloud instance
// nodeStatuses: []SolrNodeStatus the nodeStatuses
// ingressBaseDomain: string baseDomain of the ingress
func GenerateIngress(solrCloud *solr.SolrCloud, nodeNames []string, ingressBaseDomain string) (ingress *extv1.Ingress) {
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
var annotations map[string]string
customOptions := solrCloud.Spec.CustomSolrKubeOptions.IngressOptions
if nil != customOptions {
labels = MergeLabelsOrAnnotations(labels, customOptions.Labels)
annotations = MergeLabelsOrAnnotations(annotations, customOptions.Annotations)
}
extOpts := solrCloud.Spec.SolrAddressability.External
// Create advertised domain name and possible additional domain names
rules := CreateSolrIngressRules(solrCloud, nodeNames, append([]string{extOpts.DomainName}, extOpts.AdditionalDomainNames...))
ingress = &extv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.CommonIngressName(),
Namespace: solrCloud.GetNamespace(),
Labels: labels,
Annotations: annotations,
},
Spec: extv1.IngressSpec{
Rules: rules,
},
}
return ingress
}
// CreateSolrIngressRules returns all applicable ingress rules for a cloud.
// solrCloud: SolrCloud instance
// nodeNames: the names for each of the solr pods
// domainName: string Domain for the ingress rule to use
func CreateSolrIngressRules(solrCloud *solr.SolrCloud, nodeNames []string, domainNames []string) []extv1.IngressRule {
var ingressRules []extv1.IngressRule
if !solrCloud.Spec.SolrAddressability.External.HideCommon {
for _, domainName := range domainNames {
ingressRules = append(ingressRules, CreateCommonIngressRule(solrCloud, domainName))
}
}
if !solrCloud.Spec.SolrAddressability.External.HideNodes {
for _, nodeName := range nodeNames {
for _, domainName := range domainNames {
ingressRules = append(ingressRules, CreateNodeIngressRule(solrCloud, nodeName, domainName))
}
}
}
return ingressRules
}
// CreateCommonIngressRule returns a new Ingress Rule generated for a SolrCloud under the given domainName
// solrCloud: SolrCloud instance
// domainName: string Domain for the ingress rule to use
func CreateCommonIngressRule(solrCloud *solr.SolrCloud, domainName string) (ingressRule extv1.IngressRule) {
pathType := extv1.PathTypeImplementationSpecific
ingressRule = extv1.IngressRule{
Host: solrCloud.ExternalCommonUrl(domainName, false),
IngressRuleValue: extv1.IngressRuleValue{
HTTP: &extv1.HTTPIngressRuleValue{
Paths: []extv1.HTTPIngressPath{
{
Backend: extv1.IngressBackend{
ServiceName: solrCloud.CommonServiceName(),
ServicePort: intstr.FromInt(solrCloud.Spec.SolrAddressability.CommonServicePort),
},
PathType: &pathType,
},
},
},
},
}
return ingressRule
}
// CreateNodeIngressRule returns a new Ingress Rule generated for a specific Solr Node under the given domainName
// solrCloud: SolrCloud instance
// nodeName: string Name of the node
// domainName: string Domain for the ingress rule to use
func CreateNodeIngressRule(solrCloud *solr.SolrCloud, nodeName string, domainName string) (ingressRule extv1.IngressRule) {
pathType := extv1.PathTypeImplementationSpecific
ingressRule = extv1.IngressRule{
Host: solrCloud.ExternalNodeUrl(nodeName, domainName, false),
IngressRuleValue: extv1.IngressRuleValue{
HTTP: &extv1.HTTPIngressRuleValue{
Paths: []extv1.HTTPIngressPath{
{
Backend: extv1.IngressBackend{
ServiceName: nodeName,
ServicePort: intstr.FromInt(solrCloud.NodePort()),
},
PathType: &pathType,
},
},
},
},
}
return ingressRule
}