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