diff --git a/cmd/task/main.go b/cmd/task/main.go index e31f03b83bafe6af209aa2a8d75d008fe0418428..7e5d905c8a1eea722d50cc626bbf7a989b454b1e 100644 --- a/cmd/task/main.go +++ b/cmd/task/main.go @@ -22,8 +22,10 @@ import ( goahealthsrv "code.vereign.com/gaiax/tsa/task/gen/http/health/server" goaopenapisrv "code.vereign.com/gaiax/tsa/task/gen/http/openapi/server" goatasksrv "code.vereign.com/gaiax/tsa/task/gen/http/task/server" + goatasklistsrv "code.vereign.com/gaiax/tsa/task/gen/http/task_list/server" "code.vereign.com/gaiax/tsa/task/gen/openapi" goatask "code.vereign.com/gaiax/tsa/task/gen/task" + goatasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" "code.vereign.com/gaiax/tsa/task/internal/clients/cache" "code.vereign.com/gaiax/tsa/task/internal/clients/policy" "code.vereign.com/gaiax/tsa/task/internal/config" @@ -31,6 +33,7 @@ import ( "code.vereign.com/gaiax/tsa/task/internal/service" "code.vereign.com/gaiax/tsa/task/internal/service/health" "code.vereign.com/gaiax/tsa/task/internal/service/task" + "code.vereign.com/gaiax/tsa/task/internal/service/tasklist" "code.vereign.com/gaiax/tsa/task/internal/storage" ) @@ -86,22 +89,26 @@ func main() { // create services var ( - taskSvc goatask.Service - healthSvc goahealth.Service + taskSvc goatask.Service + taskListSvc goatasklist.Service + healthSvc goahealth.Service ) { taskSvc = task.New(storage, storage, cache, logger) + taskListSvc = tasklist.New(storage, storage, logger) healthSvc = health.New() } // create endpoints var ( - taskEndpoints *goatask.Endpoints - healthEndpoints *goahealth.Endpoints - openapiEndpoints *openapi.Endpoints + taskEndpoints *goatask.Endpoints + taskListEndpoints *goatasklist.Endpoints + healthEndpoints *goahealth.Endpoints + openapiEndpoints *openapi.Endpoints ) { taskEndpoints = goatask.NewEndpoints(taskSvc) + taskListEndpoints = goatasklist.NewEndpoints(taskListSvc) healthEndpoints = goahealth.NewEndpoints(healthSvc) openapiEndpoints = openapi.NewEndpoints(nil) } @@ -124,18 +131,21 @@ func main() { // the service input and output data structures to HTTP requests and // responses. var ( - taskServer *goatasksrv.Server - healthServer *goahealthsrv.Server - openapiServer *goaopenapisrv.Server + taskServer *goatasksrv.Server + taskListServer *goatasklistsrv.Server + healthServer *goahealthsrv.Server + openapiServer *goaopenapisrv.Server ) { taskServer = goatasksrv.New(taskEndpoints, mux, dec, enc, nil, errFormatter) + taskListServer = goatasklistsrv.New(taskListEndpoints, mux, dec, enc, nil, errFormatter) healthServer = goahealthsrv.New(healthEndpoints, mux, dec, enc, nil, errFormatter) openapiServer = goaopenapisrv.New(openapiEndpoints, mux, dec, enc, nil, errFormatter, nil, nil) } // Configure the mux. goatasksrv.Mount(mux, taskServer) + goatasklistsrv.Mount(mux, taskListServer) goahealthsrv.Mount(mux, healthServer) goaopenapisrv.Mount(mux, openapiServer) diff --git a/design/design.go b/design/design.go index f78f0680296dcbe79ae5a2edd5e413d12446c117..19bce998ec88c06dcd99234fa87b4ca34afb399f 100644 --- a/design/design.go +++ b/design/design.go @@ -20,8 +20,8 @@ var _ = Service("task", func() { Method("Create", func() { Description("Create a task and put it in a queue for execution.") - Payload(CreateRequest) - Result(CreateResult) + Payload(CreateTaskRequest) + Result(CreateTaskResult) HTTP(func() { POST("/v1/task/{taskName}") @@ -49,6 +49,30 @@ var _ = Service("task", func() { }) }) +var _ = Service("taskList", func() { + Description("TaskList service provides endpoints to work with task lists.") + + Method("Create", func() { + Description("Create a task list and corresponding tasks and put them in respective queues for execution.") + Payload(CreateTaskListRequest) + Result(CreateTaskListResult) + HTTP(func() { + POST("/v1/taskList/{taskListName}") + + Header("cacheNamespace:x-cache-namespace", String, "Cache key namespace", func() { + Example("login") + }) + Header("cacheScope:x-cache-scope", String, "Cache key scope", func() { + Example("user") + }) + + Body("data") + + Response(StatusOK) + }) + }) +}) + var _ = Service("health", func() { Description("Health service provides health check endpoints.") diff --git a/design/types.go b/design/types.go index 7625fe78437e5f08fb4a3a4c2c9cce2f699b316f..7178a145f82034751203139cd631fcca56bdbf0d 100644 --- a/design/types.go +++ b/design/types.go @@ -3,7 +3,7 @@ package design import . "goa.design/goa/v3/dsl" -var CreateRequest = Type("CreateRequest", func() { +var CreateTaskRequest = Type("CreateTaskRequest", func() { Field(1, "taskName", String, "Task name.") Field(2, "data", Any, "Data contains JSON payload that will be used for task execution.") Field(3, "cacheNamespace", String, "Cache key namespace.") @@ -11,7 +11,7 @@ var CreateRequest = Type("CreateRequest", func() { Required("taskName", "data") }) -var CreateResult = Type("CreateResult", func() { +var CreateTaskResult = Type("CreateTaskResult", func() { Field(1, "taskID", String, "Unique task identifier.") Required("taskID") }) @@ -20,3 +20,16 @@ var TaskResultRequest = Type("TaskResultRequest", func() { Field(1, "taskID", String, "Unique task identifier.") Required("taskID") }) + +var CreateTaskListRequest = Type("CreateTaskListRequest", func() { + Field(1, "taskListName", String, "TaskList name.") + Field(2, "data", Any, "Data contains JSON payload that will be used for taskList execution.") + Field(3, "cacheNamespace", String, "Cache key namespace.") + Field(4, "cacheScope", String, "Cache key scope.") + Required("taskListName", "data") +}) + +var CreateTaskListResult = Type("CreateTaskListResult", func() { + Field(1, "taskListID", String, "Unique taskList identifier.") + Required("taskListID") +}) diff --git a/gen/http/cli/task/cli.go b/gen/http/cli/task/cli.go index ca12630d5df90424c74955045a70afd451dcb583..e125d9bae7407f0fabda3324cb9c3d1966b29ed4 100644 --- a/gen/http/cli/task/cli.go +++ b/gen/http/cli/task/cli.go @@ -15,6 +15,7 @@ import ( healthc "code.vereign.com/gaiax/tsa/task/gen/http/health/client" taskc "code.vereign.com/gaiax/tsa/task/gen/http/task/client" + tasklistc "code.vereign.com/gaiax/tsa/task/gen/http/task_list/client" goahttp "goa.design/goa/v3/http" goa "goa.design/goa/v3/pkg" ) @@ -26,13 +27,15 @@ import ( func UsageCommands() string { return `health (liveness|readiness) task (create|task-result) +task-list create ` } // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { return os.Args[0] + ` health liveness` + "\n" + - os.Args[0] + ` task create --body "Eaque excepturi suscipit veritatis nemo." --task-name "Corrupti facere sequi tempora eius assumenda molestiae." --cache-namespace "Laborum sapiente." --cache-scope "Nam et."` + "\n" + + os.Args[0] + ` task create --body "Ipsam et est accusantium." --task-name "Corrupti quia autem dolorum sunt aperiam quaerat." --cache-namespace "Eveniet et eligendi sint quibusdam quia maxime." --cache-scope "Et ipsa voluptate."` + "\n" + + os.Args[0] + ` task-list create --body "Rerum quod error est esse nisi." --task-list-name "Sapiente et." --cache-namespace "Delectus natus eos." --cache-scope "Quae ut dolores ab."` + "\n" + "" } @@ -62,6 +65,14 @@ func ParseEndpoint( taskTaskResultFlags = flag.NewFlagSet("task-result", flag.ExitOnError) taskTaskResultTaskIDFlag = taskTaskResultFlags.String("task-id", "REQUIRED", "Unique task identifier.") + + taskListFlags = flag.NewFlagSet("task-list", flag.ContinueOnError) + + taskListCreateFlags = flag.NewFlagSet("create", flag.ExitOnError) + taskListCreateBodyFlag = taskListCreateFlags.String("body", "REQUIRED", "") + taskListCreateTaskListNameFlag = taskListCreateFlags.String("task-list-name", "REQUIRED", "TaskList name.") + taskListCreateCacheNamespaceFlag = taskListCreateFlags.String("cache-namespace", "", "") + taskListCreateCacheScopeFlag = taskListCreateFlags.String("cache-scope", "", "") ) healthFlags.Usage = healthUsage healthLivenessFlags.Usage = healthLivenessUsage @@ -71,6 +82,9 @@ func ParseEndpoint( taskCreateFlags.Usage = taskCreateUsage taskTaskResultFlags.Usage = taskTaskResultUsage + taskListFlags.Usage = taskListUsage + taskListCreateFlags.Usage = taskListCreateUsage + if err := flag.CommandLine.Parse(os.Args[1:]); err != nil { return nil, nil, err } @@ -90,6 +104,8 @@ func ParseEndpoint( svcf = healthFlags case "task": svcf = taskFlags + case "task-list": + svcf = taskListFlags default: return nil, nil, fmt.Errorf("unknown service %q", svcn) } @@ -125,6 +141,13 @@ func ParseEndpoint( } + case "task-list": + switch epn { + case "create": + epf = taskListCreateFlags + + } + } } if epf == nil { @@ -165,6 +188,13 @@ func ParseEndpoint( endpoint = c.TaskResult() data, err = taskc.BuildTaskResultPayload(*taskTaskResultTaskIDFlag) } + case "task-list": + c := tasklistc.NewClient(scheme, host, doer, enc, dec, restore) + switch epn { + case "create": + endpoint = c.Create() + data, err = tasklistc.BuildCreatePayload(*taskListCreateBodyFlag, *taskListCreateTaskListNameFlag, *taskListCreateCacheNamespaceFlag, *taskListCreateCacheScopeFlag) + } } } if err != nil { @@ -232,7 +262,7 @@ Create a task and put it in a queue for execution. -cache-scope STRING: Example: - %[1]s task create --body "Eaque excepturi suscipit veritatis nemo." --task-name "Corrupti facere sequi tempora eius assumenda molestiae." --cache-namespace "Laborum sapiente." --cache-scope "Nam et." + %[1]s task create --body "Ipsam et est accusantium." --task-name "Corrupti quia autem dolorum sunt aperiam quaerat." --cache-namespace "Eveniet et eligendi sint quibusdam quia maxime." --cache-scope "Et ipsa voluptate." `, os.Args[0]) } @@ -243,6 +273,34 @@ TaskResult retrieves task result from the Cache service. -task-id STRING: Unique task identifier. Example: - %[1]s task task-result --task-id "Et ipsa voluptate." + %[1]s task task-result --task-id "Excepturi aut consequatur animi rerum." +`, os.Args[0]) +} + +// task-listUsage displays the usage of the task-list command and its +// subcommands. +func taskListUsage() { + fmt.Fprintf(os.Stderr, `TaskList service provides endpoints to work with task lists. +Usage: + %[1]s [globalflags] task-list COMMAND [flags] + +COMMAND: + create: Create a task list, corresponding groups and tasks and put them in respective queues for execution. + +Additional help: + %[1]s task-list COMMAND --help +`, os.Args[0]) +} +func taskListCreateUsage() { + fmt.Fprintf(os.Stderr, `%[1]s [flags] task-list create -body JSON -task-list-name STRING -cache-namespace STRING -cache-scope STRING + +Create a task list, corresponding groups and tasks and put them in respective queues for execution. + -body JSON: + -task-list-name STRING: TaskList name. + -cache-namespace STRING: + -cache-scope STRING: + +Example: + %[1]s task-list create --body "Rerum quod error est esse nisi." --task-list-name "Sapiente et." --cache-namespace "Delectus natus eos." --cache-scope "Quae ut dolores ab." `, os.Args[0]) } diff --git a/gen/http/openapi.json b/gen/http/openapi.json index 78e36c4475d9a753284914cfcbc51172644ffe72..13e4956be85c69bb6664de4204a7dfbcf60f9db0 100644 --- a/gen/http/openapi.json +++ b/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Task Service","description":"The task service is executing tasks created from policies.","version":""},"host":"localhost:8082","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/v1/task/{taskName}":{"post":{"tags":["task"],"summary":"Create task","description":"Create a task and put it in a queue for execution.","operationId":"task#Create","parameters":[{"name":"taskName","in":"path","description":"Task name.","required":true,"type":"string"},{"name":"x-cache-namespace","in":"header","description":"Cache key namespace","required":false,"type":"string"},{"name":"x-cache-scope","in":"header","description":"Cache key scope","required":false,"type":"string"},{"name":"any","in":"body","description":"Data contains JSON payload that will be used for task execution.","required":true,"schema":{"type":"string","format":"binary"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TaskCreateResponseBody","required":["taskID"]}}},"schemes":["http"]}},"/v1/taskResult/{taskID}":{"get":{"tags":["task"],"summary":"TaskResult task","description":"TaskResult retrieves task result from the Cache service.","operationId":"task#TaskResult","parameters":[{"name":"taskID","in":"path","description":"Unique task identifier.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"binary"}}},"schemes":["http"]}}},"definitions":{"TaskCreateResponseBody":{"title":"TaskCreateResponseBody","type":"object","properties":{"taskID":{"type":"string","description":"Unique task identifier.","example":"Vel odio et doloribus est quod laborum."}},"example":{"taskID":"Harum aut autem aliquam dolorem non soluta."},"required":["taskID"]}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Task Service","description":"The task service is executing tasks created from policies.","version":""},"host":"localhost:8082","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/v1/task/{taskName}":{"post":{"tags":["task"],"summary":"Create task","description":"Create a task and put it in a queue for execution.","operationId":"task#Create","parameters":[{"name":"taskName","in":"path","description":"Task name.","required":true,"type":"string"},{"name":"x-cache-namespace","in":"header","description":"Cache key namespace","required":false,"type":"string"},{"name":"x-cache-scope","in":"header","description":"Cache key scope","required":false,"type":"string"},{"name":"any","in":"body","description":"Data contains JSON payload that will be used for task execution.","required":true,"schema":{"type":"string","format":"binary"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TaskCreateResponseBody","required":["taskID"]}}},"schemes":["http"]}},"/v1/taskList/{taskListName}":{"post":{"tags":["taskList"],"summary":"Create taskList","description":"Create a task list, corresponding groups and tasks and put them in respective queues for execution.","operationId":"taskList#Create","parameters":[{"name":"taskListName","in":"path","description":"TaskList name.","required":true,"type":"string"},{"name":"x-cache-namespace","in":"header","description":"Cache key namespace","required":false,"type":"string"},{"name":"x-cache-scope","in":"header","description":"Cache key scope","required":false,"type":"string"},{"name":"any","in":"body","description":"Data contains JSON payload that will be used for taskList execution.","required":true,"schema":{"type":"string","format":"binary"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/TaskListCreateResponseBody","required":["taskListID"]}}},"schemes":["http"]}},"/v1/taskResult/{taskID}":{"get":{"tags":["task"],"summary":"TaskResult task","description":"TaskResult retrieves task result from the Cache service.","operationId":"task#TaskResult","parameters":[{"name":"taskID","in":"path","description":"Unique task identifier.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"binary"}}},"schemes":["http"]}}},"definitions":{"TaskCreateResponseBody":{"title":"TaskCreateResponseBody","type":"object","properties":{"taskID":{"type":"string","description":"Unique task identifier.","example":"Et officiis aut."}},"example":{"taskID":"Atque veritatis."},"required":["taskID"]},"TaskListCreateResponseBody":{"title":"TaskListCreateResponseBody","type":"object","properties":{"taskListID":{"type":"string","description":"Unique taskList identifier.","example":"Deserunt dolor et autem quidem fugiat sint."}},"example":{"taskListID":"Aut voluptas possimus quia aliquam sit."},"required":["taskListID"]}}} \ No newline at end of file diff --git a/gen/http/openapi.yaml b/gen/http/openapi.yaml index 1ccd3b61d33f7ff620ecc4d959899033d4d70ded..b9008d5a089c97a647cc5fb22c831bd7c86a9d92 100644 --- a/gen/http/openapi.yaml +++ b/gen/http/openapi.yaml @@ -74,6 +74,46 @@ paths: - taskID schemes: - http + /v1/taskList/{taskListName}: + post: + tags: + - taskList + summary: Create taskList + description: Create a task list, corresponding groups and tasks and put them + in respective queues for execution. + operationId: taskList#Create + parameters: + - name: taskListName + in: path + description: TaskList name. + required: true + type: string + - name: x-cache-namespace + in: header + description: Cache key namespace + required: false + type: string + - name: x-cache-scope + in: header + description: Cache key scope + required: false + type: string + - name: any + in: body + description: Data contains JSON payload that will be used for taskList execution. + required: true + schema: + type: string + format: binary + responses: + "200": + description: OK response. + schema: + $ref: '#/definitions/TaskListCreateResponseBody' + required: + - taskListID + schemes: + - http /v1/taskResult/{taskID}: get: tags: @@ -103,8 +143,20 @@ definitions: taskID: type: string description: Unique task identifier. - example: Vel odio et doloribus est quod laborum. + example: Et officiis aut. example: - taskID: Harum aut autem aliquam dolorem non soluta. + taskID: Atque veritatis. required: - taskID + TaskListCreateResponseBody: + title: TaskListCreateResponseBody + type: object + properties: + taskListID: + type: string + description: Unique taskList identifier. + example: Deserunt dolor et autem quidem fugiat sint. + example: + taskListID: Aut voluptas possimus quia aliquam sit. + required: + - taskListID diff --git a/gen/http/openapi3.json b/gen/http/openapi3.json index 926387963b08082fda67874bb159f6e86d4c8295..4a39db1b50f4e6ac9185165a43d3ae6c3b14c9f6 100644 --- a/gen/http/openapi3.json +++ b/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Task Service","description":"The task service is executing tasks created from policies.","version":"1.0"},"servers":[{"url":"http://localhost:8082","description":"Task Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}},"/v1/task/{taskName}":{"post":{"tags":["task"],"summary":"Create task","description":"Create a task and put it in a queue for execution.","operationId":"task#Create","parameters":[{"name":"taskName","in":"path","description":"Task name.","required":true,"schema":{"type":"string","description":"Task name.","example":"Aut qui aut itaque et commodi vel."},"example":"Sapiente et."},{"name":"x-cache-namespace","in":"header","description":"Cache key namespace","allowEmptyValue":true,"schema":{"type":"string","description":"Cache key namespace","example":"login"},"example":"login"},{"name":"x-cache-scope","in":"header","description":"Cache key scope","allowEmptyValue":true,"schema":{"type":"string","description":"Cache key scope","example":"user"},"example":"user"}],"requestBody":{"description":"Data contains JSON payload that will be used for task execution.","required":true,"content":{"application/json":{"schema":{"type":"string","description":"Data contains JSON payload that will be used for task execution.","example":"Excepturi aut consequatur animi rerum.","format":"binary"},"example":"Dolorem illo officiis ipsa impedit harum et."}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateResult"},"example":{"taskID":"Corrupti quia autem dolorum sunt aperiam quaerat."}}}}}}},"/v1/taskResult/{taskID}":{"get":{"tags":["task"],"summary":"TaskResult task","description":"TaskResult retrieves task result from the Cache service.","operationId":"task#TaskResult","parameters":[{"name":"taskID","in":"path","description":"Unique task identifier.","required":true,"schema":{"type":"string","description":"Unique task identifier.","example":"Delectus natus eos."},"example":"Quae ut dolores ab."}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Illum quidem sapiente sed velit.","format":"binary"},"example":"Omnis commodi reiciendis eum non."}}}}}}},"components":{"schemas":{"CreateResult":{"type":"object","properties":{"taskID":{"type":"string","description":"Unique task identifier.","example":"Dolores atque error ab."}},"example":{"taskID":"Laboriosam perspiciatis vitae numquam."},"required":["taskID"]}}},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"task","description":"Task service provides endpoints to work with tasks."}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Task Service","description":"The task service is executing tasks created from policies.","version":"1.0"},"servers":[{"url":"http://localhost:8082","description":"Task Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}},"/v1/task/{taskName}":{"post":{"tags":["task"],"summary":"Create task","description":"Create a task and put it in a queue for execution.","operationId":"task#Create","parameters":[{"name":"taskName","in":"path","description":"Task name.","required":true,"schema":{"type":"string","description":"Task name.","example":"Voluptatem iure qui facilis aut."},"example":"Excepturi non."},{"name":"x-cache-namespace","in":"header","description":"Cache key namespace","allowEmptyValue":true,"schema":{"type":"string","description":"Cache key namespace","example":"login"},"example":"login"},{"name":"x-cache-scope","in":"header","description":"Cache key scope","allowEmptyValue":true,"schema":{"type":"string","description":"Cache key scope","example":"user"},"example":"user"}],"requestBody":{"description":"Data contains JSON payload that will be used for task execution.","required":true,"content":{"application/json":{"schema":{"type":"string","description":"Data contains JSON payload that will be used for task execution.","example":"Impedit iste suscipit.","format":"binary"},"example":"Excepturi in ex ratione."}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTaskResult"},"example":{"taskID":"Vel odio et doloribus est quod laborum."}}}}}}},"/v1/taskList/{taskListName}":{"post":{"tags":["taskList"],"summary":"Create taskList","description":"Create a task list, corresponding groups and tasks and put them in respective queues for execution.","operationId":"taskList#Create","parameters":[{"name":"taskListName","in":"path","description":"TaskList name.","required":true,"schema":{"type":"string","description":"TaskList name.","example":"Incidunt autem eaque."},"example":"Fugit ut eius sint earum."},{"name":"x-cache-namespace","in":"header","description":"Cache key namespace","allowEmptyValue":true,"schema":{"type":"string","description":"Cache key namespace","example":"login"},"example":"login"},{"name":"x-cache-scope","in":"header","description":"Cache key scope","allowEmptyValue":true,"schema":{"type":"string","description":"Cache key scope","example":"user"},"example":"user"}],"requestBody":{"description":"Data contains JSON payload that will be used for taskList execution.","required":true,"content":{"application/json":{"schema":{"type":"string","description":"Data contains JSON payload that will be used for taskList execution.","example":"Reiciendis numquam.","format":"binary"},"example":"At consequatur nulla praesentium totam dolores voluptas."}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTaskListResult"},"example":{"taskListID":"Quaerat ut fugit voluptatem dolores deserunt in."}}}}}}},"/v1/taskResult/{taskID}":{"get":{"tags":["task"],"summary":"TaskResult task","description":"TaskResult retrieves task result from the Cache service.","operationId":"task#TaskResult","parameters":[{"name":"taskID","in":"path","description":"Unique task identifier.","required":true,"schema":{"type":"string","description":"Unique task identifier.","example":"Ut et est aut quae magnam."},"example":"Amet sapiente qui non."}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Placeat molestias praesentium necessitatibus sed.","format":"binary"},"example":"Sint nulla."}}}}}}},"components":{"schemas":{"CreateTaskListResult":{"type":"object","properties":{"taskListID":{"type":"string","description":"Unique taskList identifier.","example":"Doloribus ullam voluptas quos aut tempore."}},"example":{"taskListID":"Porro perspiciatis qui vitae totam eligendi officiis."},"required":["taskListID"]},"CreateTaskResult":{"type":"object","properties":{"taskID":{"type":"string","description":"Unique task identifier.","example":"Facilis distinctio asperiores ut architecto ducimus."}},"example":{"taskID":"Omnis et."},"required":["taskID"]}}},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"task","description":"Task service provides endpoints to work with tasks."},{"name":"taskList","description":"TaskList service provides endpoints to work with task lists."}]} \ No newline at end of file diff --git a/gen/http/openapi3.yaml b/gen/http/openapi3.yaml index 7822203e80727be6ad349a505c7520f5f53bf1be..3b234d0b2694c25ae7b41a466c4a52329a6b48d7 100644 --- a/gen/http/openapi3.yaml +++ b/gen/http/openapi3.yaml @@ -40,8 +40,8 @@ paths: schema: type: string description: Task name. - example: Aut qui aut itaque et commodi vel. - example: Sapiente et. + example: Voluptatem iure qui facilis aut. + example: Excepturi non. - name: x-cache-namespace in: header description: Cache key namespace @@ -68,18 +68,75 @@ paths: schema: type: string description: Data contains JSON payload that will be used for task execution. - example: Excepturi aut consequatur animi rerum. + example: Impedit iste suscipit. format: binary - example: Dolorem illo officiis ipsa impedit harum et. + example: Excepturi in ex ratione. responses: "200": description: OK response. content: application/json: schema: - $ref: '#/components/schemas/CreateResult' + $ref: '#/components/schemas/CreateTaskResult' example: - taskID: Corrupti quia autem dolorum sunt aperiam quaerat. + taskID: Vel odio et doloribus est quod laborum. + /v1/taskList/{taskListName}: + post: + tags: + - taskList + summary: Create taskList + description: Create a task list, corresponding groups and tasks and put them + in respective queues for execution. + operationId: taskList#Create + parameters: + - name: taskListName + in: path + description: TaskList name. + required: true + schema: + type: string + description: TaskList name. + example: Incidunt autem eaque. + example: Fugit ut eius sint earum. + - name: x-cache-namespace + in: header + description: Cache key namespace + allowEmptyValue: true + schema: + type: string + description: Cache key namespace + example: login + example: login + - name: x-cache-scope + in: header + description: Cache key scope + allowEmptyValue: true + schema: + type: string + description: Cache key scope + example: user + example: user + requestBody: + description: Data contains JSON payload that will be used for taskList execution. + required: true + content: + application/json: + schema: + type: string + description: Data contains JSON payload that will be used for taskList + execution. + example: Reiciendis numquam. + format: binary + example: At consequatur nulla praesentium totam dolores voluptas. + responses: + "200": + description: OK response. + content: + application/json: + schema: + $ref: '#/components/schemas/CreateTaskListResult' + example: + taskListID: Quaerat ut fugit voluptatem dolores deserunt in. /v1/taskResult/{taskID}: get: tags: @@ -95,8 +152,8 @@ paths: schema: type: string description: Unique task identifier. - example: Delectus natus eos. - example: Quae ut dolores ab. + example: Ut et est aut quae magnam. + example: Amet sapiente qui non. responses: "200": description: OK response. @@ -104,20 +161,31 @@ paths: application/json: schema: type: string - example: Illum quidem sapiente sed velit. + example: Placeat molestias praesentium necessitatibus sed. format: binary - example: Omnis commodi reiciendis eum non. + example: Sint nulla. components: schemas: - CreateResult: + CreateTaskListResult: + type: object + properties: + taskListID: + type: string + description: Unique taskList identifier. + example: Doloribus ullam voluptas quos aut tempore. + example: + taskListID: Porro perspiciatis qui vitae totam eligendi officiis. + required: + - taskListID + CreateTaskResult: type: object properties: taskID: type: string description: Unique task identifier. - example: Dolores atque error ab. + example: Facilis distinctio asperiores ut architecto ducimus. example: - taskID: Laboriosam perspiciatis vitae numquam. + taskID: Omnis et. required: - taskID tags: @@ -125,3 +193,5 @@ tags: description: Health service provides health check endpoints. - name: task description: Task service provides endpoints to work with tasks. +- name: taskList + description: TaskList service provides endpoints to work with task lists. diff --git a/gen/http/task/client/cli.go b/gen/http/task/client/cli.go index f4e478f9179ff782d6cf169ef51804e1f7d6eed9..3080e4ebc945b6f4f29af532b072a18c0ad3094c 100644 --- a/gen/http/task/client/cli.go +++ b/gen/http/task/client/cli.go @@ -16,13 +16,13 @@ import ( // BuildCreatePayload builds the payload for the task Create endpoint from CLI // flags. -func BuildCreatePayload(taskCreateBody string, taskCreateTaskName string, taskCreateCacheNamespace string, taskCreateCacheScope string) (*task.CreateRequest, error) { +func BuildCreatePayload(taskCreateBody string, taskCreateTaskName string, taskCreateCacheNamespace string, taskCreateCacheScope string) (*task.CreateTaskRequest, error) { var err error var body interface{} { err = json.Unmarshal([]byte(taskCreateBody), &body) if err != nil { - return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "\"Eaque excepturi suscipit veritatis nemo.\"") + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "\"Ipsam et est accusantium.\"") } } var taskName string @@ -42,7 +42,7 @@ func BuildCreatePayload(taskCreateBody string, taskCreateTaskName string, taskCr } } v := body - res := &task.CreateRequest{ + res := &task.CreateTaskRequest{ Data: v, } res.TaskName = taskName diff --git a/gen/http/task/client/encode_decode.go b/gen/http/task/client/encode_decode.go index a7ecbf5caea14083c7bcfdac4483920e08fa1835..1fd5f926ececb598eb85c0a940cc1090d0577690 100644 --- a/gen/http/task/client/encode_decode.go +++ b/gen/http/task/client/encode_decode.go @@ -25,9 +25,9 @@ func (c *Client) BuildCreateRequest(ctx context.Context, v interface{}) (*http.R taskName string ) { - p, ok := v.(*task.CreateRequest) + p, ok := v.(*task.CreateTaskRequest) if !ok { - return nil, goahttp.ErrInvalidType("task", "Create", "*task.CreateRequest", v) + return nil, goahttp.ErrInvalidType("task", "Create", "*task.CreateTaskRequest", v) } taskName = p.TaskName } @@ -47,9 +47,9 @@ func (c *Client) BuildCreateRequest(ctx context.Context, v interface{}) (*http.R // server. func EncodeCreateRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, interface{}) error { return func(req *http.Request, v interface{}) error { - p, ok := v.(*task.CreateRequest) + p, ok := v.(*task.CreateTaskRequest) if !ok { - return goahttp.ErrInvalidType("task", "Create", "*task.CreateRequest", v) + return goahttp.ErrInvalidType("task", "Create", "*task.CreateTaskRequest", v) } if p.CacheNamespace != nil { head := *p.CacheNamespace @@ -98,7 +98,7 @@ func DecodeCreateResponse(decoder func(*http.Response) goahttp.Decoder, restoreB if err != nil { return nil, goahttp.ErrValidationError("task", "Create", err) } - res := NewCreateResultOK(&body) + res := NewCreateTaskResultOK(&body) return res, nil default: body, _ := ioutil.ReadAll(resp.Body) diff --git a/gen/http/task/client/types.go b/gen/http/task/client/types.go index 7f5244d65480df67bf86504d92affb4d836124ae..2b4b14fee01e71ca55048514cafe37f945455e33 100644 --- a/gen/http/task/client/types.go +++ b/gen/http/task/client/types.go @@ -19,10 +19,10 @@ type CreateResponseBody struct { TaskID *string `form:"taskID,omitempty" json:"taskID,omitempty" xml:"taskID,omitempty"` } -// NewCreateResultOK builds a "task" service "Create" endpoint result from a -// HTTP "OK" response. -func NewCreateResultOK(body *CreateResponseBody) *task.CreateResult { - v := &task.CreateResult{ +// NewCreateTaskResultOK builds a "task" service "Create" endpoint result from +// a HTTP "OK" response. +func NewCreateTaskResultOK(body *CreateResponseBody) *task.CreateTaskResult { + v := &task.CreateTaskResult{ TaskID: *body.TaskID, } diff --git a/gen/http/task/server/encode_decode.go b/gen/http/task/server/encode_decode.go index 716de0ada1b13cec9bb4401ef6e06a8ec19b3a26..309b093acd6f4ed65e7126e9d0dee1af70c33574 100644 --- a/gen/http/task/server/encode_decode.go +++ b/gen/http/task/server/encode_decode.go @@ -21,7 +21,7 @@ import ( // Create endpoint. func EncodeCreateResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, interface{}) error { return func(ctx context.Context, w http.ResponseWriter, v interface{}) error { - res, _ := v.(*task.CreateResult) + res, _ := v.(*task.CreateTaskResult) enc := encoder(ctx, w) body := NewCreateResponseBody(res) w.WriteHeader(http.StatusOK) @@ -61,7 +61,7 @@ func DecodeCreateRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp. if cacheScopeRaw != "" { cacheScope = &cacheScopeRaw } - payload := NewCreateRequest(body, taskName, cacheNamespace, cacheScope) + payload := NewCreateTaskRequest(body, taskName, cacheNamespace, cacheScope) return payload, nil } diff --git a/gen/http/task/server/types.go b/gen/http/task/server/types.go index c1482c9a8bc09f8781fd87e399293afa5c4392c6..b5a3db61fe4375f8ab37e32ec2d46a279144f211 100644 --- a/gen/http/task/server/types.go +++ b/gen/http/task/server/types.go @@ -20,17 +20,17 @@ type CreateResponseBody struct { // NewCreateResponseBody builds the HTTP response body from the result of the // "Create" endpoint of the "task" service. -func NewCreateResponseBody(res *task.CreateResult) *CreateResponseBody { +func NewCreateResponseBody(res *task.CreateTaskResult) *CreateResponseBody { body := &CreateResponseBody{ TaskID: res.TaskID, } return body } -// NewCreateRequest builds a task service Create endpoint payload. -func NewCreateRequest(body interface{}, taskName string, cacheNamespace *string, cacheScope *string) *task.CreateRequest { +// NewCreateTaskRequest builds a task service Create endpoint payload. +func NewCreateTaskRequest(body interface{}, taskName string, cacheNamespace *string, cacheScope *string) *task.CreateTaskRequest { v := body - res := &task.CreateRequest{ + res := &task.CreateTaskRequest{ Data: v, } res.TaskName = taskName diff --git a/gen/http/task_list/client/cli.go b/gen/http/task_list/client/cli.go new file mode 100644 index 0000000000000000000000000000000000000000..a46f7e6b65abf426b31ebafbdd65df0c52b216a8 --- /dev/null +++ b/gen/http/task_list/client/cli.go @@ -0,0 +1,53 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList HTTP client CLI support package +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package client + +import ( + "encoding/json" + "fmt" + + tasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" +) + +// BuildCreatePayload builds the payload for the taskList Create endpoint from +// CLI flags. +func BuildCreatePayload(taskListCreateBody string, taskListCreateTaskListName string, taskListCreateCacheNamespace string, taskListCreateCacheScope string) (*tasklist.CreateTaskListRequest, error) { + var err error + var body interface{} + { + err = json.Unmarshal([]byte(taskListCreateBody), &body) + if err != nil { + return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "\"Rerum quod error est esse nisi.\"") + } + } + var taskListName string + { + taskListName = taskListCreateTaskListName + } + var cacheNamespace *string + { + if taskListCreateCacheNamespace != "" { + cacheNamespace = &taskListCreateCacheNamespace + } + } + var cacheScope *string + { + if taskListCreateCacheScope != "" { + cacheScope = &taskListCreateCacheScope + } + } + v := body + res := &tasklist.CreateTaskListRequest{ + Data: v, + } + res.TaskListName = taskListName + res.CacheNamespace = cacheNamespace + res.CacheScope = cacheScope + + return res, nil +} diff --git a/gen/http/task_list/client/client.go b/gen/http/task_list/client/client.go new file mode 100644 index 0000000000000000000000000000000000000000..7beb029b37d637bc6fc7ca2249ee7a84e2d100fe --- /dev/null +++ b/gen/http/task_list/client/client.go @@ -0,0 +1,74 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList client HTTP transport +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package client + +import ( + "context" + "net/http" + + goahttp "goa.design/goa/v3/http" + goa "goa.design/goa/v3/pkg" +) + +// Client lists the taskList service endpoint HTTP clients. +type Client struct { + // Create Doer is the HTTP client used to make requests to the Create endpoint. + CreateDoer goahttp.Doer + + // RestoreResponseBody controls whether the response bodies are reset after + // decoding so they can be read again. + RestoreResponseBody bool + + scheme string + host string + encoder func(*http.Request) goahttp.Encoder + decoder func(*http.Response) goahttp.Decoder +} + +// NewClient instantiates HTTP clients for all the taskList service servers. +func NewClient( + scheme string, + host string, + doer goahttp.Doer, + enc func(*http.Request) goahttp.Encoder, + dec func(*http.Response) goahttp.Decoder, + restoreBody bool, +) *Client { + return &Client{ + CreateDoer: doer, + RestoreResponseBody: restoreBody, + scheme: scheme, + host: host, + decoder: dec, + encoder: enc, + } +} + +// Create returns an endpoint that makes HTTP requests to the taskList service +// Create server. +func (c *Client) Create() goa.Endpoint { + var ( + encodeRequest = EncodeCreateRequest(c.encoder) + decodeResponse = DecodeCreateResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v interface{}) (interface{}, error) { + req, err := c.BuildCreateRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.CreateDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("taskList", "Create", err) + } + return decodeResponse(resp) + } +} diff --git a/gen/http/task_list/client/encode_decode.go b/gen/http/task_list/client/encode_decode.go new file mode 100644 index 0000000000000000000000000000000000000000..a8c5892c07ad6acdd709ff1f6875b1138eba762a --- /dev/null +++ b/gen/http/task_list/client/encode_decode.go @@ -0,0 +1,108 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList HTTP client encoders and decoders +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package client + +import ( + "bytes" + "context" + "io/ioutil" + "net/http" + "net/url" + + tasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" + goahttp "goa.design/goa/v3/http" +) + +// BuildCreateRequest instantiates a HTTP request object with method and path +// set to call the "taskList" service "Create" endpoint +func (c *Client) BuildCreateRequest(ctx context.Context, v interface{}) (*http.Request, error) { + var ( + taskListName string + ) + { + p, ok := v.(*tasklist.CreateTaskListRequest) + if !ok { + return nil, goahttp.ErrInvalidType("taskList", "Create", "*tasklist.CreateTaskListRequest", v) + } + taskListName = p.TaskListName + } + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: CreateTaskListPath(taskListName)} + req, err := http.NewRequest("POST", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("taskList", "Create", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeCreateRequest returns an encoder for requests sent to the taskList +// Create server. +func EncodeCreateRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, interface{}) error { + return func(req *http.Request, v interface{}) error { + p, ok := v.(*tasklist.CreateTaskListRequest) + if !ok { + return goahttp.ErrInvalidType("taskList", "Create", "*tasklist.CreateTaskListRequest", v) + } + if p.CacheNamespace != nil { + head := *p.CacheNamespace + req.Header.Set("x-cache-namespace", head) + } + if p.CacheScope != nil { + head := *p.CacheScope + req.Header.Set("x-cache-scope", head) + } + body := p.Data + if err := encoder(req).Encode(&body); err != nil { + return goahttp.ErrEncodingError("taskList", "Create", err) + } + return nil + } +} + +// DecodeCreateResponse returns a decoder for responses returned by the +// taskList Create endpoint. restoreBody controls whether the response body +// should be restored after having been read. +func DecodeCreateResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (interface{}, error) { + return func(resp *http.Response) (interface{}, error) { + if restoreBody { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = ioutil.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = ioutil.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: + var ( + body CreateResponseBody + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("taskList", "Create", err) + } + err = ValidateCreateResponseBody(&body) + if err != nil { + return nil, goahttp.ErrValidationError("taskList", "Create", err) + } + res := NewCreateTaskListResultOK(&body) + return res, nil + default: + body, _ := ioutil.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("taskList", "Create", resp.StatusCode, string(body)) + } + } +} diff --git a/gen/http/task_list/client/paths.go b/gen/http/task_list/client/paths.go new file mode 100644 index 0000000000000000000000000000000000000000..ce764600818ffe4a5a0df13ee8cdd75e3db92428 --- /dev/null +++ b/gen/http/task_list/client/paths.go @@ -0,0 +1,17 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// HTTP request path constructors for the taskList service. +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package client + +import ( + "fmt" +) + +// CreateTaskListPath returns the URL path to the taskList service Create HTTP endpoint. +func CreateTaskListPath(taskListName string) string { + return fmt.Sprintf("/v1/taskList/%v", taskListName) +} diff --git a/gen/http/task_list/client/types.go b/gen/http/task_list/client/types.go new file mode 100644 index 0000000000000000000000000000000000000000..902273026c2e1b2237fc0b70252f96669b7e42c1 --- /dev/null +++ b/gen/http/task_list/client/types.go @@ -0,0 +1,38 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList HTTP client types +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package client + +import ( + tasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" + goa "goa.design/goa/v3/pkg" +) + +// CreateResponseBody is the type of the "taskList" service "Create" endpoint +// HTTP response body. +type CreateResponseBody struct { + // Unique taskList identifier. + TaskListID *string `form:"taskListID,omitempty" json:"taskListID,omitempty" xml:"taskListID,omitempty"` +} + +// NewCreateTaskListResultOK builds a "taskList" service "Create" endpoint +// result from a HTTP "OK" response. +func NewCreateTaskListResultOK(body *CreateResponseBody) *tasklist.CreateTaskListResult { + v := &tasklist.CreateTaskListResult{ + TaskListID: *body.TaskListID, + } + + return v +} + +// ValidateCreateResponseBody runs the validations defined on CreateResponseBody +func ValidateCreateResponseBody(body *CreateResponseBody) (err error) { + if body.TaskListID == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("taskListID", "body")) + } + return +} diff --git a/gen/http/task_list/server/encode_decode.go b/gen/http/task_list/server/encode_decode.go new file mode 100644 index 0000000000000000000000000000000000000000..be27fbb71c3e3e8a5cfc1859b3aabb3352be4fab --- /dev/null +++ b/gen/http/task_list/server/encode_decode.go @@ -0,0 +1,68 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList HTTP server encoders and decoders +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package server + +import ( + "context" + "io" + "net/http" + + tasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" + goahttp "goa.design/goa/v3/http" + goa "goa.design/goa/v3/pkg" +) + +// EncodeCreateResponse returns an encoder for responses returned by the +// taskList Create endpoint. +func EncodeCreateResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, interface{}) error { + return func(ctx context.Context, w http.ResponseWriter, v interface{}) error { + res, _ := v.(*tasklist.CreateTaskListResult) + enc := encoder(ctx, w) + body := NewCreateResponseBody(res) + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeCreateRequest returns a decoder for requests sent to the taskList +// Create endpoint. +func DecodeCreateRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (interface{}, error) { + return func(r *http.Request) (interface{}, error) { + var ( + body interface{} + err error + ) + err = decoder(r).Decode(&body) + if err != nil { + if err == io.EOF { + return nil, goa.MissingPayloadError() + } + return nil, goa.DecodePayloadError(err.Error()) + } + + var ( + taskListName string + cacheNamespace *string + cacheScope *string + + params = mux.Vars(r) + ) + taskListName = params["taskListName"] + cacheNamespaceRaw := r.Header.Get("x-cache-namespace") + if cacheNamespaceRaw != "" { + cacheNamespace = &cacheNamespaceRaw + } + cacheScopeRaw := r.Header.Get("x-cache-scope") + if cacheScopeRaw != "" { + cacheScope = &cacheScopeRaw + } + payload := NewCreateTaskListRequest(body, taskListName, cacheNamespace, cacheScope) + + return payload, nil + } +} diff --git a/gen/http/task_list/server/paths.go b/gen/http/task_list/server/paths.go new file mode 100644 index 0000000000000000000000000000000000000000..80662ac5bb501534cf2f22138127b98eb86bcece --- /dev/null +++ b/gen/http/task_list/server/paths.go @@ -0,0 +1,17 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// HTTP request path constructors for the taskList service. +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package server + +import ( + "fmt" +) + +// CreateTaskListPath returns the URL path to the taskList service Create HTTP endpoint. +func CreateTaskListPath(taskListName string) string { + return fmt.Sprintf("/v1/taskList/%v", taskListName) +} diff --git a/gen/http/task_list/server/server.go b/gen/http/task_list/server/server.go new file mode 100644 index 0000000000000000000000000000000000000000..4d4d72dcec6b49130f5823ceb8148687eb4ccab0 --- /dev/null +++ b/gen/http/task_list/server/server.go @@ -0,0 +1,131 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList HTTP server +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package server + +import ( + "context" + "net/http" + + tasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" + goahttp "goa.design/goa/v3/http" + goa "goa.design/goa/v3/pkg" +) + +// Server lists the taskList service endpoint HTTP handlers. +type Server struct { + Mounts []*MountPoint + Create http.Handler +} + +// ErrorNamer is an interface implemented by generated error structs that +// exposes the name of the error as defined in the design. +type ErrorNamer interface { + ErrorName() string +} + +// MountPoint holds information about the mounted endpoints. +type MountPoint struct { + // Method is the name of the service method served by the mounted HTTP handler. + Method string + // Verb is the HTTP method used to match requests to the mounted handler. + Verb string + // Pattern is the HTTP request path pattern used to match requests to the + // mounted handler. + Pattern string +} + +// New instantiates HTTP handlers for all the taskList service endpoints using +// the provided encoder and decoder. The handlers are mounted on the given mux +// using the HTTP verb and path defined in the design. errhandler is called +// whenever a response fails to be encoded. formatter is used to format errors +// returned by the service methods prior to encoding. Both errhandler and +// formatter are optional and can be nil. +func New( + e *tasklist.Endpoints, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(err error) goahttp.Statuser, +) *Server { + return &Server{ + Mounts: []*MountPoint{ + {"Create", "POST", "/v1/taskList/{taskListName}"}, + }, + Create: NewCreateHandler(e.Create, mux, decoder, encoder, errhandler, formatter), + } +} + +// Service returns the name of the service served. +func (s *Server) Service() string { return "taskList" } + +// Use wraps the server handlers with the given middleware. +func (s *Server) Use(m func(http.Handler) http.Handler) { + s.Create = m(s.Create) +} + +// Mount configures the mux to serve the taskList endpoints. +func Mount(mux goahttp.Muxer, h *Server) { + MountCreateHandler(mux, h.Create) +} + +// Mount configures the mux to serve the taskList endpoints. +func (s *Server) Mount(mux goahttp.Muxer) { + Mount(mux, s) +} + +// MountCreateHandler configures the mux to serve the "taskList" service +// "Create" endpoint. +func MountCreateHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("POST", "/v1/taskList/{taskListName}", f) +} + +// NewCreateHandler creates a HTTP handler which loads the HTTP request and +// calls the "taskList" service "Create" endpoint. +func NewCreateHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeCreateRequest(mux, decoder) + encodeResponse = EncodeCreateResponse(encoder) + encodeError = goahttp.ErrorEncoder(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "Create") + ctx = context.WithValue(ctx, goa.ServiceKey, "taskList") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + errhandler(ctx, w, err) + } + }) +} diff --git a/gen/http/task_list/server/types.go b/gen/http/task_list/server/types.go new file mode 100644 index 0000000000000000000000000000000000000000..879b0a1fa38948d6d92357413004176b75adea8c --- /dev/null +++ b/gen/http/task_list/server/types.go @@ -0,0 +1,41 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList HTTP server types +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package server + +import ( + tasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" +) + +// CreateResponseBody is the type of the "taskList" service "Create" endpoint +// HTTP response body. +type CreateResponseBody struct { + // Unique taskList identifier. + TaskListID string `form:"taskListID" json:"taskListID" xml:"taskListID"` +} + +// NewCreateResponseBody builds the HTTP response body from the result of the +// "Create" endpoint of the "taskList" service. +func NewCreateResponseBody(res *tasklist.CreateTaskListResult) *CreateResponseBody { + body := &CreateResponseBody{ + TaskListID: res.TaskListID, + } + return body +} + +// NewCreateTaskListRequest builds a taskList service Create endpoint payload. +func NewCreateTaskListRequest(body interface{}, taskListName string, cacheNamespace *string, cacheScope *string) *tasklist.CreateTaskListRequest { + v := body + res := &tasklist.CreateTaskListRequest{ + Data: v, + } + res.TaskListName = taskListName + res.CacheNamespace = cacheNamespace + res.CacheScope = cacheScope + + return res +} diff --git a/gen/task/client.go b/gen/task/client.go index 3f812485b1c4b9ff706e6278e0bb385f7f4da636..55b4c3000e146d898a66155c3092df2732eee219 100644 --- a/gen/task/client.go +++ b/gen/task/client.go @@ -28,13 +28,13 @@ func NewClient(create, taskResult goa.Endpoint) *Client { } // Create calls the "Create" endpoint of the "task" service. -func (c *Client) Create(ctx context.Context, p *CreateRequest) (res *CreateResult, err error) { +func (c *Client) Create(ctx context.Context, p *CreateTaskRequest) (res *CreateTaskResult, err error) { var ires interface{} ires, err = c.CreateEndpoint(ctx, p) if err != nil { return } - return ires.(*CreateResult), nil + return ires.(*CreateTaskResult), nil } // TaskResult calls the "TaskResult" endpoint of the "task" service. diff --git a/gen/task/endpoints.go b/gen/task/endpoints.go index b648dd83cb34f626e54dbfba734439cbfbe813ca..5ea36902f9d0493c4337209bcb40a9899537111e 100644 --- a/gen/task/endpoints.go +++ b/gen/task/endpoints.go @@ -37,7 +37,7 @@ func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { // "Create" of service "task". func NewCreateEndpoint(s Service) goa.Endpoint { return func(ctx context.Context, req interface{}) (interface{}, error) { - p := req.(*CreateRequest) + p := req.(*CreateTaskRequest) return s.Create(ctx, p) } } diff --git a/gen/task/service.go b/gen/task/service.go index f5c4b6954680960745e92c7556d1f243413b7647..81ff7d1ff5b93eb7cb3b05e00d55b30cfbefa0f1 100644 --- a/gen/task/service.go +++ b/gen/task/service.go @@ -14,7 +14,7 @@ import ( // Task service provides endpoints to work with tasks. type Service interface { // Create a task and put it in a queue for execution. - Create(context.Context, *CreateRequest) (res *CreateResult, err error) + Create(context.Context, *CreateTaskRequest) (res *CreateTaskResult, err error) // TaskResult retrieves task result from the Cache service. TaskResult(context.Context, *TaskResultRequest) (res interface{}, err error) } @@ -29,8 +29,8 @@ const ServiceName = "task" // MethodKey key. var MethodNames = [2]string{"Create", "TaskResult"} -// CreateRequest is the payload type of the task service Create method. -type CreateRequest struct { +// CreateTaskRequest is the payload type of the task service Create method. +type CreateTaskRequest struct { // Task name. TaskName string // Data contains JSON payload that will be used for task execution. @@ -41,8 +41,8 @@ type CreateRequest struct { CacheScope *string } -// CreateResult is the result type of the task service Create method. -type CreateResult struct { +// CreateTaskResult is the result type of the task service Create method. +type CreateTaskResult struct { // Unique task identifier. TaskID string } diff --git a/gen/task_list/client.go b/gen/task_list/client.go new file mode 100644 index 0000000000000000000000000000000000000000..dcaf4ef0bdab346d90e25b0f4773756a72c10f42 --- /dev/null +++ b/gen/task_list/client.go @@ -0,0 +1,36 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList client +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package tasklist + +import ( + "context" + + goa "goa.design/goa/v3/pkg" +) + +// Client is the "taskList" service client. +type Client struct { + CreateEndpoint goa.Endpoint +} + +// NewClient initializes a "taskList" service client given the endpoints. +func NewClient(create goa.Endpoint) *Client { + return &Client{ + CreateEndpoint: create, + } +} + +// Create calls the "Create" endpoint of the "taskList" service. +func (c *Client) Create(ctx context.Context, p *CreateTaskListRequest) (res *CreateTaskListResult, err error) { + var ires interface{} + ires, err = c.CreateEndpoint(ctx, p) + if err != nil { + return + } + return ires.(*CreateTaskListResult), nil +} diff --git a/gen/task_list/endpoints.go b/gen/task_list/endpoints.go new file mode 100644 index 0000000000000000000000000000000000000000..0a2bc0e4b32a0704d6cf429df56fd1c5823cf708 --- /dev/null +++ b/gen/task_list/endpoints.go @@ -0,0 +1,40 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList endpoints +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package tasklist + +import ( + "context" + + goa "goa.design/goa/v3/pkg" +) + +// Endpoints wraps the "taskList" service endpoints. +type Endpoints struct { + Create goa.Endpoint +} + +// NewEndpoints wraps the methods of the "taskList" service with endpoints. +func NewEndpoints(s Service) *Endpoints { + return &Endpoints{ + Create: NewCreateEndpoint(s), + } +} + +// Use applies the given middleware to all the "taskList" service endpoints. +func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { + e.Create = m(e.Create) +} + +// NewCreateEndpoint returns an endpoint function that calls the method +// "Create" of service "taskList". +func NewCreateEndpoint(s Service) goa.Endpoint { + return func(ctx context.Context, req interface{}) (interface{}, error) { + p := req.(*CreateTaskListRequest) + return s.Create(ctx, p) + } +} diff --git a/gen/task_list/service.go b/gen/task_list/service.go new file mode 100644 index 0000000000000000000000000000000000000000..e7dbf3ae62c41719450b9ba97002b00c184f3fce --- /dev/null +++ b/gen/task_list/service.go @@ -0,0 +1,49 @@ +// Code generated by goa v3.7.0, DO NOT EDIT. +// +// taskList service +// +// Command: +// $ goa gen code.vereign.com/gaiax/tsa/task/design + +package tasklist + +import ( + "context" +) + +// TaskList service provides endpoints to work with task lists. +type Service interface { + // Create a task list, corresponding groups and tasks and put them in + // respective queues for execution. + Create(context.Context, *CreateTaskListRequest) (res *CreateTaskListResult, err error) +} + +// ServiceName is the name of the service as defined in the design. This is the +// same value that is set in the endpoint request contexts under the ServiceKey +// key. +const ServiceName = "taskList" + +// MethodNames lists the service method names as defined in the design. These +// are the same values that are set in the endpoint request contexts under the +// MethodKey key. +var MethodNames = [1]string{"Create"} + +// CreateTaskListRequest is the payload type of the taskList service Create +// method. +type CreateTaskListRequest struct { + // TaskList name. + TaskListName string + // Data contains JSON payload that will be used for taskList execution. + Data interface{} + // Cache key namespace. + CacheNamespace *string + // Cache key scope. + CacheScope *string +} + +// CreateTaskListResult is the result type of the taskList service Create +// method. +type CreateTaskListResult struct { + // Unique taskList identifier. + TaskListID string +} diff --git a/internal/service/task/service.go b/internal/service/task/service.go index a4ea25ad30982f806f5b9f1431107321c9601b9d..c999eace7b3237e631178439158c250bb1b995a5 100644 --- a/internal/service/task/service.go +++ b/internal/service/task/service.go @@ -54,7 +54,7 @@ func New(template Storage, queue Queue, cache Cache, logger *zap.Logger) *Servic } // Create a new task and put it in a Queue for later execution. -func (s *Service) Create(ctx context.Context, req *goatask.CreateRequest) (res *goatask.CreateResult, err error) { +func (s *Service) Create(ctx context.Context, req *goatask.CreateTaskRequest) (res *goatask.CreateTaskResult, err error) { if req.TaskName == "" { return nil, errors.New(errors.BadRequest, "missing taskName") } @@ -92,7 +92,7 @@ func (s *Service) Create(ctx context.Context, req *goatask.CreateRequest) (res * return nil, errors.New("failed to create task", err) } - return &goatask.CreateResult{TaskID: task.ID}, nil + return &goatask.CreateTaskResult{TaskID: task.ID}, nil } // TaskResult retrieves task result from the Cache service. diff --git a/internal/service/task/service_test.go b/internal/service/task/service_test.go index 70051423452d36b66e184e8d3ea6f28fb504db3b..eef64de7aaf67300f1b19782421cdb3771f14b2b 100644 --- a/internal/service/task/service_test.go +++ b/internal/service/task/service_test.go @@ -22,7 +22,7 @@ func TestNew(t *testing.T) { func TestService_Create(t *testing.T) { tests := []struct { name string - req *goatask.CreateRequest + req *goatask.CreateTaskRequest storage *taskfakes.FakeStorage queue *taskfakes.FakeQueue cache *taskfakes.FakeCache @@ -32,13 +32,13 @@ func TestService_Create(t *testing.T) { }{ { name: "empty task name", - req: &goatask.CreateRequest{}, + req: &goatask.CreateTaskRequest{}, errkind: errors.BadRequest, errtext: "missing taskName", }, { name: "task template not found", - req: &goatask.CreateRequest{TaskName: "taskname"}, + req: &goatask.CreateTaskRequest{TaskName: "taskname"}, storage: &taskfakes.FakeStorage{ TaskTemplateStub: func(ctx context.Context, taskName string) (*task.Task, error) { return nil, errors.New(errors.NotFound) @@ -49,7 +49,7 @@ func TestService_Create(t *testing.T) { }, { name: "fail to add task to queue", - req: &goatask.CreateRequest{TaskName: "taskname"}, + req: &goatask.CreateTaskRequest{TaskName: "taskname"}, storage: &taskfakes.FakeStorage{ TaskTemplateStub: func(ctx context.Context, taskName string) (*task.Task, error) { return &task.Task{}, nil @@ -65,7 +65,7 @@ func TestService_Create(t *testing.T) { }, { name: "successfully add task to queue", - req: &goatask.CreateRequest{TaskName: "taskname"}, + req: &goatask.CreateTaskRequest{TaskName: "taskname"}, storage: &taskfakes.FakeStorage{ TaskTemplateStub: func(ctx context.Context, taskName string) (*task.Task, error) { return &task.Task{}, nil @@ -79,7 +79,7 @@ func TestService_Create(t *testing.T) { }, { name: "successfully add task to queue with namespace and scope", - req: &goatask.CreateRequest{ + req: &goatask.CreateTaskRequest{ TaskName: "taskname", CacheNamespace: ptr.String("login"), CacheScope: ptr.String("user"), diff --git a/internal/service/task/task.go b/internal/service/task/task.go index dd53a5175c505258f3822fa0ee6e8cdd57533dd7..e164797533886ed5536fd10de231b92a0f3a0185 100644 --- a/internal/service/task/task.go +++ b/internal/service/task/task.go @@ -22,7 +22,7 @@ const ( type Task struct { ID string `json:"id"` // ID is unique task identifier. - GroupID string `json:"groupID"` // GroupID is set when the task is part of `task.Group`. + GroupID string `json:"groupID"` // GroupID is set when the task is part of `tasklist.Group`. Name string `json:"name"` // Name is used by external callers use to create tasks. State State `json:"state"` // State of the task. URL string `json:"url"` // URL against which the task request will be executed (optional). @@ -40,17 +40,6 @@ type Task struct { FinishedAt time.Time `json:"finishedAt"` // FinishedAt specifies the time when the task is done. } -type Group struct { - ID string - Tasks []Task - State State - Metadata interface{} // TODO(penkovski): not yet clear - FinalPolicy string - CreateTime time.Time - StartTime time.Time - FinishTime time.Time -} - // CacheKey constructs the key for storing task result in the cache. func (t *Task) CacheKey() string { key := t.ID diff --git a/internal/service/tasklist/service.go b/internal/service/tasklist/service.go new file mode 100644 index 0000000000000000000000000000000000000000..9bbeeb4ed9db27d5f10e4315c968b544dd371ae0 --- /dev/null +++ b/internal/service/tasklist/service.go @@ -0,0 +1,177 @@ +package tasklist + +import ( + "context" + "encoding/json" + "time" + + "github.com/google/uuid" + "go.uber.org/zap" + + "code.vereign.com/gaiax/tsa/golib/errors" + goatasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" + "code.vereign.com/gaiax/tsa/task/internal/service/task" +) + +//go:generate counterfeiter . Storage +//go:generate counterfeiter . Queue + +// Storage for retrieving predefined task templates. +type Storage interface { + TaskListTemplate(ctx context.Context, taskListName string) (*Template, error) + TaskTemplates(ctx context.Context, names []string) (map[string]*task.Task, error) +} + +type Queue interface { + AddTaskList(ctx context.Context, taskList *TaskList, tasks []*task.Task) error +} + +type Service struct { + storage Storage + queue Queue + + logger *zap.Logger +} + +func New(template Storage, queue Queue, logger *zap.Logger) *Service { + return &Service{ + storage: template, + queue: queue, + logger: logger, + } +} + +// Create a taskList and corresponding tasks and put them in +// respective queues for execution. +func (s *Service) Create(ctx context.Context, req *goatasklist.CreateTaskListRequest) (*goatasklist.CreateTaskListResult, error) { + if req.TaskListName == "" { + return nil, errors.New(errors.BadRequest, "missing taskListName") + } + + logger := s.logger.With(zap.String("taskListName", req.TaskListName)) + + // get predefined taskList definition from storage + template, err := s.storage.TaskListTemplate(ctx, req.TaskListName) + if err != nil { + logger.Error("error getting taskList template from storage", zap.Error(err)) + return nil, err + } + + // get predefined task definitions from storage + taskTemplates, err := s.storage.TaskTemplates(ctx, taskNamesFromTaskListTemplate(template)) + if err != nil { + logger.Error("error getting task templates from storage") + return nil, err + } + + taskListRequest, err := json.Marshal(req.Data) + if err != nil { + logger.Error("error marshaling request data to JSON", zap.Error(err)) + return nil, errors.New(errors.BadRequest, "error marshaling request data to JSON", err) + } + + taskList := &TaskList{ + ID: uuid.NewString(), + Groups: createGroups(template), + Name: template.Name, + Request: taskListRequest, + CacheScope: template.CacheScope, + CacheNamespace: template.CacheNamespace, + State: task.Created, + CreateTime: time.Now(), + } + + // if cache namespace and scope are given, use them instead of the defaults + if req.CacheNamespace != nil && *req.CacheNamespace != "" { + taskList.CacheNamespace = *req.CacheNamespace + } + if req.CacheScope != nil && *req.CacheScope != "" { + taskList.CacheScope = *req.CacheScope + } + + tasks, err := createTasks(taskList, taskTemplates) + if err != nil { + logger.Error("failed to create tasks for taskList", zap.Error(err)) + return nil, errors.New("failed to create tasks for taskList", err) + } + + if err := s.queue.AddTaskList(ctx, taskList, tasks); err != nil { + logger.Error("error adding taskList to queue", zap.Error(err)) + return nil, errors.New("error adding taskList to queue", err) + } + + return &goatasklist.CreateTaskListResult{ + TaskListID: taskList.ID, + }, nil +} + +func createGroups(t *Template) []Group { + var groups []Group + for _, group := range t.Groups { + g := Group{ + ID: uuid.NewString(), + Execution: group.Execution, + Tasks: group.Tasks, + State: task.Created, + Metadata: group.Metadata, + FinalPolicy: group.FinalPolicy, + } + groups = append(groups, g) + } + + return groups +} + +// createTasks creates task.Task instances out of task templates +// in order to be added to queue for execution +func createTasks(t *TaskList, templates map[string]*task.Task) ([]*task.Task, error) { + var tasks []*task.Task + for _, group := range t.Groups { + for _, taskName := range group.Tasks { + template, ok := templates[taskName] + if !ok { + return nil, errors.New(errors.NotFound, "failed to find task template") + } + + task := task.Task{ + ID: uuid.NewString(), + GroupID: group.ID, + Name: taskName, + State: task.Created, + URL: template.URL, + Method: template.Method, + RequestPolicy: template.RequestPolicy, + ResponsePolicy: template.ResponsePolicy, + FinalPolicy: template.FinalPolicy, + CacheNamespace: template.CacheNamespace, + CacheScope: template.CacheScope, + CreatedAt: time.Now(), + } + + // if cache namespace and scope are set in the taskList, use them instead of the defaults + if t.CacheNamespace != "" { + task.CacheNamespace = t.CacheNamespace + } + if t.CacheScope != "" { + task.CacheScope = t.CacheScope + } + + tasks = append(tasks, &task) + } + } + + return tasks, nil +} + +// taskNamesFromTaskListTemplate returns the names of all tasks within +// one taskList template +func taskNamesFromTaskListTemplate(template *Template) []string { + var names []string + for _, group := range template.Groups { + for _, taskName := range group.Tasks { + names = append(names, taskName) + } + } + + return names +} diff --git a/internal/service/tasklist/service_test.go b/internal/service/tasklist/service_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9dd3b39e39b25076f55f206661264627f2917c07 --- /dev/null +++ b/internal/service/tasklist/service_test.go @@ -0,0 +1,140 @@ +package tasklist_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "code.vereign.com/gaiax/tsa/golib/errors" + goatasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" + "code.vereign.com/gaiax/tsa/task/internal/service/task" + "code.vereign.com/gaiax/tsa/task/internal/service/tasklist" + "code.vereign.com/gaiax/tsa/task/internal/service/tasklist/tasklistfakes" +) + +func TestNew(t *testing.T) { + svc := tasklist.New(nil, nil, zap.NewNop()) + assert.Implements(t, (*goatasklist.Service)(nil), svc) +} + +func Test_Create(t *testing.T) { + tests := []struct { + name string + req *goatasklist.CreateTaskListRequest + storage *tasklistfakes.FakeStorage + queue *tasklistfakes.FakeQueue + + errkind errors.Kind + errtext string + }{ + { + name: "empty taskList name", + req: &goatasklist.CreateTaskListRequest{}, + errkind: errors.BadRequest, + errtext: "missing taskListName", + }, + { + name: "taskList template not found", + req: &goatasklist.CreateTaskListRequest{TaskListName: "taskList name"}, + storage: &tasklistfakes.FakeStorage{ + TaskListTemplateStub: func(ctx context.Context, s string) (*tasklist.Template, error) { + return nil, errors.New(errors.NotFound) + }, + }, + errkind: errors.NotFound, + errtext: "not found", + }, + { + name: "error getting task templates form storage", + req: &goatasklist.CreateTaskListRequest{TaskListName: "taskList name"}, + storage: &tasklistfakes.FakeStorage{ + TaskListTemplateStub: func(ctx context.Context, s string) (*tasklist.Template, error) { + return &tasklist.Template{}, nil + }, + TaskTemplatesStub: func(ctx context.Context, strings []string) (map[string]*task.Task, error) { + return nil, errors.New(errors.Internal, "internal error") + }, + }, + errkind: errors.Internal, + errtext: "internal error", + }, + { + name: "error creating tasks for a taskList, task template not found", + req: &goatasklist.CreateTaskListRequest{TaskListName: "taskList name"}, + storage: &tasklistfakes.FakeStorage{ + TaskListTemplateStub: func(ctx context.Context, s string) (*tasklist.Template, error) { + return &tasklist.Template{ + Groups: []tasklist.GroupTemplate{ + { + Tasks: []string{"non-existent task template"}, + }, + }, + }, nil + }, + TaskTemplatesStub: func(ctx context.Context, strings []string) (map[string]*task.Task, error) { + return map[string]*task.Task{"template": &task.Task{}}, nil + }, + }, + errkind: errors.NotFound, + errtext: "failed to find task template", + }, + { + name: "failed to add taskList and tasks to queue", + req: &goatasklist.CreateTaskListRequest{TaskListName: "taskList name"}, + storage: &tasklistfakes.FakeStorage{ + TaskListTemplateStub: func(ctx context.Context, s string) (*tasklist.Template, error) { + return &tasklist.Template{}, nil + }, + TaskTemplatesStub: func(ctx context.Context, strings []string) (map[string]*task.Task, error) { + return map[string]*task.Task{"template": &task.Task{}}, nil + }, + }, + queue: &tasklistfakes.FakeQueue{ + AddTaskListStub: func(ctx context.Context, list *tasklist.TaskList, tasks []*task.Task) error { + return errors.New("storage error") + }, + }, + errkind: errors.Unknown, + errtext: "storage error", + }, + { + name: "successfully add taskList and tasks to queue", + req: &goatasklist.CreateTaskListRequest{TaskListName: "taskList name"}, + storage: &tasklistfakes.FakeStorage{ + TaskListTemplateStub: func(ctx context.Context, s string) (*tasklist.Template, error) { + return &tasklist.Template{}, nil + }, + TaskTemplatesStub: func(ctx context.Context, strings []string) (map[string]*task.Task, error) { + return map[string]*task.Task{"template": &task.Task{}}, nil + }, + }, + queue: &tasklistfakes.FakeQueue{ + AddTaskListStub: func(ctx context.Context, list *tasklist.TaskList, tasks []*task.Task) error { + return nil + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + svc := tasklist.New(test.storage, test.queue, zap.NewNop()) + res, err := svc.Create(context.Background(), test.req) + if err != nil { + assert.NotEmpty(t, test.errtext) + e, ok := err.(*errors.Error) + assert.True(t, ok) + assert.Equal(t, test.errkind, e.Kind) + assert.Contains(t, e.Error(), test.errtext) + assert.Nil(t, res) + } else { + assert.Empty(t, test.errtext) + assert.NotNil(t, res) + assert.NotEmpty(t, res.TaskListID) + } + + }) + } +} diff --git a/internal/service/tasklist/task_list.go b/internal/service/tasklist/task_list.go new file mode 100644 index 0000000000000000000000000000000000000000..0e3e7f542972d3c531e55225f5a8a39f4dc2c851 --- /dev/null +++ b/internal/service/tasklist/task_list.go @@ -0,0 +1,43 @@ +package tasklist + +import ( + "time" + + "code.vereign.com/gaiax/tsa/task/internal/service/task" +) + +type Template struct { + Name string `json:"name"` + CacheNamespace string `json:"cacheNamespace"` + CacheScope string `json:"cacheScope"` + Groups []GroupTemplate `json:"groups"` +} + +type GroupTemplate struct { + Execution string `json:"execution"` + FinalPolicy string `json:"finalPolicy"` + Tasks []string `json:"tasks"` + Metadata interface{} `json:"metadata"` +} + +type TaskList struct { + ID string `json:"id"` + Name string `json:"name"` + State task.State `json:"state"` + Groups []Group `json:"groups"` + Request []byte `json:"request"` + CacheNamespace string `json:"cacheNamespace"` + CacheScope string `json:"cacheScope"` + CreateTime time.Time `json:"createdAt"` + StartTime time.Time `json:"startedAt"` + FinishTime time.Time `json:"finishedAt"` +} + +type Group struct { + ID string `json:"id"` + Execution string `json:"execution"` + Tasks []string `json:"tasks"` + State task.State `json:"state"` + Metadata interface{} `json:"metadata"` // TODO(penkovski): not yet clear + FinalPolicy string `json:"finalPolicy"` +} diff --git a/internal/service/tasklist/tasklistfakes/fake_queue.go b/internal/service/tasklist/tasklistfakes/fake_queue.go new file mode 100644 index 0000000000000000000000000000000000000000..75e572da1f1ea976808a9028be753d94b936f132 --- /dev/null +++ b/internal/service/tasklist/tasklistfakes/fake_queue.go @@ -0,0 +1,122 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package tasklistfakes + +import ( + "context" + "sync" + + "code.vereign.com/gaiax/tsa/task/internal/service/task" + "code.vereign.com/gaiax/tsa/task/internal/service/tasklist" +) + +type FakeQueue struct { + AddTaskListStub func(context.Context, *tasklist.TaskList, []*task.Task) error + addTaskListMutex sync.RWMutex + addTaskListArgsForCall []struct { + arg1 context.Context + arg2 *tasklist.TaskList + arg3 []*task.Task + } + addTaskListReturns struct { + result1 error + } + addTaskListReturnsOnCall map[int]struct { + result1 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeQueue) AddTaskList(arg1 context.Context, arg2 *tasklist.TaskList, arg3 []*task.Task) error { + var arg3Copy []*task.Task + if arg3 != nil { + arg3Copy = make([]*task.Task, len(arg3)) + copy(arg3Copy, arg3) + } + fake.addTaskListMutex.Lock() + ret, specificReturn := fake.addTaskListReturnsOnCall[len(fake.addTaskListArgsForCall)] + fake.addTaskListArgsForCall = append(fake.addTaskListArgsForCall, struct { + arg1 context.Context + arg2 *tasklist.TaskList + arg3 []*task.Task + }{arg1, arg2, arg3Copy}) + stub := fake.AddTaskListStub + fakeReturns := fake.addTaskListReturns + fake.recordInvocation("AddTaskList", []interface{}{arg1, arg2, arg3Copy}) + fake.addTaskListMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeQueue) AddTaskListCallCount() int { + fake.addTaskListMutex.RLock() + defer fake.addTaskListMutex.RUnlock() + return len(fake.addTaskListArgsForCall) +} + +func (fake *FakeQueue) AddTaskListCalls(stub func(context.Context, *tasklist.TaskList, []*task.Task) error) { + fake.addTaskListMutex.Lock() + defer fake.addTaskListMutex.Unlock() + fake.AddTaskListStub = stub +} + +func (fake *FakeQueue) AddTaskListArgsForCall(i int) (context.Context, *tasklist.TaskList, []*task.Task) { + fake.addTaskListMutex.RLock() + defer fake.addTaskListMutex.RUnlock() + argsForCall := fake.addTaskListArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeQueue) AddTaskListReturns(result1 error) { + fake.addTaskListMutex.Lock() + defer fake.addTaskListMutex.Unlock() + fake.AddTaskListStub = nil + fake.addTaskListReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeQueue) AddTaskListReturnsOnCall(i int, result1 error) { + fake.addTaskListMutex.Lock() + defer fake.addTaskListMutex.Unlock() + fake.AddTaskListStub = nil + if fake.addTaskListReturnsOnCall == nil { + fake.addTaskListReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.addTaskListReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeQueue) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.addTaskListMutex.RLock() + defer fake.addTaskListMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeQueue) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ tasklist.Queue = new(FakeQueue) diff --git a/internal/service/tasklist/tasklistfakes/fake_storage.go b/internal/service/tasklist/tasklistfakes/fake_storage.go new file mode 100644 index 0000000000000000000000000000000000000000..9ece29b9180862d8281bd5759b4104ff9a731e67 --- /dev/null +++ b/internal/service/tasklist/tasklistfakes/fake_storage.go @@ -0,0 +1,206 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package tasklistfakes + +import ( + "context" + "sync" + + "code.vereign.com/gaiax/tsa/task/internal/service/task" + "code.vereign.com/gaiax/tsa/task/internal/service/tasklist" +) + +type FakeStorage struct { + TaskListTemplateStub func(context.Context, string) (*tasklist.Template, error) + taskListTemplateMutex sync.RWMutex + taskListTemplateArgsForCall []struct { + arg1 context.Context + arg2 string + } + taskListTemplateReturns struct { + result1 *tasklist.Template + result2 error + } + taskListTemplateReturnsOnCall map[int]struct { + result1 *tasklist.Template + result2 error + } + TaskTemplatesStub func(context.Context, []string) (map[string]*task.Task, error) + taskTemplatesMutex sync.RWMutex + taskTemplatesArgsForCall []struct { + arg1 context.Context + arg2 []string + } + taskTemplatesReturns struct { + result1 map[string]*task.Task + result2 error + } + taskTemplatesReturnsOnCall map[int]struct { + result1 map[string]*task.Task + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeStorage) TaskListTemplate(arg1 context.Context, arg2 string) (*tasklist.Template, error) { + fake.taskListTemplateMutex.Lock() + ret, specificReturn := fake.taskListTemplateReturnsOnCall[len(fake.taskListTemplateArgsForCall)] + fake.taskListTemplateArgsForCall = append(fake.taskListTemplateArgsForCall, struct { + arg1 context.Context + arg2 string + }{arg1, arg2}) + stub := fake.TaskListTemplateStub + fakeReturns := fake.taskListTemplateReturns + fake.recordInvocation("TaskListTemplate", []interface{}{arg1, arg2}) + fake.taskListTemplateMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStorage) TaskListTemplateCallCount() int { + fake.taskListTemplateMutex.RLock() + defer fake.taskListTemplateMutex.RUnlock() + return len(fake.taskListTemplateArgsForCall) +} + +func (fake *FakeStorage) TaskListTemplateCalls(stub func(context.Context, string) (*tasklist.Template, error)) { + fake.taskListTemplateMutex.Lock() + defer fake.taskListTemplateMutex.Unlock() + fake.TaskListTemplateStub = stub +} + +func (fake *FakeStorage) TaskListTemplateArgsForCall(i int) (context.Context, string) { + fake.taskListTemplateMutex.RLock() + defer fake.taskListTemplateMutex.RUnlock() + argsForCall := fake.taskListTemplateArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeStorage) TaskListTemplateReturns(result1 *tasklist.Template, result2 error) { + fake.taskListTemplateMutex.Lock() + defer fake.taskListTemplateMutex.Unlock() + fake.TaskListTemplateStub = nil + fake.taskListTemplateReturns = struct { + result1 *tasklist.Template + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) TaskListTemplateReturnsOnCall(i int, result1 *tasklist.Template, result2 error) { + fake.taskListTemplateMutex.Lock() + defer fake.taskListTemplateMutex.Unlock() + fake.TaskListTemplateStub = nil + if fake.taskListTemplateReturnsOnCall == nil { + fake.taskListTemplateReturnsOnCall = make(map[int]struct { + result1 *tasklist.Template + result2 error + }) + } + fake.taskListTemplateReturnsOnCall[i] = struct { + result1 *tasklist.Template + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) TaskTemplates(arg1 context.Context, arg2 []string) (map[string]*task.Task, error) { + var arg2Copy []string + if arg2 != nil { + arg2Copy = make([]string, len(arg2)) + copy(arg2Copy, arg2) + } + fake.taskTemplatesMutex.Lock() + ret, specificReturn := fake.taskTemplatesReturnsOnCall[len(fake.taskTemplatesArgsForCall)] + fake.taskTemplatesArgsForCall = append(fake.taskTemplatesArgsForCall, struct { + arg1 context.Context + arg2 []string + }{arg1, arg2Copy}) + stub := fake.TaskTemplatesStub + fakeReturns := fake.taskTemplatesReturns + fake.recordInvocation("TaskTemplates", []interface{}{arg1, arg2Copy}) + fake.taskTemplatesMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStorage) TaskTemplatesCallCount() int { + fake.taskTemplatesMutex.RLock() + defer fake.taskTemplatesMutex.RUnlock() + return len(fake.taskTemplatesArgsForCall) +} + +func (fake *FakeStorage) TaskTemplatesCalls(stub func(context.Context, []string) (map[string]*task.Task, error)) { + fake.taskTemplatesMutex.Lock() + defer fake.taskTemplatesMutex.Unlock() + fake.TaskTemplatesStub = stub +} + +func (fake *FakeStorage) TaskTemplatesArgsForCall(i int) (context.Context, []string) { + fake.taskTemplatesMutex.RLock() + defer fake.taskTemplatesMutex.RUnlock() + argsForCall := fake.taskTemplatesArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeStorage) TaskTemplatesReturns(result1 map[string]*task.Task, result2 error) { + fake.taskTemplatesMutex.Lock() + defer fake.taskTemplatesMutex.Unlock() + fake.TaskTemplatesStub = nil + fake.taskTemplatesReturns = struct { + result1 map[string]*task.Task + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) TaskTemplatesReturnsOnCall(i int, result1 map[string]*task.Task, result2 error) { + fake.taskTemplatesMutex.Lock() + defer fake.taskTemplatesMutex.Unlock() + fake.TaskTemplatesStub = nil + if fake.taskTemplatesReturnsOnCall == nil { + fake.taskTemplatesReturnsOnCall = make(map[int]struct { + result1 map[string]*task.Task + result2 error + }) + } + fake.taskTemplatesReturnsOnCall[i] = struct { + result1 map[string]*task.Task + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.taskListTemplateMutex.RLock() + defer fake.taskListTemplateMutex.RUnlock() + fake.taskTemplatesMutex.RLock() + defer fake.taskTemplatesMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeStorage) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ tasklist.Storage = new(FakeStorage) diff --git a/internal/storage/storage.go b/internal/storage/storage.go index f5b44125ce771b4bec90965e4637bd8e115906b9..fb0f66acf9271f3a950f347017421a4038dde3e8 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -11,31 +11,40 @@ import ( "code.vereign.com/gaiax/tsa/golib/errors" "code.vereign.com/gaiax/tsa/task/internal/service/task" + "code.vereign.com/gaiax/tsa/task/internal/service/tasklist" ) const ( - taskDB = "task" - taskTemplates = "taskTemplates" - taskQueue = "tasks" - tasksHistory = "tasksHistory" + taskDB = "task" + taskTemplates = "taskTemplates" + taskQueue = "tasks" + tasksHistory = "tasksHistory" + taskListQueue = "taskLists" + taskListTemplates = "taskListTemplates" + taskListHistory = "taskListsHistory" ) type Storage struct { - templates *mongo.Collection - tasks *mongo.Collection - tasksHistory *mongo.Collection + taskTemplates *mongo.Collection + tasks *mongo.Collection + tasksHistory *mongo.Collection + taskLists *mongo.Collection + taskListTemplates *mongo.Collection + taskListsHistory *mongo.Collection } func New(db *mongo.Client) *Storage { return &Storage{ - templates: db.Database(taskDB).Collection(taskTemplates), - tasks: db.Database(taskDB).Collection(taskQueue), - tasksHistory: db.Database(taskDB).Collection(tasksHistory), + taskTemplates: db.Database(taskDB).Collection(taskTemplates), + tasks: db.Database(taskDB).Collection(taskQueue), + tasksHistory: db.Database(taskDB).Collection(tasksHistory), + taskListTemplates: db.Database(taskDB).Collection(taskListTemplates), + taskLists: db.Database(taskDB).Collection(taskListQueue), } } func (s *Storage) TaskTemplate(ctx context.Context, taskName string) (*task.Task, error) { - result := s.templates.FindOne(ctx, bson.M{ + result := s.taskTemplates.FindOne(ctx, bson.M{ "name": taskName, }) @@ -59,8 +68,8 @@ func (s *Storage) Add(ctx context.Context, task *task.Task) error { return err } -// Poll retrieves one task from the tasks collection with the -// older ones being retrieved first (FIFO). It updates the state +// Poll retrieves one task with empty groupID from the tasks collection +// with the older ones being retrieved first (FIFO). It updates the state // of the task to "pending", so that consequent calls to Poll would // not retrieve the same task. func (s *Storage) Poll(ctx context.Context) (*task.Task, error) { @@ -69,7 +78,7 @@ func (s *Storage) Poll(ctx context.Context) (*task.Task, error) { SetSort(bson.M{"createdAt": 1}). SetReturnDocument(options.After) - filter := bson.M{"state": task.Created} + filter := bson.M{"state": task.Created, "groupid": ""} update := bson.M{"$set": bson.M{"state": task.Pending}} result := s.tasks.FindOneAndUpdate( ctx, @@ -158,3 +167,76 @@ func (s *Storage) TaskHistory(ctx context.Context, taskID string) (*task.Task, e return &task, nil } + +// TaskListTemplate retrieves one taskList definition by name from storage +func (s *Storage) TaskListTemplate(ctx context.Context, taskListName string) (*tasklist.Template, error) { + result := s.taskListTemplates.FindOne(ctx, bson.M{ + "name": taskListName, + }) + + if result.Err() != nil { + if strings.Contains(result.Err().Error(), "no documents in result") { + return nil, errors.New(errors.NotFound, "taskList template not found") + } + return nil, result.Err() + } + + var tasklist tasklist.Template + if err := result.Decode(&tasklist); err != nil { + return nil, err + } + + return &tasklist, nil +} + +// TaskTemplates retrieves task definitions from storage by names. +// +// The result is a map where 'key' is the task name and 'value' is the task definition +func (s *Storage) TaskTemplates(ctx context.Context, names []string) (map[string]*task.Task, error) { + cursor, err := s.taskTemplates.Find(ctx, bson.M{ + "name": bson.M{"$in": names}, + }) + if err != nil { + return nil, err + } + defer cursor.Close(ctx) + + res := make(map[string]*task.Task) + for cursor.Next(ctx) { + var task task.Task + if err := cursor.Decode(&task); err != nil { + return nil, err + } + res[task.Name] = &task + } + + return res, nil +} + +func (s *Storage) AddTaskList(ctx context.Context, taskList *tasklist.TaskList, tasks []*task.Task) error { + _, err := s.taskLists.InsertOne(ctx, taskList) + if err != nil { + return err + } + + var ti []interface{} + for _, task := range tasks { + ti = append(ti, task) + } + + _, err = s.tasks.InsertMany(ctx, ti) + if err != nil { + if err := s.AckList(ctx, taskList); err != nil { // remove taskList from queue + return errors.New("failed to ack taskList", err) + } + return err + } + + return nil +} + +// AckList removes a taskList from the `tasksLists` collection. +func (s *Storage) AckList(ctx context.Context, taskList *tasklist.TaskList) error { + _, err := s.taskLists.DeleteOne(ctx, bson.M{"id": taskList.ID}) + return err +}