Skip to content

Commit

Permalink
bugfix(apache#5755): avoiding deadlock between builds with dependenci…
Browse files Browse the repository at this point in the history
…es build order strategy when the builds share enough dependencies
  • Loading branch information
lsergio committed Aug 13, 2024
1 parent e039e22 commit 9752b77
Show file tree
Hide file tree
Showing 2 changed files with 341 additions and 6 deletions.
301 changes: 301 additions & 0 deletions pkg/apis/camel/v1/build_type_support_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package v1

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -37,6 +38,9 @@ func TestMatchingBuildsPending(t *testing.T) {
"camel:timer",
"camel:log",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
Expand All @@ -58,6 +62,9 @@ func TestMatchingBuildsPending(t *testing.T) {
"camel:log",
"camel:bean",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
Expand All @@ -80,6 +87,9 @@ func TestMatchingBuildsPending(t *testing.T) {
"camel:bean",
"camel:zipfile",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
Expand All @@ -101,6 +111,9 @@ func TestMatchingBuildsPending(t *testing.T) {
"camel:component-a",
"camel:component-b",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
Expand All @@ -126,3 +139,291 @@ func TestMatchingBuildsPending(t *testing.T) {
assert.False(t, matches)
assert.Nil(t, buildMatch)
}

func TestMatchingBuildsSchedulingSharedDependencies(t *testing.T) {
timestamp, _ := time.Parse("2006-01-02T15:04:05-0700", "2024-08-09T10:00:00Z")
creationTimestamp := v1.Time{Time: timestamp}
buildA := Build{
ObjectMeta: v1.ObjectMeta{
Name: "buildA",
},
Spec: BuildSpec{
Tasks: []Task{
{
Builder: &BuilderTask{
Dependencies: []string{
"camel:core",
"camel:rest",
"mvn:org.apache.camel.k:camel-k-runtime",
"mvn:org.apache.camel.quarkus:camel-quarkus-yaml-dsl",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
},
Status: BuildStatus{
Phase: BuildPhaseScheduling,
},
}
buildB := Build{
ObjectMeta: v1.ObjectMeta{
Name: "buildB",
CreationTimestamp: creationTimestamp,
},
Spec: BuildSpec{
Tasks: []Task{
{
Builder: &BuilderTask{
Dependencies: []string{
"camel:quartz",
"mvn:org.apache.camel.k:camel-k-cron",
"mvn:org.apache.camel.k:camel-k-runtime",
"mvn:org.apache.camel.quarkus:camel-quarkus-yaml-dsl",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
}},
},
},
},
Status: BuildStatus{
Phase: BuildPhaseScheduling,
},
}

buildList := BuildList{
Items: []Build{buildA, buildB},
}

// both builds share dependencies and have the same creationTimestamp
// buildA should be prioritized so there should be not matching build for it

matches, buildMatch := buildList.HasMatchingBuild(&buildA)
assert.False(t, matches)
assert.Nil(t, buildMatch)
matches, buildMatch = buildList.HasMatchingBuild(&buildB)
assert.True(t, matches)
assert.True(t, buildMatch.Name == buildA.Name)
}

func TestMatchingBuildsSchedulingSameDependenciesDIfferentRuntimes(t *testing.T) {
timestamp, _ := time.Parse("2006-01-02T15:04:05-0700", "2024-08-09T10:00:00Z")
creationTimestamp := v1.Time{Time: timestamp}
buildA := Build{
ObjectMeta: v1.ObjectMeta{
Name: "buildA",
},
Spec: BuildSpec{
Tasks: []Task{
{
Builder: &BuilderTask{
Dependencies: []string{
"camel:quartz",
"mvn:org.apache.camel.k:camel-k-cron",
"mvn:org.apache.camel.k:camel-k-runtime",
"mvn:org.apache.camel.quarkus:camel-quarkus-yaml-dsl",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
},
Status: BuildStatus{
Phase: BuildPhaseScheduling,
},
}
buildB := Build{
ObjectMeta: v1.ObjectMeta{
Name: "buildB",
CreationTimestamp: creationTimestamp,
},
Spec: BuildSpec{
Tasks: []Task{
{
Builder: &BuilderTask{
Dependencies: []string{
"camel:quartz",
"mvn:org.apache.camel.k:camel-k-cron",
"mvn:org.apache.camel.k:camel-k-runtime",
"mvn:org.apache.camel.quarkus:camel-quarkus-yaml-dsl",
},
Runtime: RuntimeSpec{
Version: "3.2.3",
},
},
},
},
},
Status: BuildStatus{
Phase: BuildPhaseScheduling,
},
}

buildList := BuildList{
Items: []Build{buildA, buildB},
}

// each build uses a different runtime, so they should not match

matches, buildMatch := buildList.HasMatchingBuild(&buildA)
assert.False(t, matches)
assert.Nil(t, buildMatch)
matches, buildMatch = buildList.HasMatchingBuild(&buildB)
assert.False(t, matches)
assert.Nil(t, buildMatch)
}

func TestMatchingBuildsSchedulingSameDependenciesSameRuntime(t *testing.T) {
timestamp, _ := time.Parse("2006-01-02T15:04:05-0700", "2024-08-09T10:00:00Z")
creationTimestamp := v1.Time{Time: timestamp}
buildA := Build{
ObjectMeta: v1.ObjectMeta{
Name: "buildA",
},
Spec: BuildSpec{
Tasks: []Task{
{
Builder: &BuilderTask{
Dependencies: []string{
"camel:quartz",
"mvn:org.apache.camel.k:camel-k-cron",
"mvn:org.apache.camel.k:camel-k-runtime",
"mvn:org.apache.camel.quarkus:camel-quarkus-yaml-dsl",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
},
Status: BuildStatus{
Phase: BuildPhaseScheduling,
},
}
buildB := Build{
ObjectMeta: v1.ObjectMeta{
Name: "buildB",
CreationTimestamp: creationTimestamp,
},
Spec: BuildSpec{
Tasks: []Task{
{
Builder: &BuilderTask{
Dependencies: []string{
"camel:quartz",
"mvn:org.apache.camel.k:camel-k-cron",
"mvn:org.apache.camel.k:camel-k-runtime",
"mvn:org.apache.camel.quarkus:camel-quarkus-yaml-dsl",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
},
Status: BuildStatus{
Phase: BuildPhaseScheduling,
},
}

buildList := BuildList{
Items: []Build{buildA, buildB},
}

// ebuilds have the same dependencies, runtime and creation timestamp

matches, buildMatch := buildList.HasMatchingBuild(&buildA)
assert.False(t, matches)
assert.Nil(t, buildMatch)
matches, buildMatch = buildList.HasMatchingBuild(&buildB)
assert.True(t, matches)
assert.True(t, buildMatch.Name == buildA.Name)
}

func TestMatchingBuildsSchedulingFewCommonDependencies(t *testing.T) {
timestamp, _ := time.Parse("2006-01-02T15:04:05-0700", "2024-08-09T10:00:00Z")
creationTimestamp := v1.Time{Time: timestamp}
buildA := Build{
ObjectMeta: v1.ObjectMeta{
Name: "buildA",
},
Spec: BuildSpec{
Tasks: []Task{
{
Builder: &BuilderTask{
Dependencies: []string{
"camel:quartz",
"camel:componenta1",
"camel:componentb1",
"camel:componentc1",
"camel:componentd1",
"camel:componente1",
"camel:componentf1",
"camel:componentg1",
"camel:componenth1",
"camel:componenti1",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
},
Status: BuildStatus{
Phase: BuildPhaseScheduling,
},
}
buildB := Build{
ObjectMeta: v1.ObjectMeta{
Name: "buildB",
CreationTimestamp: creationTimestamp,
},
Spec: BuildSpec{
Tasks: []Task{
{
Builder: &BuilderTask{
Dependencies: []string{
"camel:quartz",
"camel:componenta2",
"camel:componentb2",
"camel:componentc2",
"camel:componentd2",
"camel:componente2",
"camel:componentf2",
"camel:componentg2",
"camel:componenth2",
"camel:componenti2",
},
Runtime: RuntimeSpec{
Version: "3.8.1",
},
},
},
},
},
Status: BuildStatus{
Phase: BuildPhaseScheduling,
},
}

buildList := BuildList{
Items: []Build{buildA, buildB},
}

// builds have only 1 out of 10 shared dependencies. they should not match

matches, buildMatch := buildList.HasMatchingBuild(&buildA)
assert.False(t, matches)
assert.Nil(t, buildMatch)
matches, buildMatch = buildList.HasMatchingBuild(&buildB)
assert.False(t, matches)
assert.Nil(t, buildMatch)
}
Loading

0 comments on commit 9752b77

Please sign in to comment.