Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Odlm subcheck and label length in CD #2264

Merged
merged 2 commits into from
Oct 18, 2024
Merged
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
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
Loading