diff --git a/e2e/proxy_test.go b/e2e/proxy_test.go index 91e1d9f..738afa4 100644 --- a/e2e/proxy_test.go +++ b/e2e/proxy_test.go @@ -839,8 +839,7 @@ var _ = Describe("Proxy", func() { if lockMode == proxyrule.OptimisticLockMode { Expect(err).To(Succeed()) } else { - fmt.Println(err) - Expect(k8serrors.IsUnauthorized(err)).To(BeTrue()) + Expect(k8serrors.IsConflict(err) || k8serrors.IsUnauthorized(err)).To(BeTrue()) // paul sees the request fail, so he tries again: Expect(DeletePod(ctx, paulClient, paulNamespace, paulPod)).To(Succeed()) } diff --git a/magefiles/test.go b/magefiles/test.go index 79027a7..0dae0cd 100644 --- a/magefiles/test.go +++ b/magefiles/test.go @@ -27,6 +27,17 @@ func (t Test) Unit() error { // E2e runs the end-to-end tests against a real apiserver. func (t Test) E2e() error { + args := append(e2eArgs(), "../e2e") + return RunSh("go", Tool())(args...) +} + +// E2eUntilItFails runs the end-to-end tests indefinitely against a real apiserver until it fails. +func (t Test) E2eUntilItFails() error { + args := append(e2eArgs(), "--until-it-fails", "../e2e") + return RunSh("go", Tool())(args...) +} + +func e2eArgs() []string { args := []string{"run", "github.com/onsi/ginkgo/v2/ginkgo"} args = append(args, "--tags=e2e,failpoints", @@ -41,6 +52,5 @@ func (t Test) E2e() error { "-vv", "--fail-fast", "--randomize-all") - args = append(args, "../e2e") - return RunSh("go", Tool())(args...) + return args } diff --git a/pkg/authz/distributedtx/workflow.go b/pkg/authz/distributedtx/workflow.go index 58bbd72..968ca08 100644 --- a/pkg/authz/distributedtx/workflow.go +++ b/pkg/authz/distributedtx/workflow.go @@ -420,11 +420,29 @@ func ResourceLockRel(input *WriteObjInput, workflowID string) *v1.RelationshipUp // KubeConflict wraps an error and turns it into a standard kube conflict // response. func KubeConflict(err error, input *WriteObjInput) *KubeResp { + var group, resource, name string + + if input == nil { + klog.Warningf("input to KubeConflict is nil for error %s", err) + } else { + if input.RequestInfo == nil { + klog.Warningf("input to KubeConflict has nil RequestInfo for error %s", err) + } else { + group = input.RequestInfo.APIGroup + resource = input.RequestInfo.Resource + } + if input.ObjectMeta == nil { + klog.Warningf("input to KubeConflict has nil ObjectMeta for error %s", err) + } else { + name = input.ObjectMeta.Name + } + } + var out KubeResp statusError := k8serrors.NewConflict(schema.GroupResource{ - Group: input.RequestInfo.APIGroup, - Resource: input.RequestInfo.Resource, - }, input.ObjectMeta.Name, err) + Group: group, + Resource: resource, + }, name, err) out.StatusCode = http.StatusConflict out.Err = *statusError out.Body, _ = json.Marshal(statusError) diff --git a/pkg/authz/distributedtx/workflow_test.go b/pkg/authz/distributedtx/workflow_test.go index 9f0d966..5c3d20d 100644 --- a/pkg/authz/distributedtx/workflow_test.go +++ b/pkg/authz/distributedtx/workflow_test.go @@ -2,6 +2,7 @@ package distributedtx import ( "context" + "errors" "io" "net/http" "strings" @@ -119,3 +120,94 @@ func TestWorkflow(t *testing.T) { }) } } + +func TestKubeConflict(t *testing.T) { + t.Parallel() + testCases := map[string]struct { + inputErr error + input *WriteObjInput + expectedJSON string + }{ + `nil input`: { + inputErr: errors.New("some err"), + input: nil, + expectedJSON: `{ + "ErrStatus" : { + "metadata" : { }, + "status" : "Failure", + "message" : "Operation cannot be fulfilled on \"\": some err", + "reason" : "Conflict", + "details" : { }, + "code" : 409 + } +}`, + }, + `nil request info`: { + inputErr: errors.New("some err"), + input: &WriteObjInput{ + ObjectMeta: &metav1.ObjectMeta{Name: "my_object_meta"}, + }, + expectedJSON: `{ + "ErrStatus" : { + "metadata" : { }, + "status" : "Failure", + "message" : "Operation cannot be fulfilled on \"my_object_meta\": some err", + "reason" : "Conflict", + "details" : { + "name" : "my_object_meta" + }, + "code" : 409 + } +}`, + }, + `nil object meta`: { + inputErr: errors.New("some err"), + input: &WriteObjInput{ + RequestInfo: &request.RequestInfo{APIGroup: "foo", Resource: "bar"}, + }, + expectedJSON: `{ + "ErrStatus" : { + "metadata" : { }, + "status" : "Failure", + "message" : "Operation cannot be fulfilled on bar.foo \"\": some err", + "reason" : "Conflict", + "details" : { + "group" : "foo", + "kind" : "bar" + }, + "code" : 409 + } +}`, + }, + `valid input`: { + inputErr: errors.New("some err"), + input: &WriteObjInput{ + RequestInfo: &request.RequestInfo{APIGroup: "foo", Resource: "bar"}, + ObjectMeta: &metav1.ObjectMeta{Name: "my_object_meta"}, + }, + expectedJSON: `{ + "ErrStatus" : { + "metadata" : { }, + "status" : "Failure", + "message" : "Operation cannot be fulfilled on bar.foo \"my_object_meta\": some err", + "reason" : "Conflict", + "details" : { + "name" : "my_object_meta", + "group" : "foo", + "kind" : "bar" + }, + "code" : 409 + } +}`, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + res := KubeConflict(tc.inputErr, tc.input) + require.NotNil(t, res, "expected non-nil response") + require.JSONEq(t, tc.expectedJSON, string(res.Body), "unexpected response body") + }) + } +}