From 1b6263b77d5f76bdb54b23fb7963908273b4ded1 Mon Sep 17 00:00:00 2001 From: Soham Ratnaparkhi Date: Wed, 16 Aug 2023 11:20:58 +0530 Subject: [PATCH] Seperate experiment runs from experiments in chaos_center (#4053) * feat: seperate exp_run and exp grapql schemas Signed-off-by: SohamRatnaparkhi * feat: seperate exp_run and exp graphql resolvers Signed-off-by: SohamRatnaparkhi * feat: add generated types Signed-off-by: SohamRatnaparkhi * feat: add types for exp_run Signed-off-by: SohamRatnaparkhi * feat: add exp_run services Signed-off-by: SohamRatnaparkhi * feat: add exp_run handler Signed-off-by: SohamRatnaparkhi * refractor: remove functions in experiments service related to runs Signed-off-by: SohamRatnaparkhi * refractor: remove functions in experiments handler related to runs Signed-off-by: SohamRatnaparkhi * fix: issues due to handler and resolver types Signed-off-by: SohamRatnaparkhi * fix: breaking changes in generated and service files Signed-off-by: SohamRatnaparkhi * fix: bugs in experiment handlers and roles Signed-off-by: SohamRatnaparkhi * fix: add exp run operator and update handler Signed-off-by: SohamRatnaparkhi * fix: update infra opr to return exp-run Signed-off-by: SohamRatnaparkhi * refractor: package name chaos_exp_run Signed-off-by: SohamRatnaparkhi --------- Signed-off-by: SohamRatnaparkhi --- .../shared/chaos_experiment.graphqls | 29 - .../shared/chaos_experiment_run.graphqls | 35 + .../graph/chaos_experiment.resolvers.go | 106 +- .../graph/chaos_experiment_run.resolvers.go | 117 ++ .../server/graph/generated/generated.go | 198 +-- chaoscenter/graphql/server/graph/resolver.go | 15 +- .../graphql/server/pkg/authorization/roles.go | 2 + .../pkg/chaos_experiment/handler/handler.go | 1217 +--------------- .../server/pkg/chaos_experiment/service.go | 98 +- .../choas_experiment_run/handler/handler.go | 1223 +++++++++++++++++ .../pkg/choas_experiment_run/service.go | 133 ++ .../server/pkg/choas_experiment_run/types.go | 130 ++ .../chaos_experiment_run/operations.go | 28 +- .../mongodb/chaos_experiment_run/schema.go | 24 + 14 files changed, 1831 insertions(+), 1524 deletions(-) create mode 100644 chaoscenter/graphql/definitions/shared/chaos_experiment_run.graphqls create mode 100644 chaoscenter/graphql/server/graph/chaos_experiment_run.resolvers.go create mode 100644 chaoscenter/graphql/server/pkg/choas_experiment_run/handler/handler.go create mode 100644 chaoscenter/graphql/server/pkg/choas_experiment_run/service.go create mode 100644 chaoscenter/graphql/server/pkg/choas_experiment_run/types.go diff --git a/chaoscenter/graphql/definitions/shared/chaos_experiment.graphqls b/chaoscenter/graphql/definitions/shared/chaos_experiment.graphqls index 02cf6135f59..b0794034a0d 100644 --- a/chaoscenter/graphql/definitions/shared/chaos_experiment.graphqls +++ b/chaoscenter/graphql/definitions/shared/chaos_experiment.graphqls @@ -702,18 +702,7 @@ type GetExperimentStatsResponse { } extend type Query { - """ - Returns experiment run based on experiment run ID - """ - getExperimentRun(projectID: ID!, experimentRunID: String!): ExperimentRun! - """ - Returns the list of experiment run based on various filter parameters - """ - listExperimentRun( - projectID: ID! - request: ListExperimentRunRequest! - ): ListExperimentRunResponse! """ Returns the experiment based on experiment ID @@ -728,10 +717,6 @@ extend type Query { request: ListExperimentRequest! ): ListExperimentResponse! - """ - Query to get experiment run stats - """ - getExperimentRunStats(projectID: ID!): GetExperimentRunStatsResponse! """ Query to get experiment stats """ @@ -755,14 +740,6 @@ extend type Mutation { projectID: ID! ): String! - """ - Run the chaos experiment (used by frontend) - """ - runChaosExperiment( - experimentID: String! - projectID: ID! - ): RunChaosExperimentResponse! - """ Updates the experiment """ @@ -779,10 +756,4 @@ extend type Mutation { experimentRunID: String projectID: ID! ): Boolean! - - """ - Creates a new experiment run and sends it to subscriber - """ - # authorized directive not required - chaosExperimentRun(request: ExperimentRunRequest!): String! } diff --git a/chaoscenter/graphql/definitions/shared/chaos_experiment_run.graphqls b/chaoscenter/graphql/definitions/shared/chaos_experiment_run.graphqls new file mode 100644 index 00000000000..46f859e3b37 --- /dev/null +++ b/chaoscenter/graphql/definitions/shared/chaos_experiment_run.graphqls @@ -0,0 +1,35 @@ +extend type Query { + """ + Returns experiment run based on experiment run ID + """ + getExperimentRun(projectID: ID!, experimentRunID: String!): ExperimentRun! + + """ + Returns the list of experiment run based on various filter parameters + """ + listExperimentRun( + projectID: ID! + request: ListExperimentRunRequest! + ): ListExperimentRunResponse! + + """ + Query to get experiment run stats + """ + getExperimentRunStats(projectID: ID!): GetExperimentRunStatsResponse! +} + +extend type Mutation { + """ + Creates a new experiment run and sends it to subscriber + """ + # authorized directive not required + chaosExperimentRun(request: ExperimentRunRequest!): String! + + """ + Run the chaos experiment (used by frontend) + """ + runChaosExperiment( + experimentID: String! + projectID: ID! + ): RunChaosExperimentResponse! +} \ No newline at end of file diff --git a/chaoscenter/graphql/server/graph/chaos_experiment.resolvers.go b/chaoscenter/graphql/server/graph/chaos_experiment.resolvers.go index e9bcd4465e4..a764d51bb26 100644 --- a/chaoscenter/graphql/server/graph/chaos_experiment.resolvers.go +++ b/chaoscenter/graphql/server/graph/chaos_experiment.resolvers.go @@ -46,7 +46,7 @@ func (r *mutationResolver) CreateChaosExperiment(ctx context.Context, request mo if experiment.CronSyntax != "" { - if err = r.chaosExperimentHandler.RunCronExperiment(ctx, projectID, experiment, data_store.Store); err != nil { + if err = r.chaosExperimentRunHandler.RunCronExperiment(ctx, projectID, experiment, data_store.Store); err != nil { logrus.WithFields(logFields).Error(err) return nil, err } @@ -54,7 +54,7 @@ func (r *mutationResolver) CreateChaosExperiment(ctx context.Context, request mo return uiResponse, nil } - _, err = r.chaosExperimentHandler.RunChaosWorkFlow(ctx, projectID, experiment, data_store.Store) + _, err = r.chaosExperimentRunHandler.RunChaosWorkFlow(ctx, projectID, experiment, data_store.Store) if err != nil { logrus.WithFields(logFields).Error(err) return nil, err @@ -89,41 +89,6 @@ func (r *mutationResolver) SaveChaosExperiment(ctx context.Context, request mode return uiResponse, nil } -func (r *mutationResolver) RunChaosExperiment(ctx context.Context, experimentID string, projectID string) (*model.RunChaosExperimentResponse, error) { - logFields := logrus.Fields{ - "projectId": projectID, - "chaosExperimentId": experimentID, - } - - logrus.WithFields(logFields).Info("request received to run chaos experiment") - err := authorization.ValidateRole(ctx, projectID, - authorization.MutationRbacRules[authorization.CreateChaosWorkFlow], - model.InvitationAccepted.String()) - if err != nil { - return nil, err - } - - query := bson.D{ - {"experiment_id", experimentID}, - {"is_removed", false}, - } - - experiment, err := r.chaosExperimentHandler.GetDBExperiment(query) - if err != nil { - return nil, errors.New("could not get experiment run, error: " + err.Error()) - } - - var uiResponse *model.RunChaosExperimentResponse - - uiResponse, err = r.chaosExperimentHandler.RunChaosWorkFlow(ctx, projectID, experiment, data_store.Store) - if err != nil { - logrus.WithFields(logFields).Error(err) - return nil, err - } - - return &model.RunChaosExperimentResponse{NotifyID: uiResponse.NotifyID}, err -} - func (r *mutationResolver) UpdateChaosExperiment(ctx context.Context, request *model.ChaosExperimentRequest, projectID string) (*model.ChaosExperimentResponse, error) { logFields := logrus.Fields{ "projectId": projectID, @@ -170,53 +135,6 @@ func (r *mutationResolver) DeleteChaosExperiment(ctx context.Context, experiment return uiResponse, err } -func (r *mutationResolver) ChaosExperimentRun(ctx context.Context, request model.ExperimentRunRequest) (string, error) { - return r.chaosExperimentHandler.ChaosExperimentRunEvent(request) -} - -func (r *queryResolver) GetExperimentRun(ctx context.Context, projectID string, experimentRunID string) (*model.ExperimentRun, error) { - logFields := logrus.Fields{ - "projectId": projectID, - "chaosExperimentRunId": experimentRunID, - } - logrus.WithFields(logFields).Info("request received to fetch chaos experiment run") - err := authorization.ValidateRole(ctx, projectID, - authorization.MutationRbacRules[authorization.ListWorkflowRuns], - model.InvitationAccepted.String()) - if err != nil { - return nil, err - } - - expRunResponse, err := r.chaosExperimentHandler.GetExperimentRun(ctx, projectID, experimentRunID) - if err != nil { - logrus.WithFields(logFields).Error(err) - return nil, err - } - return expRunResponse, err -} - -func (r *queryResolver) ListExperimentRun(ctx context.Context, projectID string, request model.ListExperimentRunRequest) (*model.ListExperimentRunResponse, error) { - logFields := logrus.Fields{ - "projectId": projectID, - "chaosExperimentIds": request.ExperimentIDs, - "chaosExperimentRunIds": request.ExperimentRunIDs, - } - logrus.WithFields(logFields).Info("request received to list chaos experiment run") - - err := authorization.ValidateRole(ctx, projectID, - authorization.MutationRbacRules[authorization.ListWorkflowRuns], - model.InvitationAccepted.String()) - if err != nil { - return nil, err - } - uiResponse, err := r.chaosExperimentHandler.ListExperimentRun(projectID, request) - if err != nil { - logrus.WithFields(logFields).Error(err) - return nil, err - } - return uiResponse, err -} - func (r *queryResolver) GetExperiment(ctx context.Context, projectID string, experimentID string) (*model.GetExperimentResponse, error) { logFields := logrus.Fields{ "projectId": projectID, @@ -259,26 +177,6 @@ func (r *queryResolver) ListExperiment(ctx context.Context, projectID string, re return uiResponse, err } -func (r *queryResolver) GetExperimentRunStats(ctx context.Context, projectID string) (*model.GetExperimentRunStatsResponse, error) { - logFields := logrus.Fields{ - "projectId": projectID, - } - logrus.WithFields(logFields).Info("request received to get chaos experiment run stats") - err := authorization.ValidateRole(ctx, projectID, - authorization.MutationRbacRules[authorization.ListWorkflowRuns], - model.InvitationAccepted.String()) - if err != nil { - return nil, err - } - - uiResponse, err := r.chaosExperimentHandler.GetExperimentRunStats(ctx, projectID) - if err != nil { - logrus.WithFields(logFields).Error(err) - return nil, err - } - return uiResponse, err -} - func (r *queryResolver) GetExperimentStats(ctx context.Context, projectID string) (*model.GetExperimentStatsResponse, error) { logFields := logrus.Fields{ "projectId": projectID, diff --git a/chaoscenter/graphql/server/graph/chaos_experiment_run.resolvers.go b/chaoscenter/graphql/server/graph/chaos_experiment_run.resolvers.go new file mode 100644 index 00000000000..eb429b2b569 --- /dev/null +++ b/chaoscenter/graphql/server/graph/chaos_experiment_run.resolvers.go @@ -0,0 +1,117 @@ +package graph + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. + +import ( + "context" + "errors" + + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/graph/model" + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/authorization" + data_store "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/data-store" + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" +) + +func (r *mutationResolver) ChaosExperimentRun(ctx context.Context, request model.ExperimentRunRequest) (string, error) { + return r.chaosExperimentRunHandler.ChaosExperimentRunEvent(request) +} + +func (r *mutationResolver) RunChaosExperiment(ctx context.Context, experimentID string, projectID string) (*model.RunChaosExperimentResponse, error) { + logFields := logrus.Fields{ + "projectId": projectID, + "chaosExperimentId": experimentID, + } + + logrus.WithFields(logFields).Info("request received to run chaos experiment") + err := authorization.ValidateRole(ctx, projectID, + authorization.MutationRbacRules[authorization.CreateChaosWorkFlow], + model.InvitationAccepted.String()) + if err != nil { + return nil, err + } + + query := bson.D{ + {"experiment_id", experimentID}, + {"is_removed", false}, + } + + experiment, err := r.chaosExperimentHandler.GetDBExperiment(query) + if err != nil { + return nil, errors.New("could not get experiment run, error: " + err.Error()) + } + + var uiResponse *model.RunChaosExperimentResponse + + uiResponse, err = r.chaosExperimentRunHandler.RunChaosWorkFlow(ctx, projectID, experiment, data_store.Store) + if err != nil { + logrus.WithFields(logFields).Error(err) + return nil, err + } + + return &model.RunChaosExperimentResponse{NotifyID: uiResponse.NotifyID}, err +} + +func (r *queryResolver) GetExperimentRun(ctx context.Context, projectID string, experimentRunID string) (*model.ExperimentRun, error) { + logFields := logrus.Fields{ + "projectId": projectID, + "chaosExperimentRunId": experimentRunID, + } + logrus.WithFields(logFields).Info("request received to fetch chaos experiment run") + err := authorization.ValidateRole(ctx, projectID, + authorization.MutationRbacRules[authorization.GetWorkflowRun], + model.InvitationAccepted.String()) + if err != nil { + return nil, err + } + + expRunResponse, err := r.chaosExperimentRunHandler.GetExperimentRun(ctx, projectID, experimentRunID) + if err != nil { + logrus.WithFields(logFields).Error(err) + return nil, err + } + return expRunResponse, err +} + +func (r *queryResolver) ListExperimentRun(ctx context.Context, projectID string, request model.ListExperimentRunRequest) (*model.ListExperimentRunResponse, error) { + logFields := logrus.Fields{ + "projectId": projectID, + "chaosExperimentIds": request.ExperimentIDs, + "chaosExperimentRunIds": request.ExperimentRunIDs, + } + logrus.WithFields(logFields).Info("request received to list chaos experiment run") + + err := authorization.ValidateRole(ctx, projectID, + authorization.MutationRbacRules[authorization.ListWorkflowRuns], + model.InvitationAccepted.String()) + if err != nil { + return nil, err + } + uiResponse, err := r.chaosExperimentRunHandler.ListExperimentRun(projectID, request) + if err != nil { + logrus.WithFields(logFields).Error(err) + return nil, err + } + return uiResponse, err +} + +func (r *queryResolver) GetExperimentRunStats(ctx context.Context, projectID string) (*model.GetExperimentRunStatsResponse, error) { + logFields := logrus.Fields{ + "projectId": projectID, + } + logrus.WithFields(logFields).Info("request received to get chaos experiment run stats") + err := authorization.ValidateRole(ctx, projectID, + authorization.MutationRbacRules[authorization.ListWorkflowRuns], + model.InvitationAccepted.String()) + if err != nil { + return nil, err + } + + uiResponse, err := r.chaosExperimentRunHandler.GetExperimentRunStats(ctx, projectID) + if err != nil { + logrus.WithFields(logFields).Error(err) + return nil, err + } + return uiResponse, err +} diff --git a/chaoscenter/graphql/server/graph/generated/generated.go b/chaoscenter/graphql/server/graph/generated/generated.go index a553fa10916..63f7d5f635e 100644 --- a/chaoscenter/graphql/server/graph/generated/generated.go +++ b/chaoscenter/graphql/server/graph/generated/generated.go @@ -550,10 +550,10 @@ type ComplexityRoot struct { type MutationResolver interface { CreateChaosExperiment(ctx context.Context, request model.ChaosExperimentRequest, projectID string) (*model.ChaosExperimentResponse, error) SaveChaosExperiment(ctx context.Context, request model.SaveChaosExperimentRequest, projectID string) (string, error) - RunChaosExperiment(ctx context.Context, experimentID string, projectID string) (*model.RunChaosExperimentResponse, error) UpdateChaosExperiment(ctx context.Context, request *model.ChaosExperimentRequest, projectID string) (*model.ChaosExperimentResponse, error) DeleteChaosExperiment(ctx context.Context, experimentID string, experimentRunID *string, projectID string) (bool, error) ChaosExperimentRun(ctx context.Context, request model.ExperimentRunRequest) (string, error) + RunChaosExperiment(ctx context.Context, experimentID string, projectID string) (*model.RunChaosExperimentResponse, error) RegisterInfra(ctx context.Context, projectID string, request model.RegisterInfraRequest) (*model.RegisterInfraResponse, error) ConfirmInfraRegistration(ctx context.Context, request model.InfraIdentity) (*model.ConfirmInfraRegistrationResponse, error) DeleteInfra(ctx context.Context, projectID string, infraID string) (string, error) @@ -579,12 +579,12 @@ type MutationResolver interface { DeleteImageRegistry(ctx context.Context, imageRegistryID string, projectID string) (string, error) } type QueryResolver interface { - GetExperimentRun(ctx context.Context, projectID string, experimentRunID string) (*model.ExperimentRun, error) - ListExperimentRun(ctx context.Context, projectID string, request model.ListExperimentRunRequest) (*model.ListExperimentRunResponse, error) GetExperiment(ctx context.Context, projectID string, experimentID string) (*model.GetExperimentResponse, error) ListExperiment(ctx context.Context, projectID string, request model.ListExperimentRequest) (*model.ListExperimentResponse, error) - GetExperimentRunStats(ctx context.Context, projectID string) (*model.GetExperimentRunStatsResponse, error) GetExperimentStats(ctx context.Context, projectID string) (*model.GetExperimentStatsResponse, error) + GetExperimentRun(ctx context.Context, projectID string, experimentRunID string) (*model.ExperimentRun, error) + ListExperimentRun(ctx context.Context, projectID string, request model.ListExperimentRunRequest) (*model.ListExperimentRunResponse, error) + GetExperimentRunStats(ctx context.Context, projectID string) (*model.GetExperimentRunStatsResponse, error) GetInfra(ctx context.Context, projectID string, infraID string) (*model.Infra, error) ListInfras(ctx context.Context, projectID string, request *model.ListInfraRequest) (*model.ListInfraResponse, error) GetInfraDetails(ctx context.Context, infraID string, projectID string) (*model.Infra, error) @@ -4072,18 +4072,7 @@ type GetExperimentStatsResponse { } extend type Query { - """ - Returns experiment run based on experiment run ID - """ - getExperimentRun(projectID: ID!, experimentRunID: String!): ExperimentRun! - """ - Returns the list of experiment run based on various filter parameters - """ - listExperimentRun( - projectID: ID! - request: ListExperimentRunRequest! - ): ListExperimentRunResponse! """ Returns the experiment based on experiment ID @@ -4098,10 +4087,6 @@ extend type Query { request: ListExperimentRequest! ): ListExperimentResponse! - """ - Query to get experiment run stats - """ - getExperimentRunStats(projectID: ID!): GetExperimentRunStatsResponse! """ Query to get experiment stats """ @@ -4125,14 +4110,6 @@ extend type Mutation { projectID: ID! ): String! - """ - Run the chaos experiment (used by frontend) - """ - runChaosExperiment( - experimentID: String! - projectID: ID! - ): RunChaosExperimentResponse! - """ Updates the experiment """ @@ -4149,14 +4126,43 @@ extend type Mutation { experimentRunID: String projectID: ID! ): Boolean! +} +`, BuiltIn: false}, + &ast.Source{Name: "../definitions/shared/chaos_experiment_run.graphqls", Input: `extend type Query { + """ + Returns experiment run based on experiment run ID + """ + getExperimentRun(projectID: ID!, experimentRunID: String!): ExperimentRun! + """ + Returns the list of experiment run based on various filter parameters + """ + listExperimentRun( + projectID: ID! + request: ListExperimentRunRequest! + ): ListExperimentRunResponse! + + """ + Query to get experiment run stats + """ + getExperimentRunStats(projectID: ID!): GetExperimentRunStatsResponse! +} + +extend type Mutation { """ Creates a new experiment run and sends it to subscriber """ # authorized directive not required chaosExperimentRun(request: ExperimentRunRequest!): String! -} -`, BuiltIn: false}, + + """ + Run the chaos experiment (used by frontend) + """ + runChaosExperiment( + experimentID: String! + projectID: ID! + ): RunChaosExperimentResponse! +}`, BuiltIn: false}, &ast.Source{Name: "../definitions/shared/chaos_infrastructure.graphqls", Input: `directive @authorized on FIELD_DEFINITION """ @@ -14789,7 +14795,7 @@ func (ec *executionContext) _Mutation_saveChaosExperiment(ctx context.Context, f return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Mutation_runChaosExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Mutation_updateChaosExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -14805,7 +14811,7 @@ func (ec *executionContext) _Mutation_runChaosExperiment(ctx context.Context, fi ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Mutation_runChaosExperiment_args(ctx, rawArgs) + args, err := ec.field_Mutation_updateChaosExperiment_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -14813,7 +14819,7 @@ func (ec *executionContext) _Mutation_runChaosExperiment(ctx context.Context, fi fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().RunChaosExperiment(rctx, args["experimentID"].(string), args["projectID"].(string)) + return ec.resolvers.Mutation().UpdateChaosExperiment(rctx, args["request"].(*model.ChaosExperimentRequest), args["projectID"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -14825,12 +14831,12 @@ func (ec *executionContext) _Mutation_runChaosExperiment(ctx context.Context, fi } return graphql.Null } - res := resTmp.(*model.RunChaosExperimentResponse) + res := resTmp.(*model.ChaosExperimentResponse) fc.Result = res - return ec.marshalNRunChaosExperimentResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐRunChaosExperimentResponse(ctx, field.Selections, res) + return ec.marshalNChaosExperimentResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐChaosExperimentResponse(ctx, field.Selections, res) } -func (ec *executionContext) _Mutation_updateChaosExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Mutation_deleteChaosExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -14846,7 +14852,7 @@ func (ec *executionContext) _Mutation_updateChaosExperiment(ctx context.Context, ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Mutation_updateChaosExperiment_args(ctx, rawArgs) + args, err := ec.field_Mutation_deleteChaosExperiment_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -14854,7 +14860,7 @@ func (ec *executionContext) _Mutation_updateChaosExperiment(ctx context.Context, fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().UpdateChaosExperiment(rctx, args["request"].(*model.ChaosExperimentRequest), args["projectID"].(string)) + return ec.resolvers.Mutation().DeleteChaosExperiment(rctx, args["experimentID"].(string), args["experimentRunID"].(*string), args["projectID"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -14866,12 +14872,12 @@ func (ec *executionContext) _Mutation_updateChaosExperiment(ctx context.Context, } return graphql.Null } - res := resTmp.(*model.ChaosExperimentResponse) + res := resTmp.(bool) fc.Result = res - return ec.marshalNChaosExperimentResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐChaosExperimentResponse(ctx, field.Selections, res) + return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _Mutation_deleteChaosExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Mutation_chaosExperimentRun(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -14887,7 +14893,7 @@ func (ec *executionContext) _Mutation_deleteChaosExperiment(ctx context.Context, ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Mutation_deleteChaosExperiment_args(ctx, rawArgs) + args, err := ec.field_Mutation_chaosExperimentRun_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -14895,7 +14901,7 @@ func (ec *executionContext) _Mutation_deleteChaosExperiment(ctx context.Context, fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().DeleteChaosExperiment(rctx, args["experimentID"].(string), args["experimentRunID"].(*string), args["projectID"].(string)) + return ec.resolvers.Mutation().ChaosExperimentRun(rctx, args["request"].(model.ExperimentRunRequest)) }) if err != nil { ec.Error(ctx, err) @@ -14907,12 +14913,12 @@ func (ec *executionContext) _Mutation_deleteChaosExperiment(ctx context.Context, } return graphql.Null } - res := resTmp.(bool) + res := resTmp.(string) fc.Result = res - return ec.marshalNBoolean2bool(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Mutation_chaosExperimentRun(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Mutation_runChaosExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -14928,7 +14934,7 @@ func (ec *executionContext) _Mutation_chaosExperimentRun(ctx context.Context, fi ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Mutation_chaosExperimentRun_args(ctx, rawArgs) + args, err := ec.field_Mutation_runChaosExperiment_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -14936,7 +14942,7 @@ func (ec *executionContext) _Mutation_chaosExperimentRun(ctx context.Context, fi fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().ChaosExperimentRun(rctx, args["request"].(model.ExperimentRunRequest)) + return ec.resolvers.Mutation().RunChaosExperiment(rctx, args["experimentID"].(string), args["projectID"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -14948,9 +14954,9 @@ func (ec *executionContext) _Mutation_chaosExperimentRun(ctx context.Context, fi } return graphql.Null } - res := resTmp.(string) + res := resTmp.(*model.RunChaosExperimentResponse) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalNRunChaosExperimentResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐRunChaosExperimentResponse(ctx, field.Selections, res) } func (ec *executionContext) _Mutation_registerInfra(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -16631,7 +16637,7 @@ func (ec *executionContext) _Provider_name(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _Query_getExperimentRun(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_getExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -16647,7 +16653,7 @@ func (ec *executionContext) _Query_getExperimentRun(ctx context.Context, field g ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_getExperimentRun_args(ctx, rawArgs) + args, err := ec.field_Query_getExperiment_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -16655,7 +16661,7 @@ func (ec *executionContext) _Query_getExperimentRun(ctx context.Context, field g fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().GetExperimentRun(rctx, args["projectID"].(string), args["experimentRunID"].(string)) + return ec.resolvers.Query().GetExperiment(rctx, args["projectID"].(string), args["experimentID"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -16667,12 +16673,12 @@ func (ec *executionContext) _Query_getExperimentRun(ctx context.Context, field g } return graphql.Null } - res := resTmp.(*model.ExperimentRun) + res := resTmp.(*model.GetExperimentResponse) fc.Result = res - return ec.marshalNExperimentRun2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐExperimentRun(ctx, field.Selections, res) + return ec.marshalNGetExperimentResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐGetExperimentResponse(ctx, field.Selections, res) } -func (ec *executionContext) _Query_listExperimentRun(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_listExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -16688,7 +16694,7 @@ func (ec *executionContext) _Query_listExperimentRun(ctx context.Context, field ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_listExperimentRun_args(ctx, rawArgs) + args, err := ec.field_Query_listExperiment_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -16696,7 +16702,7 @@ func (ec *executionContext) _Query_listExperimentRun(ctx context.Context, field fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().ListExperimentRun(rctx, args["projectID"].(string), args["request"].(model.ListExperimentRunRequest)) + return ec.resolvers.Query().ListExperiment(rctx, args["projectID"].(string), args["request"].(model.ListExperimentRequest)) }) if err != nil { ec.Error(ctx, err) @@ -16708,12 +16714,12 @@ func (ec *executionContext) _Query_listExperimentRun(ctx context.Context, field } return graphql.Null } - res := resTmp.(*model.ListExperimentRunResponse) + res := resTmp.(*model.ListExperimentResponse) fc.Result = res - return ec.marshalNListExperimentRunResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐListExperimentRunResponse(ctx, field.Selections, res) + return ec.marshalNListExperimentResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐListExperimentResponse(ctx, field.Selections, res) } -func (ec *executionContext) _Query_getExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_getExperimentStats(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -16729,7 +16735,7 @@ func (ec *executionContext) _Query_getExperiment(ctx context.Context, field grap ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_getExperiment_args(ctx, rawArgs) + args, err := ec.field_Query_getExperimentStats_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -16737,7 +16743,7 @@ func (ec *executionContext) _Query_getExperiment(ctx context.Context, field grap fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().GetExperiment(rctx, args["projectID"].(string), args["experimentID"].(string)) + return ec.resolvers.Query().GetExperimentStats(rctx, args["projectID"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -16749,12 +16755,12 @@ func (ec *executionContext) _Query_getExperiment(ctx context.Context, field grap } return graphql.Null } - res := resTmp.(*model.GetExperimentResponse) + res := resTmp.(*model.GetExperimentStatsResponse) fc.Result = res - return ec.marshalNGetExperimentResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐGetExperimentResponse(ctx, field.Selections, res) + return ec.marshalNGetExperimentStatsResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐGetExperimentStatsResponse(ctx, field.Selections, res) } -func (ec *executionContext) _Query_listExperiment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_getExperimentRun(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -16770,7 +16776,7 @@ func (ec *executionContext) _Query_listExperiment(ctx context.Context, field gra ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_listExperiment_args(ctx, rawArgs) + args, err := ec.field_Query_getExperimentRun_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -16778,7 +16784,7 @@ func (ec *executionContext) _Query_listExperiment(ctx context.Context, field gra fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().ListExperiment(rctx, args["projectID"].(string), args["request"].(model.ListExperimentRequest)) + return ec.resolvers.Query().GetExperimentRun(rctx, args["projectID"].(string), args["experimentRunID"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -16790,12 +16796,12 @@ func (ec *executionContext) _Query_listExperiment(ctx context.Context, field gra } return graphql.Null } - res := resTmp.(*model.ListExperimentResponse) + res := resTmp.(*model.ExperimentRun) fc.Result = res - return ec.marshalNListExperimentResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐListExperimentResponse(ctx, field.Selections, res) + return ec.marshalNExperimentRun2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐExperimentRun(ctx, field.Selections, res) } -func (ec *executionContext) _Query_getExperimentRunStats(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_listExperimentRun(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -16811,7 +16817,7 @@ func (ec *executionContext) _Query_getExperimentRunStats(ctx context.Context, fi ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_getExperimentRunStats_args(ctx, rawArgs) + args, err := ec.field_Query_listExperimentRun_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -16819,7 +16825,7 @@ func (ec *executionContext) _Query_getExperimentRunStats(ctx context.Context, fi fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().GetExperimentRunStats(rctx, args["projectID"].(string)) + return ec.resolvers.Query().ListExperimentRun(rctx, args["projectID"].(string), args["request"].(model.ListExperimentRunRequest)) }) if err != nil { ec.Error(ctx, err) @@ -16831,12 +16837,12 @@ func (ec *executionContext) _Query_getExperimentRunStats(ctx context.Context, fi } return graphql.Null } - res := resTmp.(*model.GetExperimentRunStatsResponse) + res := resTmp.(*model.ListExperimentRunResponse) fc.Result = res - return ec.marshalNGetExperimentRunStatsResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐGetExperimentRunStatsResponse(ctx, field.Selections, res) + return ec.marshalNListExperimentRunResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐListExperimentRunResponse(ctx, field.Selections, res) } -func (ec *executionContext) _Query_getExperimentStats(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_getExperimentRunStats(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -16852,7 +16858,7 @@ func (ec *executionContext) _Query_getExperimentStats(ctx context.Context, field ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_getExperimentStats_args(ctx, rawArgs) + args, err := ec.field_Query_getExperimentRunStats_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -16860,7 +16866,7 @@ func (ec *executionContext) _Query_getExperimentStats(ctx context.Context, field fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().GetExperimentStats(rctx, args["projectID"].(string)) + return ec.resolvers.Query().GetExperimentRunStats(rctx, args["projectID"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -16872,9 +16878,9 @@ func (ec *executionContext) _Query_getExperimentStats(ctx context.Context, field } return graphql.Null } - res := resTmp.(*model.GetExperimentStatsResponse) + res := resTmp.(*model.GetExperimentRunStatsResponse) fc.Result = res - return ec.marshalNGetExperimentStatsResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐGetExperimentStatsResponse(ctx, field.Selections, res) + return ec.marshalNGetExperimentRunStatsResponse2ᚖgithubᚗcomᚋlitmuschaosᚋlitmusᚋchaoscenterᚋgraphqlᚋserverᚋgraphᚋmodelᚐGetExperimentRunStatsResponse(ctx, field.Selections, res) } func (ec *executionContext) _Query_getInfra(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -24202,11 +24208,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } - case "runChaosExperiment": - out.Values[i] = ec._Mutation_runChaosExperiment(ctx, field) - if out.Values[i] == graphql.Null { - invalids++ - } case "updateChaosExperiment": out.Values[i] = ec._Mutation_updateChaosExperiment(ctx, field) if out.Values[i] == graphql.Null { @@ -24222,6 +24223,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "runChaosExperiment": + out.Values[i] = ec._Mutation_runChaosExperiment(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "registerInfra": out.Values[i] = ec._Mutation_registerInfra(ctx, field) if out.Values[i] == graphql.Null { @@ -24527,7 +24533,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("Query") - case "getExperimentRun": + case "getExperiment": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -24535,13 +24541,13 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_getExperimentRun(ctx, field) + res = ec._Query_getExperiment(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } return res }) - case "listExperimentRun": + case "listExperiment": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -24549,13 +24555,13 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_listExperimentRun(ctx, field) + res = ec._Query_listExperiment(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } return res }) - case "getExperiment": + case "getExperimentStats": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -24563,13 +24569,13 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_getExperiment(ctx, field) + res = ec._Query_getExperimentStats(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } return res }) - case "listExperiment": + case "getExperimentRun": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -24577,13 +24583,13 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_listExperiment(ctx, field) + res = ec._Query_getExperimentRun(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } return res }) - case "getExperimentRunStats": + case "listExperimentRun": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -24591,13 +24597,13 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_getExperimentRunStats(ctx, field) + res = ec._Query_listExperimentRun(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } return res }) - case "getExperimentStats": + case "getExperimentRunStats": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -24605,7 +24611,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_getExperimentStats(ctx, field) + res = ec._Query_getExperimentRunStats(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } diff --git a/chaoscenter/graphql/server/graph/resolver.go b/chaoscenter/graphql/server/graph/resolver.go index 27521f30dfc..93e249fd938 100644 --- a/chaoscenter/graphql/server/graph/resolver.go +++ b/chaoscenter/graphql/server/graph/resolver.go @@ -10,8 +10,11 @@ import ( "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/chaos_experiment/handler" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/chaos_infrastructure" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/chaoshub" + chaos_experiment_run2 "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/choas_experiment_run" + runHandler "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/choas_experiment_run/handler" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment" + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run" dbSchemaChaosHub "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_hub" dbChaosInfra "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_infrastructure" gitops2 "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/gitops" @@ -29,8 +32,10 @@ type Resolver struct { imageRegistryService image_registry.Service chaosInfrastructureService chaos_infrastructure.Service chaosExperimentService chaos_experiment2.Service - chaosExperimentHandler handler.ChaosExperimentHandler + choasExperimentRunService chaos_experiment_run2.Service gitopsService gitops3.Service + chaosExperimentHandler handler.ChaosExperimentHandler + chaosExperimentRunHandler runHandler.ChaosExperimentRunHandler } func NewConfig(mongodbOperator mongodb.MongoOperator) generated.Config { @@ -38,7 +43,7 @@ func NewConfig(mongodbOperator mongodb.MongoOperator) generated.Config { chaosHubOperator := dbSchemaChaosHub.NewChaosHubOperator(mongodbOperator) chaosInfraOperator := dbChaosInfra.NewInfrastructureOperator(mongodbOperator) chaosExperimentOperator := chaos_experiment.NewChaosExperimentOperator(mongodbOperator) - + chaosExperimentRunOperator := chaos_experiment_run.NewChaosExperimentRunOperator(mongodbOperator) gitopsOperator := gitops2.NewGitOpsOperator(mongodbOperator) imageRegistryOperator := image_registry2.NewImageRegistryOperator(mongodbOperator) @@ -46,20 +51,24 @@ func NewConfig(mongodbOperator mongodb.MongoOperator) generated.Config { chaosHubService := chaoshub.NewService(chaosHubOperator) chaosInfrastructureService := chaos_infrastructure.NewChaosInfrastructureService(chaosInfraOperator) chaosExperimentService := chaos_experiment2.NewChaosExperimentService(chaosExperimentOperator, chaosInfraOperator) + chaosExperimentRunService := chaos_experiment_run2.NewChaosExperimentRunService(chaosExperimentOperator, chaosInfraOperator, chaosExperimentRunOperator) gitOpsService := gitops3.NewGitOpsService(gitopsOperator, chaosExperimentService, *chaosExperimentOperator) imageRegistryService := image_registry.NewImageRegistryService(imageRegistryOperator) //handler - chaosExperimentHandler := handler.NewChaosExperimentHandler(chaosExperimentService, chaosInfrastructureService, gitOpsService, chaosExperimentOperator, mongodbOperator) + chaosExperimentHandler := handler.NewChaosExperimentHandler(chaosExperimentService, chaosExperimentRunService, chaosInfrastructureService, gitOpsService, chaosExperimentOperator, chaosExperimentRunOperator, mongodbOperator) + choasExperimentRunHandler := runHandler.NewChaosExperimentRunHandler(chaosExperimentRunService, chaosInfrastructureService, gitOpsService, chaosExperimentOperator, chaosExperimentRunOperator, mongodbOperator) config := generated.Config{ Resolvers: &Resolver{ chaosHubService: chaosHubService, chaosInfrastructureService: chaosInfrastructureService, chaosExperimentService: chaosExperimentService, + choasExperimentRunService: chaosExperimentRunService, imageRegistryService: imageRegistryService, gitopsService: gitOpsService, chaosExperimentHandler: *chaosExperimentHandler, + chaosExperimentRunHandler: *choasExperimentRunHandler, }} config.Directives.Authorized = func(ctx context.Context, obj interface{}, next graphql.Resolver) (interface{}, error) { diff --git a/chaoscenter/graphql/server/pkg/authorization/roles.go b/chaoscenter/graphql/server/pkg/authorization/roles.go index c6d2f6b3131..599af91ffbf 100644 --- a/chaoscenter/graphql/server/pkg/authorization/roles.go +++ b/chaoscenter/graphql/server/pkg/authorization/roles.go @@ -36,6 +36,7 @@ const ( DeleteDashboard RoleQuery = "DeleteDashboard" DeleteDataSource RoleQuery = "DeleteDataSource" ListWorkflowRuns RoleQuery = "ListWorkflowRuns" + GetWorkflowRun RoleQuery = "GetWorkflowRun" ListInfrastructures RoleQuery = "ListInfrastructures" GetInfrastructure RoleQuery = "GetInfrastructure " GetManifest RoleQuery = "GetManifest" @@ -106,6 +107,7 @@ var MutationRbacRules = map[RoleQuery][]string{ DeleteDashboard: {MemberRoleOwnerString, MemberRoleEditorString}, DeleteDataSource: {MemberRoleOwnerString, MemberRoleEditorString}, ListWorkflowRuns: {MemberRoleOwnerString, MemberRoleEditorString, MemberRoleViewerString}, + GetWorkflowRun: {MemberRoleOwnerString, MemberRoleEditorString, MemberRoleViewerString}, ListInfrastructures: {MemberRoleOwnerString, MemberRoleEditorString, MemberRoleViewerString}, GetInfrastructure: {MemberRoleOwnerString, MemberRoleEditorString, diff --git a/chaoscenter/graphql/server/pkg/chaos_experiment/handler/handler.go b/chaoscenter/graphql/server/pkg/chaos_experiment/handler/handler.go index dadee525e95..551cc7add55 100644 --- a/chaoscenter/graphql/server/pkg/chaos_experiment/handler/handler.go +++ b/chaoscenter/graphql/server/pkg/chaos_experiment/handler/handler.go @@ -2,70 +2,63 @@ package handler import ( "context" - "encoding/base64" "encoding/json" "errors" - "fmt" - "sort" "strconv" - "strings" "time" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/authorization" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/chaos_infrastructure" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/gitops" - "go.mongodb.org/mongo-driver/mongo/options" - "go.mongodb.org/mongo-driver/mongo/readconcern" - "go.mongodb.org/mongo-driver/mongo/writeconcern" - "github.com/ghodss/yaml" - chaosTypes "github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/graph/model" "github.com/tidwall/sjson" - "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" - "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb" dbChaosExperimentRun "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run" "github.com/litmuschaos/litmus/chaoscenter/graphql/server/utils" "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" types "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/chaos_experiment" + chaosExperimentRun "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/choas_experiment_run" store "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/data-store" dbChaosExperiment "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment" - dbChaosInfra "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_infrastructure" - "github.com/google/uuid" ) // ChaosExperimentHandler is the handler for chaos experiment type ChaosExperimentHandler struct { - chaosExperimentService types.Service - infrastructureService chaos_infrastructure.Service - gitOpsService gitops.Service - chaosExperimentOperator *dbChaosExperiment.Operator - mongodbOperator mongodb.MongoOperator + chaosExperimentService types.Service + chaosExperimentRunService chaosExperimentRun.Service + infrastructureService chaos_infrastructure.Service + gitOpsService gitops.Service + chaosExperimentOperator *dbChaosExperiment.Operator + chaosExperimentRunOperator *dbChaosExperimentRun.Operator + mongodbOperator mongodb.MongoOperator } // NewChaosExperimentHandler returns a new instance of ChaosWorkflowHandler func NewChaosExperimentHandler( chaosExperimentService types.Service, + chaosExperimentRunService chaosExperimentRun.Service, infrastructureService chaos_infrastructure.Service, gitOpsService gitops.Service, chaosExperimentOperator *dbChaosExperiment.Operator, + chaosExperimentRunOperator *dbChaosExperimentRun.Operator, mongodbOperator mongodb.MongoOperator, ) *ChaosExperimentHandler { return &ChaosExperimentHandler{ - chaosExperimentService: chaosExperimentService, - infrastructureService: infrastructureService, - gitOpsService: gitOpsService, - chaosExperimentOperator: chaosExperimentOperator, - mongodbOperator: mongodbOperator, + chaosExperimentService: chaosExperimentService, + chaosExperimentRunService: chaosExperimentRunService, + infrastructureService: infrastructureService, + gitOpsService: gitOpsService, + chaosExperimentOperator: chaosExperimentOperator, + chaosExperimentRunOperator: chaosExperimentRunOperator, + mongodbOperator: mongodbOperator, } } @@ -207,14 +200,14 @@ func (c *ChaosExperimentHandler) DeleteChaosExperiment(ctx context.Context, proj {"experiment_id", workflowID}, {"experiment_run_id", workflowRunID}, } - workflowRun, err := dbChaosExperimentRun.GetExperimentRun(query) + workflowRun, err := c.chaosExperimentRunOperator.GetExperimentRun(query) if err != nil { return false, err } workflowRun.IsRemoved = true - err = c.chaosExperimentService.ProcessExperimentRunDelete(ctx, query, workflowRunID, workflowRun, workflow, uid, r) + err = c.chaosExperimentRunService.ProcessExperimentRunDelete(ctx, query, workflowRunID, workflowRun, workflow, uid, r) if err != nil { return false, err } @@ -235,564 +228,17 @@ func (c *ChaosExperimentHandler) UpdateChaosExperiment(ctx context.Context, requ tkn := ctx.Value(authorization.AuthKey).(string) uid, err := authorization.GetUsername(tkn) err = c.chaosExperimentService.ProcessExperimentUpdate(newRequest, uid, wfType, revID, false, projectID, r) - if err != nil { - return nil, err - } - - return &model.ChaosExperimentResponse{ - ExperimentID: *newRequest.ExperimentID, - CronSyntax: newRequest.CronSyntax, - ExperimentName: newRequest.ExperimentName, - ExperimentDescription: newRequest.ExperimentDescription, - IsCustomExperiment: newRequest.IsCustomExperiment, - }, nil -} - -// GetExperimentRun returns details of a requested experiment run -func (c *ChaosExperimentHandler) GetExperimentRun(ctx context.Context, projectID string, experimentRunID string) (*model.ExperimentRun, error) { - var pipeline mongo.Pipeline - - // Matching with identifiers - matchIdentifiersStage := bson.D{ - { - "$match", bson.D{ - {"experiment_run_id", experimentRunID}, - {"project_id", projectID}, - {"is_removed", false}, - }, - }, - } - pipeline = append(pipeline, matchIdentifiersStage) - - // Adds details of experiment - addExperimentDetails := bson.D{ - {"$lookup", - bson.D{ - {"from", "chaosExperiments"}, - {"let", bson.D{{"experimentID", "$experiment_id"}, {"revID", "$revision_id"}}}, - { - "pipeline", bson.A{ - bson.D{{"$match", bson.D{{"$expr", bson.D{{"$eq", bson.A{"$experiment_id", "$$experimentID"}}}}}}}, - bson.D{ - {"$project", bson.D{ - {"name", 1}, - {"is_custom_experiment", 1}, - {"revision", bson.D{{ - "$filter", bson.D{ - {"input", "$revision"}, - {"as", "revs"}, - {"cond", bson.D{{ - "$eq", bson.A{"$$revs.revision_id", "$$revID"}, - }}}, - }, - }}}, - }}, - }, - }, - }, - {"as", "experiment"}, - }, - }, - } - pipeline = append(pipeline, addExperimentDetails) - - // fetchKubernetesInfraDetailsStage adds kubernetes infra details of corresponding experiment_id to each document - fetchKubernetesInfraDetailsStage := bson.D{ - {"$lookup", bson.D{ - {"from", "chaosInfrastructures"}, - {"let", bson.M{"infraID": "$infra_id"}}, - { - "pipeline", bson.A{ - bson.D{ - {"$match", bson.D{ - {"$expr", bson.D{ - {"$eq", bson.A{"$infra_id", "$$infraID"}}, - }}, - }}, - }, - bson.D{ - {"$project", bson.D{ - {"token", 0}, - {"infra_ns_exists", 0}, - {"infra_sa_exists", 0}, - {"access_key", 0}, - }}, - }, - }, - }, - {"as", "kubernetesInfraDetails"}, - }}, - } - - pipeline = append(pipeline, fetchKubernetesInfraDetailsStage) - - // Call aggregation on pipeline - expRunCursor, err := dbChaosExperimentRun.GetAggregateExperimentRuns(pipeline) - if err != nil { - return nil, errors.New("DB aggregate stage error: " + err.Error()) - } - - var ( - expRunResponse *model.ExperimentRun - expRunDetails []dbChaosExperiment.FlattenedExperimentRun - ) - - if err = expRunCursor.All(context.Background(), &expRunDetails); err != nil { - return nil, errors.New("error decoding experiment run cursor: " + err.Error()) - } - if len(expRunDetails) == 0 { - return nil, errors.New("no matching experiment run") - } - if len(expRunDetails[0].KubernetesInfraDetails) == 0 { - return nil, errors.New("no matching infra found for given experiment run") - } - - for _, wfRun := range expRunDetails { - var ( - weightages []*model.Weightages - workflowRunManifest string - ) - - if len(wfRun.ExperimentDetails[0].Revision) > 0 { - revision := wfRun.ExperimentDetails[0].Revision[0] - for _, v := range revision.Weightages { - weightages = append(weightages, &model.Weightages{ - FaultName: v.FaultName, - Weightage: v.Weightage, - }) - } - workflowRunManifest = revision.ExperimentManifest - } - var chaosInfrastructure *model.Infra - - if len(wfRun.KubernetesInfraDetails) > 0 { - infra := wfRun.KubernetesInfraDetails[0] - chaosInfrastructure = &model.Infra{ - InfraID: infra.InfraID, - Name: infra.Name, - EnvironmentID: infra.EnvironmentID, - Description: &infra.Description, - PlatformName: infra.PlatformName, - IsActive: infra.IsActive, - UpdatedAt: strconv.FormatInt(infra.UpdatedAt, 10), - CreatedAt: strconv.FormatInt(infra.CreatedAt, 10), - InfraNamespace: infra.InfraNamespace, - ServiceAccount: infra.ServiceAccount, - InfraScope: infra.InfraScope, - StartTime: infra.StartTime, - Version: infra.Version, - Tags: infra.Tags, - } - } - - expRunResponse = &model.ExperimentRun{ - ExperimentName: wfRun.ExperimentDetails[0].ExperimentName, - ExperimentID: wfRun.ExperimentID, - ExperimentRunID: wfRun.ExperimentRunID, - Weightages: weightages, - ExperimentManifest: workflowRunManifest, - ProjectID: wfRun.ProjectID, - Infra: chaosInfrastructure, - Phase: model.ExperimentRunStatus(wfRun.Phase), - ResiliencyScore: wfRun.ResiliencyScore, - FaultsPassed: wfRun.FaultsPassed, - FaultsFailed: wfRun.FaultsFailed, - FaultsAwaited: wfRun.FaultsAwaited, - FaultsStopped: wfRun.FaultsStopped, - FaultsNa: wfRun.FaultsNA, - TotalFaults: wfRun.TotalFaults, - ExecutionData: wfRun.ExecutionData, - IsRemoved: &wfRun.IsRemoved, - UpdatedBy: &model.UserDetails{ - Username: wfRun.UpdatedBy, - }, - UpdatedAt: strconv.FormatInt(wfRun.UpdatedAt, 10), - CreatedAt: strconv.FormatInt(wfRun.CreatedAt, 10), - } - } - - return expRunResponse, nil -} - -// ListExperimentRun returns all the workflow runs for matching identifiers from the DB -func (c *ChaosExperimentHandler) ListExperimentRun(projectID string, request model.ListExperimentRunRequest) (*model.ListExperimentRunResponse, error) { - var pipeline mongo.Pipeline - - // Matching with identifiers - matchIdentifiersStage := bson.D{ - { - "$match", bson.D{{ - "$and", bson.A{ - bson.D{ - {"project_id", projectID}, - }, - }, - }}, - }, - } - pipeline = append(pipeline, matchIdentifiersStage) - - // Match the workflowRunIds from the input array - if request.ExperimentRunIDs != nil && len(request.ExperimentRunIDs) != 0 { - matchWfRunIdStage := bson.D{ - {"$match", bson.D{ - {"experiment_run_id", bson.D{ - {"$in", request.ExperimentRunIDs}, - }}, - }}, - } - - pipeline = append(pipeline, matchWfRunIdStage) - } - - // Match the workflowIds from the input array - if request.ExperimentIDs != nil && len(request.ExperimentIDs) != 0 { - matchWfIdStage := bson.D{ - {"$match", bson.D{ - {"experiment_id", bson.D{ - {"$in", request.ExperimentIDs}, - }}, - }}, - } - - pipeline = append(pipeline, matchWfIdStage) - } - - // Filtering out the workflows that are deleted/removed - matchExpIsRemovedStage := bson.D{ - {"$match", bson.D{ - {"is_removed", bson.D{ - {"$eq", false}, - }}, - }}, - } - pipeline = append(pipeline, matchExpIsRemovedStage) - - addExperimentDetails := bson.D{ - { - "$lookup", - bson.D{ - {"from", "chaosExperiments"}, - {"let", bson.D{{"experimentID", "$experiment_id"}, {"revID", "$revision_id"}}}, - { - "pipeline", bson.A{ - bson.D{{"$match", bson.D{{"$expr", bson.D{{"$eq", bson.A{"$experiment_id", "$$experimentID"}}}}}}}, - bson.D{ - {"$project", bson.D{ - {"name", 1}, - {"experiment_type", 1}, - {"is_custom_experiment", 1}, - {"revision", bson.D{{ - "$filter", bson.D{ - {"input", "$revision"}, - {"as", "revs"}, - {"cond", bson.D{{ - "$eq", bson.A{"$$revs.revision_id", "$$revID"}, - }}}, - }, - }}}, - }}, - }, - }, - }, - {"as", "experiment"}, - }, - }, - } - pipeline = append(pipeline, addExperimentDetails) - - // Filtering based on multiple parameters - if request.Filter != nil { - - // Filtering based on workflow name - if request.Filter.ExperimentName != nil && *request.Filter.ExperimentName != "" { - matchWfNameStage := bson.D{ - {"$match", bson.D{ - {"experiment.name", bson.D{ - {"$regex", request.Filter.ExperimentName}, - }}, - }}, - } - pipeline = append(pipeline, matchWfNameStage) - } - - // Filtering based on workflow run ID - if request.Filter.ExperimentRunID != nil && *request.Filter.ExperimentRunID != "" { - matchWfRunIDStage := bson.D{ - {"$match", bson.D{ - {"experiment_run_id", bson.D{ - {"$regex", request.Filter.ExperimentRunID}, - }}, - }}, - } - pipeline = append(pipeline, matchWfRunIDStage) - } - - // Filtering based on workflow run status array - if len(request.Filter.ExperimentRunStatus) > 0 { - matchWfRunStatusStage := bson.D{ - {"$match", bson.D{ - {"phase", bson.D{ - {"$in", request.Filter.ExperimentRunStatus}, - }}, - }}, - } - pipeline = append(pipeline, matchWfRunStatusStage) - } - - // Filtering based on infraID - if request.Filter.InfraID != nil && *request.Filter.InfraID != "All" && *request.Filter.InfraID != "" { - matchInfraStage := bson.D{ - {"$match", bson.D{ - {"infra_id", request.Filter.InfraID}, - }}, - } - pipeline = append(pipeline, matchInfraStage) - } - - // Filtering based on phase - if request.Filter.ExperimentStatus != nil && *request.Filter.ExperimentStatus != "All" && *request.Filter.ExperimentStatus != "" { - filterWfRunPhaseStage := bson.D{ - {"$match", bson.D{ - {"phase", string(*request.Filter.ExperimentStatus)}, - }}, - } - pipeline = append(pipeline, filterWfRunPhaseStage) - } - - // Filtering based on date range - if request.Filter.DateRange != nil { - endDate := strconv.FormatInt(time.Now().UnixMilli(), 10) - if request.Filter.DateRange.EndDate != nil { - endDate = *request.Filter.DateRange.EndDate - } - filterWfRunDateStage := bson.D{ - { - "$match", - bson.D{{"updated_at", bson.D{ - {"$lte", endDate}, - {"$gte", request.Filter.DateRange.StartDate}, - }}}, - }, - } - pipeline = append(pipeline, filterWfRunDateStage) - } - } - - var sortStage bson.D - - switch { - case request.Sort != nil && request.Sort.Field == model.ExperimentSortingFieldTime: - // Sorting based on created time - if request.Sort.Ascending != nil && *request.Sort.Ascending { - sortStage = bson.D{ - {"$sort", bson.D{ - {"created_at", 1}, - }}, - } - } else { - sortStage = bson.D{ - {"$sort", bson.D{ - {"created_at", -1}, - }}, - } - } - case request.Sort != nil && request.Sort.Field == model.ExperimentSortingFieldName: - // Sorting based on ExperimentName time - if request.Sort.Ascending != nil && *request.Sort.Ascending { - sortStage = bson.D{ - {"$sort", bson.D{ - {"experiment.name", 1}, - }}, - } - } else { - sortStage = bson.D{ - {"$sort", bson.D{ - {"experiment.name", -1}, - }}, - } - } - default: - // Default sorting: sorts it by created_at time in descending order - sortStage = bson.D{ - {"$sort", bson.D{ - {"created_at", -1}, - }}, - } - } - - // fetchKubernetesInfraDetailsStage adds infra details of corresponding experiment_id to each document - fetchKubernetesInfraDetailsStage := bson.D{ - {"$lookup", bson.D{ - {"from", "chaosInfrastructures"}, - {"let", bson.M{"infraID": "$infra_id"}}, - { - "pipeline", bson.A{ - bson.D{ - {"$match", bson.D{ - {"$expr", bson.D{ - {"$eq", bson.A{"$infra_id", "$$infraID"}}, - }}, - }}, - }, - bson.D{ - {"$project", bson.D{ - {"token", 0}, - {"infra_ns_exists", 0}, - {"infra_sa_exists", 0}, - {"access_key", 0}, - }}, - }, - }, - }, - {"as", "kubernetesInfraDetails"}, - }}, - } - - pipeline = append(pipeline, fetchKubernetesInfraDetailsStage) - - // Pagination or adding a default limit of 15 if pagination not provided - paginatedExperiments := bson.A{ - sortStage, - } - - if request.Pagination != nil { - paginationSkipStage := bson.D{ - {"$skip", request.Pagination.Page * request.Pagination.Limit}, - } - paginationLimitStage := bson.D{ - {"$limit", request.Pagination.Limit}, - } - - paginatedExperiments = append(paginatedExperiments, paginationSkipStage, paginationLimitStage) - } else { - limitStage := bson.D{ - {"$limit", 15}, - } - - paginatedExperiments = append(paginatedExperiments, limitStage) - } - - // Add two stages where we first count the number of filtered workflow and then paginate the results - facetStage := bson.D{ - {"$facet", bson.D{ - {"total_filtered_experiment_runs", bson.A{ - bson.D{{"$count", "count"}}, - }}, - {"flattened_experiment_runs", paginatedExperiments}, - }}, - } - pipeline = append(pipeline, facetStage) - - // Call aggregation on pipeline - workflowsCursor, err := dbChaosExperimentRun.GetAggregateExperimentRuns(pipeline) - if err != nil { - return nil, errors.New("DB aggregate stage error: " + err.Error()) - } - - var ( - result []*model.ExperimentRun - workflows []dbChaosExperiment.AggregatedExperimentRuns - ) - - if err = workflowsCursor.All(context.Background(), &workflows); err != nil || len(workflows) == 0 { - return &model.ListExperimentRunResponse{ - TotalNoOfExperimentRuns: 0, - ExperimentRuns: result, - }, errors.New("error decoding experiment runs cursor: " + err.Error()) - } - if len(workflows) == 0 { - return &model.ListExperimentRunResponse{ - TotalNoOfExperimentRuns: 0, - ExperimentRuns: result, - }, nil - } - - for _, workflow := range workflows[0].FlattenedExperimentRuns { - var ( - weightages []*model.Weightages - workflowRunManifest string - workflowType string - workflowName string - ) - - if len(workflow.ExperimentDetails) > 0 { - workflowType = string(workflow.ExperimentDetails[0].ExperimentType) - workflowName = workflow.ExperimentDetails[0].ExperimentName - if len(workflow.ExperimentDetails[0].Revision) > 0 { - revision := workflow.ExperimentDetails[0].Revision[0] - for _, v := range revision.Weightages { - weightages = append(weightages, &model.Weightages{ - FaultName: v.FaultName, - Weightage: v.Weightage, - }) - } - workflowRunManifest = revision.ExperimentManifest - } - } - var chaosInfrastructure *model.Infra - - if len(workflow.KubernetesInfraDetails) > 0 { - infra := workflow.KubernetesInfraDetails[0] - infraType := model.InfrastructureType(infra.InfraType) - chaosInfrastructure = &model.Infra{ - InfraID: infra.InfraID, - Name: infra.Name, - EnvironmentID: infra.EnvironmentID, - Description: &infra.Description, - PlatformName: infra.PlatformName, - IsActive: infra.IsActive, - UpdatedAt: strconv.FormatInt(infra.UpdatedAt, 10), - CreatedAt: strconv.FormatInt(infra.CreatedAt, 10), - InfraNamespace: infra.InfraNamespace, - ServiceAccount: infra.ServiceAccount, - InfraScope: infra.InfraScope, - StartTime: infra.StartTime, - Version: infra.Version, - Tags: infra.Tags, - InfraType: &infraType, - } - } - - newExperimentRun := model.ExperimentRun{ - ExperimentName: workflowName, - ExperimentType: &workflowType, - ExperimentID: workflow.ExperimentID, - ExperimentRunID: workflow.ExperimentRunID, - Weightages: weightages, - ExperimentManifest: workflowRunManifest, - ProjectID: workflow.ProjectID, - Infra: chaosInfrastructure, - Phase: model.ExperimentRunStatus(workflow.Phase), - ResiliencyScore: workflow.ResiliencyScore, - FaultsPassed: workflow.FaultsPassed, - FaultsFailed: workflow.FaultsFailed, - FaultsAwaited: workflow.FaultsAwaited, - FaultsStopped: workflow.FaultsStopped, - FaultsNa: workflow.FaultsNA, - TotalFaults: workflow.TotalFaults, - ExecutionData: workflow.ExecutionData, - IsRemoved: &workflow.IsRemoved, - UpdatedBy: &model.UserDetails{ - Username: workflow.UpdatedBy, - }, - UpdatedAt: strconv.FormatInt(workflow.UpdatedAt, 10), - CreatedAt: strconv.FormatInt(workflow.CreatedAt, 10), - } - result = append(result, &newExperimentRun) - } - - totalFilteredExperimentRunsCounter := 0 - if len(workflows) > 0 && len(workflows[0].TotalFilteredExperimentRuns) > 0 { - totalFilteredExperimentRunsCounter = workflows[0].TotalFilteredExperimentRuns[0].Count - } - - output := model.ListExperimentRunResponse{ - TotalNoOfExperimentRuns: totalFilteredExperimentRunsCounter, - ExperimentRuns: result, + if err != nil { + return nil, err } - return &output, nil + return &model.ChaosExperimentResponse{ + ExperimentID: *newRequest.ExperimentID, + CronSyntax: newRequest.CronSyntax, + ExperimentName: newRequest.ExperimentName, + ExperimentDescription: newRequest.ExperimentDescription, + IsCustomExperiment: newRequest.IsCustomExperiment, + }, nil } // GetExperiment returns details of the requested experiment @@ -1351,321 +797,8 @@ func (c *ChaosExperimentHandler) ListExperiment(projectID string, request model. return &output, nil } -// RunChaosWorkFlow sends workflow run request(single run workflow only) to chaos_infra on workflow re-run request -func (c *ChaosExperimentHandler) RunChaosWorkFlow(ctx context.Context, projectID string, workflow dbChaosExperiment.ChaosExperimentRequest, r *store.StateData) (*model.RunChaosExperimentResponse, error) { - var notifyID string - infra, err := dbChaosInfra.NewInfrastructureOperator(c.mongodbOperator).GetInfra(workflow.InfraID) - if err != nil { - return nil, err - } - if !infra.IsActive { - return nil, errors.New("experiment re-run failed due to inactive infra") - } - - var ( - workflowManifest v1alpha1.Workflow - ) - - currentTime := time.Now().UnixMilli() - - if len(workflow.Revision) == 0 { - return nil, errors.New("no revisions found") - } - - sort.Slice(workflow.Revision, func(i, j int) bool { - return workflow.Revision[i].UpdatedAt > workflow.Revision[j].UpdatedAt - }) - - resKind := gjson.Get(workflow.Revision[0].ExperimentManifest, "kind").String() - if strings.ToLower(resKind) == "cronworkflow" { - return &model.RunChaosExperimentResponse{NotifyID: notifyID}, c.RunCronExperiment(ctx, projectID, workflow, r) - } - notifyID = uuid.New().String() - - err = json.Unmarshal([]byte(workflow.Revision[0].ExperimentManifest), &workflowManifest) - if err != nil { - return nil, errors.New("failed to unmarshal workflow manifest") - } - - var resScore float64 = 0 - - if _, found := workflowManifest.Labels["infra_id"]; !found { - return nil, errors.New("failed to rerun the chaos experiment due to invalid metadata/labels. Check the troubleshooting guide or contact support") - } - workflowManifest.Labels["notify_id"] = notifyID - workflowManifest.Name = workflowManifest.Name + "-" + strconv.FormatInt(currentTime, 10) - - for i, template := range workflowManifest.Spec.Templates { - artifact := template.Inputs.Artifacts - if len(artifact) > 0 { - if artifact[0].Raw == nil { - continue - } - var data = artifact[0].Raw.Data - if len(data) > 0 { - - var ( - meta chaosTypes.ChaosEngine - annotation = make(map[string]string) - ) - err := yaml.Unmarshal([]byte(data), &meta) - if err != nil { - return nil, errors.New("failed to unmarshal chaosengine") - } - if strings.ToLower(meta.Kind) == "chaosengine" { - if meta.Annotations != nil { - annotation = meta.Annotations - } - meta.Annotations = annotation - - if meta.Labels == nil { - meta.Labels = map[string]string{ - "infra_id": workflow.InfraID, - "step_pod_name": "{{pod.name}}", - "workflow_run_id": "{{workflow.uid}}", - } - } else { - meta.Labels["infra_id"] = workflow.InfraID - meta.Labels["step_pod_name"] = "{{pod.name}}" - meta.Labels["workflow_run_id"] = "{{workflow.uid}}" - } - - res, err := yaml.Marshal(&meta) - if err != nil { - return nil, errors.New("failed to marshal chaosengine") - } - workflowManifest.Spec.Templates[i].Inputs.Artifacts[0].Raw.Data = string(res) - } - } - } - } - - // Updating updated_at field - filter := bson.D{ - {"experiment_id", workflow.ExperimentID}, - } - update := bson.D{ - { - "$set", bson.D{ - {"updated_at", currentTime}, - }, - }, - } - err = c.chaosExperimentOperator.UpdateChaosExperiment(context.Background(), filter, update) - if err != nil { - logrus.Error("Failed to update updated_at") - return nil, err - } - - executionData := types.ExecutionData{ - Name: workflowManifest.Name, - Phase: "Queued", - ExperimentID: workflow.ExperimentID, - } - - parsedData, err := json.Marshal(executionData) - if err != nil { - logrus.Error("Failed to parse execution data") - return nil, err - } - - tkn := ctx.Value(authorization.AuthKey).(string) - username, err := authorization.GetUsername(tkn) - var ( - wc = writeconcern.New(writeconcern.WMajority()) - rc = readconcern.Snapshot() - txnOpts = options.Transaction().SetWriteConcern(wc).SetReadConcern(rc) - ) - - session, err := mongodb.MgoClient.StartSession() - if err != nil { - logrus.Errorf("failed to start mongo session %v", err) - return nil, err - } - - err = mongo.WithSession(context.Background(), session, func(sessionContext mongo.SessionContext) error { - if err = session.StartTransaction(txnOpts); err != nil { - logrus.Errorf("failed to start mongo session transaction %v", err) - return err - } - - expRunDetail := []dbChaosExperiment.ExperimentRunDetail{ - { - Phase: executionData.Phase, - Completed: false, - ProjectID: projectID, - NotifyID: ¬ifyID, - Audit: mongodb.Audit{ - IsRemoved: false, - CreatedAt: currentTime, - CreatedBy: username, - UpdatedAt: currentTime, - UpdatedBy: username, - }, - }, - } - - filter = bson.D{ - {"experiment_id", workflow.ExperimentID}, - } - update = bson.D{ - { - "$set", bson.D{ - {"updated_at", currentTime}, - {"total_experiment_runs", workflow.TotalExperimentRuns + 1}, - }, - }, - { - "$push", bson.D{ - {"recent_experiment_run_details", bson.D{ - {"$each", expRunDetail}, - {"$position", 0}, - {"$slice", 10}, - }}, - }, - }, - } - - err = c.chaosExperimentOperator.UpdateChaosExperiment(sessionContext, filter, update) - if err != nil { - logrus.Error("Failed to update experiment collection") - } - - err = dbChaosExperimentRun.CreateExperimentRun(sessionContext, dbChaosExperimentRun.ChaosExperimentRun{ - InfraID: workflow.InfraID, - ExperimentID: workflow.ExperimentID, - Phase: "Queued", - RevisionID: workflow.Revision[0].RevisionID, - ProjectID: projectID, - Audit: mongodb.Audit{ - IsRemoved: false, - CreatedAt: currentTime, - CreatedBy: username, - UpdatedAt: currentTime, - UpdatedBy: username, - }, - NotifyID: ¬ifyID, - Completed: false, - ResiliencyScore: &resScore, - ExecutionData: string(parsedData), - }) - if err != nil { - logrus.Error("Failed to create run operation in db") - return err - } - - if err = session.CommitTransaction(sessionContext); err != nil { - logrus.Errorf("failed to commit session transaction %v", err) - return err - } - return nil - }) - - if err != nil { - if abortErr := session.AbortTransaction(ctx); abortErr != nil { - logrus.Errorf("failed to abort session transaction %v", err) - return nil, abortErr - } - return nil, err - } - - session.EndSession(ctx) - - manifest, err := yaml.Marshal(workflowManifest) - if err != nil { - return nil, err - } - if r != nil { - chaos_infrastructure.SendExperimentToSubscriber(projectID, &model.ChaosExperimentRequest{ - ExperimentID: &workflow.ExperimentID, - ExperimentManifest: string(manifest), - InfraID: workflow.InfraID, - }, &username, nil, "create", r) - } - return &model.RunChaosExperimentResponse{ - NotifyID: notifyID, - }, nil -} - -func (c *ChaosExperimentHandler) RunCronExperiment(ctx context.Context, projectID string, workflow dbChaosExperiment.ChaosExperimentRequest, r *store.StateData) error { - var ( - cronExperimentManifest v1alpha1.CronWorkflow - ) - - if len(workflow.Revision) == 0 { - return errors.New("no revisions found") - } - sort.Slice(workflow.Revision, func(i, j int) bool { - return workflow.Revision[i].UpdatedAt > workflow.Revision[j].UpdatedAt - }) - - err := json.Unmarshal([]byte(workflow.Revision[0].ExperimentManifest), &cronExperimentManifest) - if err != nil { - return errors.New("failed to unmarshal experiment manifest") - } - - for i, template := range cronExperimentManifest.Spec.WorkflowSpec.Templates { - artifact := template.Inputs.Artifacts - if len(artifact) > 0 { - if artifact[0].Raw == nil { - continue - } - var data = artifact[0].Raw.Data - if len(data) > 0 { - var meta chaosTypes.ChaosEngine - annotation := make(map[string]string) - err := yaml.Unmarshal([]byte(data), &meta) - if err != nil { - return errors.New("failed to unmarshal chaosengine") - } - if strings.ToLower(meta.Kind) == "chaosengine" { - if meta.Annotations != nil { - annotation = meta.Annotations - } - meta.Annotations = annotation - - if meta.Labels == nil { - meta.Labels = map[string]string{ - "infra_id": workflow.InfraID, - "step_pod_name": "{{pod.name}}", - "workflow_run_id": "{{workflow.uid}}", - } - } else { - meta.Labels["infra_id"] = workflow.InfraID - meta.Labels["step_pod_name"] = "{{pod.name}}" - meta.Labels["workflow_run_id"] = "{{workflow.uid}}" - } - res, err := yaml.Marshal(&meta) - if err != nil { - return errors.New("failed to marshal chaosengine") - } - cronExperimentManifest.Spec.WorkflowSpec.Templates[i].Inputs.Artifacts[0].Raw.Data = string(res) - } - } - } - } - - manifest, err := yaml.Marshal(cronExperimentManifest) - if err != nil { - return err - } - - tkn := ctx.Value(authorization.AuthKey).(string) - username, err := authorization.GetUsername(tkn) - - if r != nil { - chaos_infrastructure.SendExperimentToSubscriber(projectID, &model.ChaosExperimentRequest{ - ExperimentID: &workflow.ExperimentID, - ExperimentManifest: string(manifest), - InfraID: workflow.InfraID, - }, &username, nil, "create", r) - } - - return nil -} - // getWfRunDetails returns details of the latest workflow run of passed workflows. -func getWfRunDetails(workflowIDs []string) (map[string]*types.LastRunDetails, error) { +func (c *ChaosExperimentHandler) getWfRunDetails(workflowIDs []string) (map[string]*types.LastRunDetails, error) { var pipeline mongo.Pipeline // Match the workflowID @@ -1765,7 +898,7 @@ func getWfRunDetails(workflowIDs []string) (map[string]*types.LastRunDetails, er } pipeline = append(pipeline, facetStage) // Call aggregation on pipeline - workflowsRunDetailCursor, err := dbChaosExperimentRun.GetAggregateExperimentRuns(pipeline) + workflowsRunDetailCursor, err := c.chaosExperimentRunOperator.GetAggregateExperimentRuns(pipeline) if err != nil { return nil, err } @@ -1849,65 +982,6 @@ func (c *ChaosExperimentHandler) DisableCronExperiment(username string, experime return nil } -func (c *ChaosExperimentHandler) GetExperimentRunStats(ctx context.Context, projectID string) (*model.GetExperimentRunStatsResponse, error) { - var pipeline mongo.Pipeline - // Match with identifiers - matchIdentifierStage := bson.D{ - {"$match", bson.D{ - {"project_id", projectID}, - }}, - } - - pipeline = append(pipeline, matchIdentifierStage) - - // Group and counts total experiment runs by phase - groupByPhaseStage := bson.D{ - { - "$group", bson.D{ - {"_id", "$phase"}, - {"count", bson.D{ - {"$sum", 1}, - }}, - }, - }, - } - pipeline = append(pipeline, groupByPhaseStage) - // Call aggregation on pipeline - experimentRunCursor, err := dbChaosExperimentRun.GetAggregateExperimentRuns(pipeline) - if err != nil { - return nil, err - } - - var res []dbChaosExperiment.AggregatedExperimentRunStats - - if err = experimentRunCursor.All(context.Background(), &res); err != nil || len(res) == 0 { - return nil, err - } - - resMap := map[string]int{ - "Completed": 0, - "Stopped": 0, - "Running": 0, - "Terminated": 0, - "Error": 0, - } - - totalExperimentRuns := 0 - for _, phase := range res { - resMap[phase.Id] = phase.Count - totalExperimentRuns = totalExperimentRuns + phase.Count - } - - return &model.GetExperimentRunStatsResponse{ - TotalExperimentRuns: totalExperimentRuns, - TotalCompletedExperimentRuns: resMap["Completed"], - TotalTerminatedExperimentRuns: resMap["Terminated"], - TotalRunningExperimentRuns: resMap["Running"], - TotalStoppedExperimentRuns: resMap["Stopped"], - TotalErroredExperimentRuns: resMap["Error"], - }, nil -} - func (c *ChaosExperimentHandler) GetExperimentStats(ctx context.Context, projectID string) (*model.GetExperimentStatsResponse, error) { var pipeline mongo.Pipeline // Match with identifiers @@ -2047,237 +1121,6 @@ func (c *ChaosExperimentHandler) GetExperimentStats(ctx context.Context, project return result, nil } -func (c *ChaosExperimentHandler) ChaosExperimentRunEvent(event model.ExperimentRunRequest) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - experiment, err := c.chaosExperimentOperator.GetExperiment(ctx, bson.D{ - {"experiment_id", event.ExperimentID}, - {"is_removed", false}, - }) - if err != nil { - if err == mongo.ErrNoDocuments { - return fmt.Sprintf("no experiment found with experimentID: %s, experiment run discarded: %s", event.ExperimentID, event.ExperimentRunID), nil - } - return "", err - } - - logFields := logrus.Fields{ - "projectID": experiment.ProjectID, - "experimentID": experiment.ExperimentID, - "experimentRunID": event.ExperimentRunID, - "infraID": experiment.InfraID, - } - - logrus.WithFields(logFields).Info("new workflow event received") - - var ( - executionData types.ExecutionData - exeData []byte - ) - - // Parse and store execution data - if event.ExecutionData != "" { - exeData, err = base64.StdEncoding.DecodeString(event.ExecutionData) - if err != nil { - logrus.WithFields(logFields).Warn("Failed to decode execution data: ", err) - - //Required for backward compatibility of subscribers - //which are not sending execution data in base64 encoded format - //remove it once all subscribers are updated - exeData = []byte(event.ExperimentID) - } - err = json.Unmarshal(exeData, &executionData) - if err != nil { - return "", err - } - } - - var workflowRunMetrics types.ExperimentRunMetrics - // Resiliency Score will be calculated only if workflow execution is completed - if event.Completed { - workflowRunMetrics, err = c.chaosExperimentService.ProcessCompletedExperimentRun(executionData, event.ExperimentID, event.ExperimentRunID) - if err != nil { - logrus.WithFields(logFields).Errorf("failed to process completed workflow run %v", err) - return "", err - } - - } - - //TODO check for mongo transaction - var ( - wc = writeconcern.New(writeconcern.WMajority()) - rc = readconcern.Snapshot() - txnOpts = options.Transaction().SetWriteConcern(wc).SetReadConcern(rc) - ) - - session, err := mongodb.MgoClient.StartSession() - if err != nil { - logrus.WithFields(logFields).Errorf("failed to start mongo session %v", err) - return "", err - } - // - var ( - isRemoved = false - currentTime = time.Now() - ) - - err = mongo.WithSession(ctx, session, func(sessionContext mongo.SessionContext) error { - if err = session.StartTransaction(txnOpts); err != nil { - logrus.WithFields(logFields).Errorf("failed to start mongo session transaction %v", err) - return err - } - - query := bson.D{ - {"experiment_id", event.ExperimentID}, - {"experiment_run_id", event.ExperimentRunID}, - } - - if event.NotifyID != nil { - query = bson.D{ - {"experiment_id", event.ExperimentID}, - {"notify_id", event.NotifyID}, - } - } - - experimentRunCount, err := dbChaosExperimentRun.CountExperimentRuns(sessionContext, query) - if err != nil { - return err - } - fmt.Println("event", event.UpdatedBy) - updatedBy, err := base64.RawURLEncoding.DecodeString(event.UpdatedBy) - if err != nil { - logrus.Fatalf("Failed to parse updated by field %v", err) - } - expRunDetail := []dbChaosExperiment.ExperimentRunDetail{ - { - Phase: executionData.Phase, - ResiliencyScore: &workflowRunMetrics.ResiliencyScore, - ExperimentRunID: event.ExperimentRunID, - Completed: false, - Audit: mongodb.Audit{ - IsRemoved: false, - CreatedAt: time.Now().UnixMilli(), - UpdatedAt: time.Now().UnixMilli(), - UpdatedBy: string(updatedBy), - }, - }, - } - if experimentRunCount == 0 { - filter := bson.D{ - {"experiment_id", event.ExperimentID}, - } - update := bson.D{ - { - "$set", bson.D{ - {"updated_at", time.Now().UnixMilli()}, - {"total_experiment_runs", experiment.TotalExperimentRuns + 1}, - }, - }, - { - "$push", bson.D{ - {"recent_experiment_run_details", bson.D{ - {"$each", expRunDetail}, - {"$position", 0}, - {"$slice", 10}, - }}, - }, - }, - } - - err = c.chaosExperimentOperator.UpdateChaosExperiment(sessionContext, filter, update) - if err != nil { - logrus.Error("Failed to update experiment collection") - return err - } - } else if experimentRunCount > 0 { - filter := bson.D{ - {"experiment_id", event.ExperimentID}, - {"recent_experiment_run_details.experiment_run_id", event.ExperimentRunID}, - {"recent_experiment_run_details.completed", false}, - } - if event.NotifyID != nil { - filter = bson.D{ - {"experiment_id", event.ExperimentID}, - {"recent_experiment_run_details.completed", false}, - {"recent_experiment_run_details.notify_id", event.NotifyID}, - } - } - - update := bson.D{ - { - "$set", bson.D{ - {"recent_experiment_run_details.$.phase", executionData.Phase}, - {"recent_experiment_run_details.$.completed", event.Completed}, - {"recent_experiment_run_details.$.experiment_run_id", event.ExperimentRunID}, - {"recent_experiment_run_details.$.resiliency_score", workflowRunMetrics.ResiliencyScore}, - {"recent_experiment_run_details.$.updated_at", currentTime.UnixMilli()}, - {"recent_experiment_run_details.$.updated_by", string(updatedBy)}, - }, - }, - } - - err = c.chaosExperimentOperator.UpdateChaosExperiment(sessionContext, filter, update) - if err != nil { - logrus.Error("Failed to update experiment collection") - return err - } - } - - count, err := dbChaosExperimentRun.UpdateExperimentRun(sessionContext, dbChaosExperimentRun.ChaosExperimentRun{ - InfraID: event.InfraID.InfraID, - ProjectID: experiment.ProjectID, - ExperimentRunID: event.ExperimentRunID, - ExperimentID: event.ExperimentID, - NotifyID: event.NotifyID, - Phase: executionData.Phase, - ResiliencyScore: &workflowRunMetrics.ResiliencyScore, - FaultsPassed: &workflowRunMetrics.FaultsPassed, - FaultsFailed: &workflowRunMetrics.FaultsFailed, - FaultsAwaited: &workflowRunMetrics.FaultsAwaited, - FaultsStopped: &workflowRunMetrics.FaultsStopped, - FaultsNA: &workflowRunMetrics.FaultsNA, - TotalFaults: &workflowRunMetrics.TotalExperiments, - ExecutionData: string(exeData), - RevisionID: event.RevisionID, - Completed: event.Completed, - Audit: mongodb.Audit{ - IsRemoved: isRemoved, - UpdatedAt: currentTime.UnixMilli(), - UpdatedBy: string(updatedBy), - CreatedBy: string(updatedBy), - }, - }) - if err != nil { - logrus.WithFields(logFields).Errorf("failed to update workflow run %v", err) - return err - } - - if count == 0 { - err := fmt.Sprintf("experiment run has been discarded due the duplicate event, workflowId: %s, workflowRunId: %s", event.ExperimentID, event.ExperimentRunID) - return errors.New(err) - } - - if err = session.CommitTransaction(sessionContext); err != nil { - logrus.WithFields(logFields).Errorf("failed to commit session transaction %v", err) - return err - } - return nil - }) - - if err != nil { - if abortErr := session.AbortTransaction(ctx); abortErr != nil { - logrus.WithFields(logFields).Errorf("failed to abort session transaction %v", err) - return "", abortErr - } - return "", err - } - - session.EndSession(ctx) - - return fmt.Sprintf("Experiment run received for for ExperimentID: %s, ExperimentRunID: %s", event.ExperimentID, event.ExperimentRunID), nil -} - // GetLogs query is used to fetch the logs from the cluster func (c *ChaosExperimentHandler) GetLogs(reqID string, pod model.PodLogRequest, r store.StateData) { data, err := json.Marshal(pod) diff --git a/chaoscenter/graphql/server/pkg/chaos_experiment/service.go b/chaoscenter/graphql/server/pkg/chaos_experiment/service.go index baac7fb70ec..ba0409a412f 100644 --- a/chaoscenter/graphql/server/pkg/chaos_experiment/service.go +++ b/chaoscenter/graphql/server/pkg/chaos_experiment/service.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "strconv" "strings" "time" @@ -24,8 +23,6 @@ import ( dbChaosInfra "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_infrastructure" - "github.com/litmuschaos/litmus/chaoscenter/graphql/server/utils" - "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" "github.com/ghodss/yaml" "github.com/google/uuid" @@ -41,14 +38,13 @@ type Service interface { ProcessExperimentCreation(ctx context.Context, input *model.ChaosExperimentRequest, username string, projectID string, wfType *dbChaosExperiment.ChaosExperimentType, revisionID string, r *store.StateData) error ProcessExperimentUpdate(workflow *model.ChaosExperimentRequest, username string, wfType *dbChaosExperiment.ChaosExperimentType, revisionID string, updateRevision bool, projectID string, r *store.StateData) error ProcessExperimentDelete(query bson.D, workflow dbChaosExperiment.ChaosExperimentRequest, username string, r *store.StateData) error - ProcessExperimentRunDelete(ctx context.Context, query bson.D, workflowRunID *string, experimentRun dbChaosExperimentRun.ChaosExperimentRun, workflow dbChaosExperiment.ChaosExperimentRequest, username string, r *store.StateData) error - ProcessCompletedExperimentRun(execData ExecutionData, wfID string, runID string) (ExperimentRunMetrics, error) } // chaosWorkflowService is the implementation of the chaos workflow service type chaosExperimentService struct { chaosExperimentOperator *dbChaosExperiment.Operator chaosInfrastructureOperator *dbChaosInfra.Operator + chaosExperimentRunOperator *dbChaosExperimentRun.Operator } // NewChaosExperimentService returns a new instance of the chaos workflow service @@ -313,7 +309,7 @@ func (c *chaosExperimentService) ProcessExperimentDelete(query bson.D, workflow } //Update chaosExperimentRuns collection - err = dbChaosExperimentRun.UpdateExperimentRunsWithQuery(sessionContext, bson.D{{"experiment_id", workflow.ExperimentID}}, update) + err = c.chaosExperimentRunOperator.UpdateExperimentRunsWithQuery(sessionContext, bson.D{{"experiment_id", workflow.ExperimentID}}, update) if err != nil { return err } @@ -339,96 +335,6 @@ func (c *chaosExperimentService) ProcessExperimentDelete(query bson.D, workflow return nil } - -// ProcessExperimentRunDelete deletes a workflow entry and updates the database -func (c *chaosExperimentService) ProcessExperimentRunDelete(ctx context.Context, query bson.D, workflowRunID *string, experimentRun dbChaosExperimentRun.ChaosExperimentRun, workflow dbChaosExperiment.ChaosExperimentRequest, username string, r *store.StateData) error { - update := bson.D{ - {"$set", bson.D{ - {"is_removed", experimentRun.IsRemoved}, - {"updated_at", time.Now().UnixMilli()}, - {"updated_by", username}, - }}, - } - - err := dbChaosExperimentRun.UpdateExperimentRunWithQuery(ctx, query, update) - if err != nil { - return err - } - if r != nil { - chaos_infrastructure.SendExperimentToSubscriber(experimentRun.ProjectID, &model.ChaosExperimentRequest{ - InfraID: workflow.InfraID, - }, &username, workflowRunID, "workflow_run_delete", r) - } - - return nil -} - -// ProcessCompletedExperimentRun calculates the Resiliency Score and returns the updated ExecutionData -func (c *chaosExperimentService) ProcessCompletedExperimentRun(execData ExecutionData, wfID string, runID string) (ExperimentRunMetrics, error) { - var weightSum, totalTestResult = 0, 0 - var result ExperimentRunMetrics - weightMap := map[string]int{} - - chaosExperiments, err := c.chaosExperimentOperator.GetExperiment(context.TODO(), bson.D{ - {"experiment_id", wfID}, - }) - if err != nil { - return result, fmt.Errorf("failed to get experiment from db on complete, error: %w", err) - } - for _, rev := range chaosExperiments.Revision { - if rev.RevisionID == execData.RevisionID { - for _, weights := range rev.Weightages { - weightMap[weights.FaultName] = weights.Weightage - // Total weight calculated for all experiments - weightSum = weightSum + weights.Weightage - } - } - } - - result.TotalExperiments = len(weightMap) - - for _, value := range execData.Nodes { - if value.Type == "ChaosEngine" { - experimentName := "" - if value.ChaosExp == nil { - continue - } - - for expName := range weightMap { - if strings.Contains(value.ChaosExp.EngineName, expName) { - experimentName = expName - } - } - weight, ok := weightMap[experimentName] - // probeSuccessPercentage will be included only if chaosData is present - if ok { - x, _ := strconv.Atoi(value.ChaosExp.ProbeSuccessPercentage) - totalTestResult += weight * x - } - if value.ChaosExp.FaultVerdict == "Pass" { - result.FaultsPassed += 1 - } - if value.ChaosExp.FaultVerdict == "Fail" { - result.FaultsFailed += 1 - } - if value.ChaosExp.FaultVerdict == "Awaited" { - result.FaultsAwaited += 1 - } - if value.ChaosExp.FaultVerdict == "Stopped" { - result.FaultsStopped += 1 - } - if value.ChaosExp.FaultVerdict == "N/A" || value.ChaosExp.FaultVerdict == "" { - result.FaultsNA += 1 - } - } - } - if weightSum != 0 { - result.ResiliencyScore = utils.Truncate(float64(totalTestResult) / float64(weightSum)) - } - - return result, nil -} - func processExperimentManifest(workflow *model.ChaosExperimentRequest, weights map[string]int, revID string) error { var ( newWeights []*model.WeightagesInput diff --git a/chaoscenter/graphql/server/pkg/choas_experiment_run/handler/handler.go b/chaoscenter/graphql/server/pkg/choas_experiment_run/handler/handler.go new file mode 100644 index 00000000000..b5b3c8441c1 --- /dev/null +++ b/chaoscenter/graphql/server/pkg/choas_experiment_run/handler/handler.go @@ -0,0 +1,1223 @@ +package handler + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "sort" + "strconv" + "strings" + "time" + + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/authorization" + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/chaos_infrastructure" + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/gitops" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readconcern" + "go.mongodb.org/mongo-driver/mongo/writeconcern" + + "github.com/ghodss/yaml" + chaosTypes "github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1" + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/graph/model" + + "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb" + dbChaosExperimentRun "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run" + + "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + + types "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/choas_experiment_run" + store "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/data-store" + dbChaosExperiment "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment" + + dbChaosInfra "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_infrastructure" + + "github.com/google/uuid" +) + +// ChaosExperimentRunHandler is the handler for chaos experiment +type ChaosExperimentRunHandler struct { + chaosExperimentRunService types.Service + infrastructureService chaos_infrastructure.Service + gitOpsService gitops.Service + chaosExperimentOperator *dbChaosExperiment.Operator + chaosExperimentRunOperator *dbChaosExperimentRun.Operator + mongodbOperator mongodb.MongoOperator +} + +// NewChaosExperimentRunHandler returns a new instance of ChaosWorkflowHandler +func NewChaosExperimentRunHandler( + chaosExperimentRunService types.Service, + infrastructureService chaos_infrastructure.Service, + gitOpsService gitops.Service, + chaosExperimentOperator *dbChaosExperiment.Operator, + chaosExperimentRunOperator *dbChaosExperimentRun.Operator, + mongodbOperator mongodb.MongoOperator, +) *ChaosExperimentRunHandler { + return &ChaosExperimentRunHandler{ + chaosExperimentRunService: chaosExperimentRunService, + infrastructureService: infrastructureService, + gitOpsService: gitOpsService, + chaosExperimentOperator: chaosExperimentOperator, + chaosExperimentRunOperator: chaosExperimentRunOperator, + mongodbOperator: mongodbOperator, + } +} + +// GetExperimentRun returns details of a requested experiment run +func (c *ChaosExperimentRunHandler) GetExperimentRun(ctx context.Context, projectID string, experimentRunID string) (*model.ExperimentRun, error) { + var pipeline mongo.Pipeline + + // Matching with identifiers + matchIdentifiersStage := bson.D{ + { + "$match", bson.D{ + {"experiment_run_id", experimentRunID}, + {"project_id", projectID}, + {"is_removed", false}, + }, + }, + } + pipeline = append(pipeline, matchIdentifiersStage) + + // Adds details of experiment + addExperimentDetails := bson.D{ + {"$lookup", + bson.D{ + {"from", "chaosExperiments"}, + {"let", bson.D{{"experimentID", "$experiment_id"}, {"revID", "$revision_id"}}}, + { + "pipeline", bson.A{ + bson.D{{"$match", bson.D{{"$expr", bson.D{{"$eq", bson.A{"$experiment_id", "$$experimentID"}}}}}}}, + bson.D{ + {"$project", bson.D{ + {"name", 1}, + {"is_custom_experiment", 1}, + {"revision", bson.D{{ + "$filter", bson.D{ + {"input", "$revision"}, + {"as", "revs"}, + {"cond", bson.D{{ + "$eq", bson.A{"$$revs.revision_id", "$$revID"}, + }}}, + }, + }}}, + }}, + }, + }, + }, + {"as", "experiment"}, + }, + }, + } + pipeline = append(pipeline, addExperimentDetails) + + // fetchKubernetesInfraDetailsStage adds kubernetes infra details of corresponding experiment_id to each document + fetchKubernetesInfraDetailsStage := bson.D{ + {"$lookup", bson.D{ + {"from", "chaosInfrastructures"}, + {"let", bson.M{"infraID": "$infra_id"}}, + { + "pipeline", bson.A{ + bson.D{ + {"$match", bson.D{ + {"$expr", bson.D{ + {"$eq", bson.A{"$infra_id", "$$infraID"}}, + }}, + }}, + }, + bson.D{ + {"$project", bson.D{ + {"token", 0}, + {"infra_ns_exists", 0}, + {"infra_sa_exists", 0}, + {"access_key", 0}, + }}, + }, + }, + }, + {"as", "kubernetesInfraDetails"}, + }}, + } + + pipeline = append(pipeline, fetchKubernetesInfraDetailsStage) + + // Call aggregation on pipeline + expRunCursor, err := c.chaosExperimentRunOperator.GetAggregateExperimentRuns(pipeline) + if err != nil { + return nil, errors.New("DB aggregate stage error: " + err.Error()) + } + + var ( + expRunResponse *model.ExperimentRun + expRunDetails []dbChaosExperiment.FlattenedExperimentRun + ) + + if err = expRunCursor.All(context.Background(), &expRunDetails); err != nil { + return nil, errors.New("error decoding experiment run cursor: " + err.Error()) + } + if len(expRunDetails) == 0 { + return nil, errors.New("no matching experiment run") + } + if len(expRunDetails[0].KubernetesInfraDetails) == 0 { + return nil, errors.New("no matching infra found for given experiment run") + } + + for _, wfRun := range expRunDetails { + var ( + weightages []*model.Weightages + workflowRunManifest string + ) + + if len(wfRun.ExperimentDetails[0].Revision) > 0 { + revision := wfRun.ExperimentDetails[0].Revision[0] + for _, v := range revision.Weightages { + weightages = append(weightages, &model.Weightages{ + FaultName: v.FaultName, + Weightage: v.Weightage, + }) + } + workflowRunManifest = revision.ExperimentManifest + } + var chaosInfrastructure *model.Infra + + if len(wfRun.KubernetesInfraDetails) > 0 { + infra := wfRun.KubernetesInfraDetails[0] + chaosInfrastructure = &model.Infra{ + InfraID: infra.InfraID, + Name: infra.Name, + EnvironmentID: infra.EnvironmentID, + Description: &infra.Description, + PlatformName: infra.PlatformName, + IsActive: infra.IsActive, + UpdatedAt: strconv.FormatInt(infra.UpdatedAt, 10), + CreatedAt: strconv.FormatInt(infra.CreatedAt, 10), + InfraNamespace: infra.InfraNamespace, + ServiceAccount: infra.ServiceAccount, + InfraScope: infra.InfraScope, + StartTime: infra.StartTime, + Version: infra.Version, + Tags: infra.Tags, + } + } + + expRunResponse = &model.ExperimentRun{ + ExperimentName: wfRun.ExperimentDetails[0].ExperimentName, + ExperimentID: wfRun.ExperimentID, + ExperimentRunID: wfRun.ExperimentRunID, + Weightages: weightages, + ExperimentManifest: workflowRunManifest, + ProjectID: wfRun.ProjectID, + Infra: chaosInfrastructure, + Phase: model.ExperimentRunStatus(wfRun.Phase), + ResiliencyScore: wfRun.ResiliencyScore, + FaultsPassed: wfRun.FaultsPassed, + FaultsFailed: wfRun.FaultsFailed, + FaultsAwaited: wfRun.FaultsAwaited, + FaultsStopped: wfRun.FaultsStopped, + FaultsNa: wfRun.FaultsNA, + TotalFaults: wfRun.TotalFaults, + ExecutionData: wfRun.ExecutionData, + IsRemoved: &wfRun.IsRemoved, + UpdatedBy: &model.UserDetails{ + Username: wfRun.UpdatedBy, + }, + UpdatedAt: strconv.FormatInt(wfRun.UpdatedAt, 10), + CreatedAt: strconv.FormatInt(wfRun.CreatedAt, 10), + } + } + + return expRunResponse, nil +} + +// ListExperimentRun returns all the workflow runs for matching identifiers from the DB +func (c *ChaosExperimentRunHandler) ListExperimentRun(projectID string, request model.ListExperimentRunRequest) (*model.ListExperimentRunResponse, error) { + var pipeline mongo.Pipeline + + // Matching with identifiers + matchIdentifiersStage := bson.D{ + { + "$match", bson.D{{ + "$and", bson.A{ + bson.D{ + {"project_id", projectID}, + }, + }, + }}, + }, + } + pipeline = append(pipeline, matchIdentifiersStage) + + // Match the workflowRunIds from the input array + if request.ExperimentRunIDs != nil && len(request.ExperimentRunIDs) != 0 { + matchWfRunIdStage := bson.D{ + {"$match", bson.D{ + {"experiment_run_id", bson.D{ + {"$in", request.ExperimentRunIDs}, + }}, + }}, + } + + pipeline = append(pipeline, matchWfRunIdStage) + } + + // Match the workflowIds from the input array + if request.ExperimentIDs != nil && len(request.ExperimentIDs) != 0 { + matchWfIdStage := bson.D{ + {"$match", bson.D{ + {"experiment_id", bson.D{ + {"$in", request.ExperimentIDs}, + }}, + }}, + } + + pipeline = append(pipeline, matchWfIdStage) + } + + // Filtering out the workflows that are deleted/removed + matchExpIsRemovedStage := bson.D{ + {"$match", bson.D{ + {"is_removed", bson.D{ + {"$eq", false}, + }}, + }}, + } + pipeline = append(pipeline, matchExpIsRemovedStage) + + addExperimentDetails := bson.D{ + { + "$lookup", + bson.D{ + {"from", "chaosExperiments"}, + {"let", bson.D{{"experimentID", "$experiment_id"}, {"revID", "$revision_id"}}}, + { + "pipeline", bson.A{ + bson.D{{"$match", bson.D{{"$expr", bson.D{{"$eq", bson.A{"$experiment_id", "$$experimentID"}}}}}}}, + bson.D{ + {"$project", bson.D{ + {"name", 1}, + {"experiment_type", 1}, + {"is_custom_experiment", 1}, + {"revision", bson.D{{ + "$filter", bson.D{ + {"input", "$revision"}, + {"as", "revs"}, + {"cond", bson.D{{ + "$eq", bson.A{"$$revs.revision_id", "$$revID"}, + }}}, + }, + }}}, + }}, + }, + }, + }, + {"as", "experiment"}, + }, + }, + } + pipeline = append(pipeline, addExperimentDetails) + + // Filtering based on multiple parameters + if request.Filter != nil { + + // Filtering based on workflow name + if request.Filter.ExperimentName != nil && *request.Filter.ExperimentName != "" { + matchWfNameStage := bson.D{ + {"$match", bson.D{ + {"experiment.name", bson.D{ + {"$regex", request.Filter.ExperimentName}, + }}, + }}, + } + pipeline = append(pipeline, matchWfNameStage) + } + + // Filtering based on workflow run ID + if request.Filter.ExperimentRunID != nil && *request.Filter.ExperimentRunID != "" { + matchWfRunIDStage := bson.D{ + {"$match", bson.D{ + {"experiment_run_id", bson.D{ + {"$regex", request.Filter.ExperimentRunID}, + }}, + }}, + } + pipeline = append(pipeline, matchWfRunIDStage) + } + + // Filtering based on workflow run status array + if len(request.Filter.ExperimentRunStatus) > 0 { + matchWfRunStatusStage := bson.D{ + {"$match", bson.D{ + {"phase", bson.D{ + {"$in", request.Filter.ExperimentRunStatus}, + }}, + }}, + } + pipeline = append(pipeline, matchWfRunStatusStage) + } + + // Filtering based on infraID + if request.Filter.InfraID != nil && *request.Filter.InfraID != "All" && *request.Filter.InfraID != "" { + matchInfraStage := bson.D{ + {"$match", bson.D{ + {"infra_id", request.Filter.InfraID}, + }}, + } + pipeline = append(pipeline, matchInfraStage) + } + + // Filtering based on phase + if request.Filter.ExperimentStatus != nil && *request.Filter.ExperimentStatus != "All" && *request.Filter.ExperimentStatus != "" { + filterWfRunPhaseStage := bson.D{ + {"$match", bson.D{ + {"phase", string(*request.Filter.ExperimentStatus)}, + }}, + } + pipeline = append(pipeline, filterWfRunPhaseStage) + } + + // Filtering based on date range + if request.Filter.DateRange != nil { + endDate := strconv.FormatInt(time.Now().UnixMilli(), 10) + if request.Filter.DateRange.EndDate != nil { + endDate = *request.Filter.DateRange.EndDate + } + filterWfRunDateStage := bson.D{ + { + "$match", + bson.D{{"updated_at", bson.D{ + {"$lte", endDate}, + {"$gte", request.Filter.DateRange.StartDate}, + }}}, + }, + } + pipeline = append(pipeline, filterWfRunDateStage) + } + } + + var sortStage bson.D + + switch { + case request.Sort != nil && request.Sort.Field == model.ExperimentSortingFieldTime: + // Sorting based on created time + if request.Sort.Ascending != nil && *request.Sort.Ascending { + sortStage = bson.D{ + {"$sort", bson.D{ + {"created_at", 1}, + }}, + } + } else { + sortStage = bson.D{ + {"$sort", bson.D{ + {"created_at", -1}, + }}, + } + } + case request.Sort != nil && request.Sort.Field == model.ExperimentSortingFieldName: + // Sorting based on ExperimentName time + if request.Sort.Ascending != nil && *request.Sort.Ascending { + sortStage = bson.D{ + {"$sort", bson.D{ + {"experiment.name", 1}, + }}, + } + } else { + sortStage = bson.D{ + {"$sort", bson.D{ + {"experiment.name", -1}, + }}, + } + } + default: + // Default sorting: sorts it by created_at time in descending order + sortStage = bson.D{ + {"$sort", bson.D{ + {"created_at", -1}, + }}, + } + } + + // fetchKubernetesInfraDetailsStage adds infra details of corresponding experiment_id to each document + fetchKubernetesInfraDetailsStage := bson.D{ + {"$lookup", bson.D{ + {"from", "chaosInfrastructures"}, + {"let", bson.M{"infraID": "$infra_id"}}, + { + "pipeline", bson.A{ + bson.D{ + {"$match", bson.D{ + {"$expr", bson.D{ + {"$eq", bson.A{"$infra_id", "$$infraID"}}, + }}, + }}, + }, + bson.D{ + {"$project", bson.D{ + {"token", 0}, + {"infra_ns_exists", 0}, + {"infra_sa_exists", 0}, + {"access_key", 0}, + }}, + }, + }, + }, + {"as", "kubernetesInfraDetails"}, + }}, + } + + pipeline = append(pipeline, fetchKubernetesInfraDetailsStage) + + // Pagination or adding a default limit of 15 if pagination not provided + paginatedExperiments := bson.A{ + sortStage, + } + + if request.Pagination != nil { + paginationSkipStage := bson.D{ + {"$skip", request.Pagination.Page * request.Pagination.Limit}, + } + paginationLimitStage := bson.D{ + {"$limit", request.Pagination.Limit}, + } + + paginatedExperiments = append(paginatedExperiments, paginationSkipStage, paginationLimitStage) + } else { + limitStage := bson.D{ + {"$limit", 15}, + } + + paginatedExperiments = append(paginatedExperiments, limitStage) + } + + // Add two stages where we first count the number of filtered workflow and then paginate the results + facetStage := bson.D{ + {"$facet", bson.D{ + {"total_filtered_experiment_runs", bson.A{ + bson.D{{"$count", "count"}}, + }}, + {"flattened_experiment_runs", paginatedExperiments}, + }}, + } + pipeline = append(pipeline, facetStage) + + // Call aggregation on pipeline + workflowsCursor, err := c.chaosExperimentRunOperator.GetAggregateExperimentRuns(pipeline) + if err != nil { + return nil, errors.New("DB aggregate stage error: " + err.Error()) + } + + var ( + result []*model.ExperimentRun + workflows []dbChaosExperiment.AggregatedExperimentRuns + ) + + if err = workflowsCursor.All(context.Background(), &workflows); err != nil || len(workflows) == 0 { + return &model.ListExperimentRunResponse{ + TotalNoOfExperimentRuns: 0, + ExperimentRuns: result, + }, errors.New("error decoding experiment runs cursor: " + err.Error()) + } + if len(workflows) == 0 { + return &model.ListExperimentRunResponse{ + TotalNoOfExperimentRuns: 0, + ExperimentRuns: result, + }, nil + } + + for _, workflow := range workflows[0].FlattenedExperimentRuns { + var ( + weightages []*model.Weightages + workflowRunManifest string + workflowType string + workflowName string + ) + + if len(workflow.ExperimentDetails) > 0 { + workflowType = string(workflow.ExperimentDetails[0].ExperimentType) + workflowName = workflow.ExperimentDetails[0].ExperimentName + if len(workflow.ExperimentDetails[0].Revision) > 0 { + revision := workflow.ExperimentDetails[0].Revision[0] + for _, v := range revision.Weightages { + weightages = append(weightages, &model.Weightages{ + FaultName: v.FaultName, + Weightage: v.Weightage, + }) + } + workflowRunManifest = revision.ExperimentManifest + } + } + var chaosInfrastructure *model.Infra + + if len(workflow.KubernetesInfraDetails) > 0 { + infra := workflow.KubernetesInfraDetails[0] + infraType := model.InfrastructureType(infra.InfraType) + chaosInfrastructure = &model.Infra{ + InfraID: infra.InfraID, + Name: infra.Name, + EnvironmentID: infra.EnvironmentID, + Description: &infra.Description, + PlatformName: infra.PlatformName, + IsActive: infra.IsActive, + UpdatedAt: strconv.FormatInt(infra.UpdatedAt, 10), + CreatedAt: strconv.FormatInt(infra.CreatedAt, 10), + InfraNamespace: infra.InfraNamespace, + ServiceAccount: infra.ServiceAccount, + InfraScope: infra.InfraScope, + StartTime: infra.StartTime, + Version: infra.Version, + Tags: infra.Tags, + InfraType: &infraType, + } + } + + newExperimentRun := model.ExperimentRun{ + ExperimentName: workflowName, + ExperimentType: &workflowType, + ExperimentID: workflow.ExperimentID, + ExperimentRunID: workflow.ExperimentRunID, + Weightages: weightages, + ExperimentManifest: workflowRunManifest, + ProjectID: workflow.ProjectID, + Infra: chaosInfrastructure, + Phase: model.ExperimentRunStatus(workflow.Phase), + ResiliencyScore: workflow.ResiliencyScore, + FaultsPassed: workflow.FaultsPassed, + FaultsFailed: workflow.FaultsFailed, + FaultsAwaited: workflow.FaultsAwaited, + FaultsStopped: workflow.FaultsStopped, + FaultsNa: workflow.FaultsNA, + TotalFaults: workflow.TotalFaults, + ExecutionData: workflow.ExecutionData, + IsRemoved: &workflow.IsRemoved, + UpdatedBy: &model.UserDetails{ + Username: workflow.UpdatedBy, + }, + UpdatedAt: strconv.FormatInt(workflow.UpdatedAt, 10), + CreatedAt: strconv.FormatInt(workflow.CreatedAt, 10), + } + result = append(result, &newExperimentRun) + } + + totalFilteredExperimentRunsCounter := 0 + if len(workflows) > 0 && len(workflows[0].TotalFilteredExperimentRuns) > 0 { + totalFilteredExperimentRunsCounter = workflows[0].TotalFilteredExperimentRuns[0].Count + } + + output := model.ListExperimentRunResponse{ + TotalNoOfExperimentRuns: totalFilteredExperimentRunsCounter, + ExperimentRuns: result, + } + + return &output, nil +} + +// RunChaosWorkFlow sends workflow run request(single run workflow only) to chaos_infra on workflow re-run request +func (c *ChaosExperimentRunHandler) RunChaosWorkFlow(ctx context.Context, projectID string, workflow dbChaosExperiment.ChaosExperimentRequest, r *store.StateData) (*model.RunChaosExperimentResponse, error) { + var notifyID string + infra, err := dbChaosInfra.NewInfrastructureOperator(c.mongodbOperator).GetInfra(workflow.InfraID) + if err != nil { + return nil, err + } + if !infra.IsActive { + return nil, errors.New("experiment re-run failed due to inactive infra") + } + + var ( + workflowManifest v1alpha1.Workflow + ) + + currentTime := time.Now().UnixMilli() + + if len(workflow.Revision) == 0 { + return nil, errors.New("no revisions found") + } + + sort.Slice(workflow.Revision, func(i, j int) bool { + return workflow.Revision[i].UpdatedAt > workflow.Revision[j].UpdatedAt + }) + + resKind := gjson.Get(workflow.Revision[0].ExperimentManifest, "kind").String() + if strings.ToLower(resKind) == "cronworkflow" { + //return nil, errors.New("cron-workflows cannot be re-run") + return &model.RunChaosExperimentResponse{NotifyID: notifyID}, c.RunCronExperiment(ctx, projectID, workflow, r) + } + notifyID = uuid.New().String() + + err = json.Unmarshal([]byte(workflow.Revision[0].ExperimentManifest), &workflowManifest) + if err != nil { + return nil, errors.New("failed to unmarshal workflow manifest") + } + + var resScore float64 = 0 + + if _, found := workflowManifest.Labels["infra_id"]; !found { + return nil, errors.New("failed to rerun the chaos experiment due to invalid metadata/labels. Check the troubleshooting guide or contact support") + } + workflowManifest.Labels["notify_id"] = notifyID + workflowManifest.Name = workflowManifest.Name + "-" + strconv.FormatInt(currentTime, 10) + + for i, template := range workflowManifest.Spec.Templates { + artifact := template.Inputs.Artifacts + if len(artifact) > 0 { + if artifact[0].Raw == nil { + continue + } + var data = artifact[0].Raw.Data + if len(data) > 0 { + + var ( + meta chaosTypes.ChaosEngine + annotation = make(map[string]string) + ) + err := yaml.Unmarshal([]byte(data), &meta) + if err != nil { + return nil, errors.New("failed to unmarshal chaosengine") + } + if strings.ToLower(meta.Kind) == "chaosengine" { + if meta.Annotations != nil { + annotation = meta.Annotations + } + meta.Annotations = annotation + + if meta.Labels == nil { + meta.Labels = map[string]string{ + "infra_id": workflow.InfraID, + "step_pod_name": "{{pod.name}}", + "workflow_run_id": "{{workflow.uid}}", + } + } else { + meta.Labels["infra_id"] = workflow.InfraID + meta.Labels["step_pod_name"] = "{{pod.name}}" + meta.Labels["workflow_run_id"] = "{{workflow.uid}}" + } + + res, err := yaml.Marshal(&meta) + if err != nil { + return nil, errors.New("failed to marshal chaosengine") + } + workflowManifest.Spec.Templates[i].Inputs.Artifacts[0].Raw.Data = string(res) + } + } + } + } + + // Updating updated_at field + filter := bson.D{ + {"experiment_id", workflow.ExperimentID}, + } + update := bson.D{ + { + "$set", bson.D{ + {"updated_at", currentTime}, + }, + }, + } + err = c.chaosExperimentOperator.UpdateChaosExperiment(context.Background(), filter, update) + if err != nil { + logrus.Error("Failed to update updated_at") + return nil, err + } + + executionData := types.ExecutionData{ + Name: workflowManifest.Name, + Phase: "Queued", + ExperimentID: workflow.ExperimentID, + } + + parsedData, err := json.Marshal(executionData) + if err != nil { + logrus.Error("Failed to parse execution data") + return nil, err + } + + tkn := ctx.Value(authorization.AuthKey).(string) + username, err := authorization.GetUsername(tkn) + var ( + wc = writeconcern.New(writeconcern.WMajority()) + rc = readconcern.Snapshot() + txnOpts = options.Transaction().SetWriteConcern(wc).SetReadConcern(rc) + ) + + session, err := mongodb.MgoClient.StartSession() + if err != nil { + logrus.Errorf("failed to start mongo session %v", err) + return nil, err + } + + err = mongo.WithSession(context.Background(), session, func(sessionContext mongo.SessionContext) error { + if err = session.StartTransaction(txnOpts); err != nil { + logrus.Errorf("failed to start mongo session transaction %v", err) + return err + } + + expRunDetail := []dbChaosExperiment.ExperimentRunDetail{ + { + Phase: executionData.Phase, + Completed: false, + ProjectID: projectID, + NotifyID: ¬ifyID, + Audit: mongodb.Audit{ + IsRemoved: false, + CreatedAt: currentTime, + CreatedBy: username, + UpdatedAt: currentTime, + UpdatedBy: username, + }, + }, + } + + filter = bson.D{ + {"experiment_id", workflow.ExperimentID}, + } + update = bson.D{ + { + "$set", bson.D{ + {"updated_at", currentTime}, + {"total_experiment_runs", workflow.TotalExperimentRuns + 1}, + }, + }, + { + "$push", bson.D{ + {"recent_experiment_run_details", bson.D{ + {"$each", expRunDetail}, + {"$position", 0}, + {"$slice", 10}, + }}, + }, + }, + } + + err = c.chaosExperimentOperator.UpdateChaosExperiment(sessionContext, filter, update) + if err != nil { + logrus.Error("Failed to update experiment collection") + } + + err = c.chaosExperimentRunOperator.CreateExperimentRun(sessionContext, dbChaosExperimentRun.ChaosExperimentRun{ + InfraID: workflow.InfraID, + ExperimentID: workflow.ExperimentID, + Phase: "Queued", + RevisionID: workflow.Revision[0].RevisionID, + ProjectID: projectID, + Audit: mongodb.Audit{ + IsRemoved: false, + CreatedAt: currentTime, + CreatedBy: username, + UpdatedAt: currentTime, + UpdatedBy: username, + }, + NotifyID: ¬ifyID, + Completed: false, + ResiliencyScore: &resScore, + ExecutionData: string(parsedData), + }) + if err != nil { + logrus.Error("Failed to create run operation in db") + return err + } + + if err = session.CommitTransaction(sessionContext); err != nil { + logrus.Errorf("failed to commit session transaction %v", err) + return err + } + return nil + }) + + if err != nil { + if abortErr := session.AbortTransaction(ctx); abortErr != nil { + logrus.Errorf("failed to abort session transaction %v", err) + return nil, abortErr + } + return nil, err + } + + session.EndSession(ctx) + + manifest, err := yaml.Marshal(workflowManifest) + if err != nil { + return nil, err + } + if r != nil { + chaos_infrastructure.SendExperimentToSubscriber(projectID, &model.ChaosExperimentRequest{ + ExperimentID: &workflow.ExperimentID, + ExperimentManifest: string(manifest), + InfraID: workflow.InfraID, + }, &username, nil, "create", r) + } + return &model.RunChaosExperimentResponse{ + NotifyID: notifyID, + }, nil +} + +func (c *ChaosExperimentRunHandler) RunCronExperiment(ctx context.Context, projectID string, workflow dbChaosExperiment.ChaosExperimentRequest, r *store.StateData) error { + var ( + //usrID = currentUser.Name + cronExperimentManifest v1alpha1.CronWorkflow + ) + + if len(workflow.Revision) == 0 { + return errors.New("no revisions found") + } + sort.Slice(workflow.Revision, func(i, j int) bool { + return workflow.Revision[i].UpdatedAt > workflow.Revision[j].UpdatedAt + }) + + err := json.Unmarshal([]byte(workflow.Revision[0].ExperimentManifest), &cronExperimentManifest) + if err != nil { + return errors.New("failed to unmarshal experiment manifest") + } + + for i, template := range cronExperimentManifest.Spec.WorkflowSpec.Templates { + artifact := template.Inputs.Artifacts + if len(artifact) > 0 { + if artifact[0].Raw == nil { + continue + } + var data = artifact[0].Raw.Data + if len(data) > 0 { + var meta chaosTypes.ChaosEngine + annotation := make(map[string]string) + err := yaml.Unmarshal([]byte(data), &meta) + if err != nil { + return errors.New("failed to unmarshal chaosengine") + } + if strings.ToLower(meta.Kind) == "chaosengine" { + if meta.Annotations != nil { + annotation = meta.Annotations + } + meta.Annotations = annotation + + if meta.Labels == nil { + meta.Labels = map[string]string{ + "infra_id": workflow.InfraID, + "step_pod_name": "{{pod.name}}", + "workflow_run_id": "{{workflow.uid}}", + } + } else { + meta.Labels["infra_id"] = workflow.InfraID + meta.Labels["step_pod_name"] = "{{pod.name}}" + meta.Labels["workflow_run_id"] = "{{workflow.uid}}" + } + res, err := yaml.Marshal(&meta) + if err != nil { + return errors.New("failed to marshal chaosengine") + } + cronExperimentManifest.Spec.WorkflowSpec.Templates[i].Inputs.Artifacts[0].Raw.Data = string(res) + } + } + } + } + + manifest, err := yaml.Marshal(cronExperimentManifest) + if err != nil { + return err + } + + tkn := ctx.Value(authorization.AuthKey).(string) + username, err := authorization.GetUsername(tkn) + + if r != nil { + chaos_infrastructure.SendExperimentToSubscriber(projectID, &model.ChaosExperimentRequest{ + ExperimentID: &workflow.ExperimentID, + ExperimentManifest: string(manifest), + InfraID: workflow.InfraID, + }, &username, nil, "create", r) + } + + return nil +} + +func (c *ChaosExperimentRunHandler) GetExperimentRunStats(ctx context.Context, projectID string) (*model.GetExperimentRunStatsResponse, error) { + var pipeline mongo.Pipeline + // Match with identifiers + matchIdentifierStage := bson.D{ + {"$match", bson.D{ + {"project_id", projectID}, + }}, + } + + pipeline = append(pipeline, matchIdentifierStage) + + // Group and counts total experiment runs by phase + groupByPhaseStage := bson.D{ + { + "$group", bson.D{ + {"_id", "$phase"}, + {"count", bson.D{ + {"$sum", 1}, + }}, + }, + }, + } + pipeline = append(pipeline, groupByPhaseStage) + // Call aggregation on pipeline + experimentRunCursor, err := c.chaosExperimentRunOperator.GetAggregateExperimentRuns(pipeline) + if err != nil { + return nil, err + } + + var res []dbChaosExperiment.AggregatedExperimentRunStats + + if err = experimentRunCursor.All(context.Background(), &res); err != nil || len(res) == 0 { + return nil, err + } + + resMap := map[string]int{ + "Completed": 0, + "Stopped": 0, + "Running": 0, + "Terminated": 0, + "Error": 0, + } + + totalExperimentRuns := 0 + for _, phase := range res { + resMap[phase.Id] = phase.Count + totalExperimentRuns = totalExperimentRuns + phase.Count + } + + return &model.GetExperimentRunStatsResponse{ + TotalExperimentRuns: totalExperimentRuns, + TotalCompletedExperimentRuns: resMap["Completed"], + TotalTerminatedExperimentRuns: resMap["Terminated"], + TotalRunningExperimentRuns: resMap["Running"], + TotalStoppedExperimentRuns: resMap["Stopped"], + TotalErroredExperimentRuns: resMap["Error"], + }, nil +} + +func (c *ChaosExperimentRunHandler) ChaosExperimentRunEvent(event model.ExperimentRunRequest) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + experiment, err := c.chaosExperimentOperator.GetExperiment(ctx, bson.D{ + {"experiment_id", event.ExperimentID}, + {"is_removed", false}, + }) + if err != nil { + if err == mongo.ErrNoDocuments { + return fmt.Sprintf("no experiment found with experimentID: %s, experiment run discarded: %s", event.ExperimentID, event.ExperimentRunID), nil + } + return "", err + } + + logFields := logrus.Fields{ + "projectID": experiment.ProjectID, + "experimentID": experiment.ExperimentID, + "experimentRunID": event.ExperimentRunID, + "infraID": experiment.InfraID, + } + + logrus.WithFields(logFields).Info("new workflow event received") + + var ( + executionData types.ExecutionData + exeData []byte + ) + + // Parse and store execution data + if event.ExecutionData != "" { + exeData, err = base64.StdEncoding.DecodeString(event.ExecutionData) + if err != nil { + logrus.WithFields(logFields).Warn("Failed to decode execution data: ", err) + + //Required for backward compatibility of subscribers + //which are not sending execution data in base64 encoded format + //remove it once all subscribers are updated + exeData = []byte(event.ExperimentID) + } + err = json.Unmarshal(exeData, &executionData) + if err != nil { + return "", err + } + } + + var workflowRunMetrics types.ExperimentRunMetrics + // Resiliency Score will be calculated only if workflow execution is completed + if event.Completed { + workflowRunMetrics, err = c.chaosExperimentRunService.ProcessCompletedExperimentRun(executionData, event.ExperimentID, event.ExperimentRunID) + if err != nil { + logrus.WithFields(logFields).Errorf("failed to process completed workflow run %v", err) + return "", err + } + + } + + //TODO check for mongo transaction + var ( + wc = writeconcern.New(writeconcern.WMajority()) + rc = readconcern.Snapshot() + txnOpts = options.Transaction().SetWriteConcern(wc).SetReadConcern(rc) + ) + + session, err := mongodb.MgoClient.StartSession() + if err != nil { + logrus.WithFields(logFields).Errorf("failed to start mongo session %v", err) + return "", err + } + // + var ( + isRemoved = false + currentTime = time.Now() + ) + + err = mongo.WithSession(ctx, session, func(sessionContext mongo.SessionContext) error { + if err = session.StartTransaction(txnOpts); err != nil { + logrus.WithFields(logFields).Errorf("failed to start mongo session transaction %v", err) + return err + } + + query := bson.D{ + {"experiment_id", event.ExperimentID}, + {"experiment_run_id", event.ExperimentRunID}, + } + + if event.NotifyID != nil { + query = bson.D{ + {"experiment_id", event.ExperimentID}, + {"notify_id", event.NotifyID}, + } + } + + experimentRunCount, err := c.chaosExperimentRunOperator.CountExperimentRuns(sessionContext, query) + if err != nil { + return err + } + fmt.Println("event", event.UpdatedBy) + updatedBy, err := base64.RawURLEncoding.DecodeString(event.UpdatedBy) + if err != nil { + logrus.Fatalf("Failed to parse updated by field %v", err) + } + expRunDetail := []dbChaosExperiment.ExperimentRunDetail{ + { + Phase: executionData.Phase, + ResiliencyScore: &workflowRunMetrics.ResiliencyScore, + ExperimentRunID: event.ExperimentRunID, + Completed: false, + Audit: mongodb.Audit{ + IsRemoved: false, + CreatedAt: time.Now().UnixMilli(), + UpdatedAt: time.Now().UnixMilli(), + UpdatedBy: string(updatedBy), + }, + }, + } + if experimentRunCount == 0 { + filter := bson.D{ + {"experiment_id", event.ExperimentID}, + } + update := bson.D{ + { + "$set", bson.D{ + {"updated_at", time.Now().UnixMilli()}, + {"total_experiment_runs", experiment.TotalExperimentRuns + 1}, + }, + }, + { + "$push", bson.D{ + {"recent_experiment_run_details", bson.D{ + {"$each", expRunDetail}, + {"$position", 0}, + {"$slice", 10}, + }}, + }, + }, + } + + err = c.chaosExperimentOperator.UpdateChaosExperiment(sessionContext, filter, update) + if err != nil { + logrus.Error("Failed to update experiment collection") + return err + } + } else if experimentRunCount > 0 { + filter := bson.D{ + {"experiment_id", event.ExperimentID}, + {"recent_experiment_run_details.experiment_run_id", event.ExperimentRunID}, + {"recent_experiment_run_details.completed", false}, + } + if event.NotifyID != nil { + filter = bson.D{ + {"experiment_id", event.ExperimentID}, + {"recent_experiment_run_details.completed", false}, + {"recent_experiment_run_details.notify_id", event.NotifyID}, + } + } + + update := bson.D{ + { + "$set", bson.D{ + {"recent_experiment_run_details.$.phase", executionData.Phase}, + {"recent_experiment_run_details.$.completed", event.Completed}, + {"recent_experiment_run_details.$.experiment_run_id", event.ExperimentRunID}, + {"recent_experiment_run_details.$.resiliency_score", workflowRunMetrics.ResiliencyScore}, + {"recent_experiment_run_details.$.updated_at", currentTime.UnixMilli()}, + {"recent_experiment_run_details.$.updated_by", string(updatedBy)}, + }, + }, + } + + err = c.chaosExperimentOperator.UpdateChaosExperiment(sessionContext, filter, update) + if err != nil { + logrus.Error("Failed to update experiment collection") + return err + } + } + + count, err := c.chaosExperimentRunOperator.UpdateExperimentRun(sessionContext, dbChaosExperimentRun.ChaosExperimentRun{ + InfraID: event.InfraID.InfraID, + ProjectID: experiment.ProjectID, + ExperimentRunID: event.ExperimentRunID, + ExperimentID: event.ExperimentID, + NotifyID: event.NotifyID, + Phase: executionData.Phase, + ResiliencyScore: &workflowRunMetrics.ResiliencyScore, + FaultsPassed: &workflowRunMetrics.FaultsPassed, + FaultsFailed: &workflowRunMetrics.FaultsFailed, + FaultsAwaited: &workflowRunMetrics.FaultsAwaited, + FaultsStopped: &workflowRunMetrics.FaultsStopped, + FaultsNA: &workflowRunMetrics.FaultsNA, + TotalFaults: &workflowRunMetrics.TotalExperiments, + ExecutionData: string(exeData), + RevisionID: event.RevisionID, + Completed: event.Completed, + Audit: mongodb.Audit{ + IsRemoved: isRemoved, + UpdatedAt: currentTime.UnixMilli(), + UpdatedBy: string(updatedBy), + CreatedBy: string(updatedBy), + }, + }) + if err != nil { + logrus.WithFields(logFields).Errorf("failed to update workflow run %v", err) + return err + } + + if count == 0 { + err := fmt.Sprintf("experiment run has been discarded due the duplicate event, workflowId: %s, workflowRunId: %s", event.ExperimentID, event.ExperimentRunID) + return errors.New(err) + } + + if err = session.CommitTransaction(sessionContext); err != nil { + logrus.WithFields(logFields).Errorf("failed to commit session transaction %v", err) + return err + } + return nil + }) + + if err != nil { + if abortErr := session.AbortTransaction(ctx); abortErr != nil { + logrus.WithFields(logFields).Errorf("failed to abort session transaction %v", err) + return "", abortErr + } + return "", err + } + + session.EndSession(ctx) + + return fmt.Sprintf("Experiment run received for for ExperimentID: %s, ExperimentRunID: %s", event.ExperimentID, event.ExperimentRunID), nil +} diff --git a/chaoscenter/graphql/server/pkg/choas_experiment_run/service.go b/chaoscenter/graphql/server/pkg/choas_experiment_run/service.go new file mode 100644 index 00000000000..8434c8e6ee6 --- /dev/null +++ b/chaoscenter/graphql/server/pkg/choas_experiment_run/service.go @@ -0,0 +1,133 @@ +package chaos_experiment_run + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/chaos_infrastructure" + + dbChaosExperimentRun "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run" + + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/graph/model" + store "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/data-store" + dbChaosExperiment "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment" + + dbChaosInfra "github.com/litmuschaos/litmus/chaoscenter/graphql/server/pkg/database/mongodb/chaos_infrastructure" + + "github.com/litmuschaos/litmus/chaoscenter/graphql/server/utils" + + "go.mongodb.org/mongo-driver/bson" +) + +type Service interface { + ProcessExperimentRunDelete(ctx context.Context, query bson.D, workflowRunID *string, experimentRun dbChaosExperimentRun.ChaosExperimentRun, workflow dbChaosExperiment.ChaosExperimentRequest, username string, r *store.StateData) error + ProcessCompletedExperimentRun(execData ExecutionData, wfID string, runID string) (ExperimentRunMetrics, error) +} + +// chaosWorkflowService is the implementation of the chaos workflow service +type chaosExperimentRunService struct { + chaosExperimentOperator *dbChaosExperiment.Operator + chaosInfrastructureOperator *dbChaosInfra.Operator + chaosExperimentRunOperator *dbChaosExperimentRun.Operator +} + +// NewChaosExperimentRunService returns a new instance of the chaos workflow run service +func NewChaosExperimentRunService(chaosWorkflowOperator *dbChaosExperiment.Operator, clusterOperator *dbChaosInfra.Operator, chaosExperimentRunOperator *dbChaosExperimentRun.Operator) Service { + return &chaosExperimentRunService{ + chaosExperimentOperator: chaosWorkflowOperator, + chaosInfrastructureOperator: clusterOperator, + chaosExperimentRunOperator: chaosExperimentRunOperator, + } +} + +// ProcessExperimentRunDelete deletes a workflow entry and updates the database +func (c *chaosExperimentRunService) ProcessExperimentRunDelete(ctx context.Context, query bson.D, workflowRunID *string, experimentRun dbChaosExperimentRun.ChaosExperimentRun, workflow dbChaosExperiment.ChaosExperimentRequest, username string, r *store.StateData) error { + update := bson.D{ + {"$set", bson.D{ + {"is_removed", experimentRun.IsRemoved}, + {"updated_at", time.Now().UnixMilli()}, + {"updated_by", username}, + }}, + } + + err := c.chaosExperimentRunOperator.UpdateExperimentRunWithQuery(ctx, query, update) + if err != nil { + return err + } + if r != nil { + chaos_infrastructure.SendExperimentToSubscriber(experimentRun.ProjectID, &model.ChaosExperimentRequest{ + InfraID: workflow.InfraID, + }, &username, workflowRunID, "workflow_run_delete", r) + } + + return nil +} + +// ProcessCompletedExperimentRun calculates the Resiliency Score and returns the updated ExecutionData +func (c *chaosExperimentRunService) ProcessCompletedExperimentRun(execData ExecutionData, wfID string, runID string) (ExperimentRunMetrics, error) { + var weightSum, totalTestResult = 0, 0 + var result ExperimentRunMetrics + weightMap := map[string]int{} + + chaosExperiments, err := c.chaosExperimentOperator.GetExperiment(context.TODO(), bson.D{ + {"experiment_id", wfID}, + }) + if err != nil { + return result, fmt.Errorf("failed to get experiment from db on complete, error: %w", err) + } + for _, rev := range chaosExperiments.Revision { + if rev.RevisionID == execData.RevisionID { + for _, weights := range rev.Weightages { + weightMap[weights.FaultName] = weights.Weightage + // Total weight calculated for all experiments + weightSum = weightSum + weights.Weightage + } + } + } + + result.TotalExperiments = len(weightMap) + + for _, value := range execData.Nodes { + if value.Type == "ChaosEngine" { + experimentName := "" + if value.ChaosExp == nil { + continue + } + + for expName := range weightMap { + if strings.Contains(value.ChaosExp.EngineName, expName) { + experimentName = expName + } + } + weight, ok := weightMap[experimentName] + // probeSuccessPercentage will be included only if chaosData is present + if ok { + x, _ := strconv.Atoi(value.ChaosExp.ProbeSuccessPercentage) + totalTestResult += weight * x + } + if value.ChaosExp.FaultVerdict == "Pass" { + result.FaultsPassed += 1 + } + if value.ChaosExp.FaultVerdict == "Fail" { + result.FaultsFailed += 1 + } + if value.ChaosExp.FaultVerdict == "Awaited" { + result.FaultsAwaited += 1 + } + if value.ChaosExp.FaultVerdict == "Stopped" { + result.FaultsStopped += 1 + } + if value.ChaosExp.FaultVerdict == "N/A" || value.ChaosExp.FaultVerdict == "" { + result.FaultsNA += 1 + } + } + } + if weightSum != 0 { + result.ResiliencyScore = utils.Truncate(float64(totalTestResult) / float64(weightSum)) + } + + return result, nil +} diff --git a/chaoscenter/graphql/server/pkg/choas_experiment_run/types.go b/chaoscenter/graphql/server/pkg/choas_experiment_run/types.go new file mode 100644 index 00000000000..6229d1c60ba --- /dev/null +++ b/chaoscenter/graphql/server/pkg/choas_experiment_run/types.go @@ -0,0 +1,130 @@ +package chaos_experiment_run + +import ( + chaosTypes "github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1" +) + +type ExperimentRunMetrics struct { + ResiliencyScore float64 `json:"resiliency_score"` + FaultsPassed int `json:"faults_passed"` + FaultsFailed int `json:"faults_failed"` + FaultsAwaited int `json:"faults_awaited"` + FaultsStopped int `json:"faults_stopped"` + FaultsNA int `json:"experiments_na"` + TotalExperiments int `json:"total_faults"` +} + +type ExecutionData struct { + ExperimentType string `json:"experimentType"` + ExperimentID string `json:"experimentID"` + EventType string `json:"eventType"` + RevisionID string `json:"revisionID"` + UID string `json:"uid"` + Namespace string `json:"namespace"` + Name string `json:"name"` + CreationTimestamp string `json:"creationTimestamp"` + Phase string `json:"phase"` + Message string `json:"message"` + StartedAt string `json:"startedAt"` + FinishedAt string `json:"finishedAt"` + Nodes map[string]Node `json:"nodes"` +} + +// Node represents each node/step data +type Node struct { + Name string `json:"name"` + Phase string `json:"phase"` + Message string `json:"message"` + StartedAt string `json:"startedAt"` + FinishedAt string `json:"finishedAt"` + Children []string `json:"children"` + Type string `json:"type"` + ChaosExp *ChaosData `json:"chaos_data,omitempty"` +} + +// ChaosData is the data we get from chaos exporter +type ChaosData struct { + EngineUID string `json:"engineUID"` + EngineContext string `json:"engineContext"` + EngineName string `json:"engineName"` + Namespace string `json:"namespace"` + FaultName string `json:"faultName"` + FaultStatus string `json:"faultStatus"` + LastUpdatedAt string `json:"lastUpdatedAt"` + FaultVerdict string `json:"faultVerdict"` + ExperimentPod string `json:"experimentPod"` + RunnerPod string `json:"runnerPod"` + ProbeSuccessPercentage string `json:"probeSuccessPercentage"` + FailStep string `json:"failStep"` + ChaosResult *chaosTypes.ChaosResult `json:"chaosResult"` +} + +type ExperimentSyncExternalData struct { + ExperimentID string `json:"experiment_id"` + ExperimentRunID string `json:"experiment_run_id"` + ExperimentRunIDs []*string `json:"experiment_run_ids"` +} + +// LatestExperimentRun represents the details of the latest experiment run +type LatestExperimentRun struct { + ExperimentRunID string `bson:"experiment_run_id"` + // Resiliency score of the experiment + ResiliencyScore *float64 `bson:"resiliency_score"` + // Timestamp at which experiment run was last updated + LastUpdated string `bson:"updated_at"` + // Phase of the experiment run + Phase string `bson:"phase"` +} + +// AverageResScore represents the avg_resiliency_score and total_experiment_runs of a experiment +type AverageResScore struct { + ID string `bson:"_id"` + Avg float64 `bson:"avg_resiliency_score"` + TotalExperimentRuns int `bson:"total_experiment_runs"` +} + +// experimentRunDetails is used to decode mongo cursor consisting of experiment run details +type experimentRunDetails struct { + ID string `bson:"_id"` + ExperimentRunDetails LatestExperimentRun `bson:"experiment_run_details"` +} + +// ExperimentDetails is used to decode mongo cursor consisting of experiment details +type ExperimentDetails struct { + ID string `bson:"_id"` + AverageResScore []AverageResScore `bson:"avg_resiliency_score"` + PercentageChange float64 `bson:"percentage_change"` + LatestExperimentRun []experimentRunDetails `bson:"latest_experiment_run"` +} + +// LastRunDetails represents the details of latest experiment run. +// avg_resiliency_score and percentage_change in resiliency_score of a experiment +type LastRunDetails struct { + ID string `bson:"_id"` + AvgResScore float64 `bson:"avg_resiliency_score"` + PercentageChange float64 `bson:"percentage_change"` + LatestExperimentRun LatestExperimentRun `bson:"latest_experiment_run"` +} + +type StopExperimentInputs struct { + ExperimentName string `json:"experiment_name"` + ExperimentID string `json:"experiment_id"` + ExperimentRunIDs []string `json:"experiment_run_ids"` +} + +type InfraDetails struct { + InfraID string `json:"infra_id"` + Version string `json:"version"` +} + +type ProbeDetailsForAnalytics struct { + Name string `json:"probe_name"` + Type string `json:"probe_type"` + Mode string `json:"probe_mode"` +} + +type FaultDetailsForAnalytics struct { + FaultName string `json:"fault_name"` + TotalProbes int `json:"total_probes"` + Probes []ProbeDetailsForAnalytics `json:"probes"` +} diff --git a/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run/operations.go b/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run/operations.go index 0e5fa1847b5..3f69e9adc26 100644 --- a/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run/operations.go +++ b/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run/operations.go @@ -14,7 +14,17 @@ var ( backgroundContext = context.Background() ) -func CreateExperimentRun(ctx context.Context, wfRun ChaosExperimentRun) error { +type Operator struct { + operator mongodb.MongoOperator +} + +func NewChaosExperimentRunOperator(mongodbOperator mongodb.MongoOperator) *Operator { + return &Operator{ + operator: mongodbOperator, + } +} + +func (c *Operator) CreateExperimentRun(ctx context.Context, wfRun ChaosExperimentRun) error { err := mongodb.Operator.Create(ctx, mongodb.ChaosExperimentRunsCollection, wfRun) if err != nil { return err @@ -22,7 +32,7 @@ func CreateExperimentRun(ctx context.Context, wfRun ChaosExperimentRun) error { return nil } -func GetExperimentRuns(query bson.D) ([]ChaosExperimentRun, error) { +func (c *Operator) GetExperimentRuns(query bson.D) ([]ChaosExperimentRun, error) { ctx, cancel := context.WithTimeout(backgroundContext, 10*time.Second) defer cancel() var experiments []ChaosExperimentRun @@ -39,7 +49,7 @@ func GetExperimentRuns(query bson.D) ([]ChaosExperimentRun, error) { return experiments, nil } -func CountExperimentRuns(ctx context.Context, query bson.D) (int64, error) { +func (c *Operator) CountExperimentRuns(ctx context.Context, query bson.D) (int64, error) { results, err := mongodb.Operator.CountDocuments(ctx, mongodb.ChaosExperimentRunsCollection, query) if err != nil { return 0, err @@ -47,7 +57,7 @@ func CountExperimentRuns(ctx context.Context, query bson.D) (int64, error) { return results, nil } -func GetExperimentRun(query bson.D) (ChaosExperimentRun, error) { +func (c *Operator) GetExperimentRun(query bson.D) (ChaosExperimentRun, error) { ctx, cancel := context.WithTimeout(backgroundContext, 10*time.Second) defer cancel() @@ -65,7 +75,7 @@ func GetExperimentRun(query bson.D) (ChaosExperimentRun, error) { } // UpdateExperimentRun takes experimentID and wfRun parameters to update the experiment run details in the database -func UpdateExperimentRun(ctx context.Context, wfRun ChaosExperimentRun) (int, error) { +func (c *Operator) UpdateExperimentRun(ctx context.Context, wfRun ChaosExperimentRun) (int, error) { query := bson.D{ {"experiment_id", wfRun.ExperimentID}, {"experiment_run_id", wfRun.ExperimentRunID}, @@ -134,7 +144,7 @@ func UpdateExperimentRun(ctx context.Context, wfRun ChaosExperimentRun) (int, er return updateCount, nil } -func UpdateExperimentRunWithQuery(ctx context.Context, query bson.D, update bson.D) error { +func (c *Operator) UpdateExperimentRunWithQuery(ctx context.Context, query bson.D, update bson.D) error { _, err := mongodb.Operator.Update(ctx, mongodb.ChaosExperimentRunsCollection, query, update) if err != nil { return err @@ -143,7 +153,7 @@ func UpdateExperimentRunWithQuery(ctx context.Context, query bson.D, update bson return nil } -func UpdateExperimentRunsWithQuery(ctx context.Context, query bson.D, update bson.D) error { +func (c *Operator) UpdateExperimentRunsWithQuery(ctx context.Context, query bson.D, update bson.D) error { _, err := mongodb.Operator.UpdateMany(ctx, mongodb.ChaosExperimentRunsCollection, query, update) if err != nil { @@ -154,7 +164,7 @@ func UpdateExperimentRunsWithQuery(ctx context.Context, query bson.D, update bso } // GetExperimentRunsByInfraID takes a infraID parameter to retrieve the experiment details from the database -func GetExperimentRunsByInfraID(infraID string) ([]ChaosExperimentRun, error) { +func (c *Operator) GetExperimentRunsByInfraID(infraID string) ([]ChaosExperimentRun, error) { ctx, cancel := context.WithTimeout(backgroundContext, 10*time.Second) defer cancel() @@ -173,7 +183,7 @@ func GetExperimentRunsByInfraID(infraID string) ([]ChaosExperimentRun, error) { } // GetAggregateExperimentRuns takes a mongo pipeline to retrieve the experiment details from the database -func GetAggregateExperimentRuns(pipeline mongo.Pipeline) (*mongo.Cursor, error) { +func (c *Operator) GetAggregateExperimentRuns(pipeline mongo.Pipeline) (*mongo.Cursor, error) { ctx, cancel := context.WithTimeout(backgroundContext, 10*time.Second) defer cancel() diff --git a/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run/schema.go b/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run/schema.go index 1aebf402172..01487925651 100644 --- a/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run/schema.go +++ b/chaoscenter/graphql/server/pkg/database/mongodb/chaos_experiment_run/schema.go @@ -22,3 +22,27 @@ type ChaosExperimentRun struct { TotalFaults *int `bson:"total_faults,omitempty"` Completed bool `bson:"completed"` } + +type TotalFilteredData struct { + Count int `bson:"count"` +} + +type WeightagesInput struct { + ExperimentName string `bson:"experiment_name"` + Weightage int `bson:"weightage"` +} + +type FlattenedExperimentRun struct { + ProjectID string `bson:"project_id"` + InfraID string `bson:"infra_id"` + ExperimentRunID string `bson:"experiment_run_id"` + ExperimentID string `bson:"experiment_id"` + ExperimentName string `bson:"experiment_name"` + CronSyntax string `bson:"cronSyntax"` + Weightages []*WeightagesInput `bson:"weightages"` + IsCustomWorkflow bool `bson:"isCustomWorkflow"` + UpdatedAt string `bson:"updated_at"` + CreatedAt string `bson:"created_at"` + ExperimentRuns ChaosExperimentRun `bson:"experiment_runs"` + IsRemoved bool `bson:"isRemoved"` +}