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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/IBM/go-sdk-core/v5 v5.17.4
github.com/IBM/networking-go-sdk v0.26.0
github.com/aws/aws-sdk-go v1.38.49
github.com/blang/semver/v4 v4.0.0
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/florianl/go-nfqueue v1.3.2
github.com/go-logr/logr v1.4.2
Expand Down Expand Up @@ -61,7 +62,6 @@ require (
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
Expand Down
1 change: 1 addition & 0 deletions manifests/00-cluster-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ rules:
resources:
- subscriptions
- installplans
- clusterserviceversions
verbs:
- '*'

Expand Down
6 changes: 6 additions & 0 deletions manifests/02-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ spec:
value: redhat-operators
- name: GATEWAY_API_OPERATOR_CHANNEL
value: stable
# NOTE:
# Use an operator version that exists in the catalog.
# If the specified version is not found, the latest available
# OSSM operator version will be installed as of the upgrade date.
Comment on lines +92 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we would always fail if the specified version isn't found.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately it's not possible to find out whether the expected version is present in the upgrade graph. There is no OLM custom resource which conveys this information. So, the upgrade logic from this PR is pretty straightforward - it approves any next installplan assuming the expected version is there up the upgrade graph.

A related discussion with Joe Lanford: #1247 (comment).

# Downgrades are not supported; the new version must be
# semantically greater than the previous one.
- name: GATEWAY_API_OPERATOR_VERSION
value: servicemeshoperator3.v3.1.0
- name: ISTIO_VERSION
Expand Down
40 changes: 4 additions & 36 deletions pkg/operator/controller/gatewayclass/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,38 +39,6 @@ const (
// inferencepoolExperimentalCrdName is the name of the experimental
// (alpha version) InferencePool CRD.
inferencepoolExperimentalCrdName = "inferencepools.inference.networking.x-k8s.io"

// subscriptionCatalogOverrideAnnotationKey is the key for an
// unsupported annotation on the gatewayclass using which a custom
// catalog source can be specified for the OSSM subscription. This
// annotation is only intended for use by OpenShift developers. Note
// that this annotation is intended to be used only when initially
// creating the gatewayclass and subscription; changing the catalog
// source on an existing subscription will likely have no effect or
// cause errors.
subscriptionCatalogOverrideAnnotationKey = "unsupported.do-not-use.openshift.io/ossm-catalog"
// subscriptionChannelOverrideAnnotationKey is the key for an
// unsupported annotation on the gatewayclass using which a custom
// channel can be specified for the OSSM subscription. This annotation
// is only intended for use by OpenShift developers. Note that this
// annotation is intended to be used only when initially creating the
// gatewayclass and subscription; changing the channel on an existing
// subscription will likely have no effect or cause errors.
subscriptionChannelOverrideAnnotationKey = "unsupported.do-not-use.openshift.io/ossm-channel"
// subscriptionVersionOverrideAnnotationKey is the key for an
// unsupported annotation on the gatewayclass using which a custom
// version of OSSM can be specified. This annotation is only intended
// for use by OpenShift developers. Note that this annotation is
// intended to be used only when initially creating the gatewayclass and
// subscription; OLM will not allow downgrades, and upgrades are
// generally restricted to the next version after the currently
// installed version.
subscriptionVersionOverrideAnnotationKey = "unsupported.do-not-use.openshift.io/ossm-version"
// istioVersionOverrideAnnotationKey is the key for an unsupported
// annotation on the gatewayclass using which a custom version of Istio
// can be specified. This annotation is only intended for use by
// OpenShift developers.
istioVersionOverrideAnnotationKey = "unsupported.do-not-use.openshift.io/istio-version"
)

var log = logf.Logger.WithName(controllerName)
Expand Down Expand Up @@ -237,15 +205,15 @@ func (r *reconciler) Reconcile(ctx context.Context, request reconcile.Request) (

var errs []error
ossmCatalog := r.config.GatewayAPIOperatorCatalog
if v, ok := gatewayclass.Annotations[subscriptionCatalogOverrideAnnotationKey]; ok {
if v, ok := gatewayclass.Annotations[operatorcontroller.SubscriptionCatalogOverrideAnnotationKey]; ok {
ossmCatalog = v
}
ossmChannel := r.config.GatewayAPIOperatorChannel
if v, ok := gatewayclass.Annotations[subscriptionChannelOverrideAnnotationKey]; ok {
if v, ok := gatewayclass.Annotations[operatorcontroller.SubscriptionChannelOverrideAnnotationKey]; ok {
ossmChannel = v
}
ossmVersion := r.config.GatewayAPIOperatorVersion
if v, ok := gatewayclass.Annotations[subscriptionVersionOverrideAnnotationKey]; ok {
if v, ok := gatewayclass.Annotations[operatorcontroller.SubscriptionVersionOverrideAnnotationKey]; ok {
ossmVersion = v
}
if _, _, err := r.ensureServiceMeshOperatorSubscription(ctx, ossmCatalog, ossmChannel, ossmVersion); err != nil {
Expand All @@ -255,7 +223,7 @@ func (r *reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
errs = append(errs, err)
}
istioVersion := r.config.IstioVersion
if v, ok := gatewayclass.Annotations[istioVersionOverrideAnnotationKey]; ok {
if v, ok := gatewayclass.Annotations[operatorcontroller.IstioVersionOverrideAnnotationKey]; ok {
istioVersion = v
}
if _, _, err := r.ensureIstio(ctx, &gatewayclass, istioVersion); err != nil {
Expand Down
82 changes: 80 additions & 2 deletions pkg/operator/controller/gatewayclass/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package gatewayclass
import (
"context"
"fmt"
"regexp"

"github.com/blang/semver/v4"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -31,6 +33,11 @@ const (
WorkloadPartitioningManagementPreferredScheduling = `{"effect": "PreferredDuringScheduling"}`
)

var (
// csvSemVerRegexp is a RegExp to extract semantic version from OLM CSV name.
csvSemVerRegexp = regexp.MustCompile(`v(\d+\.\d+\.\d+)`)
)

// ensureServiceMeshOperatorSubscription attempts to ensure that a subscription
// for servicemeshoperator is present and returns a Boolean indicating whether
// it exists, the subscription if it exists, and an error value.
Expand Down Expand Up @@ -176,7 +183,9 @@ func (r *reconciler) ensureServiceMeshOperatorInstallPlan(ctx context.Context, v
}

// currentInstallPlan returns the InstallPlan that describes installing the expected version of the GatewayAPI
// implementation, if one exists.
// implementation. If no InstallPlan exists for the expected version, return the InstallPlan which replaces the currently installed one.
// This InstallPlan is expected to advance the OSSM operator toward the next CSV in the upgrade graph,
// assuming that the configured version is available further up the graph.
func (r *reconciler) currentInstallPlan(ctx context.Context, version string) (bool, *operatorsv1alpha1.InstallPlan, error) {
_, subscription, err := r.currentSubscription(ctx, operatorcontroller.ServiceMeshOperatorSubscriptionName())
if err != nil {
Expand All @@ -189,8 +198,9 @@ func (r *reconciler) currentInstallPlan(ctx context.Context, version string) (bo
if installPlans == nil || len(installPlans.Items) == 0 {
return false, nil, nil
}
var currentInstallPlan *operatorsv1alpha1.InstallPlan
var currentInstallPlan, nextInstallPlan *operatorsv1alpha1.InstallPlan
multipleInstallPlans := false

for _, installPlan := range installPlans.Items {
if len(installPlan.OwnerReferences) == 0 || len(installPlan.Spec.ClusterServiceVersionNames) == 0 {
continue
Expand All @@ -209,6 +219,7 @@ func (r *reconciler) currentInstallPlan(ctx context.Context, version string) (bo
if installPlan.Status.Phase != operatorsv1alpha1.InstallPlanPhaseRequiresApproval {
continue
}
// Check whether InstallPlan implements the expected operator version.
for _, csvName := range installPlan.Spec.ClusterServiceVersionNames {
if csvName == version {
// Keep the newest InstallPlan to return at the end of the loop.
Expand All @@ -223,10 +234,62 @@ func (r *reconciler) currentInstallPlan(ctx context.Context, version string) (bo
}
}
}
// Check whether InstallPlan implements the next operator version in the upgrade graph.
for _, csvName := range installPlan.Spec.ClusterServiceVersionNames {
// The definitions of InstalledCSV and CurrentCSV are non-trivial:
//
// - InstalledCSV represents the currently running CSV.
// - CurrentCSV represents the version that "subscription is progressing to"
// which practically means "the next CSV in the upgrade graph."
//
// - If InstalledCSV < CurrentCSV:
// No CSV replacement is ongoing. InstalledCSV is the current version,
// and CurrentCSV is the next one in the upgrade graph.
// - If InstalledCSV == CurrentCSV:
// One of the following scenarios is possible:
// 1. CSV replacement is in progress "InstalledCSV-1" is being replaced with CurrentCSV.
// 2. Installation of the first CSV is in progress.
// 3. There is no "next CSV" in the upgrade graph, so CurrentCSV
// cannot point to a future version.
// CurrentCSV only becomes the next version once the replacement finishes,
// and the next InstallPlan appears around the same time.
//
// The first condition (below) prevents setting the next InstallPlan while a replacement
// or installation is ongoing, or when the end of the upgrade graph is reached.
if subscription.Status.InstalledCSV != subscription.Status.CurrentCSV && csvName == subscription.Status.CurrentCSV {
// CurrentCSV should not be greater (semver-wise) than desiredCSV to avoid upgrading past the desired version,
// in case the desired one was skipped.
// Note: since we use the "stable" channel, the upgrade graph allows transitions between minor releases.
// For example, after installing 3.0.3, we may see 3.1.0 in currentCSV.
desiredCSVSemVer, currentCSVSemVer := extractSemVerFromCSV(version), extractSemVerFromCSV(subscription.Status.CurrentCSV)
if currentCSVSemVer != nil && desiredCSVSemVer != nil && currentCSVSemVer.Compare(*desiredCSVSemVer) != 1 {
if nextInstallPlan == nil {
nextInstallPlan = &installPlan
break
}
}
}
}
}
if multipleInstallPlans {
log.Info(fmt.Sprintf("found multiple valid InstallPlans. using %s because it's the newest", currentInstallPlan.Name))
}
// No InstallPlan with the expected operator version was found,
// but the next one in the upgrade graph exists.
// Return the next InstallPlan to continue the upgrade.
if currentInstallPlan == nil && nextInstallPlan != nil {
// The condition below prevents approving an InstallPlan
// that targets a version beyond the expected operator version.
// This can happen when:
// - InstallPlan with the expected version is complete (no approval needed).
// - Newer versions exist in the upgrade graph.
// The check ensures that the currently running CSV is different
// from the expected version. Once they match, no further action is needed.
if subscription.Status.InstalledCSV != version {
log.Info("installplan with expected operator version was not found; proceedng with an intermedite installplan", "name", nextInstallPlan.Name, "csv", subscription.Status.CurrentCSV)
currentInstallPlan = nextInstallPlan
}
}
return (currentInstallPlan != nil), currentInstallPlan, nil
}

Expand Down Expand Up @@ -263,3 +326,18 @@ func installPlanChanged(current, expected *operatorsv1alpha1.InstallPlan) (bool,

return true, updated
}

// extractSemVerFromCSV exctracts the semantic version from an OLM CSV name.
// It returns a semver.Version pointer, or nil if the CSV name does not contain
// a valid semantic version.
func extractSemVerFromCSV(csv string) *semver.Version {
match := csvSemVerRegexp.FindStringSubmatch(csv)
if len(match) == 0 {
return nil
}
version, err := semver.Make(match[1])
if err != nil {
return nil
}
return &version
}
Loading