Skip to content

Commit

Permalink
fix(ENTESB-18466): Flag operator as non-upgradeable
Browse files Browse the repository at this point in the history
* When operator is installed via OLM, a separate OperatorCondition
  CR is created which can be updated with an "upgradeable"
  condition flagging whether the OLM can upgrade the operator

* Turn off upgradeable state when
 * operator is initializing
 * operator is upgrading

* Turn on upgradeable state when
 * operator has started
 * operator has completed an upgrade

* Finds the OperatorCondition using the deployment name, deployment
  owner (CSV) and the CSV name (OperatorCondition has same name as
  the CSV).

* Adds conditions tests but cannot completely implement due to
  operator-framework/operator-lib#103
  • Loading branch information
phantomjinx committed Feb 23, 2022
1 parent 53d5231 commit 78a983e
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 16 deletions.
1 change: 1 addition & 0 deletions install/operator/config/dev/operator/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ patchesStrategicMerge:
- ./env-var-patch.yaml
- ./annotation-patch.gen.yaml
- ./image-stream-patch.gen.yaml
- ./patch-image-pull-policy-always.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# ---------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ---------------------------------------------------------------------------

apiVersion: apps/v1
kind: Deployment
metadata:
name: syndesis-operator
spec:
template:
spec:
containers:
- name: syndesis-operator
imagePullPolicy: Always
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
- operatorgroups
- subscriptions
- installplans
- operatorconditions
verbs: [ create, delete, update, get, list, watch ]
- apiGroups:
- operators.coreos.com
Expand Down
18 changes: 16 additions & 2 deletions install/operator/pkg/syndesis/action/checkupdates.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (

synapi "github.com/syndesisio/syndesis/install/operator/pkg/apis/syndesis/v1beta3"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/clienttools"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/olm"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/manager"
)

Expand Down Expand Up @@ -39,7 +41,7 @@ func (a checkUpdatesAction) Execute(ctx context.Context, syndesis *synapi.Syndes
// Everything fine
return nil
} else {
return a.setPhaseToUpgrading(ctx, syndesis)
return a.setPhaseToUpgrading(ctx, syndesis, operatorNamespace)
}
}

Expand All @@ -48,7 +50,19 @@ func (a checkUpdatesAction) Execute(ctx context.Context, syndesis *synapi.Syndes
* needed to avoid race conditions where k8s wasn't able to update or
* kubernetes didn't change the object yet
*/
func (a checkUpdatesAction) setPhaseToUpgrading(ctx context.Context, syndesis *synapi.Syndesis) (err error) {
func (a checkUpdatesAction) setPhaseToUpgrading(ctx context.Context, syndesis *synapi.Syndesis, operatorNamespace string) (err error) {

// Declare an upgradeable Condition as false if applicable
state := olm.ConditionState{
Status: metav1.ConditionFalse,
Reason: "Upgrading",
Message: "Operator is upgrading the components",
}
err = olm.SetUpgradeCondition(ctx, a.clientTools, operatorNamespace, state)
if err != nil {
a.log.Error(err, "Failed to set the upgrade condition on the operator")
}

target := syndesis.DeepCopy()
target.Status.Phase = synapi.SyndesisPhaseUpgrading
target.Status.TargetVersion = a.operatorVersion
Expand Down
14 changes: 14 additions & 0 deletions install/operator/pkg/syndesis/action/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (

synapi "github.com/syndesisio/syndesis/install/operator/pkg/apis/syndesis/v1beta3"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/clienttools"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/olm"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Initializes a Syndesis resource with no status and starts the installation process
Expand Down Expand Up @@ -45,6 +48,17 @@ func (a *initializeAction) Execute(ctx context.Context, syndesis *synapi.Syndesi
target.Status.Description = "Cannot install two Syndesis resources in the same namespace"
a.log.Error(nil, "Cannot initialize Syndesis resource because its a duplicate", "name", syndesis.Name)
} else {
// Declare an upgradeable Condition as false if applicable
state := olm.ConditionState{
Status: metav1.ConditionFalse,
Reason: "Initializing",
Message: "Operator is installing",
}
err = olm.SetUpgradeCondition(ctx, a.clientTools, operatorNamespace, state)
if err != nil {
a.log.Error(err, "Failed to set the upgrade condition on the operator")
}

syndesisVersion := pkg.DefaultOperatorTag
target.Status.Phase = synapi.SyndesisPhaseInstalling
target.Status.Reason = synapi.SyndesisStatusReasonMissing
Expand Down
12 changes: 12 additions & 0 deletions install/operator/pkg/syndesis/action/startup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
synpkg "github.com/syndesisio/syndesis/install/operator/pkg"
synapi "github.com/syndesisio/syndesis/install/operator/pkg/apis/syndesis/v1beta3"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/clienttools"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/olm"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
Expand Down Expand Up @@ -71,6 +72,17 @@ func (a *startupAction) Execute(ctx context.Context, syndesis *synapi.Syndesis,
}

if ready {
// Declare the operator upgradeable, if applicable
state := olm.ConditionState{
Status: metav1.ConditionTrue,
Reason: "Started",
Message: "Operator and components have been successfully started",
}
err = olm.SetUpgradeCondition(ctx, a.clientTools, operatorNamespace, state)
if err != nil {
a.log.Error(err, "Failed to set the upgrade condition on the operator")
}

target := syndesis.DeepCopy()
target.Status.Phase = synapi.SyndesisPhaseInstalled
target.Status.Reason = synapi.SyndesisStatusReasonMissing
Expand Down
16 changes: 14 additions & 2 deletions install/operator/pkg/syndesis/action/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/clienttools"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/olm"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/upgrade"

"github.com/syndesisio/syndesis/install/operator/pkg"
Expand Down Expand Up @@ -75,7 +76,7 @@ func (a *upgradeAction) Execute(ctx context.Context, syndesis *synapi.Syndesis,
} else if syndesis.Status.Phase == synapi.SyndesisPhasePostUpgradeRunSucceed {
// We land here only if the install phase after upgrading finished correctly
a.log.Info("syndesis resource post upgrade ran successfully", "name", syndesis.Name, "previous version", syndesis.Status.Version, "target version", targetVersion)
return a.completeUpgrade(ctx, syndesis, targetVersion)
return a.completeUpgrade(ctx, syndesis, targetVersion, operatorNamespace)
} else if syndesis.Status.Phase == synapi.SyndesisPhasePostUpgradeRun {
// If the first run of the install action failed, we land here. We need to retry
// this few times to consider the cases where install action return error due to
Expand Down Expand Up @@ -103,7 +104,18 @@ func (a *upgradeAction) Execute(ctx context.Context, syndesis *synapi.Syndesis,
* needed to avoid race conditions where k8s wasn't yet able to update or
* kubernetes didn't change the object yet
*/
func (a *upgradeAction) completeUpgrade(ctx context.Context, syndesis *synapi.Syndesis, newVersion string) (err error) {
func (a *upgradeAction) completeUpgrade(ctx context.Context, syndesis *synapi.Syndesis, newVersion string, operatorNamespace string) (err error) {
// Declare the operator upgradeable, if applicable
state := olm.ConditionState{
Status: metav1.ConditionTrue,
Reason: "CompletedUpgrade",
Message: "Operator component state has been upgraded",
}
err = olm.SetUpgradeCondition(ctx, a.clientTools, operatorNamespace, state)
if err != nil {
a.log.Error(err, "Failed to set the upgrade condition on the operator")
}

target := syndesis.DeepCopy()
target.Status.Phase = synapi.SyndesisPhaseInstalled
target.Status.TargetVersion = ""
Expand Down
2 changes: 2 additions & 0 deletions install/operator/pkg/syndesis/clienttools/clienttools.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
olmapiv1 "github.com/operator-framework/api/pkg/operators/v1"
olmapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
olmapiv1alpha2 "github.com/operator-framework/api/pkg/operators/v1alpha2"
olmapiv2 "github.com/operator-framework/api/pkg/operators/v2"
olmcli "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
olmpkgsvr "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1"
"github.com/syndesisio/syndesis/install/operator/pkg/util"
Expand Down Expand Up @@ -74,6 +75,7 @@ func (ck *ClientTools) GetScheme() *runtime.Scheme {
olmapiv1alpha2.SchemeBuilder.AddToScheme(ck.scheme)
olmapiv1alpha1.SchemeBuilder.AddToScheme(ck.scheme)
olmapiv1.SchemeBuilder.AddToScheme(ck.scheme)
olmapiv2.AddToScheme(ck.scheme)
olmpkgsvr.SchemeBuilder.AddToScheme(ck.scheme)
projectv1.AddToScheme(ck.scheme)
}
Expand Down
20 changes: 20 additions & 0 deletions install/operator/pkg/syndesis/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,26 @@ func GetProperties(ctx context.Context, file string, clientTools *clienttools.Cl
return configuration, nil
}

// Load the configuration and return the name of this product
func GetProductName(file string) (string, error) {
configuration := &Config{}
if err := configuration.loadFromFile(file); err != nil {
return "", err
}

return configuration.ProductName, nil
}

// Load the configuration and return the name of this product
func GetVersion(file string) (string, error) {
configuration := &Config{}
if err := configuration.loadFromFile(file); err != nil {
return "", err
}

return configuration.Version, nil
}

// Load configuration from config file. Config file is expected to be a yaml
// The returned configuration is parsed to JSON and returned as a Config object
func (config *Config) loadFromFile(file string) error {
Expand Down
20 changes: 20 additions & 0 deletions install/operator/pkg/syndesis/configuration/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,26 @@ func Test_loadFromFile(t *testing.T) {
}
}

func Test_getProductName(t *testing.T) {
configFile := "../../../build/conf/config-test.yaml"
name, err := GetProductName(configFile)
assert.NoError(t, err)

andOrName := func() bool {
return name == "syndesis" || name == "fuse-online"
}

assert.Condition(t, andOrName)
}

func Test_getVersion(t *testing.T) {
configFile := "../../../build/conf/config-test.yaml"
version, err := GetVersion(configFile)
assert.NoError(t, err)

assert.Equal(t, "7.7.0", version)
}

func Test_setConfigFromEnv(t *testing.T) {
tests := []struct {
name string
Expand Down
147 changes: 147 additions & 0 deletions install/operator/pkg/syndesis/olm/operator_conditions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright (C) 2020 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package olm

import (
"context"
"fmt"
"os"

olmapiv2 "github.com/operator-framework/api/pkg/operators/v2"
conditions "github.com/operator-framework/operator-lib/conditions"
errs "github.com/pkg/errors"
synpkg "github.com/syndesisio/syndesis/install/operator/pkg"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/capabilities"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/clienttools"
"github.com/syndesisio/syndesis/install/operator/pkg/syndesis/configuration"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

var opCondLog = logf.Log.WithName("operator-condition-log")

type ConditionState struct {
// The value of the condition, either metav1.ConditionTrue or metav1.ConditionFalse
Status metav1.ConditionStatus
// The single word reason for the condition setting
// Must start with a letter
// Rest of the world can include letters, numbers, commas and colons
// Cannot end with comma or colon
Reason string
// The description of the reason for the condition change.
Message string
}

func GetConditionName(ctx context.Context, clientTools *clienttools.ClientTools, namespace string) (string, error) {
opCondLog.V(synpkg.DEBUG_LOGGING_LVL).Info("Finding OLM Operator Condition")

apiSpec, err := capabilities.ApiCapabilities(clientTools)
if err != nil {
return "", err
}

if !apiSpec.OlmSupport {
//
// This cluster does not support OLM so nothing to do
//
opCondLog.V(synpkg.DEBUG_LOGGING_LVL).Info("No OLM support ... aborting Operation Condition Search")
return "", nil
}

rtClient, err := clientTools.RuntimeClient()
if err != nil {
return "", errs.Wrap(err, "Failed to initialise runtime client")
}

//
// Find the operator condition associated with this operator
//
// deployment -> owned by CSV -> operator condition has the same name
//
configName, err := configuration.GetProductName(configuration.TemplateConfig)
if err != nil {
return "", errs.Wrap(err, "Failed to determine product name")
}
deploymentName := configName + "-operator"

opCondLog.V(synpkg.DEBUG_LOGGING_LVL).Info("Finding Operator Deployment", "name", deploymentName)
deployment := &appsv1.Deployment{}
if err = rtClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: deploymentName}, deployment); err != nil {
return "", err // Should find the deployment of this operator
}

opCondLog.V(synpkg.DEBUG_LOGGING_LVL).Info("Operator Deployment found", "name", deploymentName)
ownerRefs := deployment.GetOwnerReferences()
if len(ownerRefs) > 1 || len(ownerRefs) == 0 {
// No operator condition as this is not owned by a CSV
return "", nil
}

if ownerRefs[0].Kind != "ClusterServiceVersion" {
// No operator condition as this is not owned by a CSV
return "", nil
}

opCondLog.V(synpkg.DEBUG_LOGGING_LVL).Info("CSV Owned Deployment", "name", deploymentName, "owner", ownerRefs[0].Name)
return ownerRefs[0].Name, nil
}

//
// Creates the condition if it does not already exist
//
func SetUpgradeCondition(ctx context.Context, clientTools *clienttools.ClientTools, namespace string, state ConditionState) error {

conditionName, err := GetConditionName(ctx, clientTools, namespace)
if err != nil {
return err
} else if conditionName == "" {
return nil
}

rtClient, err := clientTools.RuntimeClient()
if err != nil {
return errs.Wrap(err, "Failed to initialise runtime client")
}

clusterFactory := conditions.InClusterFactory{rtClient}
err = os.Setenv("OPERATOR_CONDITION_NAME", conditionName)
if err != nil {
return err
}
fmt.Println("XXX Set env var")
uc, err := clusterFactory.NewCondition(olmapiv2.ConditionType(olmapiv2.Upgradeable))
if err != nil {
fmt.Println("XXX error")
return err
}

fmt.Println("XXX Setting Condition")
err = uc.Set(ctx,
state.Status,
conditions.WithReason(state.Reason),
conditions.WithMessage(state.Message))
if err != nil {
fmt.Println("XXX Failed on the last lap")
fmt.Println(err)
return err
}

fmt.Println("XXX State set")
return nil
}
Loading

0 comments on commit 78a983e

Please sign in to comment.