blob: 732b7357469dc68ad74e898097acb15e80c6641d [file]
// Package dto is the wire contract of the process vertical.
//
// There is one resource here — the process — and everything a run needs hangs off it.
// A batch job is not a resource of its own: what a BATCH_JOB needs beyond any other
// process is carried in the batchProcess section of a process body, created with the
// process, read back nested inside it, and removed with it. The template input and
// output mappings are part of that section rather than of the process: the declarations
// they name come from the deployment's template, which only a BATCH_JOB has.
package dto
import (
"strconv"
"github.com/apache/airavata/internal/httpx"
applicationdto "github.com/apache/airavata/api/application/dto"
model "github.com/apache/airavata/api/process/model"
)
// BatchProcessRequest is the batchProcess section of a process request.
//
// The resource request is carried here rather than copied from the deployment's
// default, which is what lets a caller ask for different resources for a particular
// run.
//
// jobId is writable rather than server-generated, for the same reason the submission
// task's is: it is the scheduler's identifier for the submitted job, learned at
// submission time and recorded afterwards.
type BatchProcessRequest struct {
DeploymentID string `json:"deploymentId"`
JobID *string `json:"jobId"`
JobName *string `json:"jobName"`
BatchJobConfig *applicationdto.BatchJobConfigRequest `json:"batchJobConfig"`
// SubmissionCredentialID names the SSH endpoint credential binding this run submits
// under — the id returned by POST /api/v1/ssh-endpoint-credentials, not a bare SSH
// credential.
//
// Required: a deployment carries no credential to fall back on, so every run says
// which identity it submits under, and only a binding the caller owns or that is
// shared with them will be accepted.
SubmissionCredentialID *string `json:"submissionCredentialId"`
// BaseWorkDir is the parent directory on the cluster this run works under; it gets
// a subdirectory of its own beneath it, named for the process id, which is where
// the template's file inputs are staged to and its file outputs staged from.
//
// Carried here rather than on the deployment, for the same reason the resource
// request is: where a run works is a property of the run, so two runs of one
// deployment can work under different directories.
BaseWorkDir *string `json:"baseWorkDir"`
// The values this run supplies for the deployment template's declared inputs and
// outputs. Both sets are replaced wholesale by an update.
InputMappings []InputMapping `json:"inputMappings"`
OutputMappings []OutputMapping `json:"outputMappings"`
}
// Validate implements httpx.Validator.
func (r *BatchProcessRequest) Validate() []httpx.FieldError {
var c httpx.Constraints
c.NotBlank("deploymentId", "Deployment id cannot be blank", r.DeploymentID)
c.NotBlankPtr("submissionCredentialId", "Submission credential id cannot be blank", r.SubmissionCredentialID)
if r.BatchJobConfig == nil {
c.Add("batchJobConfig", "Batch job config cannot be null")
} else {
c.Nested("batchJobConfig", r.BatchJobConfig)
}
for i := range r.InputMappings {
c.Nested(indexed("inputMappings", i), &r.InputMappings[i])
}
for i := range r.OutputMappings {
c.Nested(indexed("outputMappings", i), &r.OutputMappings[i])
}
return c.Fields()
}
// BatchProcessResponse is the batchProcess section of a process response. The nested
// config is a snapshot of what this run actually asked for.
type BatchProcessResponse struct {
BatchProcessID string `json:"batchProcessId"`
DeploymentID *string `json:"deploymentId"`
JobID *string `json:"jobId"`
JobName *string `json:"jobName"`
BatchJobConfig *applicationdto.BatchJobConfigResponse `json:"batchJobConfig"`
SubmissionCredentialID string `json:"submissionCredentialId"`
BaseWorkDir *string `json:"baseWorkDir"`
InputMappings []InputMapping `json:"inputMappings"`
OutputMappings []OutputMapping `json:"outputMappings"`
}
// InputMapping binds one of the template's declared inputs to a value for this run.
// It is used for both reads and writes.
//
// TemplateInputMappingID is echoed back on reads but ignored on writes: an update
// replaces the mapping set wholesale, the same way a template's declarations are
// replaced, so child ids are regenerated rather than matched.
type InputMapping struct {
TemplateInputMappingID string `json:"templateInputMappingId"`
TemplateInputID string `json:"templateInputId"`
// Value is a JSON document, either {"value": "..."} for a single value or
// {"values": [...]} for a list.
Value *string `json:"value"`
}
// Validate implements httpx.Validator.
func (m *InputMapping) Validate() []httpx.FieldError {
var c httpx.Constraints
c.NotBlank("templateInputId", "Template input id cannot be blank", m.TemplateInputID)
return c.Fields()
}
// OutputMapping binds one of the template's declared outputs to a value for this run.
type OutputMapping struct {
TemplateOutputMappingID string `json:"templateOutputMappingId"`
TemplateOutputID string `json:"templateOutputId"`
// Value is a JSON document, either {"value": "..."} for a single value or
// {"values": [...]} for a list.
Value *string `json:"value"`
}
// Validate implements httpx.Validator.
func (m *OutputMapping) Validate() []httpx.FieldError {
var c httpx.Constraints
c.NotBlank("templateOutputId", "Template output id cannot be blank", m.TemplateOutputID)
return c.Fields()
}
// Request is the create/update payload for a process.
//
// There is no owner field: ownership comes from the access token.
type Request struct {
ProcessType *model.ProcessType `json:"processType"`
// BatchProcess is required when ProcessType is BATCH_JOB and rejected otherwise.
BatchProcess *BatchProcessRequest `json:"batchProcess"`
}
// Validate implements httpx.Validator.
//
// Which sections a body may carry follows from its process type, so the type is
// checked first and the sections against it: a BATCH_JOB without a batchProcess
// describes no job at all, and a batchProcess on any other kind of process is a
// caller's mistake rather than something to store and ignore.
func (r *Request) Validate() []httpx.FieldError {
var c httpx.Constraints
c.NotNil("processType", "Process type cannot be null", r.ProcessType)
if r.ProcessType != nil && !r.ProcessType.Valid() {
c.Add("processType", "Process type must be one of BATCH_JOB, CLOUD_JOB")
}
isBatch := r.ProcessType != nil && *r.ProcessType == model.ProcessTypeBatchJob
switch {
case isBatch && r.BatchProcess == nil:
c.Add("batchProcess", "Batch process cannot be null for a BATCH_JOB process")
case r.BatchProcess != nil && r.ProcessType != nil && !isBatch:
c.Add("batchProcess", "Batch process is only accepted for a BATCH_JOB process")
case r.BatchProcess != nil:
c.Nested("batchProcess", r.BatchProcess)
}
return c.Fields()
}
// Response is the read model for a process, with every section it carries nested
// inside it.
type Response struct {
ProcessID string `json:"processId"`
UserID *string `json:"userId"`
ProcessType *model.ProcessType `json:"processType"`
LastStatusID *string `json:"lastStatusId"`
BatchProcess *BatchProcessResponse `json:"batchProcess"`
}
func ToResponse(p *model.Process) Response {
return Response{
ProcessID: p.ID,
UserID: p.OwnerID,
ProcessType: p.ProcessType,
LastStatusID: p.LastStatusID,
BatchProcess: ToBatchProcessResponse(p.BatchProcess),
}
}
func ToResponses(in []model.Process) []Response {
out := make([]Response, 0, len(in))
for i := range in {
out = append(out, ToResponse(&in[i]))
}
return out
}
// ToBatchProcessResponse renders the batchProcess section, or nil for a process that
// has none.
func ToBatchProcessResponse(b *model.BatchJobProcess) *BatchProcessResponse {
if b == nil {
return nil
}
return &BatchProcessResponse{
BatchProcessID: b.ID,
DeploymentID: b.DeploymentID,
JobID: b.JobID,
JobName: b.JobName,
SubmissionCredentialID: b.SubmissionCredentialID,
BaseWorkDir: b.BaseWorkDir,
BatchJobConfig: applicationdto.ToBatchJobConfigResponse(b.BatchJobConfig),
InputMappings: ToInputMappings(b.InputMappings),
OutputMappings: ToOutputMappings(b.OutputMappings),
}
}
// ApplyBatchProcessRequest copies a section onto a batch process row, preserving the
// row's own ids so an update mutates it instead of orphaning it.
func ApplyBatchProcessRequest(dst *model.BatchJobProcess, src *BatchProcessRequest) {
dst.DeploymentID = &src.DeploymentID
dst.JobID = src.JobID
dst.JobName = src.JobName
dst.BaseWorkDir = src.BaseWorkDir
}
func ToInputMappings(in []*model.TemplateInputMapping) []InputMapping {
out := make([]InputMapping, 0, len(in))
for _, m := range in {
if m == nil {
continue
}
out = append(out, InputMapping{
TemplateInputMappingID: m.TemplateInputMappingID,
TemplateInputID: derefString(m.TemplateInputID),
Value: m.Value,
})
}
return out
}
func ToOutputMappings(in []*model.TemplateOutputMapping) []OutputMapping {
out := make([]OutputMapping, 0, len(in))
for _, m := range in {
if m == nil {
continue
}
out = append(out, OutputMapping{
TemplateOutputMappingID: m.TemplateOutputMappingID,
TemplateOutputID: derefString(m.TemplateOutputID),
Value: m.Value,
})
}
return out
}
// ToInputMappingEntities builds the rows for a batch process's mapping set. Ids are
// left unset so BeforeCreate assigns them.
func ToInputMappingEntities(batchProcessID string, in []InputMapping) []*model.TemplateInputMapping {
out := make([]*model.TemplateInputMapping, 0, len(in))
for i := range in {
m := &in[i]
templateInputID := m.TemplateInputID
out = append(out, &model.TemplateInputMapping{
BatchProcessID: &batchProcessID,
TemplateInputID: &templateInputID,
Value: m.Value,
})
}
return out
}
// ToOutputMappingEntities builds the rows for a batch process's output mapping set.
func ToOutputMappingEntities(batchProcessID string, in []OutputMapping) []*model.TemplateOutputMapping {
out := make([]*model.TemplateOutputMapping, 0, len(in))
for i := range in {
m := &in[i]
templateOutputID := m.TemplateOutputID
out = append(out, &model.TemplateOutputMapping{
BatchProcessID: &batchProcessID,
TemplateOutputID: &templateOutputID,
Value: m.Value,
})
}
return out
}
func derefString(s *string) string {
if s == nil {
return ""
}
return *s
}
func indexed(field string, i int) string {
return field + "[" + strconv.Itoa(i) + "]"
}