Skip to content

Commit

Permalink
Added stateful set / pod annotations and labels.
Browse files Browse the repository at this point in the history
Signed-off-by: Houston Putman <[email protected]>
  • Loading branch information
HoustonPutman committed Feb 14, 2020
1 parent 2803e50 commit 636e672
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 107 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,13 +407,17 @@ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/mast

## Version Compatibility & Upgrade Notes

#### v0.2.2
- `SolrCloud.spec.solrPodPolicy` has been **DEPRECATED** in favor of the `SolrCloud.spec.customSolrKubeOptions.podOptions` option.
This option is backwards compatible, but will be removed in a future version (`v0.3.0`).

#### v0.2.1
- The zkConnectionString used for provided zookeepers changed from using the string provided in the `ZkCluster.Status`, which used an IP, to using the service name. This will cause a rolling restart of your solrs using the provided zookeeper option, but there will be no data loss.

#### v0.2.0
- Uses `gomod` instead of `dep`
- `SolrCloud.zookeeperRef.provided.zookeeper.persistentVolumeClaimSpec` has been deprecated in favor of the `SolrCloud.zookeeperRef.provided.zookeeper.persistence` option.
This option is backwards compatible, but will be removed in a future version.
- `SolrCloud.spec.zookeeperRef.provided.zookeeper.persistentVolumeClaimSpec` has been **DEPRECATED** in favor of the `SolrCloud.zookeeperRef.provided.zookeeper.persistence` option.
This option is backwards compatible, but will be removed in a future version (`v0.3.0`).
- An upgrade to the ZKOperator version `0.2.4` is required.

#### v0.1.1
Expand Down
2 changes: 1 addition & 1 deletion api/v1beta1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type PodOptions struct {

// Additional environment variables to pass to the default container.
// +optional
EnvVariable []corev1.EnvVar `json:"envVars,omitempty"`
EnvVariables []corev1.EnvVar `json:"envVars,omitempty"`

// Annotations to be added for pods.
// +optional
Expand Down
75 changes: 20 additions & 55 deletions api/v1beta1/solrcloud_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ type SolrCloudSpec struct {
// +optional
SolrImage *ContainerImage `json:"solrImage,omitempty"`

// DEPRECATED: Please use the options provided in customSolrKubeOptions.podOptions
//
// Pod defines the policy to create pod for the SolrCloud.
// Updating the Pod does not take effect on any existing pods.
// +optional
SolrPod SolrPodPolicy `json:"solrPodPolicy,omitempty"`

// DataPvcSpec is the spec to describe PVC for the solr node to store its data.
// This field is optional. If no PVC spec is provided, each solr node will use emptyDir as the data volume
// +optional
Expand All @@ -93,10 +100,6 @@ type SolrCloudSpec struct {
// +optional
CustomSolrKubeOptions CustomSolrKubeOptions `json:"customSolrKubeOptions,omitempty"`

// Customize how Solr is addressed both internally and externally in kubernetes.
// +optional
SolrAddressabilityOptions SolrAddressabilityOptions `json:"solrAddressabilityOptions,omitempty"`

// +optional
BusyBoxImage *ContainerImage `json:"busyBoxImage,omitempty"`

Expand Down Expand Up @@ -188,6 +191,8 @@ type CustomSolrKubeOptions struct {
// +optional
StatefulSetOptions *StatefulSetOptions `json:"statefulSetOptions,omitempty"`

/* These features are not yet supported
// CommonServiceOptions defines the custom options for the common solrCloud Service.
// +optional
CommonServiceOptions *ServiceOptions `json:"commonServiceOptions,omitempty"`
Expand All @@ -208,64 +213,24 @@ type CustomSolrKubeOptions struct {
// IngressOptions defines the custom options for solrCloud Ingress.
// +optional
IngressOptions *IngressOptions `json:"ingressOptions,omitempty"`
*/
}

type SolrAddressabilityOptions struct {
// The way in which this SolrCloud nodes should be made addressable externally.
// If none is provided, nodes will not be addressable externally.
// +optional
External *ExternalAddressability `json:"externalOptions,omitempty"`

// PodPort defines the port to have the Solr Pod listen on.
// Defaults to 8983
// +optional
PodPort int `json:"podPort,omitempty"`

// ServicePort defines the port to have all Solr services listen on. This is the port Solr will advertise itself as.
// Defaults to 80
// +optional
ServicePort int `json:"servicePort,omitempty"`
}

// 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 service(s) should be made addressable externally.
Method ExternalAddressabilityMethod `json:"method"`

// Expose the common Solr service externally. This is a single service.
// +optional
ExposeCommon bool `json:"exposeCommon"`

// Expose each of the Solr Node services externally. This is equal to the number of Solr nodes running within the statefulSet.
// +optional
ExposeNodes bool `json:"ExposeNodes"`

// Override the baseDomain provided as startup parameters to the operator, used by ingresses and externalDNS
// +optional
BaseDomain string `json:"baseDomain,omitempty"`

// Provide additional baseDomains that ingresses or ExternalDNS should listen on.
// DEPRECATED: Please use the options provided in SolrCloud.Spec.customSolrKubeOptions.podOptions
//
// SolrPodPolicy defines the common pod configuration for Pods, including when used
// in deployments, stateful-sets, etc.
type SolrPodPolicy struct {
// The scheduling constraints on pods.
// +optional
AdditionalBaseDomains []string `json:"additionalDomains,omitempty"`
Affinity *corev1.Affinity `json:"affinity,omitempty"`

// UseExternalDNS for services and/or ingresses. Defaults to false
// Resources is the resource requirements for the container.
// This field cannot be updated once the cluster is created.
// +optional
UseExternalDNS bool `json:"useExternalDNS,omitempty"`
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}

// ExternalAddressability is a string enumeration type that enumerates
// all possible ways that a SolrCloud can be made addressable external to the kubernetes cluster.
type ExternalAddressabilityMethod string

const (
// Use an ingress to make the Solr services externally addressable
Ingress = "Ingress"

// Use LoadBalancedServices to make the Solr services externally addressable
LoadBalancedService = "LoadBalancedService"
)

// 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 {
Expand Down
24 changes: 23 additions & 1 deletion controllers/controller_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ func expectStatefulSet(t *testing.T, g *gomega.GomegaWithT, requests chan reconc
Should(gomega.Succeed())

// Verify the statefulSet Specs
assert.Equal(t, stateful.Spec.Template.Labels, stateful.Spec.Selector.MatchLabels, "StatefulSet has different Pod template labels and selector labels.")
testMapContainsOther(t, "StatefulSet pod template selector", stateful.Spec.Template.Labels, stateful.Spec.Selector.MatchLabels)
assert.GreaterOrEqual(t, len(stateful.Spec.Selector.MatchLabels), 1, "StatefulSet pod template selector must have at least 1 label")

// Delete the StatefulSet and expect Reconcile to be called for StatefulSet deletion
g.Expect(testClient.Delete(context.TODO(), stateful)).NotTo(gomega.HaveOccurred())
Expand Down Expand Up @@ -172,6 +173,27 @@ func testPodEnvVariables(t *testing.T, expectedEnvVars map[string]string, foundE
assert.Equal(t, len(expectedEnvVars), matchCount, "Not all expected env variables found in podSpec")
}

func testMapsEqual(t *testing.T, mapName string, expected map[string]string, found map[string]string) {
for k, v := range expected {
foundV, foundExists := found[k]
assert.True(t, foundExists, "Expected key '%s' does not exist in found %s", k, mapName)
if foundExists {
assert.Equal(t, v, foundV, "Wrong value for %s key '%s'", mapName, k)
}
}
assert.Equal(t, len(expected), len(found), "Expected and found %s do not have the same number of entries", mapName)
}

func testMapContainsOther(t *testing.T, mapName string, base map[string]string, other map[string]string) {
for k, v := range other {
foundV, foundExists := base[k]
assert.True(t, foundExists, "Expected key '%s' does not exist in found %s", k, mapName)
if foundExists {
assert.Equal(t, v, foundV, "Wrong value for %s key '%s'", mapName, k)
}
}
}

func cleanupTest(g *gomega.GomegaWithT, namespace string) {
deleteOpts := []client.DeleteAllOfOption{
client.InNamespace(namespace),
Expand Down
45 changes: 41 additions & 4 deletions controllers/solrcloud_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package controllers

import (
"github.com/bloomberg/solr-operator/controllers/util"
"github.com/stretchr/testify/assert"
"testing"

Expand Down Expand Up @@ -125,15 +126,45 @@ func TestCloudReconcileWithIngress(t *testing.T) {
UseEtcdCRD(false)
UseZkCRD(true)
g := gomega.NewGomegaWithT(t)
testPodAnnotations := map[string]string {
"test1": "value1",
"test2": "value2",
}
testPodLabels := map[string]string {
"test3": "value3",
"test4": "value4",
}
testSSAnnotations := map[string]string {
"test5": "value5",
"test6": "value6",
}
testSSLabels := map[string]string {
"test7": "value7",
"test8": "value8",
}
instance := &solr.SolrCloud{
ObjectMeta: metav1.ObjectMeta{Name: expectedCloudRequest.Name, Namespace: expectedCloudRequest.Namespace},
ObjectMeta: metav1.ObjectMeta{
Name: expectedCloudRequest.Name,
Namespace: expectedCloudRequest.Namespace,
Labels: map[string]string{"base": "here"},
},
Spec: solr.SolrCloudSpec{
ZookeeperRef: &solr.ZookeeperRef{
ConnectionInfo: &solr.ZookeeperConnectionInfo{
InternalConnectionString: "host:7271",
},
},
SolrGCTune: "gc Options",
CustomSolrKubeOptions: solr.CustomSolrKubeOptions{
PodOptions: &solr.PodOptions{
Annotations: testPodAnnotations,
Labels: testPodLabels,
},
StatefulSetOptions: &solr.StatefulSetOptions{
Annotations: testSSAnnotations,
Labels: testSSLabels,
},
},
},
}

Expand Down Expand Up @@ -184,13 +215,19 @@ func TestCloudReconcileWithIngress(t *testing.T) {
"SOLR_PORT": "8983",
"GC_TUNE": "gc Options",
}
expectedStatefulSetLabels := util.MergeLabelsOrAnnotations(instance.SharedLabelsWith(instance.Labels), map[string]string {"technology": "solr-cloud"})
expectedStatefulSetAnnotations := map[string]string {util.SolrZKConnectionStringAnnotation: "host:7271/"}
testPodEnvVariables(t, expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
testMapsEqual(t, "statefulSet labels", util.MergeLabelsOrAnnotations(expectedStatefulSetLabels, testSSLabels), statefulSet.Labels)
testMapsEqual(t, "statefulSet annotations", util.MergeLabelsOrAnnotations(expectedStatefulSetAnnotations, testSSAnnotations), statefulSet.Annotations)
testMapsEqual(t, "pod labels", util.MergeLabelsOrAnnotations(expectedStatefulSetLabels, testPodLabels), statefulSet.Spec.Template.ObjectMeta.Labels)
testMapsEqual(t, "pod annotations", testPodAnnotations, statefulSet.Spec.Template.Annotations)

// Check the client Service
expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Template.Labels)
expectService(t, g, requests, expectedCloudRequest, cloudCsKey, statefulSet.Spec.Selector.MatchLabels)

// Check the headless Service
expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Template.Labels)
expectService(t, g, requests, expectedCloudRequest, cloudHsKey, statefulSet.Spec.Selector.MatchLabels)

// Check the ingress
expectIngress(g, requests, expectedCloudRequest, cloudIKey)
Expand Down Expand Up @@ -409,4 +446,4 @@ func TestDefaults(t *testing.T) {
assert.NotNil(t, instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec, "Bad Default - instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec")
assert.Equal(t, 1, len(instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec.Resources.Requests), "Bad Default - Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec.Resources length")
assert.Equal(t, 1, len(instance.Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec.AccessModes), "Bad Default - Spec.ZookeeperRef.ProvidedZookeeper.Zookeeper.Persistence.PersistentVolumeClaimSpec.AccesModes length")
}
}
18 changes: 18 additions & 0 deletions controllers/util/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,21 @@ func CopyLabelsAndAnnotations(from, to *metav1.ObjectMeta) (requireUpdate bool)

return requireUpdate
}

func DuplicateLabelsOrAnnotations(from map[string]string) map[string]string {
to := make(map[string]string, len(from))
for k, v := range from {
to[k] = v
}
return to;
}

func MergeLabelsOrAnnotations(base, additional map[string]string) map[string]string {
merged := DuplicateLabelsOrAnnotations(base)
for k, v := range additional {
if _, alreadyExists := merged[k]; !alreadyExists {
merged[k] = v
}
}
return merged;
}
Loading

0 comments on commit 636e672

Please sign in to comment.