diff --git a/e2e/errors.go b/e2e/errors.go index eb14696a7..31aad8ad0 100644 --- a/e2e/errors.go +++ b/e2e/errors.go @@ -43,3 +43,71 @@ func isRetryableAPIError(err error) bool { return false } + +//nolint:lll // sample output cannot be split into multiple lines. +/* +getStdErr will extract the stderror and returns the actual error message + +Sample kubectl output: + +error running /usr/local/bin/kubectl --server=https://192.168.39.67:8443 --kubeconfig=***** --namespace=default create -f -: +Command stdout: + +stderr: +Error from server (AlreadyExists): error when creating "STDIN": services "csi-rbdplugin-provisioner" already exists +Error from server (AlreadyExists): error when creating "STDIN": deployments.apps "csi-rbdplugin-provisioner" already exists + +error: +exit status 1 + +Sample message returned from this function: + +Error from server (AlreadyExists): error when creating "STDIN": services "csi-rbdplugin-provisioner" already exists +Error from server (AlreadyExists): error when creating "STDIN": deployments.apps "csi-rbdplugin-provisioner" already exists. +*/ +func getStdErr(errString string) string { + stdErrStr := "stderr:\n" + errStr := "error:\n" + stdErrPosition := strings.Index(errString, stdErrStr) + if stdErrPosition == -1 { + return "" + } + + errPosition := strings.Index(errString, errStr) + if errPosition == -1 { + return "" + } + + stdErrPositionLength := stdErrPosition + len(stdErrStr) + if stdErrPositionLength >= errPosition { + return "" + } + + return errString[stdErrPosition+len(stdErrStr) : errPosition] +} + +// isAlreadyExistsCLIError checks for already exists error from kubectl CLI. +func isAlreadyExistsCLIError(err error) bool { + if err == nil { + return false + } + // if multiple resources already exists. each error is separated by newline + stdErr := getStdErr(err.Error()) + if stdErr == "" { + return false + } + + stdErrs := strings.Split(stdErr, "\n") + for _, s := range stdErrs { + // If the string is just a new line continue + if strings.TrimSuffix(s, "\n") == "" { + continue + } + // Resource already exists error message + if !strings.Contains(s, "Error from server (AlreadyExists)") { + return false + } + } + + return true +} diff --git a/e2e/errors_test.go b/e2e/errors_test.go new file mode 100644 index 000000000..6ee54217d --- /dev/null +++ b/e2e/errors_test.go @@ -0,0 +1,89 @@ +/* +Copyright 2020 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 e2e + +import ( + "fmt" + "testing" +) + +// nolint:lll // error string cannot be split into multiple lines as is a +// output from kubectl. +func TestGetStdErr(t *testing.T) { + t.Parallel() + tests := []struct { + name string + errString string + expected string + isAlreadyExistError bool + }{ + { + name: "stdErr output when error is found", + errString: ` +error running /usr/local/bin/kubectl --server=https://192.168.39.67:8443 --kubeconfig=***** --namespace=default create -f -: +Command stdout: + +stderr: +Error from server (AlreadyExists): error when creating "STDIN": services "csi-rbdplugin-provisioner" already exists +Error from server (AlreadyExists): error when creating "STDIN": deployments.apps "csi-rbdplugin-provisioner" already exists + +error: +exit status 1`, + expected: `Error from server (AlreadyExists): error when creating "STDIN": services "csi-rbdplugin-provisioner" already exists +Error from server (AlreadyExists): error when creating "STDIN": deployments.apps "csi-rbdplugin-provisioner" already exists + +`, + isAlreadyExistError: true, + }, + { + name: "stdErr output when stderr: string is not found", + errString: ` +error running /usr/local/bin/kubectl --server=https://192.168.39.67:8443 --kubeconfig=***** --namespace=default create -f -: +Command stdout: + +error: +exit status 1`, + expected: "", + isAlreadyExistError: false, + }, + { + name: "stdErr output when error: string is not found", + errString: ` +error running /usr/local/bin/kubectl --server=https://192.168.39.67:8443 --kubeconfig=***** --namespace=default create -f -: +Command stdout: + +stderr: +Error from server (AlreadyExists): error when creating "STDIN": services "csi-rbdplugin-provisioner" already exists +Error from server (AlreadyExists): error when creating "STDIN": deployments.apps "csi-rbdplugin-provisioner" already exists`, + expected: "", + isAlreadyExistError: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := getStdErr(tt.errString); got != tt.expected { + t.Errorf("getStdErr() output = %v, expected %v", got, tt.expected) + } + + if got := isAlreadyExistsCLIError(fmt.Errorf("%s", tt.errString)); got != tt.isAlreadyExistError { + t.Errorf("isAlreadyExistsCLIError() output = %v, expected %v", got, tt.isAlreadyExistError) + } + }) + } +} diff --git a/e2e/utils.go b/e2e/utils.go index 13246077d..eed83ccb9 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -1238,6 +1238,9 @@ func retryKubectlInput(namespace string, action kubectlAction, data string, t in if isRetryableAPIError(err) { return false, nil } + if isAlreadyExistsCLIError(err) { + return true, nil + } e2elog.Logf( "will run kubectl (%s) again (%d seconds elapsed)", action, @@ -1264,6 +1267,9 @@ func retryKubectlFile(namespace string, action kubectlAction, filename string, t if isRetryableAPIError(err) { return false, nil } + if isAlreadyExistsCLIError(err) { + return true, nil + } e2elog.Logf( "will run kubectl (%s -f %q) again (%d seconds elapsed)", action,