From 7d2d0382191130973d56fdcfa04ae3b8d17985d6 Mon Sep 17 00:00:00 2001 From: Amit Kumar Das Date: Tue, 28 Jul 2020 15:15:11 +0530 Subject: [PATCH] feat(http): add http as a custom resource (#107) This commit adds HTTP as a custom resource. This can currently be used to invoke http requests that do not need any authentication. Few e2e experiments added to verify HTTP custom resource. This change also includes a minor bug fix w.r.t StateCheck assert action. Before this fix any timeout error resulted in Recipe's status.phase being set as 'Error'. With current changes any timeout error will result in Recipe's status.phase as 'Failed'. In addition, ci related artifacts including experiments & yaml comments have been updated to make the ci run faster as well as make it more readable for contributors. Signed-off-by: AmitKumarDas --- .gitignore | 2 + Makefile | 15 ++-- config/metac.yaml | 20 +++++- controller/http/reconciler.go | 21 ++++-- controller/recipe/reconciler.go | 8 +-- manifests/crd.yaml | 23 +++++++ pkg/http/http.go | 8 ++- pkg/recipe/recipe.go | 13 ++-- pkg/recipe/recipe_test.go | 4 +- pkg/recipe/state_check.go | 41 ++++++++--- test/e2e/ci.yaml | 39 +++++++++-- test/e2e/inference.yaml | 68 +++++++++++++----- test/e2e/suite.sh | 18 ++--- .../assert-deprecated-daemonset.yaml | 2 +- ...github-search-invalid-method-negative.yaml | 39 +++++++++++ .../assert-github-search-invalid-method.yaml | 40 +++++++++++ .../experiments/assert-github-search-neg.yaml | 47 +++++++++++++ test/experiments/assert-github-search.yaml | 41 +++++++++++ test/experiments/assert-job-teardown.yaml | 2 +- ...-lock-persists-for-job-that-runs-once.yaml | 2 +- ...e-assert-fifty-configmaps-elapsedtime.yaml | 69 ++++++++++++++++--- .../create-assert-fifty-configmaps.yaml | 4 +- test/experiments/crud-ops-on-pod.yaml | 2 +- types/http/types.go | 10 +-- types/recipe/state_check.go | 19 +++-- 25 files changed, 455 insertions(+), 102 deletions(-) create mode 100644 test/experiments/assert-github-search-invalid-method-negative.yaml create mode 100644 test/experiments/assert-github-search-invalid-method.yaml create mode 100644 test/experiments/assert-github-search-neg.yaml create mode 100644 test/experiments/assert-github-search.yaml diff --git a/.gitignore b/.gitignore index 28da418..8cd7cb5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ d-operators test/bin/ test/kubebin/ test/e2e/uninstall-k3s.txt +uninstall-k3s.txt +dope diff --git a/Makefile b/Makefile index 7430685..c5f249e 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,8 @@ -PWD := ${CURDIR} - -OS = $(shell uname) - -PACKAGE_VERSION ?= $(shell git describe --always --tags) -GIT_TAGS = $(shell git fetch --all --tags) +# Fetch the latest tags & then set the package version +PACKAGE_VERSION ?= $(shell git fetch --all --tags | echo "" | git describe --always --tags) ALL_SRC = $(shell find . -name "*.go" | grep -v -e "vendor") # We are using docker hub as the default registry -#IMG_REGISTRY ?= quay.io IMG_NAME ?= dope IMG_REPO ?= mayadataio/dope @@ -22,8 +17,6 @@ $(IMG_NAME): $(ALL_SRC) $(ALL_SRC): ; -$(GIT_TAGS): ; - # go mod download modules to local cache # make vendored copy of dependencies # install other go binaries for code generation @@ -46,9 +39,9 @@ e2e-test: @cd test/e2e && ./suite.sh .PHONY: image -image: $(GIT_TAGS) +image: docker build -t $(IMG_REPO):$(PACKAGE_VERSION) . .PHONY: push push: image - docker push $(IMG_REPO):$(PACKAGE_VERSION \ No newline at end of file + docker push $(IMG_REPO):$(PACKAGE_VERSION) \ No newline at end of file diff --git a/config/metac.yaml b/config/metac.yaml index e4b85fe..b26488a 100644 --- a/config/metac.yaml +++ b/config/metac.yaml @@ -1,14 +1,30 @@ -apiVersion: metac.openebs.io/v1alpha1 +apiVersion: dope/v1 kind: GenericController metadata: name: sync-recipe namespace: dope spec: - watch: # kind: Recipe custom resource is watched + watch: + # kind: Recipe custom resource is watched apiVersion: dope.metacontroller.io/v1 resource: recipes hooks: sync: inline: funcName: sync/recipe +--- +apiVersion: dope/v1 +kind: GenericController +metadata: + name: sync-http + namespace: dope +spec: + watch: + # kind: HTTP custom resource is watched + apiVersion: dope.metacontroller.io/v1 + resource: https + hooks: + sync: + inline: + funcName: sync/http --- \ No newline at end of file diff --git a/controller/http/reconciler.go b/controller/http/reconciler.go index 479f374..503386f 100644 --- a/controller/http/reconciler.go +++ b/controller/http/reconciler.go @@ -74,17 +74,24 @@ func (r *Reconciler) evalObservedHTTP() { } r.observedHTTP = &http - // validate presence of secret + // extract secret name if set r.observedSecretName = http.Spec.SecretName - if r.observedSecretName == "" { - r.Err = errors.Errorf("Missing spec.secretName") - return - } + + // validate presence of secret + // if r.observedSecretName == "" { + // r.Err = errors.Errorf("Missing spec.secretName") + // return + // } } // evalObservedSecret parses the relevant secret found in Kubernetes // cluster. This secret is used to authenticate the request invocation. func (r *Reconciler) evalObservedSecret() { + if r.observedSecretName == "" { + // absence of secret name implies http invocation does not + // require authentication + return + } r.observedSecret = r.HookRequest.Attachments.FindByGroupKindName( "v1", "Secret", @@ -199,7 +206,7 @@ func (r *Reconciler) handleRuntimeError() { // NOTE: // Status forms the core business logic of reconciling a HTTP // custom resource. -func (r *Reconciler) updateWatchStatus() { +func (r *Reconciler) updateHTTPStatus() { // check for runtime errors if r.Err != nil { r.handleRuntimeError() @@ -277,7 +284,7 @@ func Sync(request *generic.SyncHookRequest, response *generic.SyncHookResponse) // NOTE: // HTTP custom resource is the watch here r.DesiredWatchFns = []func(){ - r.updateWatchStatus, + r.updateHTTPStatus, } // run reconcile diff --git a/controller/recipe/reconciler.go b/controller/recipe/reconciler.go index e50cd33..39ff24e 100644 --- a/controller/recipe/reconciler.go +++ b/controller/recipe/reconciler.go @@ -74,7 +74,7 @@ func (r *Reconciler) setSyncResponse() { } } -func (r *Reconciler) setWatchStatusAsError() { +func (r *Reconciler) setRecipeStatusAsError() { r.HookResponse.Status = map[string]interface{}{ "phase": "Error", "reason": r.Err.Error(), @@ -84,7 +84,7 @@ func (r *Reconciler) setWatchStatusAsError() { } } -func (r *Reconciler) setWatchStatusFromRecipeStatus() { +func (r *Reconciler) setRecipeStatus() { r.HookResponse.Status = map[string]interface{}{ "phase": string(r.RecipeStatus.Phase), "reason": r.RecipeStatus.Reason, @@ -110,7 +110,7 @@ func (r *Reconciler) setWatchStatus() { r.HookResponse.ResyncAfterSeconds = *r.ObservedRecipe.Spec.Refresh.OnErrorResyncAfterSeconds } - r.setWatchStatusAsError() + r.setRecipeStatusAsError() return } if r.RecipeStatus.Phase == types.RecipeStatusLocked { @@ -118,7 +118,7 @@ func (r *Reconciler) setWatchStatus() { // old status will persist return } - r.setWatchStatusFromRecipeStatus() + r.setRecipeStatus() } // Sync implements the idempotent logic to sync Recipe resource diff --git a/manifests/crd.yaml b/manifests/crd.yaml index a96c839..4bce412 100644 --- a/manifests/crd.yaml +++ b/manifests/crd.yaml @@ -18,6 +18,29 @@ spec: status: {} version: v1 versions: + - name: v1 + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + name: https.dope.metacontroller.io +spec: + group: dope.metacontroller.io + names: + kind: HTTP + listKind: HTTPList + plural: https + shortNames: + - http + singular: http + scope: Namespaced + subresources: + status: {} + version: v1 + versions: - name: v1 served: true storage: true \ No newline at end of file diff --git a/pkg/http/http.go b/pkg/http/http.go index e374f03..882c1b6 100644 --- a/pkg/http/http.go +++ b/pkg/http/http.go @@ -80,16 +80,20 @@ func (i *Invocable) buildStatus(response *resty.Response) types.HTTPResponse { // Invoke executes the http request func (i *Invocable) Invoke() (types.HTTPResponse, error) { req := resty.New().R(). - SetBasicAuth(i.Username, i.Password). SetBody(i.Body). SetHeaders(i.Headers). SetQueryParams(i.QueryParams). SetPathParams(i.PathParams) + // set credentials only if it was provided + if i.Username != "" || i.Password != "" { + req.SetBasicAuth(i.Username, i.Password) + } + var response *resty.Response var err error - switch strings.ToLower(i.HTTPMethod) { + switch strings.ToUpper(i.HTTPMethod) { case types.POST: response, err = req.Post(i.URL) case types.GET: diff --git a/pkg/recipe/recipe.go b/pkg/recipe/recipe.go index 1dd5dfb..1aacf58 100644 --- a/pkg/recipe/recipe.go +++ b/pkg/recipe/recipe.go @@ -212,7 +212,7 @@ func (r *Runner) buildLockRunner() *LockRunner { } // evalAll evaluates all tasks -func (r *Runner) evalAll() error { +func (r *Runner) evalAllTasks() error { for _, task := range r.Recipe.Spec.Tasks { err := r.eval(task) if err != nil { @@ -257,7 +257,7 @@ func (r *Runner) addRecipeElapsedTimeInSeconds(elapsedtime float64) { } // runAll runs all the tasks -func (r *Runner) runAll() (status *types.RecipeStatus, err error) { +func (r *Runner) runAllTasks() (status *types.RecipeStatus, err error) { defer func() { r.fixture.TearDown() }() @@ -280,6 +280,9 @@ func (r *Runner) runAll() (status *types.RecipeStatus, err error) { } got, err := tr.Run() if err != nil { + // We discontinue executing next tasks + // if current task execution resulted in + // error return nil, errors.Wrapf( err, "Failed to run task [%d] %q", @@ -289,6 +292,8 @@ func (r *Runner) runAll() (status *types.RecipeStatus, err error) { } r.RecipeStatus.TaskListStatus[task.Name] = got if got.Phase == types.TaskStatusFailed { + // We run subsequent tasks even if current task + // failed failedTasks++ } } @@ -360,7 +365,7 @@ func (r *Runner) Run() (status *types.RecipeStatus, err error) { }() r.RecipeStatus.TaskListStatus[r.Recipe.GetName()+"-lock"] = lockstatus - err = r.evalAll() + err = r.evalAllTasks() if err != nil { return nil, err } @@ -380,5 +385,5 @@ func (r *Runner) Run() (status *types.RecipeStatus, err error) { }, nil } - return r.runAll() + return r.runAllTasks() } diff --git a/pkg/recipe/recipe_test.go b/pkg/recipe/recipe_test.go index a2ab0c2..f9d107f 100644 --- a/pkg/recipe/recipe_test.go +++ b/pkg/recipe/recipe_test.go @@ -322,8 +322,8 @@ func TestRunnerRunAllTasks(t *testing.T) { }, fixture: f, } - r.initEnabled() // init to avoid nil pointers - got, err := r.runAll() // method under test + r.initEnabled() // init to avoid nil pointers + got, err := r.runAllTasks() // method under test if mock.isErr && err == nil { t.Fatal("Expected error got none") } diff --git a/pkg/recipe/state_check.go b/pkg/recipe/state_check.go index 41dfa17..7974269 100644 --- a/pkg/recipe/state_check.go +++ b/pkg/recipe/state_check.go @@ -184,8 +184,13 @@ func (sc *StateChecking) assertEquals() { sc.retryOnDiff = true success, err := sc.isMergeEqualsObserved(message) if err != nil { - sc.err = err - return + // verify if this was a timeout error + if _, ok := err.(*RetryTimeout); !ok { + sc.err = err + return + } + // set the timeout error against corresponding status field + sc.result.Timeout = err.Error() } // init phase as failed sc.result.Phase = types.StateCheckResultFailed @@ -209,8 +214,14 @@ func (sc *StateChecking) assertNotEquals() { sc.retryOnEqual = true success, err := sc.isMergeEqualsObserved(message) if err != nil { - sc.err = err - return + // verify if this is a timeout error + if _, ok := err.(*RetryTimeout); !ok { + // this is a runtime error + sc.err = err + return + } + // set timeout error against corresponding status field + sc.result.Timeout = err.Error() } // init phase as failed sc.result.Phase = types.StateCheckResultFailed @@ -251,21 +262,22 @@ func (sc *StateChecking) assertNotFound() { if apierrors.IsNotFound(err) { // phase is set to Passed here phase = types.StateCheckResultPassed - // Stop retrying + // Stop retrying since resource is not found in the cluster return true, nil } - // Keep retrying + // Keep retrying since get call errored out return false, err } if len(got.GetFinalizers()) == 0 && got.GetDeletionTimestamp() != nil { phase = types.StateCheckResultWarning warning = fmt.Sprintf( - "Marking StateCheck %q to passed: Finalizer count %d: Deletion timestamp %s", + "Marking StateCheck %q to passed: Finalizer count %d: Deletion timestamp %q", sc.TaskName, len(got.GetFinalizers()), got.GetDeletionTimestamp(), ) - // Stop retrying + // Stop retrying since Kubernetes has marked the resource + // to be deleted return true, nil } // Keep retrying @@ -274,10 +286,13 @@ func (sc *StateChecking) assertNotFound() { message, ) if err != nil { + // verify if this is a timeout error if _, ok := err.(*RetryTimeout); !ok { + // this is a runtime error sc.err = err return } + // set timeout error against corresponding status field sc.result.Timeout = err.Error() } sc.result.Phase = phase @@ -335,15 +350,18 @@ func (sc *StateChecking) assertListCountEquals() { message, ) if err != nil { + // verify if this is a timeout error if _, ok := err.(*RetryTimeout); !ok { + // this is a runtime error sc.err = err return } + // set timeout error against corresponding status field sc.result.Timeout = err.Error() } sc.result.Phase = phase sc.result.Message = message - sc.result.Warning = fmt.Sprintf( + sc.result.Verbose = fmt.Sprintf( "Expected count %d got %d", *sc.StateCheck.Count, sc.actualListCount, @@ -377,15 +395,18 @@ func (sc *StateChecking) assertListCountNotEquals() { message, ) if err != nil { + // verify if this this a timeout error if _, ok := err.(*RetryTimeout); !ok { + // this is a runtime error sc.err = err return } + // set timeout error against corresponding status field sc.result.Timeout = err.Error() } sc.result.Phase = phase sc.result.Message = message - sc.result.Warning = fmt.Sprintf( + sc.result.Verbose = fmt.Sprintf( "Expected count %d got %d", *sc.StateCheck.Count, sc.actualListCount, diff --git a/test/e2e/ci.yaml b/test/e2e/ci.yaml index 9db4b56..69b62f9 100644 --- a/test/e2e/ci.yaml +++ b/test/e2e/ci.yaml @@ -1,8 +1,8 @@ # This yaml file demonstrates the use of Kubernetes # and few custom resources to build a custom CI -# infrastructure. In this infrastructure, Kubernetes -# is the CI runner and custom resources are the test -# cases. +# framework. In this framework, Kubernetes is the CI +# runner and custom resources are the test case +# implementations. --- # d-testing is the Kubernetes namespace that can # be used to deploy any test artifacts @@ -43,6 +43,31 @@ spec: served: true storage: true --- +# HTTP custom resource is used to invoke http request +# in a declarative way +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + name: https.dope.metacontroller.io +spec: + group: dope.metacontroller.io + names: + kind: HTTP + listKind: HTTPList + plural: https + shortNames: + - http + singular: http + scope: Namespaced + subresources: + status: {} + version: v1 + versions: + - name: v1 + served: true + storage: true +--- # dope is the Kubernetes service account that has # all priviledges to manage any Kubernetes resources apiVersion: v1 @@ -86,7 +111,7 @@ metadata: name: dope namespace: dope spec: - replicas: 1 + replicas: 1 # higher value helps in executing experiments faster selector: matchLabels: app.mayadata.io/name: dope @@ -99,7 +124,8 @@ spec: serviceAccountName: dope containers: - name: dope - # this may be replaced by a stable release tag + # image may be replaced by a stable release tag + # e.g. mayadataio/dope:latest # # Since dope itself is the target under test, this # ci.yaml uses the master build available in the local @@ -109,6 +135,7 @@ spec: args: - --logtostderr - --run-as-local - - -v=1 + - -v=1 # increasing level will make the ci logs verbose + - --workers-count=20 # higher value helps in executing experiments faster - --discovery-interval=30s - --cache-flush-interval=240s \ No newline at end of file diff --git a/test/e2e/inference.yaml b/test/e2e/inference.yaml index 9bc7d3b..1b0d0de 100644 --- a/test/e2e/inference.yaml +++ b/test/e2e/inference.yaml @@ -1,31 +1,56 @@ # This is an internal recipe used by test suite runner -# to evaluate if desired experiments were successful -# or not +# to evaluate if desired experiments (read Recipes) were +# successful or not +# +# NOTE: +# This Recipe evaluates number of successful, failed & +# errored Recipes apiVersion: dope.metacontroller.io/v1 kind: Recipe metadata: - # should match setup.yaml - name: inference # matches spec.test.inference.experimentName - namespace: d-testing # matches spec.test.inference.experimentNamespace + name: inference + namespace: d-testing labels: d-testing.dope.metacontroller.io/internal: "true" spec: - # delay start of execution by 30 seconds - thinkTimeInSeconds: 30.0001 - # this experiment is enabled only after all other desired experiments - # are executed & are set with status.phase + # This will delay the start of execution of this Recipe based + # on the specified time + # + # NOTE: + # This think time should be sufficient to let all + # experiments (read Recipes used as test cases) get executed + # + # NOTE: + # In other words, we want this Recipe to execute its tasks + # after waiting for the specified time + # + # NOTE: + # This also implies that CI will definitely take this much + # time to decide the result of the CI run + thinkTimeInSeconds: 20.0001 + # This Recipe is eligible to run only when the checks succeed + # + # NOTE: + # In this case, this Recipe will be eligible only after the + # number of Recipes with matching labels equal the given count + # + # NOTE: + # Eligibility check will get triggered after above think time + # has elapsed eligible: checks: - labelSelector: matchLabels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" matchLabelExpressions: - key: recipe.dope.metacontroller.io/phase operator: Exists when: ListCountEquals - count: 6 + count: 10 tasks: - - name: assert-all-tests-are-completed + # Start with asserting the count of Recipes that should + # complete successfully + - name: assert-count-of-tests-that-completed assert: state: kind: Recipe @@ -33,12 +58,14 @@ spec: metadata: namespace: d-testing labels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" recipe.dope.metacontroller.io/phase: Completed stateCheck: stateCheckOperator: ListCountEquals - count: 6 - - name: assert-no-tests-have-failed + # These many Recipes should succeed + count: 8 + # Then assert the count of Recipes that should fail + - name: assert-count-of-tests-that-failed assert: state: kind: Recipe @@ -46,12 +73,14 @@ spec: metadata: namespace: d-testing labels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" recipe.dope.metacontroller.io/phase: Failed stateCheck: stateCheckOperator: ListCountEquals - count: 0 - - name: assert-no-tests-have-errored + # These many Recipes should fail + count: 2 + # Then assert the count of Recipes that should error + - name: assert-count-of-tests-that-errored assert: state: kind: Recipe @@ -59,9 +88,10 @@ spec: metadata: namespace: d-testing labels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" recipe.dope.metacontroller.io/phase: Error stateCheck: stateCheckOperator: ListCountEquals + # These many Recipes should error out count: 0 --- \ No newline at end of file diff --git a/test/e2e/suite.sh b/test/e2e/suite.sh index 1b16a52..01594d5 100755 --- a/test/e2e/suite.sh +++ b/test/e2e/suite.sh @@ -8,12 +8,6 @@ cleanup() { echo "++ Clean up started" echo "--------------------------" - echo -e "\n Display $ctrlbin-0 logs" - k3s kubectl logs -n $ctrlbin $ctrlbin-0 - - echo -e "\n Display status of experiment with name 'inference'" - k3s kubectl -n $ns get $group inference -ojson | jq .status || true - echo -e "\n Uninstall K3s" /usr/local/bin/k3s-uninstall.sh > uninstall-k3s.txt 2>&1 || true @@ -90,12 +84,12 @@ k3s kubectl get node echo -e "\n Apply d-operators based ci to K3s cluster" k3s kubectl apply -f ci.yaml -echo -e "\n Apply ci inference to K3s cluster" -k3s kubectl apply -f inference.yaml - echo -e "\n Apply test experiments to K3s cluster" k3s kubectl apply -f ./../experiments/ +echo -e "\n Apply ci inference to K3s cluster" +k3s kubectl apply -f inference.yaml + echo -e "\n Retry 25 times until inference experiment gets executed" date phase="" @@ -111,6 +105,12 @@ do done date +echo -e "\n Display $ctrlbin-0 logs" +k3s kubectl logs -n $ctrlbin $ctrlbin-0 + +echo -e "\n Display status of experiment with name 'inference'" +k3s kubectl -n $ns get $group inference -ojson | jq .status || true + if [[ "$phase" != "Completed" ]]; then echo "" echo "--------------------------" diff --git a/test/experiments/assert-deprecated-daemonset.yaml b/test/experiments/assert-deprecated-daemonset.yaml index 4d5d999..8fa57bb 100644 --- a/test/experiments/assert-deprecated-daemonset.yaml +++ b/test/experiments/assert-deprecated-daemonset.yaml @@ -4,7 +4,7 @@ metadata: name: assert-absence-of-deprecated-daemonsets namespace: d-testing labels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" spec: tasks: - name: assert-daemonset-with-extensions-v1beta1 diff --git a/test/experiments/assert-github-search-invalid-method-negative.yaml b/test/experiments/assert-github-search-invalid-method-negative.yaml new file mode 100644 index 0000000..1db2c14 --- /dev/null +++ b/test/experiments/assert-github-search-invalid-method-negative.yaml @@ -0,0 +1,39 @@ +apiVersion: dope.metacontroller.io/v1 +kind: Recipe +metadata: + name: assert-github-search-with-invalid-method-neg + namespace: d-testing + labels: + d-testing.dope.metacontroller.io/inference: "true" +spec: + tasks: + # Start by applying this HTTP resource + - name: apply-github-search-with-invalid-method-neg + apply: + state: + apiVersion: dope.metacontroller.io/v1 + kind: HTTP + metadata: + name: github-search-with-invalid-method-neg + namespace: d-testing + spec: + url: https://github.com/search + # This is an invalid value + method: GETT + # Then assert status of this HTTP resource + - name: assert-github-search-with-invalid-method-neg + assert: + state: + apiVersion: dope.metacontroller.io/v1 + kind: HTTP + metadata: + name: github-search-with-invalid-method-neg + namespace: d-testing + status: + # Phase of this HTTP resource will never be Completed + # It will have Error instead + # + # This is a negative test case + # In other words this Recipe will Fail + phase: Completed +--- \ No newline at end of file diff --git a/test/experiments/assert-github-search-invalid-method.yaml b/test/experiments/assert-github-search-invalid-method.yaml new file mode 100644 index 0000000..1db7586 --- /dev/null +++ b/test/experiments/assert-github-search-invalid-method.yaml @@ -0,0 +1,40 @@ +apiVersion: dope.metacontroller.io/v1 +kind: Recipe +metadata: + name: assert-github-search-with-invalid-method + namespace: d-testing + labels: + d-testing.dope.metacontroller.io/inference: "true" +spec: + tasks: + # Start by applying this HTTP custom resource + - name: apply-github-search-with-invalid-method + apply: + state: + apiVersion: dope.metacontroller.io/v1 + kind: HTTP + metadata: + name: github-search-with-invalid-method + namespace: d-testing + spec: + url: https://github.com/search + # Method is set to an invalid value + method: GETT + # Then assert this HTTP resource + - name: assert-github-search-with-invalid-method + assert: + state: + apiVersion: dope.metacontroller.io/v1 + kind: HTTP + metadata: + name: github-search-with-invalid-method + namespace: d-testing + status: + # This HTTP resource is expected to Error + # since its method is set to an invalid value + # + # NOTE: + # The Recipe will complete successfully since + # this assertion is correct + phase: Error +--- \ No newline at end of file diff --git a/test/experiments/assert-github-search-neg.yaml b/test/experiments/assert-github-search-neg.yaml new file mode 100644 index 0000000..98466e5 --- /dev/null +++ b/test/experiments/assert-github-search-neg.yaml @@ -0,0 +1,47 @@ +apiVersion: dope.metacontroller.io/v1 +kind: Recipe +metadata: + name: assert-github-search-neg + namespace: d-testing + labels: + d-testing.dope.metacontroller.io/inference: "true" +spec: + tasks: + # Start by applying this HTTP custom resource + # + # NOTE: + # Once this resource is applied against the Kubernetes + # cluster, it starts invoking the http request specified + # in its spec. Resulting output is set against its status. + - name: apply-github-search-neg + apply: + state: + apiVersion: dope.metacontroller.io/v1 + kind: HTTP + metadata: + name: github-search-neg + namespace: d-testing + spec: + url: https://github.com/search + # This method is invalid + method: GETT + # Then assert this HTTP resource to have completed + # successfully + - name: assert-github-search-neg + assert: + state: + apiVersion: dope.metacontroller.io/v1 + kind: HTTP + metadata: + name: github-search-neg + namespace: d-testing + status: + # If HTTP resource completes successfully then + # phase should be set to Completed + # + # NOTE: + # However, this HTTP resource will not succeed due + # to invalid http method. Hence, this assertion must + # fail resulting in failure of this Recipe. + phase: Online +--- \ No newline at end of file diff --git a/test/experiments/assert-github-search.yaml b/test/experiments/assert-github-search.yaml new file mode 100644 index 0000000..3a7bcb0 --- /dev/null +++ b/test/experiments/assert-github-search.yaml @@ -0,0 +1,41 @@ +apiVersion: dope.metacontroller.io/v1 +kind: Recipe +metadata: + name: assert-github-search + namespace: d-testing + labels: + d-testing.dope.metacontroller.io/inference: "true" +spec: + tasks: + # Start by applying this HTTP custom resource + # + # NOTE: + # Once this resource is applied against the Kubernetes + # cluster, it starts invoking the http request specified + # in its spec. Resulting output is set against its status. + - name: apply-github-search + apply: + state: + apiVersion: dope.metacontroller.io/v1 + kind: HTTP + metadata: + name: github-search + namespace: d-testing + spec: + url: https://github.com/search + method: GET + # Then assert this HTTP resource to have completed + # successfully + - name: assert-github-search + assert: + state: + apiVersion: dope.metacontroller.io/v1 + kind: HTTP + metadata: + name: github-search + namespace: d-testing + status: + # If HTTP resource completes successfully then + # phase should be set to Completed + phase: Online +--- \ No newline at end of file diff --git a/test/experiments/assert-job-teardown.yaml b/test/experiments/assert-job-teardown.yaml index abc15ee..9b1e435 100644 --- a/test/experiments/assert-job-teardown.yaml +++ b/test/experiments/assert-job-teardown.yaml @@ -4,7 +4,7 @@ metadata: name: assert-recipe-teardown namespace: d-testing labels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" spec: tasks: - name: create-a-recipe-with-teardown-enabled diff --git a/test/experiments/assert-lock-persists-for-job-that-runs-once.yaml b/test/experiments/assert-lock-persists-for-job-that-runs-once.yaml index 00d2b9e..5d802a4 100644 --- a/test/experiments/assert-lock-persists-for-job-that-runs-once.yaml +++ b/test/experiments/assert-lock-persists-for-job-that-runs-once.yaml @@ -4,7 +4,7 @@ metadata: name: assert-lock-persists-for-recipe-that-runs-once namespace: d-testing labels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" spec: tasks: - name: create-a-recipe-that-runs-only-once diff --git a/test/experiments/create-assert-fifty-configmaps-elapsedtime.yaml b/test/experiments/create-assert-fifty-configmaps-elapsedtime.yaml index cf10d86..fde9f4e 100644 --- a/test/experiments/create-assert-fifty-configmaps-elapsedtime.yaml +++ b/test/experiments/create-assert-fifty-configmaps-elapsedtime.yaml @@ -1,52 +1,101 @@ apiVersion: dope.metacontroller.io/v1 kind: Recipe metadata: - name: create-fifty-configmaps + name: create-fifty-configmaps-in-time namespace: d-testing + labels: + i-create-50-configs: "true" + i-am-tested-if-creation-of-configs-happen-in-time: "true" spec: tasks: + # This task creates 50 config maps + # The names of these config maps are suffixed + # with numbers + # + # NOTE: + # Following config maps will get created: + # - create-cm-in-time-0, + # - create-cm-in-time-1, + # ... + # - create-cm-in-time-48, + # - create-cm-in-time-49 + # + # NOTE: + # Task will error out if any of these config maps + # already exist in the cluster - name: create-fifty-configmaps create: state: kind: ConfigMap apiVersion: v1 metadata: - name: create-cm-elapsed-time + name: create-cm-in-time namespace: d-testing replicas: 50 --- apiVersion: dope.metacontroller.io/v1 kind: Recipe metadata: - name: assert-creation-of-fifty-configmaps-elapsed-time + name: assert-creation-of-fifty-configmaps-in-time namespace: d-testing labels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" spec: + # This will delay the start of execution of this Recipe based + # on the specified time + # + # NOTE: + # This think time should be sufficient to let all + # experiments (read Recipes used as test cases) get executed + # + # NOTE: + # In other words, we want this Recipe to execute its tasks + # after waiting for the specified time + thinkTimeInSeconds: 10.0001 + # This Recipe is eligible to run only when the checks succeed + # + # NOTE: + # In this case, this Recipe will be eligible only after the + # number of Recipes with matching labels equal the given count + # + # NOTE: + # Eligibility check will get triggered after above think time + # has elapsed + eligible: + checks: + - labelSelector: + matchLabels: + i-create-50-configs: "true" + i-am-tested-if-creation-of-configs-happen-in-time: "true" + matchLabelExpressions: + - key: recipe.dope.metacontroller.io/phase + operator: Exists + when: ListCountEquals + count: 1 tasks: - - name: assert-create-fifty-configmaps-recipe-completed + - name: assert-completion-of-fifty-configmaps-recipe assert: state: kind: Recipe apiVersion: dope.metacontroller.io/v1 metadata: - name: create-fifty-configmaps + name: create-fifty-configmaps-in-time namespace: d-testing status: phase: Completed - - name: assert-creation-of-fifty-configmaps-within-time + - name: assert-creation-of-fifty-configmaps-in-time assert: state: kind: Recipe apiVersion: dope.metacontroller.io/v1 metadata: - name: create-fifty-configmaps + name: create-fifty-configmaps-in-time namespace: d-testing pathCheck: path: status.taskListStatus.recipe-elapsed-time.elapsedTimeInSeconds pathCheckOperator: LTE - # assert if the entire experiment of creating 50 configmaps - # completes in around 20 seconds + # assert if the recipe to create 50 configmaps + # completes within 20 seconds value: 20.0001 dataType: float64 --- \ No newline at end of file diff --git a/test/experiments/create-assert-fifty-configmaps.yaml b/test/experiments/create-assert-fifty-configmaps.yaml index 72156a9..f47650d 100644 --- a/test/experiments/create-assert-fifty-configmaps.yaml +++ b/test/experiments/create-assert-fifty-configmaps.yaml @@ -1,10 +1,10 @@ apiVersion: dope.metacontroller.io/v1 kind: Recipe metadata: - name: create-assert-fifty-configmaps + name: create-and-assert-fifty-configmaps namespace: d-testing labels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" spec: tasks: - name: create-fifty-configmaps diff --git a/test/experiments/crud-ops-on-pod.yaml b/test/experiments/crud-ops-on-pod.yaml index 29bf9cd..d95610f 100644 --- a/test/experiments/crud-ops-on-pod.yaml +++ b/test/experiments/crud-ops-on-pod.yaml @@ -4,7 +4,7 @@ metadata: name: crud-ops-on-pod namespace: d-testing labels: - d-testing.dope.metacontroller.io/enabled: "true" + d-testing.dope.metacontroller.io/inference: "true" spec: tasks: - name: apply-a-namespace diff --git a/types/http/types.go b/types/http/types.go index 18d3b3f..cb774e1 100644 --- a/types/http/types.go +++ b/types/http/types.go @@ -35,10 +35,10 @@ const ( const ( // POST based http request - POST string = "post" + POST string = "POST" // GET based http request - GET string = "get" + GET string = "GET" ) // When is a typed definition to determine if HTTP custom resource @@ -47,14 +47,14 @@ type When string const ( // Always flags the HTTP custom resource to be reconciled always - Always When = "always" + Always When = "Always" // Never disables the HTTP custom resource from being reconciled - Never When = "never" + Never When = "Never" // Once flags the HTTP custom resource to be reconciled only once // This is useful to invoke http requests to create or delete entity - Once When = "once" + Once When = "Once" ) // Enabled determines if HTTP custom resource is enabled to be diff --git a/types/recipe/state_check.go b/types/recipe/state_check.go index 0e43c75..5414866 100644 --- a/types/recipe/state_check.go +++ b/types/recipe/state_check.go @@ -84,9 +84,18 @@ func (phase StateCheckResultPhase) ToAssertResultPhase() AssertStatusPhase { // StateCheckResult holds the result of StateCheck operation type StateCheckResult struct { - Phase StateCheckResultPhase `json:"phase"` - Message string `json:"message,omitempty"` - Verbose string `json:"verbose,omitempty"` - Warning string `json:"warning,omitempty"` - Timeout string `json:"timeout,omitempty"` + // status as a pre-defined key word + Phase StateCheckResultPhase `json:"phase"` + + // short message + Message string `json:"message,omitempty"` + + // detailed message + Verbose string `json:"verbose,omitempty"` + + // warning details + Warning string `json:"warning,omitempty"` + + // timeout details + Timeout string `json:"timeout,omitempty"` }