Skip to content

Commit

Permalink
feat(enterprise): add requests to workflows
Browse files Browse the repository at this point in the history
Resource requests for workflow runner pods can now be set via
`workflow.requests`.
  • Loading branch information
thsig committed Mar 30, 2021
1 parent c08afe7 commit d33194c
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 35 deletions.
79 changes: 66 additions & 13 deletions core/src/config/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from "./common"
import { DEFAULT_API_VERSION } from "../constants"
import { deline, dedent } from "../util/string"
import { defaultContainerLimits, ServiceLimitSpec } from "../plugins/container/config"
import { ServiceLimitSpec } from "../plugins/container/config"
import { Garden } from "../garden"
import { WorkflowConfigContext } from "./template-contexts/workflow"
import { resolveTemplateStrings } from "../template-string/template-string"
Expand All @@ -31,13 +31,39 @@ import { globalOptions } from "../cli/params"
import { isTruthy, omitUndefined } from "../util/util"
import { parseCliArgs, pickCommand } from "../cli/helpers"

export const minimumWorkflowRequests = {
cpu: 50, // 50 millicpu
memory: 64, // 64MB
}

export const defaultWorkflowRequests = minimumWorkflowRequests

export const minimumWorkflowLimits = {
cpu: 100, // 100 millicpu
memory: 64, // 64MB
}

export const defaultWorkflowLimits = {
cpu: 1000, // = 1000 millicpu = 1 CPU
memory: 1024, // = 1024MB = 1GB
}

export const defaultWorkflowResources = {
requests: defaultWorkflowRequests,
limits: defaultWorkflowLimits,
}

export interface WorkflowConfig {
apiVersion: string
description?: string
name: string
envVars: PrimitiveMap
kind: "Workflow"
path: string
resources: {
requests: ServiceLimitSpec
limits: ServiceLimitSpec
}
configPath?: string
keepAliveHours?: number
files?: WorkflowFileSpec[]
Expand All @@ -61,6 +87,28 @@ export function makeRunConfig(

export interface WorkflowResource extends WorkflowConfig {}

const workflowResourceRequestsSchema = () =>
joi.object().keys({
cpu: joi.number().min(minimumWorkflowRequests.cpu).description(deline`
The minimum amount of CPU the workflow needs in order to be scheduled, in millicpus (i.e. 1000 = 1 CPU).
`),
memory: joi.number().min(minimumWorkflowRequests.memory).description(deline`
The minimum amount of RAM the workflow needs in order to be scheduled, in megabytes (i.e. 1024 = 1 GB).
`),
})

const workflowResourceLimitsSchema = () =>
joi.object().keys({
cpu: joi
.number()
.min(minimumWorkflowLimits.cpu)
.description("The maximum amount of CPU the workflow pod can use, in millicpus (i.e. 1000 = 1 CPU)."),
memory: joi
.number()
.min(minimumWorkflowLimits.memory)
.description("The maximum amount of RAM the workflow pod can use, in megabytes (i.e. 1024 = 1 GB)."),
})

export const workflowConfigSchema = () =>
joi
.object()
Expand All @@ -87,21 +135,15 @@ export const workflowConfigSchema = () =>
.number()
.default(48)
.description("The number of hours to keep the workflow pod running after completion."),
limits: joi
resources: joi
.object()
.keys({
cpu: joi
.number()
.default(defaultContainerLimits.cpu)
.min(500)
.description("The maximum amount of CPU the workflow pod can use, in millicpus (i.e. 1000 = 1 CPU)"),
memory: joi
.number()
.default(defaultContainerLimits.memory)
.min(256)
.description("The maximum amount of RAM the workflow pod can use, in megabytes (i.e. 1024 = 1 GB)"),
requests: workflowResourceRequestsSchema().default(defaultWorkflowRequests),
limits: workflowResourceLimitsSchema().default(defaultWorkflowLimits),
})
.default(defaultContainerLimits),
// .default(() => ({}))
.meta({ enterprise: true }),
limits: workflowResourceLimitsSchema().meta({ enterprise: true, deprecated: true }),
steps: joiSparseArray(workflowStepSchema()).required().min(1).description(deline`
The steps the workflow should run. At least one step is required. Steps are run sequentially.
If a step fails, subsequent steps are skipped.
Expand Down Expand Up @@ -356,6 +398,17 @@ export function resolveWorkflowConfig(garden: Garden, config: WorkflowConfig) {
),
}

/**
* TODO: Remove support for workflow.limits the next time we make a release with breaking changes.
*
* workflow.limits is deprecated, so we copy its values into workflow.resources.limits if workflow.limits
* is specified.
*/

if (resolvedConfig.limits) {
resolvedConfig.resources.limits = resolvedConfig.limits
}

validateSteps(resolvedConfig)
validateTriggers(resolvedConfig, garden.environmentConfigs)
populateNamespaceForTriggers(resolvedConfig, garden.environmentConfigs)
Expand Down
3 changes: 2 additions & 1 deletion core/test/unit/src/commands/get/get-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { makeTestGardenA, withDefaultGlobalOpts } from "../../../../helpers"
import { GetConfigCommand } from "../../../../../src/commands/get/get-config"
import { sortBy } from "lodash"
import { DEFAULT_API_VERSION } from "../../../../../src/constants"
import { WorkflowConfig } from "../../../../../src/config/workflow"
import { defaultWorkflowResources, WorkflowConfig } from "../../../../../src/config/workflow"
import { defaultContainerLimits } from "../../../../../src/plugins/container/config"

describe("GetConfigCommand", () => {
Expand Down Expand Up @@ -73,6 +73,7 @@ describe("GetConfigCommand", () => {
limits: defaultContainerLimits,
path: garden.projectRoot,
envVars: {},
resources: defaultWorkflowResources,
steps: [{ command: ["run", "task", "foo"] }],
},
]
Expand Down
23 changes: 22 additions & 1 deletion core/test/unit/src/commands/run/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { defaultDotIgnoreFiles } from "../../../../../src/util/fs"
import { dedent } from "../../../../../src/util/string"
import stripAnsi from "strip-ansi"
import { LogEntry } from "../../../../../src/logger/log-entry"
import { WorkflowStepSpec } from "../../../../../src/config/workflow"
import { defaultWorkflowResources, WorkflowStepSpec } from "../../../../../src/config/workflow"

describe("RunWorkflowCommand", () => {
const cmd = new RunWorkflowCommand()
Expand All @@ -48,6 +48,7 @@ describe("RunWorkflowCommand", () => {
name: "workflow-a",
kind: "Workflow",
envVars: {},
resources: defaultWorkflowResources,
path: garden.projectRoot,
steps: [
{ command: ["deploy"], description: "deploy services" },
Expand Down Expand Up @@ -81,6 +82,7 @@ describe("RunWorkflowCommand", () => {
name: "workflow-a",
kind: "Workflow",
envVars: {},
resources: defaultWorkflowResources,
path: garden.projectRoot,
steps: [{ command: ["deploy"] }, { command: ["test"] }],
},
Expand Down Expand Up @@ -122,6 +124,7 @@ describe("RunWorkflowCommand", () => {
name: "workflow-a",
kind: "Workflow",
envVars: {},
resources: defaultWorkflowResources,
path: garden.projectRoot,
steps: [{ command: ["deploy"] }, { command: ["build"], skip: true }, { command: ["test"] }],
},
Expand Down Expand Up @@ -160,6 +163,7 @@ describe("RunWorkflowCommand", () => {
name: "workflow-a",
kind: "Workflow",
envVars: {},
resources: defaultWorkflowResources,
path: garden.projectRoot,
files: [],
steps: [{ command: ["run", "task", "task-a"] }],
Expand Down Expand Up @@ -303,6 +307,7 @@ describe("RunWorkflowCommand", () => {
name: "workflow-a",
kind: "Workflow",
envVars: {},
resources: defaultWorkflowResources,
path: garden.projectRoot,
steps: [{ command: ["run", "task", "some-task"] }, { command: ["test"] }],
},
Expand Down Expand Up @@ -364,6 +369,7 @@ describe("RunWorkflowCommand", () => {
kind: "Workflow",
path: garden.projectRoot,
envVars: {},
resources: defaultWorkflowResources,
files: [{ path: ".garden/test.txt", data: "test" }],
steps: [{ command: ["get", "outputs"] }],
},
Expand All @@ -381,6 +387,7 @@ describe("RunWorkflowCommand", () => {
kind: "Workflow",
path: garden.projectRoot,
envVars: {},
resources: defaultWorkflowResources,
files: [{ path: ".garden/test.txt", secretName: "test" }],
steps: [{ command: ["get", "outputs"] }],
},
Expand All @@ -403,6 +410,7 @@ describe("RunWorkflowCommand", () => {
kind: "Workflow",
path: garden.projectRoot,
envVars: {},
resources: defaultWorkflowResources,
files: [{ path: ".garden/test.txt", secretName: "missing" }],
steps: [{ command: ["get", "outputs"] }],
},
Expand All @@ -423,6 +431,7 @@ describe("RunWorkflowCommand", () => {
kind: "Workflow",
path: garden.projectRoot,
envVars: {},
resources: defaultWorkflowResources,
files: [{ path: "garden.yml/foo.txt", data: "foo" }],
steps: [{ command: ["get", "outputs"] }],
},
Expand All @@ -444,6 +453,7 @@ describe("RunWorkflowCommand", () => {
kind: "Workflow",
path: garden.projectRoot,
envVars: {},
resources: defaultWorkflowResources,
files: [{ path: ".garden", data: "foo" }],
steps: [{ command: ["get", "outputs"] }],
},
Expand All @@ -466,6 +476,7 @@ describe("RunWorkflowCommand", () => {
kind: "Workflow",
path: garden.projectRoot,
envVars: {},
resources: defaultWorkflowResources,
files: [],
steps: [{ script: "pwd" }],
},
Expand All @@ -488,6 +499,7 @@ describe("RunWorkflowCommand", () => {
kind: "Workflow",
path: garden.projectRoot,
envVars: { TEST_VAR_A: "llama" },
resources: defaultWorkflowResources,
files: [],
steps: [{ script: "echo $TEST_VAR_A" }],
},
Expand All @@ -511,6 +523,7 @@ describe("RunWorkflowCommand", () => {
kind: "Workflow",
path: garden.projectRoot,
envVars: { TEST_VAR_A: "llama" },
resources: defaultWorkflowResources,
files: [],
steps: [{ script: "echo $TEST_VAR_A", envVars: { TEST_VAR_A: "bear" } }],
},
Expand All @@ -535,6 +548,7 @@ describe("RunWorkflowCommand", () => {
path: garden.projectRoot,
files: [],
envVars: {},
resources: defaultWorkflowResources,
steps: [{ script: "echo $FOO $BAR", envVars: { FOO: "foo", BAR: 123 } }],
},
])
Expand All @@ -557,6 +571,7 @@ describe("RunWorkflowCommand", () => {
path: garden.projectRoot,
files: [],
envVars: {},
resources: defaultWorkflowResources,
steps: [{ script: "pwd" }, { script: "echo fail!; exit 1", skip: true }],
},
])
Expand Down Expand Up @@ -658,6 +673,7 @@ describe("RunWorkflowCommand", () => {
path: garden.projectRoot,
files: [],
envVars: {},
resources: defaultWorkflowResources,
steps: [
{
script: dedent`
Expand Down Expand Up @@ -685,6 +701,7 @@ describe("RunWorkflowCommand", () => {
path: garden.projectRoot,
files: [],
envVars: {},
resources: defaultWorkflowResources,
steps: [{ script: "echo boo!; exit 1" }],
},
])
Expand All @@ -704,6 +721,7 @@ describe("RunWorkflowCommand", () => {
path: garden.projectRoot,
files: [],
envVars: {},
resources: defaultWorkflowResources,
steps: [{ command: ["get", "config"] }, { command: ["run", "task", "task-a"] }],
},
])
Expand Down Expand Up @@ -731,6 +749,7 @@ describe("RunWorkflowCommand", () => {
path: garden.projectRoot,
files: [],
envVars: {},
resources: defaultWorkflowResources,
steps: [{ name: "test", command: ["run", "task", "task-a"] }],
},
])
Expand All @@ -755,6 +774,7 @@ describe("RunWorkflowCommand", () => {
path: garden.projectRoot,
files: [],
envVars: {},
resources: defaultWorkflowResources,
steps: [{ command: ["get", "outputs"] }, { command: ["run", "task", "${steps.step-1.outputs.taskName}"] }],
},
])
Expand All @@ -778,6 +798,7 @@ describe("RunWorkflowCommand", () => {
path: garden.projectRoot,
files: [],
envVars: {},
resources: defaultWorkflowResources,
steps: [{ command: ["get", "outputs"] }, { script: "echo ${steps.step-1.outputs.taskName}" }],
},
])
Expand Down
46 changes: 43 additions & 3 deletions core/test/unit/src/config/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import {
resolveWorkflowConfig,
populateNamespaceForTriggers,
TriggerSpec,
minimumWorkflowLimits,
defaultWorkflowRequests,
defaultWorkflowLimits,
} from "../../../../src/config/workflow"
import { defaultContainerLimits } from "../../../../src/plugins/container/config"
import { EnvironmentConfig, defaultNamespace } from "../../../../src/config/project"
import stripAnsi from "strip-ansi"

Expand All @@ -24,7 +26,10 @@ describe("resolveWorkflowConfig", () => {

const defaults = {
files: [],
limits: defaultContainerLimits,
resources: {
requests: defaultWorkflowRequests,
limits: defaultWorkflowLimits,
},
keepAliveHours: 48,
}

Expand Down Expand Up @@ -63,6 +68,40 @@ describe("resolveWorkflowConfig", () => {
})
})

it("should set workflow.resources.limits to workflow.limits if workflow.limits is specified", async () => {
const config: WorkflowConfig = {
...defaults,
apiVersion: DEFAULT_API_VERSION,
kind: "Workflow",
name: "workflow-a",
path: "/tmp/foo",
description: "Sample workflow",
envVars: {},
limits: minimumWorkflowLimits, // <----
steps: [
{ description: "Deploy the stack", command: ["deploy"], skip: false, when: "onSuccess", envVars: {} },
{ command: ["test"], skip: false, when: "onSuccess", envVars: {} },
],
triggers: [
{
environment: "local",
namespace: "default",
events: ["pull-request"],
branches: ["feature*"],
ignoreBranches: ["feature-ignored*"],
},
],
}

expect(resolveWorkflowConfig(garden, config)).to.eql({
...config,
resources: {
requests: defaultWorkflowRequests,
limits: minimumWorkflowLimits, // <-----
},
})
})

it("should resolve template strings", async () => {
const config: WorkflowConfig = {
...defaults,
Expand Down Expand Up @@ -162,13 +201,14 @@ describe("resolveWorkflowConfig", () => {
})

it("should populate default values in the schema", async () => {
const config: WorkflowConfig = {
const config = <WorkflowConfig>{
apiVersion: DEFAULT_API_VERSION,
kind: "Workflow",
name: "workflow-a",
path: "/tmp/foo",
description: "Description",
envVars: {},
resources: {},
steps: [{ description: "Deploy the stack", command: ["deploy"] }, { command: ["test"] }],
}

Expand Down

0 comments on commit d33194c

Please sign in to comment.