Skip to content

Commit

Permalink
Odlm subcheck and label length in CD (#2264)
Browse files Browse the repository at this point in the history
* Update ODLM CRs after ODLM has a minor version upgrade (#1971)

Signed-off-by: Daniel Fan <[email protected]>
Signed-off-by: Allen Li <[email protected]>

* label-length-limitation

Signed-off-by: Allen Li <[email protected]>

---------

Signed-off-by: Daniel Fan <[email protected]>
Signed-off-by: Allen Li <[email protected]>
Co-authored-by: Daniel Fan <[email protected]>
  • Loading branch information
qpdpQ and Daniel-Fan authored Oct 18, 2024
1 parent fb0d36c commit 0cb5865
Show file tree
Hide file tree
Showing 4 changed files with 326 additions and 0 deletions.
75 changes: 75 additions & 0 deletions controllers/bootstrap/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

utilyaml "github.com/ghodss/yaml"
olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"golang.org/x/mod/semver"
corev1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/equality"
Expand Down Expand Up @@ -240,6 +241,13 @@ func (b *Bootstrap) InitResources(instance *apiv3.CommonService, forceUpdateODLM
return err
}

klog.Info("Waiting for ODLM Operator to be ready")
if isWaiting, err := b.waitOperatorCSV("ibm-odlm", b.CSData.CPFSNs); err != nil {
return err
} else if isWaiting {
forceUpdateODLMCRs = true
}

// wait ODLM OperandRegistry and OperandConfig CRD
if err := b.waitResourceReady(constant.OpregAPIGroupVersion, constant.OpregKind); err != nil {
return err
Expand Down Expand Up @@ -1946,3 +1954,70 @@ func setEDBUserManaged(instance *apiv3.CommonService) {
instance.Spec.OperatorConfigs = append(instance.Spec.OperatorConfigs, apiv3.OperatorConfig{Name: "internal-use-only-edb", UserManaged: true})
}
}

func (b *Bootstrap) waitOperatorCSV(packageManifest, operatorNs string) (bool, error) {
var isWaiting bool
// Wait for the operator CSV to be installed
klog.Infof("Waiting for the operator CSV with packageManifest %s in namespace %s to be installed", packageManifest, operatorNs)
if err := utilwait.PollImmediate(time.Second*5, time.Minute*5, func() (done bool, err error) {
installed, err := b.checkOperatorCSV(packageManifest, operatorNs)
if err != nil {
return false, err
} else if !installed {
klog.Infof("The operator CSV with packageManifest %s in namespace %s is not installed yet", packageManifest, operatorNs)
isWaiting = true
}
return installed, nil
}); err != nil {
return isWaiting, fmt.Errorf("failed to wait for the operator CSV to be installed: %v", err)
}
return isWaiting, nil
}

func (b *Bootstrap) checkOperatorCSV(packageManifest, operatorNs string) (bool, error) {
// List the subscription by packageManifest and operatorNs
// The subscription contain label "operators.coreos.com/<packageManifest>.<operatorNs>: ''"
subList := &olmv1alpha1.SubscriptionList{}
labelKey := util.GetFirstNCharacter(packageManifest+"."+operatorNs, 63)
if err := b.Client.List(context.TODO(), subList, &client.ListOptions{
LabelSelector: labels.SelectorFromSet(labels.Set{
"operators.coreos.com/" + labelKey: "",
}),
Namespace: operatorNs,
}); err != nil {
klog.Errorf("Failed to list Subscription by packageManifest %s and operatorNs %s: %v", packageManifest, operatorNs, err)
return false, err
}

// Check if multiple subscriptions exist
if len(subList.Items) > 1 {
return false, fmt.Errorf("multiple subscriptions found by packageManifest %s and operatorNs %s", packageManifest, operatorNs)
} else if len(subList.Items) == 0 {
return false, fmt.Errorf("no subscription found by packageManifest %s and operatorNs %s", packageManifest, operatorNs)
}

// Get the channel in the subscription .spec.channel, and check if it is semver
channel := subList.Items[0].Spec.Channel
if !semver.IsValid(channel) {
klog.Warningf("channel %s is not a semver for operator with packageManifest %s and operatorNs %s", channel, packageManifest, operatorNs)
return false, nil
}

// Get the CSV from subscription .status.installedCSV
installedCSV := subList.Items[0].Status.InstalledCSV
var installedVersion string
if installedCSV != "" {
// installedVersion is the version after the first dot in installedCSV
// For example, version is v4.3.1 for operand-deployment-lifecycle-manager.v4.3.1
installedVersion = installedCSV[strings.IndexByte(installedCSV, '.')+1:]
}

// 0 if channel == installedVersion - v4.3 == v4.3.0
// -1 if channel < installedVersion - v4.3 < v4.3.1
// +1 if channel > installedVersion - v4.3 > v4.2.0
if semver.Compare(channel, installedVersion) > 0 {
return false, nil
}

return true, nil
}
243 changes: 243 additions & 0 deletions controllers/bootstrap/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
//
// Copyright 2022 IBM Corporation
//
// 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 bootstrap

import (
"context"
"fmt"
"testing"
"time"

olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func init() {
// Add the v1alpha1 version of the operators.coreos.com API to the scheme
_ = olmv1alpha1.AddToScheme(scheme.Scheme)
}

func TestCheckOperatorCSV(t *testing.T) {
// Create a fake client
fakeClient := fake.NewClientBuilder().Build()

// Create a Bootstrap instance
bootstrap := &Bootstrap{
Client: fakeClient,
}

// Define the packageManifest and operatorNs
packageManifest := "ibm-common-service-operator"
operatorNs := "cpfs-operator-ns"

// Create a SubscriptionList with a single item
subList := &olmv1alpha1.SubscriptionList{
Items: []olmv1alpha1.Subscription{
{
ObjectMeta: metav1.ObjectMeta{
Name: "subscription-1",
Namespace: operatorNs,
Labels: map[string]string{
"operators.coreos.com/" + packageManifest + "." + operatorNs: "",
},
},
Spec: &olmv1alpha1.SubscriptionSpec{
Channel: "v1.0",
},
Status: olmv1alpha1.SubscriptionStatus{
InstalledCSV: "ibm-common-service-operator.v1.0.0",
},
},
},
}

var err error
for _, item := range subList.Items {
err = fakeClient.Create(context.TODO(), &item)
assert.NoError(t, err)
}

// Test case 1: Single subscription found with valid semver
result, err := bootstrap.checkOperatorCSV(packageManifest, operatorNs)
assert.True(t, result)
assert.NoError(t, err)

// Test case 2: Multiple subscriptions found
err = fakeClient.Create(context.TODO(), &olmv1alpha1.Subscription{
ObjectMeta: metav1.ObjectMeta{
Name: "subscription-2",
Namespace: operatorNs,
Labels: map[string]string{
"operators.coreos.com/" + packageManifest + "." + operatorNs: "",
},
},
Spec: &olmv1alpha1.SubscriptionSpec{
Channel: "v2.0",
},
Status: olmv1alpha1.SubscriptionStatus{
InstalledCSV: "ibm-common-service-operator.v2.0.0",
},
})
assert.NoError(t, err)

result, err = bootstrap.checkOperatorCSV(packageManifest, operatorNs)
assert.False(t, result)
assert.EqualError(t, err, fmt.Sprintf("multiple subscriptions found by packageManifest %s and operatorNs %s", packageManifest, operatorNs))

// Test case 3: No subscription found
err = fakeClient.DeleteAllOf(context.TODO(), &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "operators.coreos.com/v1alpha1",
"kind": "Subscription",
},
}, &client.DeleteAllOfOptions{
ListOptions: client.ListOptions{
Namespace: operatorNs,
},
})
assert.NoError(t, err)

result, err = bootstrap.checkOperatorCSV(packageManifest, operatorNs)
assert.False(t, result)
assert.EqualError(t, err, fmt.Sprintf("no subscription found by packageManifest %s and operatorNs %s", packageManifest, operatorNs))

// Test case 4: Invalid semver in channel
err = fakeClient.Create(context.TODO(), &olmv1alpha1.Subscription{
ObjectMeta: metav1.ObjectMeta{
Name: "subscription-3",
Namespace: operatorNs,
Labels: map[string]string{
"operators.coreos.com/" + packageManifest + "." + operatorNs: "",
},
},
Spec: &olmv1alpha1.SubscriptionSpec{
Channel: "invalid-semver",
},
Status: olmv1alpha1.SubscriptionStatus{
InstalledCSV: "ibm-common-service-operator.v1.0.0",
},
})
assert.NoError(t, err)

result, err = bootstrap.checkOperatorCSV(packageManifest, operatorNs)
assert.False(t, result)
assert.NoError(t, err)

// Test case 5: Small semver in channel
// InstalledCSV: "ibm-common-service-operator.v1.0.0", Channel: "v0.1"
subscription := &olmv1alpha1.Subscription{}
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: "subscription-3", Namespace: "cpfs-operator-ns"}, subscription)
assert.NoError(t, err)

subscription.Spec.Channel = "v0.1"
err = fakeClient.Update(context.TODO(), subscription)
assert.NoError(t, err)

result, err = bootstrap.checkOperatorCSV(packageManifest, operatorNs)
assert.True(t, result)
assert.NoError(t, err)

// Test case 6: Large semver in channel
// InstalledCSV: "ibm-common-service-operator.v1.0.0", Channel: "v1.1"
subscription = &olmv1alpha1.Subscription{}
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: "subscription-3", Namespace: "cpfs-operator-ns"}, subscription)
assert.NoError(t, err)

subscription.Spec.Channel = "v1.1"
err = fakeClient.Update(context.TODO(), subscription)
assert.NoError(t, err)

result, err = bootstrap.checkOperatorCSV(packageManifest, operatorNs)
assert.False(t, result)
assert.NoError(t, err)

// Test case 7: same semver in channel and installedCSV
// InstalledCSV: "ibm-common-service-operator.v1.0.0", Channel: "v1.0"
subscription = &olmv1alpha1.Subscription{}
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: "subscription-3", Namespace: "cpfs-operator-ns"}, subscription)
assert.NoError(t, err)

subscription.Spec.Channel = "v1.0"
err = fakeClient.Update(context.TODO(), subscription)
assert.NoError(t, err)

result, err = bootstrap.checkOperatorCSV(packageManifest, operatorNs)
assert.True(t, result)
assert.NoError(t, err)

}

func TestWaitOperatorCSV(t *testing.T) {
// Create a fake client
fakeClient := fake.NewClientBuilder().Build()

// Create a Bootstrap instance
bootstrap := &Bootstrap{
Client: fakeClient,
}

// Define the packageManifest and operatorNs
packageManifest := "ibm-common-service-operator"
operatorNs := "cpfs-operator-ns"

// Test case 1: Operator CSV is not installed yet
err := fakeClient.Create(context.TODO(), &olmv1alpha1.Subscription{
ObjectMeta: metav1.ObjectMeta{
Name: "subscription-1",
Namespace: operatorNs,
Labels: map[string]string{
"operators.coreos.com/" + packageManifest + "." + operatorNs: "",
},
},
Spec: &olmv1alpha1.SubscriptionSpec{
Channel: "v1.2",
},
Status: olmv1alpha1.SubscriptionStatus{
InstalledCSV: "ibm-common-service-operator.v1.0.0",
},
})
assert.NoError(t, err)

// additional go routine to update the subscription status
go func() {
// sleep for 3 seconds to simulate the operator CSV installation
<-time.After(3 * time.Second)

subscription := &olmv1alpha1.Subscription{}
err := fakeClient.Get(context.TODO(), types.NamespacedName{Name: "subscription-1", Namespace: "cpfs-operator-ns"}, subscription)
assert.NoError(t, err)

subscription.Status.InstalledCSV = "ibm-common-service-operator.v1.2.0"
err = fakeClient.Update(context.TODO(), subscription)
assert.NoError(t, err)
}()

isWaiting, err := bootstrap.waitOperatorCSV(packageManifest, operatorNs)
assert.True(t, isWaiting)
assert.NoError(t, err)

// Test case 2: Operator CSV is already installed
isWaiting, err = bootstrap.waitOperatorCSV(packageManifest, operatorNs)
assert.False(t, isWaiting)
assert.NoError(t, err)
}
7 changes: 7 additions & 0 deletions controllers/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -898,3 +898,10 @@ func GetPackageNameByServiceName(opreg *odlm.OperandRegistry, operatorName strin
}
return ""
}

func GetFirstNCharacter(str string, n int) string {
if n >= len(str) {
return str
}
return str[:n]
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ require (
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/mod v0.8.0
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/sys v0.18.0 // indirect
Expand Down

0 comments on commit 0cb5865

Please sign in to comment.