Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Strategic Merge Patch #43880

Merged
merged 1 commit into from
Apr 5, 2017

Conversation

mengqiy
Copy link
Member

@mengqiy mengqiy commented Mar 30, 2017

Refactor Strategic Merge Patch

None

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Mar 30, 2017
@k8s-github-robot k8s-github-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. release-note-none Denotes a PR that doesn't merit a release note. labels Mar 30, 2017
@k8s-reviewable
Copy link

This change is Reviewable

@mengqiy
Copy link
Member Author

mengqiy commented Mar 30, 2017

/unassign @dchen1107
/assign @pwittrock

@pwittrock
Copy link
Member

@janetkuo Are you familiar with this code and able to review?

@pwittrock pwittrock assigned apelisse and unassigned janetkuo Apr 3, 2017
@pwittrock
Copy link
Member

Review status: 0 of 3 files reviewed at latest revision, 2 unresolved discussions.


staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go, line 30 at r1 (raw file):

	ErrNoMergeKeyFmt   = "map: %v does not contain declared merge key: %s"
	ErrBadArgTypeFmt   = "expected a %s, but received a %s"

Make these functions with arguments.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 57 at r1 (raw file):

type DiffOptions struct {
	IgnoreChangesAndAdditions bool

Comment what these are


Comments from Reviewable

@pwittrock
Copy link
Member

Review status: 0 of 3 files reviewed at latest revision, 20 unresolved discussions.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 161 at r1 (raw file):

		// Types are the same, so compare values
		switch originalValueTyped := originalValue.(type) {
		case map[string]interface{}:

Pass in the casted value for modifiedValue


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 198 at r1 (raw file):

// handleMapDiff diff between 2 maps `originalValueTyped` and `modifiedValue`,
// puts the diff in the `patch` associated with `key`
func handleMapDiff(key string, originalValueTyped map[string]interface{}, modifiedValue interface{},

Add comments for arguments


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 200 at r1 (raw file):

func handleMapDiff(key string, originalValueTyped map[string]interface{}, modifiedValue interface{},
	t reflect.Type, patch map[string]interface{}, diffOptions DiffOptions) error {
	modifiedValueTyped := modifiedValue.(map[string]interface{})

Cast this before passing in


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 211 at r1 (raw file):

		return err
	}
	if fieldPatchStrategy == replaceDirective {

Add a comment that the directive tells us to replace the entire object instead of diffing it


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 221 at r1 (raw file):

		return err
	}
	if len(patchValue) > 0 {

Add comment // Maps were not identical, use provided patch value


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 229 at r1 (raw file):

// handleSliceDiff diff between 2 slices `originalValueTyped` and `modifiedValue`,
// puts the diff in the `patch` associated with `key`
func handleSliceDiff(key string, originalValueTyped []interface{}, modifiedValue interface{},

drop "Typed" piece of var names


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 231 at r1 (raw file):

func handleSliceDiff(key string, originalValueTyped []interface{}, modifiedValue interface{},
	t reflect.Type, patch map[string]interface{}, diffOptions DiffOptions) error {
	modifiedValueTyped := modifiedValue.([]interface{})

pass in casted value


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 244 at r1 (raw file):

	switch fieldPatchStrategy {
	case mergeDirective:

// Merge the 2 slices using mergePatchKey


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 265 at r1 (raw file):

// updatePatchFieldOnNotDeepEqual updates the patch if original and modified are not deep equal if ignoreChangesAndAdditions is false.
func updatePatchFieldOnNotDeepEqual(key string, original, modified interface{}, patch map[string]interface{}, ignoreChangesAndAdditions bool) {

Pass in the full options.

replacePatchFieldIfNotEqual


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 266 at r1 (raw file):

// updatePatchFieldOnNotDeepEqual updates the patch if original and modified are not deep equal if ignoreChangesAndAdditions is false.
func updatePatchFieldOnNotDeepEqual(key string, original, modified interface{}, patch map[string]interface{}, ignoreChangesAndAdditions bool) {
	if !ignoreChangesAndAdditions {

if ignoreChangesAndAdditions {
// Ignoring changes - do nothing
return
}
if reflect.DeepEqual(original, modified) {
// Contents are equal - do nothing
return
}
// Create a patch to replace the old value with the new one
patch[key] = modified


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 276 at r1 (raw file):

// updatePatchOnMissing iterates over `original` when ignoreDeletions is false.
// Clear the field whose key is not present in `modified`.
func updatePatchOnMissing(original, modified, patch map[string]interface{}, ignoreDeletions bool) {

Add comment for original and modified

// original maybe either the live cluster object or the last applied configuration
// modified is always the users new config


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 277 at r1 (raw file):

// Clear the field whose key is not present in `modified`.
func updatePatchOnMissing(original, modified, patch map[string]interface{}, ignoreDeletions bool) {
	if !ignoreDeletions {

if ignoreDeletions {
// Ignoring changes - do nothing
return
}
...


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 280 at r1 (raw file):

		// Add nils for deleted values
		for key := range original {
			_, found := modified[key]

if _, found := modified[key]; !found {
patch[key] = nil
}


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 339 at r1 (raw file):

		}
		// we need to compare the string representation of the scalar,
		// because the scalar is an interface which doesn't support neither < nor <

support either < or >


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 364 at r1 (raw file):

// controlledAppendList appends the list1Index-th item in list1 to resultList
// when either only list1 is in bound or list1 has additional scalar. The behavior is gated by notAppendItem.
func controlledAppendList(list1InBound, list2InBound,

`
// If first return value is non-nil, list1 contains an element not present in list2
// If second return value is non-nil, list2 contains an element not present in list1
func compareListValuesAtIndex(list1Inbounds, list2Inbounds bool, list1Value, list2Value string) (interface{}, interface{}) {
switch {
case list1Inbounds && list2Inbounds:
if list1Value < list2Value...
else
case !list1Inbounds
return nil, list2Value
case !list2Inbounds
return list1Value, nil
}
}

`


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 371 at r1 (raw file):

	bothListsInBound := list1InBound && list2InBound
	// only list1 is in bound OR list1 has additional scalar
	if !list2InBound || bothListsInBound && list1String < list2String {

Boolean logic unclear (put parens)


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 407 at r1 (raw file):

		var originalMap, modifiedMap map[string]interface{}
		if originalInBounds {
			originalMap, originalValue, err = getMapAndMergeKeyValueByIndex(originalIndex, mergeKey, originalSorted)

originalElement, originalElementMergeKeyValue


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 438 at r1 (raw file):

			fallthrough
		// modified has additional map
		case bothInBounds && originalString > modifiedString:

help func

`
func ItemAddedToModifiedSlice(original, modified string) { return originalString > modifiedString }

func ItemRemovedFromModifiedSlice(original, modified string) { return originalString < modifiedString }

func ItemMatchesOriginalAndModifiedSlice(original, modified string) { return originalString == modifiedString }

func CreateDeleteDirective(mergeKey: originalValue, directiveMarker: deleteDirective) { }
`


Comments from Reviewable

@pwittrock
Copy link
Member

Review status: 0 of 3 files reviewed at latest revision, 21 unresolved discussions.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 604 at r1 (raw file):

			return nil, err
		}
		if shouldContinue {

should continue is vague. Call this skipProcessing or something.


Comments from Reviewable

@mengqiy mengqiy force-pushed the refactor_SMP branch 2 times, most recently from 21475e0 to 3e27df4 Compare April 4, 2017 22:46
@mengqiy
Copy link
Member Author

mengqiy commented Apr 4, 2017

Review status: 0 of 3 files reviewed at latest revision, 21 unresolved discussions.


staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go, line 30 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Make these functions with arguments.

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 57 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Comment what these are

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 161 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Pass in the casted value for modifiedValue

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 198 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Add comments for arguments

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 200 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Cast this before passing in

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 211 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Add a comment that the directive tells us to replace the entire object instead of diffing it

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 221 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Add comment // Maps were not identical, use provided patch value

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 229 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

drop "Typed" piece of var names

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 231 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

pass in casted value

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 244 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

// Merge the 2 slices using mergePatchKey

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 265 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Pass in the full options.

replacePatchFieldIfNotEqual

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 266 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

if ignoreChangesAndAdditions {
// Ignoring changes - do nothing
return
}
if reflect.DeepEqual(original, modified) {
// Contents are equal - do nothing
return
}
// Create a patch to replace the old value with the new one
patch[key] = modified

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 276 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Add comment for original and modified

// original maybe either the live cluster object or the last applied configuration
// modified is always the users new config

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 277 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

if ignoreDeletions {
// Ignoring changes - do nothing
return
}
...

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 280 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

if _, found := modified[key]; !found {
patch[key] = nil
}

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 339 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

support either < or >

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 364 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

`
// If first return value is non-nil, list1 contains an element not present in list2
// If second return value is non-nil, list2 contains an element not present in list1
func compareListValuesAtIndex(list1Inbounds, list2Inbounds bool, list1Value, list2Value string) (interface{}, interface{}) {
switch {
case list1Inbounds && list2Inbounds:
if list1Value < list2Value...
else
case !list1Inbounds
return nil, list2Value
case !list2Inbounds
return list1Value, nil
}
}

`

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 371 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

Boolean logic unclear (put parens)

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 407 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

originalElement, originalElementMergeKeyValue

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 438 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

help func

`
func ItemAddedToModifiedSlice(original, modified string) { return originalString > modifiedString }

func ItemRemovedFromModifiedSlice(original, modified string) { return originalString < modifiedString }

func ItemMatchesOriginalAndModifiedSlice(original, modified string) { return originalString == modifiedString }

func CreateDeleteDirective(mergeKey: originalValue, directiveMarker: deleteDirective) { }
`

Done.


staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go, line 604 at r1 (raw file):

Previously, pwittrock (Phillip Wittrock) wrote…

should continue is vague. Call this skipProcessing or something.

Done.


Comments from Reviewable

Copy link
Member

@apelisse apelisse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of if conditions and interface{} used here is quite scary. I wonder if both problems could be vastly improved by using an interface with a merge function and separate the logic in different types.

}

func ErrBadArgType(expected, actual string) error {
return fmt.Errorf("expected a %s, but received a %s", expected, actual)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would simplify the code to take expected, actual interface{} and the print the type with %T (instead of using reflect.TypeOf(obj).Kind().String()).

Copy link
Member Author

@mengqiy mengqiy Apr 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%T gives a type that is too concrete for us. Return a very concrete type may confuse the users sometimes.
But we can use reflect control which level of type we want to return to the users.

x := []byte("foo")
fmt.Printf("%T\n", x)
fmt.Printf(reflect.TypeOf(x).String())
fmt.Printf(reflect.TypeOf(x).Kind().String())

[]uint8
[]uint8
slice

Copy link
Member

@apelisse apelisse Apr 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thanks for pointing this out. TIL what typeOf(obj).Kind() does :-)

return original, nil
// mergeMapHandler handles how to merge `patchV` whose key is `key` with `original` respecting
// fieldPatchStrategy, fieldPatchMergeKey, isDeleteList and mergeOptions.
func mergeSliceHandler(original map[string]interface{}, key string, patchV interface{}, fieldType reflect.Type,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above

}
// mergeMapHandler handles how to merge `patchV` whose key is `key` with `original` respecting
// fieldPatchStrategy and mergeOptions.
func mergeMapHandler(original map[string]interface{}, key string, patchV interface{}, fieldType reflect.Type,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a bad feeling about this function:

  • It takes a lot of parameters
  • Given a specific condition, it does only one simple thing (ignoring many of the parameters, not a good sign). And this is also done in the function below (exactly the same)
  • Given the opposite condition, many of the types must be casted to different types

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given a specific condition, it does only one simple thing (ignoring many of the parameters, not a good sign). And this is also done in the function below (exactly the same)

This problem still exists. But I don't have a better way to that. Any suggestions?

func mergeSliceHandler(original map[string]interface{}, key string, patchV interface{}, fieldType reflect.Type,
fieldPatchStrategy, fieldPatchMergeKey string, isDeleteList bool, mergeOptions MergeOptions) error {
if fieldPatchStrategy == mergeDirective {
elemType := fieldType.Elem()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fieldType is only used to get elemType. Should we pass elemType instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to keep the signature of mergeSliceHandler and mergeSliceHandler similar. So I prefer to use fieldType

func mergeMapHandler(original map[string]interface{}, key string, patchV interface{}, fieldType reflect.Type,
fieldPatchStrategy string, mergeOptions MergeOptions) error {
if fieldPatchStrategy != replaceDirective {
typedOriginal := original[key].(map[string]interface{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're assuming that original[key] can be casted to map[string]interface{} (no error checking) because it's been done by the calling function, but that's not obvious when you read this function. I think that could be simplified.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added type assertion safety check.

@apelisse
Copy link
Member

apelisse commented Apr 5, 2017

It's already much better to read, thanks!

@apelisse
Copy link
Member

apelisse commented Apr 5, 2017

LGTM for me

@pwittrock
Copy link
Member

/approve

@pwittrock
Copy link
Member

/comment

@pwittrock
Copy link
Member

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Apr 5, 2017
@caesarxuchao
Copy link
Member

/approve

@k8s-github-robot
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: caesarxuchao, pwittrock, ymqytw

Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@k8s-github-robot k8s-github-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 5, 2017
@k8s-github-robot
Copy link

Automatic merge from submit-queue (batch tested with PRs 44097, 42772, 43880, 44031, 44066)

@k8s-github-robot k8s-github-robot merged commit f797abc into kubernetes:master Apr 5, 2017
@mengqiy mengqiy deleted the refactor_SMP branch April 6, 2017 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note-none Denotes a PR that doesn't merit a release note. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants