Skip to content

Commit

Permalink
fix(#592): Introduce build order strategy
Browse files Browse the repository at this point in the history
- Run builds on same operator namespace with user defined strategy
- Default strategy is "sequential" running only one single build at a time
- Also support "fifo" strategy where builds are run/queued based on their creation timestamp. This strategy allows parallel builds as long as individual build dependency lists are not colliding
- Users may adjust/overwrite the build order strategy via install command option, in the (local) integration platform settings or via the builder trait option
- Max number of running builds limitation is not affected by the build order strategy (ensure to always obey the limitation)
  • Loading branch information
christophd committed Jul 6, 2023
1 parent 0908a29 commit ffa5313
Show file tree
Hide file tree
Showing 35 changed files with 634 additions and 64 deletions.
12 changes: 12 additions & 0 deletions config/crd/bases/camel.apache.org_builds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ spec:
description: The namespace where to run the builder Pod (must
be the same of the operator in charge of this Build reconciliation).
type: string
orderStrategy:
description: the build order strategy to adopt
enum:
- fifo
- sequential
type: string
requestCPU:
description: The minimum amount of CPU required. Only used for
`pod` strategy
Expand Down Expand Up @@ -206,6 +212,12 @@ spec:
Pod (must be the same of the operator in charge of
this Build reconciliation).
type: string
orderStrategy:
description: the build order strategy to adopt
enum:
- fifo
- sequential
type: string
requestCPU:
description: The minimum amount of CPU required. Only
used for `pod` strategy
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/camel.apache.org_integrationkits.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ spec:
description: When using `pod` strategy, the maximum amount
of memory required by the pod builder.
type: string
orderStrategy:
description: The build order strategy to use, either `fifo`
or `sequential` (default sequential)
type: string
properties:
description: A list of properties to be provided to the build
task
Expand Down
20 changes: 20 additions & 0 deletions config/crd/bases/camel.apache.org_integrationplatforms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ spec:
description: The namespace where to run the builder Pod (must
be the same of the operator in charge of this Build reconciliation).
type: string
orderStrategy:
description: the build order strategy to adopt
enum:
- fifo
- sequential
type: string
requestCPU:
description: The minimum amount of CPU required. Only used
for `pod` strategy
Expand Down Expand Up @@ -462,6 +468,10 @@ spec:
description: When using `pod` strategy, the maximum amount
of memory required by the pod builder.
type: string
orderStrategy:
description: The build order strategy to use, either `fifo`
or `sequential` (default sequential)
type: string
properties:
description: A list of properties to be provided to the build
task
Expand Down Expand Up @@ -1753,6 +1763,12 @@ spec:
description: The namespace where to run the builder Pod (must
be the same of the operator in charge of this Build reconciliation).
type: string
orderStrategy:
description: the build order strategy to adopt
enum:
- fifo
- sequential
type: string
requestCPU:
description: The minimum amount of CPU required. Only used
for `pod` strategy
Expand Down Expand Up @@ -2147,6 +2163,10 @@ spec:
description: When using `pod` strategy, the maximum amount
of memory required by the pod builder.
type: string
orderStrategy:
description: The build order strategy to use, either `fifo`
or `sequential` (default sequential)
type: string
properties:
description: A list of properties to be provided to the build
task
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/camel.apache.org_integrations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6194,6 +6194,10 @@ spec:
description: When using `pod` strategy, the maximum amount
of memory required by the pod builder.
type: string
orderStrategy:
description: The build order strategy to use, either `fifo`
or `sequential` (default sequential)
type: string
properties:
description: A list of properties to be provided to the build
task
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/camel.apache.org_kameletbindings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6469,6 +6469,10 @@ spec:
description: When using `pod` strategy, the maximum amount
of memory required by the pod builder.
type: string
orderStrategy:
description: The build order strategy to use, either `fifo`
or `sequential` (default sequential)
type: string
properties:
description: A list of properties to be provided to the
build task
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/camel.apache.org_pipes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6466,6 +6466,10 @@ spec:
description: When using `pod` strategy, the maximum amount
of memory required by the pod builder.
type: string
orderStrategy:
description: The build order strategy to use, either `fifo`
or `sequential` (default sequential)
type: string
properties:
description: A list of properties to be provided to the
build task
Expand Down
9 changes: 9 additions & 0 deletions docs/modules/ROOT/pages/architecture/cr/build.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ At the moment the available strategies are:
- buildStrategy: pod (each build is run in a separate pod, the operator monitors the pod state)
- buildStrategy: routine (each build is run as a go routine inside the operator pod)

[[build-order-strategy]]
== Build order strategy

You can choose from different build order strategies. The strategy defines in which order queued builds are run.
At the moment the available strategies are:

- buildOrderStrategy: sequential (runs builds strictly sequential so that only one single build per operator namespace is running at a time.)
- buildOrderStrategy: fifo (performs the builds with first in first out strategy based on the creation timestamp. The strategy allows builds to run in parallel to each other but oldest builds will be run first.)

[[build-queue]]
== Build queues

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ We have several configuration used to influence the building of an integration:
--build-publish-strategy string Set the build publish strategy
--build-publish-strategy-option stringArray Add a build publish strategy option, as <name=value>
--build-strategy string Set the build strategy
--build-order-strategy string Set the build order strategy
--build-timeout string Set how long the build process can last
```
A very important set of configuration you can provide is related to Maven:
Expand Down
24 changes: 24 additions & 0 deletions docs/modules/ROOT/partials/apis/camel-k-crds.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,13 @@ The namespace where to run the builder Pod (must be the same of the operator in
the strategy to adopt
|`orderStrategy` +
*xref:#_camel_apache_org_v1_BuildOrderStrategy[BuildOrderStrategy]*
|
the build order strategy to adopt
|`requestCPU` +
string
|
Expand Down Expand Up @@ -564,6 +571,16 @@ The maximum amount of memory required. Only used for `pod` strategy
|===
[#_camel_apache_org_v1_BuildOrderStrategy]
=== BuildOrderStrategy(`string` alias)
*Appears on:*
* <<#_camel_apache_org_v1_BuildConfiguration, BuildConfiguration>>
BuildOrderStrategy specifies how builds are reconciled and queued.
[#_camel_apache_org_v1_BuildPhase]
=== BuildPhase(`string` alias)
Expand Down Expand Up @@ -5390,6 +5407,13 @@ string
The strategy to use, either `pod` or `routine` (default routine)
|`orderStrategy` +
string
|
The build order strategy to use, either `fifo` or `sequential` (default sequential)
|`requestCPU` +
string
|
Expand Down
4 changes: 4 additions & 0 deletions docs/modules/traits/pages/builder.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ The following configuration options are available:
| string
| The strategy to use, either `pod` or `routine` (default routine)

| builder.order-strategy
| string
| The build order strategy to use, either `fifo` or `sequential` (default sequential)

| builder.request-cpu
| string
| When using `pod` strategy, the minimum amount of CPU required by the pod builder.
Expand Down
77 changes: 76 additions & 1 deletion e2e/builder/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ func TestKitMaxBuildLimit(t *testing.T) {
createOperator(ns, "8m0s", "--global", "--force")

pl := Platform(ns)()
// set maximum number of running builds
// set maximum number of running builds and order strategy
pl.Spec.Build.MaxRunningBuilds = 2
pl.Spec.Build.BuildConfiguration.OrderStrategy = v1.BuildOrderStrategySequential
if err := TestClient().Update(TestContext, pl); err != nil {
t.Error(err)
t.FailNow()
Expand Down Expand Up @@ -139,6 +140,80 @@ func TestKitMaxBuildLimit(t *testing.T) {
})
}

func TestKitMaxBuildLimitFIFOStrategy(t *testing.T) {
WithNewTestNamespace(t, func(ns string) {
createOperator(ns, "8m0s", "--global", "--force")

pl := Platform(ns)()
// set maximum number of running builds and order strategy
pl.Spec.Build.MaxRunningBuilds = 2
pl.Spec.Build.BuildConfiguration.OrderStrategy = v1.BuildOrderStrategyFIFO
if err := TestClient().Update(TestContext, pl); err != nil {
t.Error(err)
t.FailNow()
}

buildA := "integration-a"
buildB := "integration-b"
buildC := "integration-c"

doKitBuildInNamespace(buildA, ns, TestTimeoutShort, kitOptions{
operatorID: fmt.Sprintf("camel-k-%s", ns),
dependencies: []string{
"camel:timer", "camel:log",
},
traits: []string{
"builder.properties=build-property=A",
},
}, v1.BuildPhaseRunning, v1.IntegrationKitPhaseBuildRunning)

doKitBuildInNamespace(buildB, ns, TestTimeoutShort, kitOptions{
operatorID: fmt.Sprintf("camel-k-%s", ns),
dependencies: []string{
"camel:timer", "camel:log",
},
traits: []string{
"builder.properties=build-property=B",
},
}, v1.BuildPhaseRunning, v1.IntegrationKitPhaseBuildRunning)

doKitBuildInNamespace(buildC, ns, TestTimeoutShort, kitOptions{
operatorID: fmt.Sprintf("camel-k-%s", ns),
dependencies: []string{
"camel:timer", "camel:log",
},
traits: []string{
"builder.properties=build-property=C",
},
}, v1.BuildPhaseScheduling, v1.IntegrationKitPhaseNone)

var notExceedsMaxBuildLimit = func(runningBuilds int) bool {
return runningBuilds <= 2
}

limit := 0
for limit < 5 && BuildPhase(ns, buildA)() == v1.BuildPhaseRunning {
// verify that number of running builds does not exceed max build limit
Consistently(BuildsRunning(BuildPhase(ns, buildA), BuildPhase(ns, buildB), BuildPhase(ns, buildC)), TestTimeoutShort, 10*time.Second).Should(Satisfy(notExceedsMaxBuildLimit))
limit++
}

// make sure we have verified max build limit at least once
if limit == 0 {
t.Error(errors.New(fmt.Sprintf("Unexpected build phase '%s' for %s - not able to verify max builds limit", BuildPhase(ns, buildA)(), buildA)))
t.FailNow()
}

// verify that all builds are successful
Eventually(BuildPhase(ns, buildA), TestTimeoutLong).Should(Equal(v1.BuildPhaseSucceeded))
Eventually(KitPhase(ns, buildA), TestTimeoutLong).Should(Equal(v1.IntegrationKitPhaseReady))
Eventually(BuildPhase(ns, buildB), TestTimeoutLong).Should(Equal(v1.BuildPhaseSucceeded))
Eventually(KitPhase(ns, buildB), TestTimeoutLong).Should(Equal(v1.IntegrationKitPhaseReady))
Eventually(BuildPhase(ns, buildC), TestTimeoutLong).Should(Equal(v1.BuildPhaseSucceeded))
Eventually(KitPhase(ns, buildC), TestTimeoutLong).Should(Equal(v1.IntegrationKitPhaseReady))
})
}

func TestKitTimerToLogFullBuild(t *testing.T) {
doKitFullBuild(t, "timer-to-log", "8m0s", TestTimeoutLong, kitOptions{
dependencies: []string{
Expand Down
35 changes: 32 additions & 3 deletions e2e/common/traits/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ import (
func TestBuilderTrait(t *testing.T) {
RegisterTestingT(t)

name := "java"

t.Run("Run build strategy routine", func(t *testing.T) {
name := "java"
Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
"--name", name,
"-t", "builder.strategy=routine").Execute()).To(Succeed())
Expand All @@ -51,6 +50,33 @@ func TestBuilderTrait(t *testing.T) {
integrationKitName := IntegrationKit(ns, name)()
builderKitName := fmt.Sprintf("camel-k-%s-builder", integrationKitName)
Eventually(BuildConfig(ns, integrationKitName)().Strategy, TestTimeoutShort).Should(Equal(v1.BuildStrategyRoutine))
Eventually(BuildConfig(ns, integrationKitName)().OrderStrategy, TestTimeoutShort).Should(Equal(v1.BuildOrderStrategySequential))
// Default resource CPU Check
Eventually(BuildConfig(ns, integrationKitName)().RequestCPU, TestTimeoutShort).Should(Equal(""))
Eventually(BuildConfig(ns, integrationKitName)().LimitCPU, TestTimeoutShort).Should(Equal(""))
Eventually(BuildConfig(ns, integrationKitName)().RequestMemory, TestTimeoutShort).Should(Equal(""))
Eventually(BuildConfig(ns, integrationKitName)().LimitMemory, TestTimeoutShort).Should(Equal(""))

Eventually(BuilderPod(ns, builderKitName), TestTimeoutShort).Should(BeNil())

// We need to remove the kit as well
Expect(Kamel("reset", "-n", ns).Execute()).To(Succeed())
})

t.Run("Run build order strategy fifo", func(t *testing.T) {
name := "java-fifo-strategy"
Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
"--name", name,
"-t", "builder.order-strategy=fifo").Execute()).To(Succeed())

Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning))
Eventually(IntegrationConditionStatus(ns, name, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))

integrationKitName := IntegrationKit(ns, name)()
builderKitName := fmt.Sprintf("camel-k-%s-builder", integrationKitName)
Eventually(BuildConfig(ns, integrationKitName)().Strategy, TestTimeoutShort).Should(Equal(v1.BuildStrategyRoutine))
Eventually(BuildConfig(ns, integrationKitName)().OrderStrategy, TestTimeoutShort).Should(Equal(v1.BuildOrderStrategyFIFO))
// Default resource CPU Check
Eventually(BuildConfig(ns, integrationKitName)().RequestCPU, TestTimeoutShort).Should(Equal(""))
Eventually(BuildConfig(ns, integrationKitName)().LimitCPU, TestTimeoutShort).Should(Equal(""))
Expand All @@ -64,6 +90,7 @@ func TestBuilderTrait(t *testing.T) {
})

t.Run("Run build resources configuration", func(t *testing.T) {
name := "java-resource-config"
Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
"--name", name,
"-t", "builder.request-cpu=500m",
Expand All @@ -81,6 +108,7 @@ func TestBuilderTrait(t *testing.T) {
builderKitName := fmt.Sprintf("camel-k-%s-builder", integrationKitName)

Eventually(BuildConfig(ns, integrationKitName)().Strategy, TestTimeoutShort).Should(Equal(v1.BuildStrategyPod))
Eventually(BuildConfig(ns, integrationKitName)().OrderStrategy, TestTimeoutShort).Should(Equal(v1.BuildOrderStrategySequential))
Eventually(BuildConfig(ns, integrationKitName)().RequestCPU, TestTimeoutShort).Should(Equal("500m"))
Eventually(BuildConfig(ns, integrationKitName)().LimitCPU, TestTimeoutShort).Should(Equal("1000m"))
Eventually(BuildConfig(ns, integrationKitName)().RequestMemory, TestTimeoutShort).Should(Equal("2Gi"))
Expand All @@ -98,6 +126,7 @@ func TestBuilderTrait(t *testing.T) {
})

t.Run("Run custom pipeline task", func(t *testing.T) {
name := "java-pipeline"
Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
"--name", name,
"-t", "builder.tasks=custom1;alpine;tree",
Expand Down Expand Up @@ -141,8 +170,8 @@ func TestBuilderTrait(t *testing.T) {
Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
})

name = "java-error"
t.Run("Run custom pipeline task error", func(t *testing.T) {
name := "java-error"
Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
"--name", name,
"-t", "builder.tasks=custom1;alpine;cat missingfile.txt",
Expand Down
1 change: 1 addition & 0 deletions e2e/commonwithcustominstall/local_platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func TestLocalPlatform(t *testing.T) {
local := Platform(ns1)()
Expect(local.Status.Build.PublishStrategy).To(Equal(pl.Status.Build.PublishStrategy))
Expect(local.Status.Build.BuildConfiguration.Strategy).To(Equal(pl.Status.Build.BuildConfiguration.Strategy))
Expect(local.Status.Build.BuildConfiguration.OrderStrategy).To(Equal(pl.Status.Build.BuildConfiguration.OrderStrategy))
Expect(local.Status.Build.Maven.LocalRepository).To(Equal(pl.Status.Build.Maven.LocalRepository))
Expect(local.Status.Build.Maven.CLIOptions).To(ContainElements(pl.Status.Build.Maven.CLIOptions))
Expect(local.Status.Build.Maven.Extension).To(BeEmpty())
Expand Down
Loading

0 comments on commit ffa5313

Please sign in to comment.