Merge pull request #73976 from jennybuckley/apply-errors

Make server-side apply's conflict errors more human readable
pull/564/head
Kubernetes Prow Robot 2019-02-14 17:05:21 -08:00 committed by GitHub
commit 62734d3670
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 199 additions and 196 deletions

View File

@ -178,26 +178,6 @@
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"

View File

@ -31,7 +31,6 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library",
],
)

View File

@ -27,7 +27,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"sigs.k8s.io/structured-merge-diff/merge"
)
const (
@ -186,16 +185,7 @@ func NewConflict(qualifiedResource schema.GroupResource, name string, err error)
}
// NewApplyConflict returns an error including details on the requests apply conflicts
func NewApplyConflict(conflicts merge.Conflicts) *StatusError {
causes := make([]metav1.StatusCause, 0, len(conflicts))
for _, conflict := range conflicts {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseType("conflict"),
Message: conflict.Error(),
Field: conflict.Path.String(),
})
}
func NewApplyConflict(causes []metav1.StatusCause, message string) *StatusError {
return &StatusError{ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusConflict,
@ -204,7 +194,7 @@ func NewApplyConflict(conflicts merge.Conflicts) *StatusError {
// TODO: Get obj details here?
Causes: causes,
},
Message: fmt.Sprintf("Apply failed with %d conflicts: %s", len(conflicts), conflicts.Error()),
Message: message,
}}
}

View File

@ -825,6 +825,9 @@ const (
// without the expected return type. The presence of this cause indicates the error may be
// due to an intervening proxy or the server software malfunctioning.
CauseTypeUnexpectedServerResponse CauseType = "UnexpectedServerResponse"
// FieldManagerConflict is used to report when another client claims to manage this field,
// It should only be returned for a request using server-side apply.
CauseTypeFieldManagerConflict CauseType = "FieldManagerConflict"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -7,7 +7,6 @@ go_library(
importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",

View File

@ -20,7 +20,6 @@ import (
"fmt"
"time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -178,7 +177,7 @@ func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, force bool) (
newObjTyped, managed, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed, manager, force)
if err != nil {
if conflicts, ok := err.(merge.Conflicts); ok {
return nil, errors.NewApplyConflict(conflicts)
return nil, internal.NewConflictError(conflicts)
}
return nil, err
}

View File

@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"conflict.go",
"fields.go",
"gvkparser.go",
"managedfields.go",
@ -14,6 +15,7 @@ go_library(
importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
@ -32,6 +34,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"conflict_test.go",
"fields_test.go",
"managedfields_test.go",
"pathelement_test.go",
@ -41,6 +44,7 @@ go_test(
data = ["//api/openapi-spec"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
@ -49,6 +53,7 @@ go_test(
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)

View File

@ -0,0 +1,82 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/merge"
)
// NewConflictError returns an error including details on the requests apply conflicts
func NewConflictError(conflicts merge.Conflicts) *errors.StatusError {
causes := []metav1.StatusCause{}
for _, conflict := range conflicts {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldManagerConflict,
Message: fmt.Sprintf("conflict with %v", printManager(conflict.Manager)),
Field: conflict.Path.String(),
})
}
return errors.NewApplyConflict(causes, getConflictMessage(conflicts))
}
func getConflictMessage(conflicts merge.Conflicts) string {
if len(conflicts) == 1 {
return fmt.Sprintf("Apply failed with 1 conflict: conflict with %v: %v", printManager(conflicts[0].Manager), conflicts[0].Path)
}
m := map[string][]fieldpath.Path{}
for _, conflict := range conflicts {
m[conflict.Manager] = append(m[conflict.Manager], conflict.Path)
}
uniqueManagers := []string{}
for manager := range m {
uniqueManagers = append(uniqueManagers, manager)
}
// Print conflicts by sorted managers.
sort.Strings(uniqueManagers)
messages := []string{}
for _, manager := range uniqueManagers {
messages = append(messages, fmt.Sprintf("conflicts with %v:", printManager(manager)))
for _, path := range m[manager] {
messages = append(messages, fmt.Sprintf("- %v", path))
}
}
return fmt.Sprintf("Apply failed with %d conflicts: %s", len(conflicts), strings.Join(messages, "\n"))
}
func printManager(manager string) string {
encodedManager := &metav1.ManagedFieldsEntry{}
if err := json.Unmarshal([]byte(manager), encodedManager); err != nil {
return fmt.Sprintf("%q", manager)
}
if encodedManager.Operation == metav1.ManagedFieldsOperationUpdate {
return fmt.Sprintf("%q using %v at %v", encodedManager.Manager, encodedManager.APIVersion, encodedManager.Time.UTC().Format(time.RFC3339))
}
return fmt.Sprintf("%q", encodedManager.Manager)
}

View File

@ -0,0 +1,106 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal_test
import (
"net/http"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/merge"
)
// TestNewConflictError tests that NewConflictError creates the correct StatusError for a given smd Conflicts
func TestNewConflictError(t *testing.T) {
testCases := []struct {
conflict merge.Conflicts
expected *errors.StatusError
}{
{
conflict: merge.Conflicts{
merge.Conflict{
Manager: `{"manager":"foo","operation":"Update","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`,
Path: fieldpath.MakePathOrDie("spec", "replicas"),
},
},
expected: &errors.StatusError{
ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusConflict,
Reason: metav1.StatusReasonConflict,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{
{
Type: metav1.CauseTypeFieldManagerConflict,
Message: `conflict with "foo" using v1 at 2001-02-03T04:05:06Z`,
Field: ".spec.replicas",
},
},
},
Message: `Apply failed with 1 conflict: conflict with "foo" using v1 at 2001-02-03T04:05:06Z: .spec.replicas`,
},
},
},
{
conflict: merge.Conflicts{
merge.Conflict{
Manager: `{"manager":"foo","operation":"Update","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`,
Path: fieldpath.MakePathOrDie("spec", "replicas"),
},
merge.Conflict{
Manager: `{"manager":"bar","operation":"Apply"}`,
Path: fieldpath.MakePathOrDie("metadata", "labels", "app"),
},
},
expected: &errors.StatusError{
ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusConflict,
Reason: metav1.StatusReasonConflict,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{
{
Type: metav1.CauseTypeFieldManagerConflict,
Message: `conflict with "foo" using v1 at 2001-02-03T04:05:06Z`,
Field: ".spec.replicas",
},
{
Type: metav1.CauseTypeFieldManagerConflict,
Message: `conflict with "bar"`,
Field: ".metadata.labels.app",
},
},
},
Message: `Apply failed with 2 conflicts: conflicts with "bar":
- .metadata.labels.app
conflicts with "foo" using v1 at 2001-02-03T04:05:06Z:
- .spec.replicas`,
},
},
},
}
for _, tc := range testCases {
actual := internal.NewConflictError(tc.conflict)
if !reflect.DeepEqual(tc.expected, actual) {
t.Errorf("Expected to get\n%+v\nbut got\n%+v", tc.expected.ErrStatus, actual.ErrStatus)
}
}
}

View File

@ -790,26 +790,6 @@
"ImportPath": "sigs.k8s.io/kustomize/pkg/types",
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"

View File

@ -626,26 +626,6 @@
"ImportPath": "k8s.io/utils/trace",
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"

View File

@ -978,26 +978,6 @@
"ImportPath": "k8s.io/utils/trace",
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"

View File

@ -542,26 +542,6 @@
"ImportPath": "k8s.io/utils/trace",
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"

View File

@ -510,26 +510,6 @@
"ImportPath": "k8s.io/utils/integer",
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"

View File

@ -542,26 +542,6 @@
"ImportPath": "k8s.io/utils/trace",
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"

View File

@ -582,26 +582,6 @@
"ImportPath": "k8s.io/utils/integer",
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"

View File

@ -1150,26 +1150,6 @@
"ImportPath": "k8s.io/utils/trace",
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/merge",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/schema",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/typed",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/structured-merge-diff/value",
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
},
{
"ImportPath": "sigs.k8s.io/yaml",
"Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"