Refactor comparison of sts and volume claims
This commit is contained in:
parent
4f97441b53
commit
4dcf2cae40
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()}),
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue