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

oran: added NodePool resource and unit tests #901

Merged
merged 1 commit into from
Jan 30, 2025
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/openshift-kni/lifecycle-agent v0.0.0-20250120220331-9547280df193 // release-4.18
github.com/openshift-kni/numaresources-operator v0.4.18-0.2024100201.0.20250114093602-01c00730991d // release-4.18
github.com/openshift-kni/oran-hwmgr-plugin/api/hwmgr-plugin v0.0.0-20250128160241-57fbcf565b32
github.com/openshift-kni/oran-o2ims/api/hardwaremanagement v0.0.0-20250129205116-6838db628c2b
github.com/openshift-kni/oran-o2ims/api/provisioning v0.0.0-20250123151805-c935b06062f9
github.com/openshift/api v3.9.1-0.20191111211345-a27ff30ebf09+incompatible
github.com/openshift/client-go v0.0.0-20241107164952-923091dd2b1a
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,8 @@ github.com/openshift-kni/numaresources-operator v0.4.18-0.2024100201.0.202501140
github.com/openshift-kni/numaresources-operator v0.4.18-0.2024100201.0.20250114093602-01c00730991d/go.mod h1:0Nx16xaZzxDbBs12tRjAWxDJdA9RknYEf2edrOjOdrU=
github.com/openshift-kni/oran-hwmgr-plugin/api/hwmgr-plugin v0.0.0-20250128160241-57fbcf565b32 h1:ufFJmCmIAN8uI1/BowCryt+tIEzaXC1KbJnRwO3UUFE=
github.com/openshift-kni/oran-hwmgr-plugin/api/hwmgr-plugin v0.0.0-20250128160241-57fbcf565b32/go.mod h1:IUuPY/mlglIpTHV1MlYbBYPBTRgad5d328i5j/F4AbY=
github.com/openshift-kni/oran-o2ims/api/hardwaremanagement v0.0.0-20250129205116-6838db628c2b h1:/wRJJk8u0+aky96XDRwDuRT1GJxq23L5rMMsvxZ0Y8k=
github.com/openshift-kni/oran-o2ims/api/hardwaremanagement v0.0.0-20250129205116-6838db628c2b/go.mod h1:WIYfQ7jH7QkTIzqVjMEClf1H47Prs6LuoPvSMxBBAGw=
github.com/openshift-kni/oran-o2ims/api/provisioning v0.0.0-20250123151805-c935b06062f9 h1:AP4psTmffaPv3IS/PkXIqRo2lmC0chWv33piK6m28sU=
github.com/openshift-kni/oran-o2ims/api/provisioning v0.0.0-20250123151805-c935b06062f9/go.mod h1:7dmhnmqiO1VP3SuqaAdnlFbBdeTLK3FI3vQHapVMNQg=
github.com/openshift/api v0.0.0-20241210155609-29859d55727b h1:IrtLpBrlSL7a3HK+Brg7C1DFO/gsztIEMlCRJW0M82s=
Expand Down
147 changes: 147 additions & 0 deletions pkg/oran/nodepool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package oran

import (
"context"
"fmt"

"github.com/golang/glog"
"github.com/openshift-kni/eco-goinfra/pkg/clients"
"github.com/openshift-kni/eco-goinfra/pkg/msg"
hardwaremanagementv1alpha1 "github.com/openshift-kni/oran-o2ims/api/hardwaremanagement/v1alpha1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
)

// NodePoolBuilder provides a struct to inferface with NodePool resources on a specific cluster.
type NodePoolBuilder struct {
// Definition of the NodePool used to create the resource.
Definition *hardwaremanagementv1alpha1.NodePool
// Object of the NodePool as it is on the cluster.
Object *hardwaremanagementv1alpha1.NodePool
// apiClient used to interact with the cluster.
apiClient runtimeclient.Client
// errorMsg used to store latest error message from functions that do not return errors.
errorMsg string
}

// PullNodePool pulls an existing NodePool into a NodePoolBuilder struct.
func PullNodePool(apiClient *clients.Settings, name, nsname string) (*NodePoolBuilder, error) {
glog.V(100).Infof("Pulling existing NodePool %s in namespace %s from cluster", name, nsname)

if apiClient == nil {
glog.V(100).Infof("The apiClient of the NodePool is nil")

return nil, fmt.Errorf("nodePool 'apiClient' cannot be nil")
}

err := apiClient.AttachScheme(hardwaremanagementv1alpha1.AddToScheme)
if err != nil {
glog.V(100).Infof("Failed to add hardwaremanagement v1alpha1 scheme to client schemes: %v", err)

return nil, err
}

builder := &NodePoolBuilder{
apiClient: apiClient.Client,
Definition: &hardwaremanagementv1alpha1.NodePool{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: nsname,
},
},
}

if name == "" {
glog.V(100).Info("The name of the NodePool is empty")

return nil, fmt.Errorf("nodePool 'name' cannot be empty")
}

if nsname == "" {
glog.V(100).Info("The nsname of the NodePool is empty")

return nil, fmt.Errorf("nodePool 'nsname' cannot be empty")
}

if !builder.Exists() {
glog.V(100).Info("The NodePool %s does not exist in namespace %s", name, nsname)

return nil, fmt.Errorf("nodePool object %s does not exist in namespace %s", name, nsname)
}

builder.Definition = builder.Object

return builder, nil
}

// Get returns the NodePool object if found.
func (builder *NodePoolBuilder) Get() (*hardwaremanagementv1alpha1.NodePool, error) {
if valid, err := builder.validate(); !valid {
return nil, err
}

glog.V(100).Infof("Getting NodePool object %s in namespace %s",
builder.Definition.Name, builder.Definition.Namespace)

nodePool := &hardwaremanagementv1alpha1.NodePool{}
err := builder.apiClient.Get(context.TODO(), runtimeclient.ObjectKey{
Name: builder.Definition.Name,
Namespace: builder.Definition.Namespace,
}, nodePool)

if err != nil {
glog.V(100).Infof("Failed to get NodePool object %s in namespace %s: %v",
builder.Definition.Name, builder.Definition.Namespace, err)

return nil, err
}

return nodePool, nil
}

// Exists checks whether this NodePool exists on the cluster.
func (builder *NodePoolBuilder) Exists() bool {
if valid, _ := builder.validate(); !valid {
return false
}

glog.V(100).Infof("Checking if NodePool %s exists in namespace %s",
builder.Definition.Name, builder.Definition.Namespace)

var err error
builder.Object, err = builder.Get()

return err == nil || !k8serrors.IsNotFound(err)
}

// validate checks that the builder, definition, and apiClient are properly initialized and there is no errorMsg.
func (builder *NodePoolBuilder) validate() (bool, error) {
resourceCRD := "nodePool"

if builder == nil {
glog.V(100).Infof("The %s builder is uninitialized", resourceCRD)

return false, fmt.Errorf("error: received nil %s builder", resourceCRD)
}

if builder.Definition == nil {
glog.V(100).Infof("The %s is uninitialized", resourceCRD)

return false, fmt.Errorf(msg.UndefinedCrdObjectErrString(resourceCRD))
}

if builder.apiClient == nil {
glog.V(100).Infof("The %s builder apiClient is nil", resourceCRD)

return false, fmt.Errorf("%s builder cannot have nil apiClient", resourceCRD)
}

if builder.errorMsg != "" {
glog.V(100).Infof("The %s builder has error message %s", resourceCRD, builder.errorMsg)

return false, fmt.Errorf(builder.errorMsg)
}

return true, nil
}
175 changes: 175 additions & 0 deletions pkg/oran/nodepool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package oran

import (
"fmt"
"testing"

"github.com/openshift-kni/eco-goinfra/pkg/clients"
hardwaremanagementv1alpha1 "github.com/openshift-kni/oran-o2ims/api/hardwaremanagement/v1alpha1"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

const (
defaultNodePoolName = "test-node-pool"
defaultNodePoolNamespace = "test-namespace"
)

var hardwaremanagementTestSchemes = []clients.SchemeAttacher{
hardwaremanagementv1alpha1.AddToScheme,
}

func TestPullNodePool(t *testing.T) {
testCases := []struct {
name string
nsname string
addToRuntimeObjects bool
client bool
expectedError error
}{
{
name: defaultNodePoolName,
nsname: defaultNodePoolNamespace,
addToRuntimeObjects: true,
client: true,
expectedError: nil,
},
{
name: "",
nsname: defaultNodePoolNamespace,
addToRuntimeObjects: true,
client: true,
expectedError: fmt.Errorf("nodePool 'name' cannot be empty"),
},
{
name: defaultNodePoolName,
nsname: "",
addToRuntimeObjects: true,
client: true,
expectedError: fmt.Errorf("nodePool 'nsname' cannot be empty"),
},
{
name: defaultNodePoolName,
nsname: defaultNodePoolNamespace,
addToRuntimeObjects: false,
client: true,
expectedError: fmt.Errorf(
"nodePool object %s does not exist in namespace %s", defaultNodePoolName, defaultNodePoolNamespace),
},
{
name: defaultNodePoolName,
nsname: defaultNodePoolNamespace,
addToRuntimeObjects: true,
client: false,
expectedError: fmt.Errorf("nodePool 'apiClient' cannot be nil"),
},
}

for _, testCase := range testCases {
var (
runtimeObjects []runtime.Object
testSettings *clients.Settings
)

if testCase.addToRuntimeObjects {
runtimeObjects = append(runtimeObjects, buildDummyNodePool(defaultNodePoolName, defaultNodePoolNamespace))
}

if testCase.client {
testSettings = clients.GetTestClients(clients.TestClientParams{
K8sMockObjects: runtimeObjects,
SchemeAttachers: hardwaremanagementTestSchemes,
})
}

testBuilder, err := PullNodePool(testSettings, testCase.name, testCase.nsname)
assert.Equal(t, testCase.expectedError, err)

if testCase.expectedError == nil {
assert.Equal(t, testCase.name, testBuilder.Definition.Name)
assert.Equal(t, testCase.nsname, testBuilder.Definition.Namespace)
}
}
}

func TestNodePoolGet(t *testing.T) {
testCases := []struct {
testBuilder *NodePoolBuilder
expectedError string
}{
{
testBuilder: buildValidNodePoolTestBuilder(buildTestClientWithDummyNodePool()),
expectedError: "",
},
{
testBuilder: buildValidNodePoolTestBuilder(clients.GetTestClients(clients.TestClientParams{})),
expectedError: fmt.Sprintf(
"nodepools.o2ims-hardwaremanagement.oran.openshift.io \"%s\" not found", defaultNodePoolName),
},
}

for _, testCase := range testCases {
nodePool, err := testCase.testBuilder.Get()

if testCase.expectedError == "" {
assert.Nil(t, err)
assert.Equal(t, testCase.testBuilder.Definition.Name, nodePool.Name)
assert.Equal(t, testCase.testBuilder.Definition.Namespace, nodePool.Namespace)
} else {
assert.EqualError(t, err, testCase.expectedError)
}
}
}

func TestNodePoolExists(t *testing.T) {
testCases := []struct {
testBuilder *NodePoolBuilder
exists bool
}{
{
testBuilder: buildValidNodePoolTestBuilder(buildTestClientWithDummyNodePool()),
exists: true,
},
{
testBuilder: buildValidNodePoolTestBuilder(clients.GetTestClients(clients.TestClientParams{})),
exists: false,
},
}

for _, testCase := range testCases {
exists := testCase.testBuilder.Exists()
assert.Equal(t, testCase.exists, exists)
}
}

// buildDummyNodePool returns a NodePool with the provided name and nsname.
func buildDummyNodePool(name, nsname string) *hardwaremanagementv1alpha1.NodePool {
return &hardwaremanagementv1alpha1.NodePool{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: nsname,
},
}
}

// buildTestClientWithDummyNodePool returns an apiClient with the correct schemes and a NodePool with default name and
// namespace.
func buildTestClientWithDummyNodePool() *clients.Settings {
return clients.GetTestClients(clients.TestClientParams{
K8sMockObjects: []runtime.Object{
buildDummyNodePool(defaultNodePoolName, defaultNodePoolNamespace),
},
SchemeAttachers: hardwaremanagementTestSchemes,
})
}

// buildValidNodePoolTestBuilder returns a valid NodePoolBuilder with all defaults and the provided apiClient.
func buildValidNodePoolTestBuilder(apiClient *clients.Settings) *NodePoolBuilder {
_ = apiClient.AttachScheme(hardwaremanagementv1alpha1.AddToScheme)

return &NodePoolBuilder{
Definition: buildDummyNodePool(defaultNodePoolName, defaultNodePoolNamespace),
apiClient: apiClient,
}
}
Loading
Loading