Skip to content

Commit

Permalink
feat(STONEINTG-974): filter Scenarios based on Snapshot context
Browse files Browse the repository at this point in the history
* Integration service uses the contexts field to decide when
  to use IntegrationTestScenarios for the given Snapshot
* The new filtering function is applied whenever a list of scenarios
  is fetched to decide which pipeline to run or when deciding if
  all required integration test scenarios finished

Signed-off-by: dirgim <[email protected]>
  • Loading branch information
dirgim committed Jul 29, 2024
1 parent 60b2610 commit db77445
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 5 deletions.
2 changes: 1 addition & 1 deletion docs/snapshot-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ flowchart TD
are_there_any_ITS{"Are there any <br>IntegrationTestScenario <br>present for the given <br>Application?"}
create_new_test_PLR(<b>Create a new Test PipelineRun</b> for each <br>of the above ITS, if it doesn't exists already)
mark_snapshot_InProgress(<b>Mark</b> Snapshot's Integration-testing <br>status as 'InProgress')
fetch_all_required_ITS("Fetch all the required <br>(non-optional) IntegrationTestScenario <br>for the given Application")
fetch_all_required_ITS("Fetch all the required <br>(non-optional) IntegrationTestScenario <br>for the given Application <br> filtered by ITS context(s)")
encountered_error1{Encountered error?}
mark_snapshot_Invalid1(<b>Mark</b> the Snapshot as Invalid)
is_atleast_1_required_ITS{Is there atleast <br>1 required ITS?}
Expand Down
62 changes: 62 additions & 0 deletions gitops/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import (
"context"
"errors"
"fmt"
"github.com/konflux-ci/integration-service/api/v1beta2"
"reflect"
"strconv"
"strings"
"time"

"github.com/google/go-containerregistry/pkg/name"
Expand Down Expand Up @@ -92,6 +94,9 @@ const (
// SnapshotOverrideType is the type of Snapshot which was created for override Global Candidate List.
SnapshotOverrideType = "override"

// SnapshotGroupType is the type of Snapshot which was created for pull request groups.
SnapshotGroupType = "group"

// PipelineAsCodeEventTypeLabel is the type of event which triggered the pipelinerun in build service
PipelineAsCodeEventTypeLabel = PipelinesAsCodePrefix + "/event-type"

Expand Down Expand Up @@ -869,6 +874,10 @@ func IsComponentSnapshot(snapshot *applicationapiv1alpha1.Snapshot) bool {
return metadata.HasLabelWithValue(snapshot, SnapshotTypeLabel, SnapshotComponentType)
}

func IsGroupSnapshot(snapshot *applicationapiv1alpha1.Snapshot) bool {
return metadata.HasLabelWithValue(snapshot, SnapshotTypeLabel, SnapshotGroupType)
}

func IsComponentSnapshotCreatedByPACPushEvent(snapshot *applicationapiv1alpha1.Snapshot) bool {
return IsComponentSnapshot(snapshot) && IsSnapshotCreatedByPACPushEvent(snapshot)
}
Expand All @@ -885,3 +894,56 @@ func SetOwnerReference(ctx context.Context, adapterClient client.Client, snapsho
}
return snapshot, nil
}

// IsContextValidForSnapshot checks the context and compares it against the Snapshot to determine if it applies
func IsContextValidForSnapshot(scenarioContextName string, snapshot *applicationapiv1alpha1.Snapshot) bool {
// `application` context is supported for backwards-compatibility and considered the same as `all`
if scenarioContextName == "application" || scenarioContextName == "all" {
return true
} else if scenarioContextName == "component" && IsComponentSnapshot(snapshot) {
return true
} else if strings.HasPrefix(scenarioContextName, "component_") {
componentName := strings.TrimPrefix(scenarioContextName, "component_")
if metadata.HasLabelWithValue(snapshot, SnapshotComponentLabel, componentName) {
return true
}
} else if scenarioContextName == "group" && IsGroupSnapshot(snapshot) {
return true
} else if scenarioContextName == "override" && IsOverrideSnapshot(snapshot) {
return true
} else if scenarioContextName == "push" && IsSnapshotCreatedByPACPushEvent(snapshot) {
return true
} else if scenarioContextName == "pull_request" && !IsSnapshotCreatedByPACPushEvent(snapshot) {
return true
}
return false
}

// IsScenarioApplicableToSnapshotsContext checks the contexts list for a given IntegrationTestScenario and
// compares it against the Snapshot to determine if the scenario applies to it
func IsScenarioApplicableToSnapshotsContext(scenario *v1beta2.IntegrationTestScenario, snapshot *applicationapiv1alpha1.Snapshot) bool {
// If the contexts list is empty, we assume that the scenario applies to all contexts by default
if len(scenario.Spec.Contexts) == 0 {
return true
}
for _, scenarioContext := range scenario.Spec.Contexts {
scenarioContext := scenarioContext //G601
if IsContextValidForSnapshot(scenarioContext.Name, snapshot) {
return true
}
}
return false
}

// FilterIntegrationTestScenariosWithContext returns a filtered list of IntegrationTestScenario from the given list
// of IntegrationTestScenarios compared against the given Snapshot based on individual IntegrationTestScenario contexts
func FilterIntegrationTestScenariosWithContext(scenarios *[]v1beta2.IntegrationTestScenario, snapshot *applicationapiv1alpha1.Snapshot) *[]v1beta2.IntegrationTestScenario {
var filteredScenarioList []v1beta2.IntegrationTestScenario
for _, scenario := range *scenarios {
scenario := scenario //G601
if IsScenarioApplicableToSnapshotsContext(&scenario, snapshot) {
filteredScenarioList = append(filteredScenarioList, scenario)
}
}
return &filteredScenarioList
}
87 changes: 87 additions & 0 deletions gitops/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package gitops_test

import (
"github.com/konflux-ci/integration-service/api/v1beta2"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gstruct"
Expand Down Expand Up @@ -623,6 +624,92 @@ var _ = Describe("Gitops functions for managing Snapshots", Ordered, func() {
Expect(err).ToNot(HaveOccurred())
})
})
})

Context("Filter integration tests for a given Snapshot based on their context", func() {
When("There are a number of integration test scenarios with different contexts", func() {
integrationTestScenario := &v1beta2.IntegrationTestScenario{
ObjectMeta: metav1.ObjectMeta{
Name: "example-pass",
Namespace: "default",

Labels: map[string]string{
"test.appstudio.openshift.io/optional": "false",
},
},
Spec: v1beta2.IntegrationTestScenarioSpec{
Application: "application-sample",
ResolverRef: v1beta2.ResolverRef{
Resolver: "git",
Params: []v1beta2.ResolverParameter{
{
Name: "url",
Value: "https://github.com/redhat-appstudio/integration-examples.git",
},
{
Name: "revision",
Value: "main",
},
{
Name: "pathInRepo",
Value: "pipelineruns/integration_pipelinerun_pass.yaml",
},
},
},
},
}
applicationScenario := integrationTestScenario.DeepCopy()
applicationScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "application", Description: "Application Testing"}}
componentScenario := integrationTestScenario.DeepCopy()
componentScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "component", Description: "Component"}}
componentSampleScenario := integrationTestScenario.DeepCopy()
componentSampleScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "component_component-sample", Description: "Component component-sample"}}
componentSample2Scenario := integrationTestScenario.DeepCopy()
componentSample2Scenario.Spec.Contexts = []v1beta2.TestContext{{Name: "component_component-sample-2", Description: "Component component-sample-2"}}
pullRequestScenario := integrationTestScenario.DeepCopy()
pullRequestScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "pull_request", Description: "Pull Request"}}
pushScenario := integrationTestScenario.DeepCopy()
pushScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "push", Description: "Component"}}
groupScenario := integrationTestScenario.DeepCopy()
groupScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "group", Description: "PR Group Testing"}}
overrideScenario := integrationTestScenario.DeepCopy()
overrideScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "override", Description: "Override Snapshot testing"}}
componentAndGroupScenario := integrationTestScenario.DeepCopy()
componentAndGroupScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "group"}, {Name: "component"}}
unsupportedScenario := integrationTestScenario.DeepCopy()
unsupportedScenario.Spec.Contexts = []v1beta2.TestContext{{Name: "n/a"}}

allScenarios := []v1beta2.IntegrationTestScenario{*integrationTestScenario, *applicationScenario,
*componentScenario, *componentSampleScenario, *componentSample2Scenario, *pullRequestScenario,
*pushScenario, *groupScenario, *componentAndGroupScenario, *unsupportedScenario}

It("Returns only the scenarios matching the context for a given kind of Snapshot", func() {
// A component Snapshot for a push event referencing the component-sample
filteredScenarios := gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot)
Expect(*filteredScenarios).To(HaveLen(6))

// A component Snapshot for pull request event referencing the component-sample
hasSnapshot.Labels[gitops.PipelineAsCodeEventTypeLabel] = gitops.PipelineAsCodePullRequestType
filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot)
Expect(*filteredScenarios).To(HaveLen(6))

// A group Snapshot for pull request event referencing component-sample-2
hasSnapshot.Labels[gitops.SnapshotComponentLabel] = "component-sample-2"
filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot)
Expect(*filteredScenarios).To(HaveLen(6))

// A group Snapshot for pull request event for a PR group
hasSnapshot.Labels[gitops.SnapshotTypeLabel] = "group"
hasSnapshot.Labels[gitops.SnapshotComponentLabel] = ""
filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot)
Expect(*filteredScenarios).To(HaveLen(5))

// An override Snapshot
hasSnapshot.Labels[gitops.SnapshotTypeLabel] = "override"
filteredScenarios = gitops.FilterIntegrationTestScenariosWithContext(&allScenarios, hasSnapshot)
Expect(*filteredScenarios).To(HaveLen(3))
})
})
})

})
8 changes: 5 additions & 3 deletions internal/controller/snapshot/snapshot_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,14 @@ func (a *Adapter) EnsureIntegrationPipelineRunsExist() (controller.OperationResu
return controller.ContinueProcessing()
}

integrationTestScenarios, err := a.loader.GetAllIntegrationTestScenariosForApplication(a.context, a.client, a.application)
allIntegrationTestScenarios, err := a.loader.GetAllIntegrationTestScenariosForApplication(a.context, a.client, a.application)
if err != nil {
a.logger.Error(err, "Failed to get Integration test scenarios for the following application",
"Application.Namespace", a.application.Namespace)
}

if integrationTestScenarios != nil {
if allIntegrationTestScenarios != nil {
integrationTestScenarios := gitops.FilterIntegrationTestScenariosWithContext(allIntegrationTestScenarios, a.snapshot)
a.logger.Info(
fmt.Sprintf("Found %d IntegrationTestScenarios for application", len(*integrationTestScenarios)),
"Application.Name", a.application.Name,
Expand Down Expand Up @@ -254,7 +255,7 @@ func (a *Adapter) EnsureIntegrationPipelineRunsExist() (controller.OperationResu
}
}

requiredIntegrationTestScenarios, err := a.loader.GetRequiredIntegrationTestScenariosForApplication(a.context, a.client, a.application)
allRequiredIntegrationTestScenarios, err := a.loader.GetRequiredIntegrationTestScenariosForApplication(a.context, a.client, a.application)
if err != nil {
a.logger.Error(err, "Failed to get all required IntegrationTestScenarios")
patch := client.MergeFrom(a.snapshot.DeepCopy())
Expand All @@ -263,6 +264,7 @@ func (a *Adapter) EnsureIntegrationPipelineRunsExist() (controller.OperationResu
a.snapshot, h.LogActionUpdate)
return controller.RequeueOnErrorOrStop(a.client.Status().Patch(a.context, a.snapshot, patch))
}
requiredIntegrationTestScenarios := gitops.FilterIntegrationTestScenariosWithContext(allRequiredIntegrationTestScenarios, a.snapshot)
if len(*requiredIntegrationTestScenarios) == 0 && !gitops.IsSnapshotMarkedAsPassed(a.snapshot) {
err := gitops.MarkSnapshotAsPassed(a.context, a.client, a.snapshot, "No required IntegrationTestScenarios found, skipped testing")
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion internal/controller/statusreport/statusreport_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,11 @@ func (a *Adapter) EnsureSnapshotTestStatusReportedToGitProvider() (controller.Op
func (a *Adapter) EnsureSnapshotFinishedAllTests() (controller.OperationResult, error) {
// Get all required integrationTestScenarios for the Application and then use the Snapshot status annotation
// to check if all Integration tests were finished for that Snapshot
integrationTestScenarios, err := a.loader.GetRequiredIntegrationTestScenariosForApplication(a.context, a.client, a.application)
allRequiredIntegrationTestScenarios, err := a.loader.GetRequiredIntegrationTestScenariosForApplication(a.context, a.client, a.application)
if err != nil {
return controller.RequeueWithError(err)
}
integrationTestScenarios := gitops.FilterIntegrationTestScenariosWithContext(allRequiredIntegrationTestScenarios, a.snapshot)
a.logger.Info(fmt.Sprintf("Found %d required integration test scenarios", len(*integrationTestScenarios)))

testStatuses, err := gitops.NewSnapshotIntegrationTestStatusesFromSnapshot(a.snapshot)
Expand Down

0 comments on commit db77445

Please sign in to comment.