Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
299 changes: 299 additions & 0 deletions pkg/oc/oc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"encoding/json"
"fmt"
"log"
"os"
"slices"
"strings"
"time"

"regexp"

"github.com/getgauge-contrib/gauge-go/testsuit"
"github.com/openshift-pipelines/release-tests/pkg/cmd"
"github.com/openshift-pipelines/release-tests/pkg/config"
Expand Down Expand Up @@ -169,3 +172,299 @@ func CopySecret(secretName string, sourceNamespace string, destNamespace string)
cmd.MustSucceed("bash", "-c", fmt.Sprintf(`echo '%s' | kubectl apply -n %s -f -`, cmdOutput, destNamespace))
log.Printf("Successfully copied secret %s from %s to %s", secretName, sourceNamespace, destNamespace)
}

func FetchOlmSkipRange() (map[string]string, error) {
skipRangesJson := cmd.MustSucceed("bash", "-c", `oc get packagemanifests openshift-pipelines-operator-rh -n openshift-marketplace -o json | jq -r '.status.channels[].currentCSVDesc.annotations["olm.skipRange"]'`).Stdout()
skipRanges := strings.Split(strings.TrimSpace(skipRangesJson), "\n")
channelsJson := cmd.MustSucceed("bash", "-c", `oc get packagemanifests openshift-pipelines-operator-rh -n openshift-marketplace -o json | jq -r '.status.channels[].name'`).Stdout()
channels := strings.Split(strings.TrimSpace(channelsJson), "\n")

if len(channels) != len(skipRanges) {
return nil, fmt.Errorf("mismatch between number of channels (%d) and skipRanges (%d)", len(channels), len(skipRanges))
}

channelSkipRangeMap := make(map[string]string)
for i, channel := range channels {
if skipRanges[i] != "null" && skipRanges[i] != "" {
channelSkipRangeMap[channel] = skipRanges[i]
}
}

if len(channelSkipRangeMap) == 0 {
return nil, fmt.Errorf("no valid OLM Skip Ranges found")
}
return channelSkipRangeMap, nil
}

// extractMajorMinor extracts major.minor version from a full version string
// e.g., "1.19.2" -> "1.19", "1.18.1" -> "1.18"
func extractMajorMinor(version string) string {
versionRegex := regexp.MustCompile(`^(\d+\.\d+)\.?\d*`)
matches := versionRegex.FindStringSubmatch(version)
if len(matches) >= 2 {
return matches[1]
}
// Fallback: if regex fails, return the original version
return version
}

func GetOlmSkipRange(upgradeType, fieldName, fileName string) {
skipRangeMap, err := FetchOlmSkipRange()
if err != nil {
log.Printf("Error fetching OLM Skip Range: %v", err)
return
}
file, err := os.OpenFile(config.Path(fileName), os.O_RDWR, 0644)
if err != nil {
log.Printf("Error opening file %s: %v", fileName, err)
return
}
defer file.Close()
var existingData map[string]interface{}
if err := json.NewDecoder(file).Decode(&existingData); err != nil {
log.Printf("Error decoding existing data from file %s: %v", fileName, err)
return
}
switch upgradeType {
case "pre-upgrade":
existingData["pre-upgrade-olm-skip-range"] = skipRangeMap
log.Printf("Pre-upgrade OLM Skip Range is stored as: %+v", skipRangeMap)
case "post-upgrade":
existingData["post-upgrade-olm-skip-range"] = skipRangeMap
log.Printf("Post-upgrade OLM Skip Range is stored as: %+v", skipRangeMap)
}
if _, err := file.Seek(0, 0); err != nil {
log.Printf("Error seeking file %s: %v", fileName, err)
return
}
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ") // Pretty-print the JSON output
if err := encoder.Encode(existingData); err != nil {
log.Printf("Error writing updated data to file %s: %v", fileName, err)
return
}
log.Printf("OLM Skip Range for '%s' has been saved to file %s", fieldName, fileName)
}

func ValidateOlmSkipRange() {
skipRangeMap, err := FetchOlmSkipRange()
if err != nil {
log.Printf("Error fetching OLM Skip Range: %v", err)
return
}

ospVersion := os.Getenv("OSP_VERSION")
log.Printf("Validating OSP_VERSION: %s", ospVersion)
found := false

if ospVersion == "5.0.5" {
log.Printf("Detected nightly build (OSP_VERSION=5.0.5), validating only skipRange, not channel name")
for channel, skipRange := range skipRangeMap {
if channel == "latest" {
log.Printf("Skipping 'latest' channel as requested")
continue
}
skipRangeContainsVersion := strings.Contains(skipRange, ospVersion)
log.Printf("Channel: %s, SkipRange: %s", channel, skipRange)
log.Printf(" - SkipRange contains OSP_VERSION '%s': %v", ospVersion, skipRangeContainsVersion)

if skipRangeContainsVersion {
log.Printf("Success: OSP_VERSION '%s' found in skipRange for channel '%s': '%s'", ospVersion, channel, skipRange)
found = true
break
}
}
} else {
log.Printf("Regular release build, validating both channel name and skipRange")

// Extract major.minor from OSP_VERSION for channel matching
// e.g., "1.19.2" -> "1.19" to match with "pipelines-1.19"
ospMajorMinor := extractMajorMinor(ospVersion)
log.Printf("Extracted major.minor '%s' from OSP_VERSION '%s' for channel matching", ospMajorMinor, ospVersion)

for channel, skipRange := range skipRangeMap {
if channel == "latest" {
log.Printf("Skipping 'latest' channel as requested")
continue
}

// Check if channel contains the major.minor version
channelContainsVersion := strings.Contains(channel, ospMajorMinor)
skipRangeContainsVersion := strings.Contains(skipRange, ospVersion)

log.Printf("Channel: %s, SkipRange: %s", channel, skipRange)
log.Printf(" - Channel contains major.minor '%s': %v", ospMajorMinor, channelContainsVersion)
log.Printf(" - SkipRange contains OSP_VERSION '%s': %v", ospVersion, skipRangeContainsVersion)

if channelContainsVersion && skipRangeContainsVersion {
log.Printf("Success: OSP_VERSION '%s' found in channel '%s' (major.minor match) and its skipRange '%s'", ospVersion, channel, skipRange)
found = true
break
}
}
}

if !found {
log.Printf("Available channels and their skipRanges:")
for channel, skipRange := range skipRangeMap {
if channel != "latest" {
log.Printf(" - Channel: %s, SkipRange: %s", channel, skipRange)
}
}

if ospVersion == "5.0.5" {
testsuit.T.Fail(fmt.Errorf("Error: OSP_VERSION '%s' not found in skipRange for any non-latest channel", ospVersion))
} else {
ospMajorMinor := extractMajorMinor(ospVersion)
testsuit.T.Fail(fmt.Errorf("Error: OSP_VERSION '%s' (major.minor: %s) not found in both channel name and skipRange for any non-latest channel", ospVersion, ospMajorMinor))
}
}
}

func isValidOspVersionPatchUpdate(preSkipRange, postSkipRange string) bool {
ospVersion := os.Getenv("OSP_VERSION")

if !skipRangeContainsVersion(postSkipRange, ospVersion) {
log.Printf("Post-upgrade skip range '%s' does not contain OSP_VERSION '%s'", postSkipRange, ospVersion)
return false
}

rangeRegex := regexp.MustCompile(`>=(\d+\.\d+\.\d+)\s*<(\d+\.\d+\.\d+)`)
preMatches := rangeRegex.FindStringSubmatch(preSkipRange)
postMatches := rangeRegex.FindStringSubmatch(postSkipRange)

preUpperBound := preMatches[2] // Upper bound from pre-upgrade
postUpperBound := postMatches[2] // Upper bound from post-upgrade

versionRegex := regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)$`)
preUpperMatches := versionRegex.FindStringSubmatch(preUpperBound)
postUpperMatches := versionRegex.FindStringSubmatch(postUpperBound)

preUpperPatch := preUpperMatches[3]
postUpperPatch := postUpperMatches[3]

var preUpperPatchInt, postUpperPatchInt int
fmt.Sscanf(preUpperPatch, "%d", &preUpperPatchInt)
fmt.Sscanf(postUpperPatch, "%d", &postUpperPatchInt)

if postUpperPatchInt <= preUpperPatchInt {
log.Printf("Version did not increase: %s -> %s", preUpperBound, postUpperBound)
return false
}

log.Printf("Valid OSP version-based patch update detected: %s -> %s (OSP_VERSION: %s)",
preUpperBound, postUpperBound, ospVersion)
return true
}

func skipRangeContainsVersion(skipRange, version string) bool {
return strings.Contains(skipRange, version)
}

func ValidateOlmSkipRangeDiff(fileName string, preUpgradeSkipRange string, postUpgradeSkipRange string) {
file, err := os.Open(config.Path(fileName))
if err != nil {
log.Printf("Error opening file %s: %v", fileName, err)
testsuit.T.Fail(fmt.Errorf("Error opening file %s: %v", fileName, err))
return
}
defer file.Close()
var skipRangeData map[string]interface{}
decoder := json.NewDecoder(file)
if err := decoder.Decode(&skipRangeData); err != nil {
log.Printf("Error decoding JSON from file %s: %v", fileName, err)
testsuit.T.Fail(fmt.Errorf("Error decoding JSON from file %s: %v", fileName, err))
return
}
preUpgradeData, preExists := skipRangeData[preUpgradeSkipRange]
postUpgradeData, postExists := skipRangeData[postUpgradeSkipRange]
if !preExists || !postExists {
log.Printf("Error: One of the skip ranges is missing. Pre-Upgrade exists: %v, Post-Upgrade exists: %v", preExists, postExists)
testsuit.T.Fail(fmt.Errorf("One of the skip ranges is missing. Pre-Upgrade exists: %v, Post-Upgrade exists: %v", preExists, postExists))
return
}

preUpgradeMap, ok1 := preUpgradeData.(map[string]interface{})
postUpgradeMap, ok2 := postUpgradeData.(map[string]interface{})

if !ok1 || !ok2 {
log.Printf("Error: Skip range data is not in expected map format")
testsuit.T.Fail(fmt.Errorf("Skip range data is not in expected map format"))
return
}

log.Printf("Pre-Upgrade Skip Range: %+v", preUpgradeMap)
log.Printf("Post-Upgrade Skip Range: %+v", postUpgradeMap)

validationErrors := []string{}

log.Printf("Validating that all pre-upgrade channels are preserved in post-upgrade (ignoring 'latest' channel)")

// Check each channel from pre-upgrade data
for preChannel, preSkipRange := range preUpgradeMap {
// Skip 'latest' channel from pre-upgrade validation
if preChannel == "latest" {
log.Printf("Skipping 'latest' channel from pre-upgrade data as requested")
continue
}

if postSkipRange, exists := postUpgradeMap[preChannel]; exists {
if preSkipRange == postSkipRange {
log.Printf("✅ Success: Channel '%s' preserved with skipRange: %v", preChannel, preSkipRange)
} else {
// There's a skipRange mismatch - check if it's related to current OSP_VERSION
preSkipRangeStr, ok1 := preSkipRange.(string)
postSkipRangeStr, ok2 := postSkipRange.(string)

if ok1 && ok2 {
ospVersion := os.Getenv("OSP_VERSION")

if ospVersion != "" && (strings.Contains(preSkipRangeStr, ospVersion) || strings.Contains(postSkipRangeStr, ospVersion)) {
log.Printf("ℹ️ SkipRange mismatch involves current OSP_VERSION '%s', applying OSP_VERSION-based validation", ospVersion)

if isValidOspVersionPatchUpdate(preSkipRangeStr, postSkipRangeStr) {
log.Printf("✅ Success: Channel '%s' updated with valid OSP_VERSION-based patch release: %v -> %v", preChannel, preSkipRange, postSkipRange)
} else {
validationErrors = append(validationErrors, fmt.Sprintf("Channel '%s' skipRange changed from '%v' to '%v' (not a valid OSP_VERSION-based patch update for OSP_VERSION '%s')", preChannel, preSkipRange, postSkipRange, ospVersion))
}
} else {
if ospVersion == "" {
log.Printf("ℹ️ OSP_VERSION not set, applying standard validation (no changes allowed)")
} else {
log.Printf("ℹ️ SkipRange mismatch does not involve current OSP_VERSION '%s', applying standard validation (no changes allowed)", ospVersion)
}
validationErrors = append(validationErrors, fmt.Sprintf("Channel '%s' skipRange changed from '%v' to '%v' (should remain unchanged)", preChannel, preSkipRange, postSkipRange))
}
} else {
validationErrors = append(validationErrors, fmt.Sprintf("Channel '%s' skipRange changed from '%v' to '%v' (invalid format)", preChannel, preSkipRange, postSkipRange))
}
}
} else {
validationErrors = append(validationErrors, fmt.Sprintf("Pre-upgrade channel '%s' is missing in post-upgrade data", preChannel))
}
}

log.Printf("Additional channels found in post-upgrade data:")
for postChannel, postSkipRange := range postUpgradeMap {
if _, existedInPre := preUpgradeMap[postChannel]; existedInPre {
continue
}

if postChannel == "latest" {
log.Printf(" - Ignoring 'latest' channel in post-upgrade as requested")
continue
}

log.Printf(" - New channel '%s' with skipRange: %v", postChannel, postSkipRange)
}

if len(validationErrors) > 0 {
log.Printf("❌ OLM Skip Range validation failed with errors:")
for _, err := range validationErrors {
log.Printf(" - %s", err)
}
testsuit.T.Fail(fmt.Errorf("OLM Skip Range validation failed: %v", strings.Join(validationErrors, "; ")))
} else {
log.Printf("✅ Success: OLM Skip Range validation passed - all expected changes detected correctly")
}
}
13 changes: 12 additions & 1 deletion specs/operator/post-upgrade.spec
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,15 @@ Importance: Critical
Steps:
* Switch to project "releasetest-upgrade-s2i"
* Get tags of the imagestream "golang" from namespace "openshift" and store to variable "golang-tags"
* Start and verify pipeline "s2i-go-pipeline" with param "VERSION" with values stored in variable "golang-tags" with workspace "name=source,claimName=shared-pvc"
* Start and verify pipeline "s2i-go-pipeline" with param "VERSION" with values stored in variable "golang-tags" with workspace "name=source,claimName=shared-pvc"

## Validate olm skiprange post upgrade: PIPELINES-19-TC06
Tags: post-upgrade, olm
Component: Pipelines
Level: Integration
Type: Functional
Importance: Critical

Steps:
* Get olm-skip-range "post-upgrade" and save to field "post-upgrade-olm-skip-range" in file "testdata/olm/skiprange.json"
* Validate the fields "pre-upgrade-olm-skip-range" and "post-upgrade-olm-skip-range" are same in file "testdata/olm/skiprange.json"
12 changes: 11 additions & 1 deletion specs/operator/pre-upgrade.spec
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,14 @@ Steps:
|S.NO|resource_dir |
|----|------------------------------------------------------|
|1 |testdata/ecosystem/pipelines/s2i-go.yaml|
|2 |testdata/pvc/pvc.yaml |
|2 |testdata/pvc/pvc.yaml |

## Validate olm skiprange pre upgrade: PIPELINES-18-TC06
Tags: pre-upgrade, olm
Component: Pipelines
Level: Integration
Type: Functional
Importance: Critical

Steps:
* Get olm-skip-range "pre-upgrade" and save to field "pre-upgrade-olm-skip-range" in file "testdata/olm/skiprange.json"
9 changes: 9 additions & 0 deletions specs/versions.spec
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ Steps:
* Check "tkn-pac" version
* Check "opc" client version
* Check "opc" server version

## Check OperatorVersion in OlmSkipRange : PIPELINES-22-TC03
Tags: e2e, sanity, olm
Component: Operator
Level: Integration
Type: Functional
Importance: High
Steps:
* Validate OperatorVersion in OlmSkipRange
12 changes: 12 additions & 0 deletions steps/olm/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,15 @@ var _ = gauge.Step("Store Cosign public key in file", func() {
var _ = gauge.Step("Verify <binary> version from the pipelinerun logs", func(binary string) {
pipelines.CheckLogVersion(store.Clients(), binary, store.Namespace())
})

var _ = gauge.Step("Get olm-skip-range <upgradeType> and save to field <fieldName> in file <fileName>", func(upgradeType string, fieldName string, filename string) {
oc.GetOlmSkipRange(upgradeType, fieldName, filename)
})

var _ = gauge.Step("Validate the fields <preUpgradeSkipRange> and <postUpgradeSkipRange> are same in file <fileName>", func(preUpgradeSkipRange string, postUpgradeSkipRange string, fileName string) {
oc.ValidateOlmSkipRangeDiff(fileName, preUpgradeSkipRange, postUpgradeSkipRange)
})

var _ = gauge.Step("Validate OperatorVersion in OlmSkipRange", func() {
oc.ValidateOlmSkipRange()
})
4 changes: 4 additions & 0 deletions testdata/olm/skiprange.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"post-upgrade-olm-skip-range": {},
"pre-upgrade-olm-skip-range": {}
}