blob: 2db33914b5541a88696129024b452b21534b74f6 [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 (
"reflect"
"strings"
"time"
solr "github.com/bloomberg/solr-operator/api/v1beta1"
etcd "github.com/coreos/etcd-operator/pkg/apis/etcd/v1beta2"
zk "github.com/pravega/zookeeper-operator/pkg/apis/zookeeper/v1beta1"
zkCli "github.com/samuel/go-zookeeper/zk"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)
var log = logf.Log.WithName("controller")
// GenerateZookeeperCluster returns a new ZookeeperCluster pointer generated for the SolrCloud instance
// object: SolrCloud instance
// zkSpec: the spec of the ZookeeperCluster to generate
func GenerateZookeeperCluster(solrCloud *solr.SolrCloud, zkSpec solr.ZookeeperSpec) *zk.ZookeeperCluster {
// TODO: Default and Validate these with Webhooks
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
labels["technology"] = solr.ZookeeperTechnologyLabel
zkCluster := &zk.ZookeeperCluster{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.ProvidedZookeeperName(),
Namespace: solrCloud.GetNamespace(),
Labels: labels,
},
Spec: zk.ZookeeperClusterSpec{
Image: zk.ContainerImage{
Repository: zkSpec.Image.Repository,
Tag: zkSpec.Image.Tag,
PullPolicy: zkSpec.Image.PullPolicy,
},
Labels: labels,
Replicas: *zkSpec.Replicas,
Persistence: zkSpec.Persistence,
Ports: []corev1.ContainerPort{
{
Name: "client",
ContainerPort: 2181,
},
{
Name: "quorum",
ContainerPort: 2888,
},
{
Name: "leader-election",
ContainerPort: 3888,
},
},
},
}
// Append Pod Policies if provided by user
if zkSpec.ZookeeperPod.Affinity != nil {
zkCluster.Spec.Pod.Affinity = zkSpec.ZookeeperPod.Affinity
}
if zkSpec.ZookeeperPod.Resources.Limits != nil || zkSpec.ZookeeperPod.Resources.Requests != nil {
zkCluster.Spec.Pod.Resources = zkSpec.ZookeeperPod.Resources
}
return zkCluster
}
// CopyZookeeperClusterFields copies the owned fields from one ZookeeperCluster to another
// Returns true if the fields copied from don't match to.
func CopyZookeeperClusterFields(from, to *zk.ZookeeperCluster) bool {
requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta)
if !reflect.DeepEqual(to.Spec.Replicas, from.Spec.Replicas) {
log.Info("Updating Zk replicas")
requireUpdate = true
}
to.Spec.Replicas = from.Spec.Replicas
if !reflect.DeepEqual(to.Spec.Image.Repository, from.Spec.Image.Repository) {
log.Info("Updating Zk image repository")
requireUpdate = true
}
to.Spec.Image.Repository = from.Spec.Image.Repository
if !reflect.DeepEqual(to.Spec.Image.Tag, from.Spec.Image.Tag) {
log.Info("Updating Zk image tag")
requireUpdate = true
}
to.Spec.Image.Tag = from.Spec.Image.Tag
if from.Spec.Persistence != nil {
if to.Spec.Persistence == nil {
log.Info("Updating Zk Persistence")
requireUpdate = true
to.Spec.Persistence = from.Spec.Persistence
} else {
if !reflect.DeepEqual(to.Spec.Persistence.PersistentVolumeClaimSpec.Resources.Requests, from.Spec.Persistence.PersistentVolumeClaimSpec.Resources.Requests) {
log.Info("Updating Zk Persistence PVC Requests")
requireUpdate = true
to.Spec.Persistence.PersistentVolumeClaimSpec.Resources.Requests = from.Spec.Persistence.PersistentVolumeClaimSpec.Resources.Requests
}
if !reflect.DeepEqual(to.Spec.Persistence.PersistentVolumeClaimSpec.AccessModes, from.Spec.Persistence.PersistentVolumeClaimSpec.AccessModes) {
log.Info("Updating Zk Persistence PVC AccessModes")
requireUpdate = true
to.Spec.Persistence.PersistentVolumeClaimSpec.AccessModes = from.Spec.Persistence.PersistentVolumeClaimSpec.AccessModes
}
if !reflect.DeepEqual(to.Spec.Persistence.PersistentVolumeClaimSpec.StorageClassName, from.Spec.Persistence.PersistentVolumeClaimSpec.StorageClassName) {
log.Info("Updating Zk Persistence PVC StorageClassName")
requireUpdate = true
to.Spec.Persistence.PersistentVolumeClaimSpec.StorageClassName = from.Spec.Persistence.PersistentVolumeClaimSpec.StorageClassName
}
if !reflect.DeepEqual(to.Spec.Persistence.VolumeReclaimPolicy, from.Spec.Persistence.VolumeReclaimPolicy) {
log.Info("Updating Zk Persistence VolumeReclaimPolicy")
requireUpdate = true
to.Spec.Persistence.VolumeReclaimPolicy = from.Spec.Persistence.VolumeReclaimPolicy
}
}
}
/* Uncomment when the following PR is merged in: https://github.com/pravega/zookeeper-operator/pull/64
Otherwise the ZK Operator will create persistence when none is given, and this will infinitely loop.
else if to.Spec.Persistence != nil {
requireUpdate = true
to.Spec.Persistence = nil
}*/
if !reflect.DeepEqual(to.Spec.Pod.Resources, from.Spec.Pod.Resources) {
log.Info("Updating Zk pod resources")
requireUpdate = true
}
to.Spec.Pod.Resources = from.Spec.Pod.Resources
if from.Spec.Pod.Affinity != nil {
if !reflect.DeepEqual(to.Spec.Pod.Affinity.NodeAffinity, from.Spec.Pod.Affinity.NodeAffinity) {
log.Info("Updating Zk pod node affinity")
log.Info("Update required because:", "Spec.Pod.Affinity.NodeAffinity changed from", to.Spec.Pod.Affinity.NodeAffinity, "To:", from.Spec.Pod.Affinity.NodeAffinity)
requireUpdate = true
}
if !reflect.DeepEqual(to.Spec.Pod.Affinity.PodAffinity, from.Spec.Pod.Affinity.PodAffinity) {
log.Info("Updating Zk pod node affinity")
log.Info("Update required because:", "Spec.Pod.Affinity.PodAffinity changed from", to.Spec.Pod.Affinity.PodAffinity, "To:", from.Spec.Pod.Affinity.PodAffinity)
requireUpdate = true
}
to.Spec.Pod.Affinity = from.Spec.Pod.Affinity
}
return requireUpdate
}
// GenerateEtcdCluster returns a new EtcdCluster pointer generated for the SolrCloud instance
// object: SolrCloud instance
// etcdSpec: the spec of the EtcdCluster to generate
// busyBoxImage: the image of busyBox to use
func GenerateEtcdCluster(solrCloud *solr.SolrCloud, etcdSpec solr.EtcdSpec, busyBoxImage solr.ContainerImage) *etcd.EtcdCluster {
// TODO: Default and Validate these with Webhooks
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
labels["technology"] = solr.ZookeeperTechnologyLabel
etcdCluster := &etcd.EtcdCluster{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.ProvidedZetcdName(),
Namespace: solrCloud.GetNamespace(),
Labels: labels,
},
Spec: etcd.ClusterSpec{
Version: etcdSpec.Image.Tag,
Repository: etcdSpec.Image.Repository,
Size: *etcdSpec.Replicas,
Pod: &etcd.PodPolicy{
Labels: labels,
BusyboxImage: busyBoxImage.ToImageName(),
},
},
}
// Append Pod Policies if provided by user
if etcdSpec.EtcdPod.Affinity != nil {
etcdCluster.Spec.Pod.Affinity = etcdSpec.EtcdPod.Affinity
}
if etcdSpec.EtcdPod.Resources.Limits != nil || etcdSpec.EtcdPod.Resources.Requests != nil {
etcdCluster.Spec.Pod.Resources = etcdSpec.EtcdPod.Resources
}
return etcdCluster
}
// CopyEtcdClusterFields copies the owned fields from one EtcdCluster to another
// Returns true if the fields copied from don't match to.
func CopyEtcdClusterFields(from, to *etcd.EtcdCluster) bool {
requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta)
if !reflect.DeepEqual(to.Spec, from.Spec) {
requireUpdate = true
}
to.Spec = from.Spec
return requireUpdate
}
// GenerateZetcdDeployment returns a new appsv1.Deployment for Zetcd
// solrCloud: SolrCloud instance
// spec: ZetcdSpec
func GenerateZetcdDeployment(solrCloud *solr.SolrCloud, spec solr.ZetcdSpec) *appsv1.Deployment {
// TODO: Default and Validate these with Webhooks
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
selectorLabels := solrCloud.SharedLabels()
labels["technology"] = solr.ZookeeperTechnologyLabel
selectorLabels["technology"] = solr.ZookeeperTechnologyLabel
labels["app"] = "zetcd"
selectorLabels["app"] = "zetcd"
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.ProvidedZetcdName(),
Namespace: solrCloud.GetNamespace(),
Labels: labels,
},
Spec: appsv1.DeploymentSpec{
Replicas: spec.Replicas,
Selector: &metav1.LabelSelector{
MatchLabels: selectorLabels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: labels},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "zetcd",
Image: spec.Image.ToImageName(),
ImagePullPolicy: spec.Image.PullPolicy,
Ports: []corev1.ContainerPort{{ContainerPort: 2181}},
},
},
},
},
},
}
if spec.ZetcdPod.Resources.Limits != nil || spec.ZetcdPod.Resources.Requests != nil {
deployment.Spec.Template.Spec.Containers[0].Resources = spec.ZetcdPod.Resources
}
if spec.ZetcdPod.Affinity != nil {
deployment.Spec.Template.Spec.Affinity = spec.ZetcdPod.Affinity
}
return deployment
}
// CopyDeploymentFields copies the owned fields from one Deployment to another
// Returns true if the fields copied from don't match to.
func CopyDeploymentFields(from, to *appsv1.Deployment) bool {
requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta)
if !reflect.DeepEqual(to.Spec.Replicas, from.Spec.Replicas) {
requireUpdate = true
to.Spec.Replicas = from.Spec.Replicas
}
if !reflect.DeepEqual(to.Spec.Selector, from.Spec.Selector) {
requireUpdate = true
to.Spec.Selector = from.Spec.Selector
}
if !reflect.DeepEqual(to.Spec.Template.Labels, from.Spec.Template.Labels) {
requireUpdate = true
to.Spec.Template.Labels = from.Spec.Template.Labels
}
if !reflect.DeepEqual(to.Spec.Template.Spec.Volumes, from.Spec.Template.Spec.Volumes) {
requireUpdate = true
to.Spec.Template.Spec.Volumes = from.Spec.Template.Spec.Volumes
}
if !reflect.DeepEqual(to.Spec.Template.Spec.Affinity, from.Spec.Template.Spec.Affinity) {
requireUpdate = true
to.Spec.Template.Spec.Affinity = from.Spec.Template.Spec.Affinity
}
if len(to.Spec.Template.Spec.Containers) != len(from.Spec.Template.Spec.Containers) {
requireUpdate = true
to.Spec.Template.Spec.Containers = from.Spec.Template.Spec.Containers
} else if !reflect.DeepEqual(to.Spec.Template.Spec.Containers, from.Spec.Template.Spec.Containers) {
for i := 0; i < len(to.Spec.Template.Spec.Containers); i++ {
if !reflect.DeepEqual(to.Spec.Template.Spec.Containers[i].Name, from.Spec.Template.Spec.Containers[i].Name) {
requireUpdate = true
to.Spec.Template.Spec.Containers[i].Name = from.Spec.Template.Spec.Containers[i].Name
}
if !reflect.DeepEqual(to.Spec.Template.Spec.Containers[i].Image, from.Spec.Template.Spec.Containers[i].Image) {
requireUpdate = true
to.Spec.Template.Spec.Containers[i].Image = from.Spec.Template.Spec.Containers[i].Image
}
if !reflect.DeepEqual(to.Spec.Template.Spec.Containers[i].ImagePullPolicy, from.Spec.Template.Spec.Containers[i].ImagePullPolicy) {
requireUpdate = true
to.Spec.Template.Spec.Containers[i].ImagePullPolicy = from.Spec.Template.Spec.Containers[i].ImagePullPolicy
}
if !reflect.DeepEqual(to.Spec.Template.Spec.Containers[i].Command, from.Spec.Template.Spec.Containers[i].Command) {
requireUpdate = true
to.Spec.Template.Spec.Containers[i].Command = from.Spec.Template.Spec.Containers[i].Command
}
if !reflect.DeepEqual(to.Spec.Template.Spec.Containers[i].Args, from.Spec.Template.Spec.Containers[i].Args) {
requireUpdate = true
to.Spec.Template.Spec.Containers[i].Args = from.Spec.Template.Spec.Containers[i].Args
}
if !reflect.DeepEqual(to.Spec.Template.Spec.Containers[i].Env, from.Spec.Template.Spec.Containers[i].Env) {
requireUpdate = true
to.Spec.Template.Spec.Containers[i].Env = from.Spec.Template.Spec.Containers[i].Env
}
if !reflect.DeepEqual(to.Spec.Template.Spec.Containers[i].Resources, from.Spec.Template.Spec.Containers[i].Resources) {
requireUpdate = true
to.Spec.Template.Spec.Containers[i].Resources = from.Spec.Template.Spec.Containers[i].Resources
}
}
}
return requireUpdate
}
// GenerateZetcdService returns a new corev1.Service pointer generated for the Zetcd deployment
// solrCloud: SolrCloud instance
// spec: ZetcdSpec
func GenerateZetcdService(solrCloud *solr.SolrCloud, spec solr.ZetcdSpec) *corev1.Service {
// TODO: Default and Validate these with Webhooks
labels := solrCloud.SharedLabelsWith(solrCloud.GetLabels())
selectorLabels := solrCloud.SharedLabels()
labels["technology"] = solr.ZookeeperTechnologyLabel
selectorLabels["technology"] = solr.ZookeeperTechnologyLabel
labels["app"] = "zetcd"
selectorLabels["app"] = "zetcd"
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: solrCloud.ProvidedZetcdName(),
Namespace: solrCloud.GetNamespace(),
Labels: labels,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Port: 2181, TargetPort: intstr.IntOrString{IntVal: 2181, Type: intstr.Int}},
},
Selector: selectorLabels,
},
}
return service
}
func CreateChRootIfNecessary(info solr.ZookeeperConnectionInfo) error {
if info.InternalConnectionString != "" && info.ChRoot != "/" {
zkClient, _, err := zkCli.Connect(strings.Split(info.InternalConnectionString, ","), time.Second)
if err != nil {
log.Error(err, "Could not connect to Zookeeper", "connectionString", info.InternalConnectionString)
return err
}
pathParts := strings.Split(strings.TrimPrefix(info.ChRoot, "/"), "/")
pathToCreate := ""
// Loop through each parent of the ZNode, and make sure that they exist recursively
for _, part := range pathParts {
if part == "" {
continue
}
pathToCreate += "/" + part
// Make sure that this part of the chRoot exists
exists, _, err := zkClient.Exists(pathToCreate)
if err != nil {
log.Error(err, "Could not check existence of Znode", "path", pathToCreate)
return err
} else if !exists {
log.Info("Creating Znode for chRoot of SolrCloud", "path", pathToCreate)
_, err = zkClient.Create(pathToCreate, []byte(""), 0, zkCli.WorldACL(zkCli.PermAll))
if err != nil {
log.Error(err, "Could not create Znode for chRoot of SolrCloud", "path", pathToCreate)
return err
}
}
}
return err
}
return nil
}