Skip to content

Commit 924279a

Browse files
Merge pull request #1112 from rfredette/gwapi-subscription
NE-1907: Manage OSSM operator subscription manually to ensure a compatible version is installed
2 parents 7dc12cf + dd0fc42 commit 924279a

File tree

10 files changed

+583
-12
lines changed

10 files changed

+583
-12
lines changed

cmd/ingress-operator/start.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import (
3232
const (
3333
// defaultTrustedCABundle is the fully qualified path of the trusted CA bundle
3434
// that is mounted from configmap openshift-ingress-operator/trusted-ca.
35-
defaultTrustedCABundle = "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
35+
defaultTrustedCABundle = "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
36+
defaultGatewayAPIOperatorChannel = "stable"
37+
defaultGatewayAPIOperatorVersion = "servicemeshoperator.v2.6.2"
3638
)
3739

3840
type StartOptions struct {
@@ -51,6 +53,10 @@ type StartOptions struct {
5153
CanaryImage string
5254
// ReleaseVersion is the cluster version which the operator will converge to.
5355
ReleaseVersion string
56+
// GatewayAPIOperatorChannel is the release channel of the Gateway API implementation to install.
57+
GatewayAPIOperatorChannel string
58+
// GatewayAPIOperatorVersion is the name and release of the Gateway API implementation to install.
59+
GatewayAPIOperatorVersion string
5460
}
5561

5662
func NewStartCommand() *cobra.Command {
@@ -74,6 +80,8 @@ func NewStartCommand() *cobra.Command {
7480
cmd.Flags().StringVarP(&options.ReleaseVersion, "release-version", "", statuscontroller.UnknownVersionValue, "the release version the operator should converge to (required)")
7581
cmd.Flags().StringVarP(&options.MetricsListenAddr, "metrics-listen-addr", "", "127.0.0.1:60000", "metrics endpoint listen address (required)")
7682
cmd.Flags().StringVarP(&options.ShutdownFile, "shutdown-file", "s", defaultTrustedCABundle, "if provided, shut down the operator when this file changes")
83+
cmd.Flags().StringVarP(&options.GatewayAPIOperatorChannel, "gateway-api-operator-channel", "", defaultGatewayAPIOperatorChannel, "release channel of the Gateway API implementation to install")
84+
cmd.Flags().StringVarP(&options.GatewayAPIOperatorVersion, "gateway-api-operator-version", "", defaultGatewayAPIOperatorVersion, "name and release of the Gateway API implementation to install")
7785

7886
if err := cmd.MarkFlagRequired("namespace"); err != nil {
7987
panic(err)
@@ -118,10 +126,12 @@ func start(opts *StartOptions) error {
118126
defer cancel()
119127

120128
operatorConfig := operatorconfig.Config{
121-
OperatorReleaseVersion: opts.ReleaseVersion,
122-
Namespace: opts.OperatorNamespace,
123-
IngressControllerImage: opts.IngressControllerImage,
124-
CanaryImage: opts.CanaryImage,
129+
OperatorReleaseVersion: opts.ReleaseVersion,
130+
Namespace: opts.OperatorNamespace,
131+
IngressControllerImage: opts.IngressControllerImage,
132+
CanaryImage: opts.CanaryImage,
133+
GatewayAPIOperatorChannel: opts.GatewayAPIOperatorChannel,
134+
GatewayAPIOperatorVersion: opts.GatewayAPIOperatorVersion,
125135
}
126136

127137
// Start operator metrics.

manifests/00-cluster-role.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ rules:
178178
- operators.coreos.com
179179
resources:
180180
- subscriptions
181+
- installplans
181182
verbs:
182183
- '*'
183184

manifests/02-deployment-ibm-cloud-managed.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ spec:
3535
- $(CANARY_IMAGE)
3636
- --release-version
3737
- $(RELEASE_VERSION)
38+
- --gateway-api-operator-channel
39+
- $(GATEWAY_API_OPERATOR_CHANNEL)
40+
- --gateway-api-operator-version
41+
- $(GATEWAY_API_OPERATOR_VERSION)
3842
env:
3943
- name: RELEASE_VERSION
4044
value: 0.0.1-snapshot
@@ -46,6 +50,10 @@ spec:
4650
value: openshift/origin-haproxy-router:v4.0
4751
- name: CANARY_IMAGE
4852
value: openshift/origin-cluster-ingress-operator:latest
53+
- name: GATEWAY_API_OPERATOR_CHANNEL
54+
value: stable
55+
- name: GATEWAY_API_OPERATOR_VERSION
56+
value: servicemeshoperator.v2.6.2
4957
image: openshift/origin-cluster-ingress-operator:latest
5058
imagePullPolicy: IfNotPresent
5159
name: ingress-operator

manifests/02-deployment.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ spec:
6464
- "$(CANARY_IMAGE)"
6565
- --release-version
6666
- "$(RELEASE_VERSION)"
67+
- --gateway-api-operator-channel
68+
- "$(GATEWAY_API_OPERATOR_CHANNEL)"
69+
- --gateway-api-operator-version
70+
- "$(GATEWAY_API_OPERATOR_VERSION)"
6771
env:
6872
- name: RELEASE_VERSION
6973
value: "0.0.1-snapshot"
@@ -75,6 +79,10 @@ spec:
7579
value: openshift/origin-haproxy-router:v4.0
7680
- name: CANARY_IMAGE
7781
value: openshift/origin-cluster-ingress-operator:latest
82+
- name: GATEWAY_API_OPERATOR_CHANNEL
83+
value: stable
84+
- name: GATEWAY_API_OPERATOR_VERSION
85+
value: servicemeshoperator.v2.6.2
7886
resources:
7987
requests:
8088
cpu: 10m

pkg/operator/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,11 @@ type Config struct {
1515
// CanaryImage is the ingress operator image, which runs a canary command.
1616
CanaryImage string
1717

18+
// GatewayAPIOperatorChannel is the release channel of the Gateway API implementation to install.
19+
GatewayAPIOperatorChannel string
20+
21+
// GatewayAPIOperatorVersion is the name and release of the Gateway API implementation to install.
22+
GatewayAPIOperatorVersion string
23+
1824
Stop chan struct{}
1925
}

pkg/operator/controller/gatewayclass/controller.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,28 @@ func NewUnmanaged(mgr manager.Manager, config Config) (controller.Controller, er
7777
enqueueRequestForDefaultGatewayClassController(config.OperandNamespace), isServiceMeshSubscription)); err != nil {
7878
return nil, err
7979
}
80+
81+
isOurInstallPlan := predicate.NewPredicateFuncs(func(o client.Object) bool {
82+
installPlan := o.(*operatorsv1alpha1.InstallPlan)
83+
if len(installPlan.Spec.ClusterServiceVersionNames) > 0 {
84+
for _, csv := range installPlan.Spec.ClusterServiceVersionNames {
85+
if csv == config.GatewayAPIOperatorVersion {
86+
return true
87+
}
88+
}
89+
}
90+
return false
91+
})
92+
// Check the approved status of an InstallPlan. The ingress operator only needs to potentially move install plans
93+
// from Approved=false to Approved=true, so we can filter out all approved plans at the Watch() level.
94+
isInstallPlanApproved := predicate.NewPredicateFuncs(func(o client.Object) bool {
95+
installPlan := o.(*operatorsv1alpha1.InstallPlan)
96+
return installPlan.Spec.Approved
97+
})
98+
if err := c.Watch(source.Kind[client.Object](operatorCache, &operatorsv1alpha1.InstallPlan{}, enqueueRequestForDefaultGatewayClassController(config.OperatorNamespace), isOurInstallPlan, predicate.Not(isInstallPlanApproved))); err != nil {
99+
return nil, err
100+
}
101+
80102
gatewayClassController = c
81103
return c, nil
82104
}
@@ -89,6 +111,10 @@ type Config struct {
89111
OperatorNamespace string
90112
// OperandNamespace is the namespace in which Istio should be deployed.
91113
OperandNamespace string
114+
// GatewayAPIOperatorChannel is the release channel of the Gateway API implementation to install.
115+
GatewayAPIOperatorChannel string
116+
// GatewayAPIOperatorVersion is the name and release of the Gateway API implementation to install.
117+
GatewayAPIOperatorVersion string
92118
}
93119

94120
// reconciler reconciles gatewayclasses.
@@ -131,6 +157,9 @@ func (r *reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
131157
if _, _, err := r.ensureServiceMeshOperatorSubscription(ctx); err != nil {
132158
errs = append(errs, fmt.Errorf("failed to ensure ServiceMeshOperatorSubscription: %w", err))
133159
}
160+
if _, _, err := r.ensureServiceMeshOperatorInstallPlan(ctx); err != nil {
161+
errs = append(errs, err)
162+
}
134163
if _, _, err := r.ensureServiceMeshControlPlane(ctx, &gatewayclass); err != nil {
135164
errs = append(errs, err)
136165
} else {

pkg/operator/controller/gatewayclass/subscription.go

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/google/go-cmp/cmp"
88
"github.com/google/go-cmp/cmp/cmpopts"
9+
"sigs.k8s.io/controller-runtime/pkg/client"
910

1011
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
1112

@@ -26,7 +27,7 @@ func (r *reconciler) ensureServiceMeshOperatorSubscription(ctx context.Context)
2627
return false, nil, err
2728
}
2829

29-
desired, err := desiredSubscription(name)
30+
desired, err := desiredSubscription(name, r.config.GatewayAPIOperatorChannel, r.config.GatewayAPIOperatorVersion)
3031
if err != nil {
3132
return have, current, err
3233
}
@@ -48,18 +49,19 @@ func (r *reconciler) ensureServiceMeshOperatorSubscription(ctx context.Context)
4849
}
4950

5051
// desiredSubscription returns the desired subscription.
51-
func desiredSubscription(name types.NamespacedName) (*operatorsv1alpha1.Subscription, error) {
52+
func desiredSubscription(name types.NamespacedName, gwapiOperatorChannel, gwapiOperatorVersion string) (*operatorsv1alpha1.Subscription, error) {
5253
subscription := operatorsv1alpha1.Subscription{
5354
ObjectMeta: metav1.ObjectMeta{
5455
Namespace: name.Namespace,
5556
Name: name.Name,
5657
},
5758
Spec: &operatorsv1alpha1.SubscriptionSpec{
58-
Channel: "stable",
59-
InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic,
59+
Channel: gwapiOperatorChannel,
60+
InstallPlanApproval: operatorsv1alpha1.ApprovalManual,
6061
Package: "servicemeshoperator",
6162
CatalogSource: "redhat-operators",
6263
CatalogSourceNamespace: "openshift-marketplace",
64+
StartingCSV: gwapiOperatorVersion,
6365
},
6466
}
6567
return &subscription, nil
@@ -115,3 +117,92 @@ func subscriptionChanged(current, expected *operatorsv1alpha1.Subscription) (boo
115117

116118
return true, updated
117119
}
120+
121+
// ensureServiceMeshOperatorInstallPlan attempts to ensure that the install plan for the appropriate OSSM operator
122+
// version is approved.
123+
func (r *reconciler) ensureServiceMeshOperatorInstallPlan(ctx context.Context) (bool, *operatorsv1alpha1.InstallPlan, error) {
124+
haveInstallPlan, current, err := r.currentInstallPlan(ctx)
125+
if err != nil {
126+
return false, nil, err
127+
}
128+
switch {
129+
case !haveInstallPlan:
130+
// The OLM operator creates the initial InstallPlan, so if it doesn't exist yet or it's been deleted, do nothing
131+
// and let the OLM operator handle it.
132+
return false, nil, nil
133+
case haveInstallPlan:
134+
desired := desiredInstallPlan(current)
135+
if updated, err := r.updateInstallPlan(ctx, current, desired); err != nil {
136+
return true, current, err
137+
} else if updated {
138+
return r.currentInstallPlan(ctx)
139+
}
140+
}
141+
return false, current, nil
142+
}
143+
144+
// currentInstallPlan returns the InstallPlan that describes installing the expected version of the GatewayAPI
145+
// implementation, if one exists.
146+
func (r *reconciler) currentInstallPlan(ctx context.Context) (bool, *operatorsv1alpha1.InstallPlan, error) {
147+
_, subscription, err := r.currentSubscription(ctx, operatorcontroller.ServiceMeshSubscriptionName())
148+
if err != nil {
149+
return false, nil, err
150+
}
151+
installPlans := &operatorsv1alpha1.InstallPlanList{}
152+
if err := r.client.List(ctx, installPlans, client.InNamespace(operatorcontroller.OpenshiftOperatorNamespace)); err != nil {
153+
return false, nil, err
154+
}
155+
if installPlans == nil || len(installPlans.Items) == 0 {
156+
return false, nil, nil
157+
}
158+
for _, installPlan := range installPlans.Items {
159+
if len(installPlan.OwnerReferences) == 0 || len(installPlan.Spec.ClusterServiceVersionNames) == 0 {
160+
continue
161+
}
162+
for _, ownerRef := range installPlan.OwnerReferences {
163+
if ownerRef.UID == subscription.UID {
164+
for _, csvName := range installPlan.Spec.ClusterServiceVersionNames {
165+
if csvName == r.config.GatewayAPIOperatorVersion {
166+
return true, &installPlan, nil
167+
}
168+
}
169+
}
170+
}
171+
}
172+
// No valid InstallPlan found.
173+
return false, nil, nil
174+
}
175+
176+
// desiredInstallPlan returns a version of the expected InstallPlan that is approved.
177+
func desiredInstallPlan(current *operatorsv1alpha1.InstallPlan) *operatorsv1alpha1.InstallPlan {
178+
desired := current.DeepCopy()
179+
desired.Spec.Approved = true
180+
return desired
181+
}
182+
183+
// updateInstallPlan updates an existing InstallPlan if it differs from the desired state.
184+
func (r *reconciler) updateInstallPlan(ctx context.Context, current, desired *operatorsv1alpha1.InstallPlan) (bool, error) {
185+
changed, updated := installPlanChanged(current, desired)
186+
if !changed {
187+
return false, nil
188+
}
189+
diff := cmp.Diff(current.Spec, updated.Spec, cmpopts.EquateEmpty())
190+
if err := r.client.Update(ctx, updated); err != nil {
191+
return false, fmt.Errorf("failed to update InstallPlan %s/%s: %w", current.Namespace, current.Name, err)
192+
}
193+
log.Info("updated InstallPlan", "namespace", updated.Namespace, "name", updated.Name, "diff", diff)
194+
return true, nil
195+
}
196+
197+
// installPlanChanged returns a Boolean indicating whether the current InstallPlan matches the expected InstallPlan and
198+
// the updated InstallPlan if they do not match.
199+
func installPlanChanged(current, expected *operatorsv1alpha1.InstallPlan) (bool, *operatorsv1alpha1.InstallPlan) {
200+
if cmp.Equal(current.Spec, expected.Spec, cmpopts.EquateEmpty()) {
201+
return false, nil
202+
}
203+
204+
updated := current.DeepCopy()
205+
updated.Spec = expected.Spec
206+
207+
return true, updated
208+
}

0 commit comments

Comments
 (0)