Refactor comparison of sts and volume claims

This commit is contained in:
erthalion 2018-06-28 16:03:36 +02:00
parent 4f97441b53
commit 4dcf2cae40
2 changed files with 213 additions and 92 deletions

View File

@ -346,55 +346,13 @@ func (c *Cluster) Create() error {
func (c *Cluster) compareStatefulSetWith(statefulSet *v1beta1.StatefulSet) *compareStatefulsetResult {
reasons := make([]string, 0)
var match, needsRollUpdate, needsReplace bool
match, needsRollUpdate, needsReplace := true, false, false
match = true
//TODO: improve me
if *c.Statefulset.Spec.Replicas != *statefulSet.Spec.Replicas {
match = false
reasons = append(reasons, "new statefulset's number of replicas doesn't match the current one")
}
if !reflect.DeepEqual(c.Statefulset.Annotations, statefulSet.Annotations) {
match = false
reasons = append(reasons, "new statefulset's annotations doesn't match the current one")
}
if len(c.Statefulset.Spec.Template.Spec.Containers) != len(statefulSet.Spec.Template.Spec.Containers) {
needsRollUpdate = true
reasons = append(reasons, "new statefulset's container specification doesn't match the current one")
} else {
needsRollUpdate, reasons = c.compareContainers(c.Statefulset, statefulSet)
}
if len(c.Statefulset.Spec.Template.Spec.Containers) == 0 {
c.logger.Warningf("statefulset %q has no container", util.NameFromMeta(c.Statefulset.ObjectMeta))
return &compareStatefulsetResult{}
}
// In the comparisons below, the needsReplace and needsRollUpdate flags are never reset, since checks fall through
// and the combined effect of all the changes should be applied.
// TODO: log all reasons for changing the statefulset, not just the last one.
// TODO: make sure this is in sync with generatePodTemplate, ideally by using the same list of fields to generate
// the template and the diff
if c.Statefulset.Spec.Template.Spec.ServiceAccountName != statefulSet.Spec.Template.Spec.ServiceAccountName {
needsReplace = true
needsRollUpdate = true
reasons = append(reasons, "new statefulset's serviceAccountName service asccount name doesn't match the current one")
}
if *c.Statefulset.Spec.Template.Spec.TerminationGracePeriodSeconds != *statefulSet.Spec.Template.Spec.TerminationGracePeriodSeconds {
needsReplace = true
needsRollUpdate = true
reasons = append(reasons, "new statefulset's terminationGracePeriodSeconds doesn't match the current one")
}
if !reflect.DeepEqual(c.Statefulset.Spec.Template.Spec.Affinity, statefulSet.Spec.Template.Spec.Affinity) {
needsReplace = true
needsRollUpdate = true
reasons = append(reasons, "new statefulset's pod affinity doesn't match the current one")
}
// Some generated fields like creationTimestamp make it not possible to use DeepCompare on Spec.Template.ObjectMeta
if !reflect.DeepEqual(c.Statefulset.Spec.Template.Labels, statefulSet.Spec.Template.Labels) {
needsReplace = true
needsRollUpdate = true
reasons = append(reasons, "new statefulset's metadata labels doesn't match the current one")
}
if (c.Statefulset.Spec.Selector != nil) && (statefulSet.Spec.Selector != nil) {
if !reflect.DeepEqual(c.Statefulset.Spec.Selector.MatchLabels, statefulSet.Spec.Selector.MatchLabels) {
// forbid introducing new labels in the selector on the new statefulset, as it would cripple replacements
@ -403,40 +361,38 @@ func (c *Cluster) compareStatefulSetWith(statefulSet *v1beta1.StatefulSet) *comp
c.logger.Warningf("new statefulset introduces extra labels in the label selector, cannot continue")
return &compareStatefulsetResult{}
}
needsReplace = true
reasons = append(reasons, "new statefulset's selector doesn't match the current one")
}
}
if !reflect.DeepEqual(c.Statefulset.Spec.Template.Annotations, statefulSet.Spec.Template.Annotations) {
match = false
needsReplace = true
needsRollUpdate = true
reasons = append(reasons, "new statefulset's pod template metadata annotations doesn't match the current one")
}
if len(c.Statefulset.Spec.VolumeClaimTemplates) != len(statefulSet.Spec.VolumeClaimTemplates) {
needsReplace = true
reasons = append(reasons, "new statefulset's volumeClaimTemplates contains different number of volumes to the old one")
}
for i := 0; i < len(c.Statefulset.Spec.VolumeClaimTemplates); i++ {
name := c.Statefulset.Spec.VolumeClaimTemplates[i].Name
// Some generated fields like creationTimestamp make it not possible to use DeepCompare on ObjectMeta
if name != statefulSet.Spec.VolumeClaimTemplates[i].Name {
needsReplace = true
reasons = append(reasons, fmt.Sprintf("new statefulset's name for volume %d doesn't match the current one", i))
for _, check := range c.getStatefulSetChecks() {
if check.statefulSetCondition == nil {
continue
}
if !reflect.DeepEqual(c.Statefulset.Spec.VolumeClaimTemplates[i].Annotations, statefulSet.Spec.VolumeClaimTemplates[i].Annotations) {
needsReplace = true
reasons = append(reasons, fmt.Sprintf("new statefulset's annotations for volume %q doesn't match the current one", name))
}
if !reflect.DeepEqual(c.Statefulset.Spec.VolumeClaimTemplates[i].Spec, statefulSet.Spec.VolumeClaimTemplates[i].Spec) {
name := c.Statefulset.Spec.VolumeClaimTemplates[i].Name
needsReplace = true
reasons = append(reasons, fmt.Sprintf("new statefulset's volumeClaimTemplates specification for volume %q doesn't match the current one", name))
if check.statefulSetCondition(c.Statefulset, statefulSet) {
if check.result.differs != nil {
match = !*check.result.differs
}
if check.result.needsRollUpdate != nil {
needsRollUpdate = *check.result.needsRollUpdate
}
if check.result.needsReplace != nil {
needsReplace = *check.result.needsReplace
}
reasons = append(reasons, check.reason)
}
}
needsRollUpdate, reasons = c.compareContainers(c.Statefulset, statefulSet)
needsReplace, reasons = c.compareVolumeClaimTemplates(c.Statefulset, statefulSet)
// In the comparisons below, the needsReplace and needsRollUpdate flags are never reset, since checks fall through
// and the combined effect of all the changes should be applied.
// TODO: log all reasons for changing the statefulset, not just the last one.
// TODO: make sure this is in sync with generatePodTemplate, ideally by using the same list of fields to generate
// the template and the diff
if needsRollUpdate || needsReplace {
match = false
}
@ -444,15 +400,27 @@ func (c *Cluster) compareStatefulSetWith(statefulSet *v1beta1.StatefulSet) *comp
return &compareStatefulsetResult{match: match, reasons: reasons, rollingUpdate: needsRollUpdate, replace: needsReplace}
}
type ContainerCondition func(a, b v1.Container) bool
func (c *Cluster) compareVolumeClaimTemplates(setA, setB *v1beta1.StatefulSet) (bool, []string) {
reasons := make([]string, 0)
needsReplace := false
type ContainerCheck struct {
condition ContainerCondition
reason string
}
for index, claimA := range setA.Spec.VolumeClaimTemplates {
claimB := setB.Spec.VolumeClaimTemplates[index]
func NewCheck(msg string, cond ContainerCondition) ContainerCheck {
return ContainerCheck{reason: msg, condition: cond}
for _, check := range c.getVolumeClaimChecks() {
if check.volumeClaimCondition == nil {
continue
}
if check.volumeClaimCondition(&claimA, &claimB) {
needsReplace = true
reasons = append(reasons, fmt.Sprintf(check.reason, index))
}
}
}
return needsReplace, reasons
}
// compareContainers: compare containers from two stateful sets
@ -462,25 +430,15 @@ func NewCheck(msg string, cond ContainerCondition) ContainerCheck {
func (c *Cluster) compareContainers(setA, setB *v1beta1.StatefulSet) (bool, []string) {
reasons := make([]string, 0)
needsRollUpdate := false
checks := []ContainerCheck{
NewCheck("new statefulset's container %d name doesn't match the current one",
func(a, b v1.Container) bool { return a.Name != b.Name }),
NewCheck("new statefulset's container %d image doesn't match the current one",
func(a, b v1.Container) bool { return a.Image != b.Image }),
NewCheck("new statefulset's container %d ports don't match the current one",
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.Ports, b.Ports) }),
NewCheck("new statefulset's container %d resources don't match the current ones",
func(a, b v1.Container) bool { return !compareResources(&a.Resources, &b.Resources) }),
NewCheck("new statefulset's container %d environment doesn't match the current one",
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.Env, b.Env) }),
NewCheck("new statefulset's container %d environment sources don't match the current one",
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.EnvFrom, b.EnvFrom) }),
}
for index, containerA := range setA.Spec.Template.Spec.Containers {
containerB := setB.Spec.Template.Spec.Containers[index]
for _, check := range checks {
if check.condition(containerA, containerB) {
for _, check := range c.getContainerChecks() {
if check.containerCondition == nil {
continue
}
if check.containerCondition(&containerA, &containerB) {
needsRollUpdate = true
reasons = append(reasons, fmt.Sprintf(check.reason, index))
}

163
pkg/cluster/comparison.go Normal file
View File

@ -0,0 +1,163 @@
package cluster
import (
"reflect"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/apps/v1beta1"
)
type ContainerCondition func(a, b *v1.Container) bool
type StatefulSetCondition func(a, b *v1beta1.StatefulSet) bool
type VolumeClaimCondition func(a, b *v1.PersistentVolumeClaim) bool
type ResourceCheck struct {
containerCondition ContainerCondition
statefulSetCondition StatefulSetCondition
volumeClaimCondition VolumeClaimCondition
result Result
reason string
}
func True() *bool {
b := true
return &b
}
type Result struct {
differs *bool
needsRollUpdate *bool
needsReplace *bool
}
func (c *Cluster) NewCheck(msg string, cond interface{}, result Result) ResourceCheck {
switch cond.(type) {
case ContainerCondition:
return ResourceCheck{
reason: msg,
containerCondition: cond.(ContainerCondition),
result: result,
}
case StatefulSetCondition:
return ResourceCheck{
reason: msg,
statefulSetCondition: cond.(StatefulSetCondition),
result: result,
}
case VolumeClaimCondition:
return ResourceCheck{
reason: msg,
volumeClaimCondition: cond.(VolumeClaimCondition),
result: result,
}
default:
c.logger.Errorf("Undefined check condition type: %v", cond)
return ResourceCheck{}
}
}
func (c *Cluster) getStatefulSetChecks() []ResourceCheck {
return []ResourceCheck{
c.NewCheck("new statefulset's number of replicas doesn't match the current one",
func(a, b *v1beta1.StatefulSet) bool { return a.Spec.Replicas != b.Spec.Replicas },
Result{differs: True()}),
c.NewCheck("new statefulset's annotations doesn't match the current one",
func(a, b *v1beta1.StatefulSet) bool { return !reflect.DeepEqual(a.Annotations, b.Annotations) },
Result{differs: True()}),
c.NewCheck("new statefulset's serviceAccountName service asccount name doesn't match the current one",
func(a, b *v1beta1.StatefulSet) bool {
return len(a.Spec.Template.Spec.Containers) != len(b.Spec.Template.Spec.Containers)
}, Result{needsRollUpdate: True()}),
c.NewCheck("new statefulset's serviceAccountName service asccount name doesn't match the current one",
func(a, b *v1beta1.StatefulSet) bool {
return a.Spec.Template.Spec.ServiceAccountName !=
b.Spec.Template.Spec.ServiceAccountName
}, Result{needsRollUpdate: True(), needsReplace: True()}),
c.NewCheck("new statefulset's terminationGracePeriodSeconds doesn't match the current one",
func(a, b *v1beta1.StatefulSet) bool {
return a.Spec.Template.Spec.TerminationGracePeriodSeconds !=
b.Spec.Template.Spec.TerminationGracePeriodSeconds
}, Result{needsRollUpdate: True(), needsReplace: True()}),
c.NewCheck("new statefulset's pod affinity doesn't match the current one",
func(a, b *v1beta1.StatefulSet) bool {
return !reflect.DeepEqual(a.Spec.Template.Spec.Affinity,
b.Spec.Template.Spec.Affinity)
}, Result{needsRollUpdate: True(), needsReplace: True()}),
// Some generated fields like creationTimestamp make it not possible to
// use DeepCompare on Spec.Template.ObjectMeta
c.NewCheck("new statefulset's metadata labels doesn't match the current one",
func(a, b *v1beta1.StatefulSet) bool {
return !reflect.DeepEqual(a.Spec.Template.Labels, b.Spec.Template.Labels)
}, Result{needsRollUpdate: True(), needsReplace: True()}),
c.NewCheck("new statefulset's pod template metadata annotations doesn't match the current one",
func(a, b *v1beta1.StatefulSet) bool {
return !reflect.DeepEqual(a.Spec.Template.Annotations, b.Spec.Template.Annotations)
}, Result{differs: True(), needsRollUpdate: True(), needsReplace: True()}),
c.NewCheck("new statefulset's volumeClaimTemplates contains different number of volumes to the old one",
func(a, b *v1beta1.StatefulSet) bool {
return len(a.Spec.VolumeClaimTemplates) != len(b.Spec.VolumeClaimTemplates)
}, Result{needsReplace: True()}),
c.NewCheck("new statefulset's selector doesn't match the current one",
func(a, b *v1beta1.StatefulSet) bool {
if a.Spec.Selector == nil || b.Spec.Selector == nil {
return false
}
return !reflect.DeepEqual(a.Spec.Selector.MatchLabels, b.Spec.Selector.MatchLabels)
}, Result{needsReplace: True()}),
}
}
func (c *Cluster) getContainerChecks() []ResourceCheck {
return []ResourceCheck{
c.NewCheck("new statefulset's container %d name doesn't match the current one",
func(a, b *v1.Container) bool { return a.Name != b.Name },
Result{needsRollUpdate: True()}),
c.NewCheck("new statefulset's container %d image doesn't match the current one",
func(a, b *v1.Container) bool { return a.Image != b.Image },
Result{needsRollUpdate: True()}),
c.NewCheck("new statefulset's container %d ports don't match the current one",
func(a, b *v1.Container) bool { return !reflect.DeepEqual(a.Ports, b.Ports) },
Result{needsRollUpdate: True()}),
c.NewCheck("new statefulset's container %d resources don't match the current ones",
func(a, b *v1.Container) bool { return !compareResources(&a.Resources, &b.Resources) },
Result{needsRollUpdate: True()}),
c.NewCheck("new statefulset's container %d environment doesn't match the current one",
func(a, b *v1.Container) bool { return !reflect.DeepEqual(a.Env, b.Env) },
Result{needsRollUpdate: True()}),
c.NewCheck("new statefulset's container %d environment sources don't match the current one",
func(a, b *v1.Container) bool { return !reflect.DeepEqual(a.EnvFrom, b.EnvFrom) },
Result{needsRollUpdate: True()}),
}
}
func (c *Cluster) getVolumeClaimChecks() []ResourceCheck {
return []ResourceCheck{
c.NewCheck("new statefulset's name for volume %d doesn't match the current one",
func(a, b *v1.PersistentVolumeClaim) bool { return a.Name != b.Name },
Result{needsReplace: True()}),
c.NewCheck("new statefulset's annotations for volume %q doesn't match the current one",
func(a, b *v1.PersistentVolumeClaim) bool {
return !reflect.DeepEqual(a.Annotations, b.Annotations)
},
Result{needsReplace: True()}),
c.NewCheck("new statefulset's volumeClaimTemplates specification for volume %q doesn't match the current one",
func(a, b *v1.PersistentVolumeClaim) bool { return !reflect.DeepEqual(a.Spec, b.Spec) },
Result{needsRollUpdate: True()}),
}
}