Skip to content

Commit

Permalink
Merge pull request #65 from frobware/issue-27/test/openshift-scc-depl…
Browse files Browse the repository at this point in the history
…oyment

Update TestBpfmanConfigReconcileAndDelete unit test to verify that the OpenShift SCC is deployed
  • Loading branch information
mergify[bot] authored Jul 31, 2024
2 parents 65b4c63 + 375cbf2 commit 18cb81a
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 72 deletions.
14 changes: 14 additions & 0 deletions controllers/bpfman-operator/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,20 @@ func (r *BpfmanConfigReconciler) ReconcileBpfmanConfig(ctx context.Context, req
return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil
}

if r.IsOpenshift {
bpfmanRestrictedSCC := &osv1.SecurityContextConstraints{}
// one-shot try to delete the bpfman
// restricted SCC object but only if it
// exists.
if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: internal.BpfmanRestrictedSccName}, bpfmanRestrictedSCC); err == nil {
r.Logger.Info("Deleting Bpfman restricted SCC object")
if err := r.Delete(ctx, bpfmanRestrictedSCC); err != nil {
r.Logger.Error(err, "Failed to delete Bpfman restricted SCC")
return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil
}
}
}

controllerutil.RemoveFinalizer(bpfmanConfig, internal.BpfmanOperatorFinalizer)
err = r.Update(ctx, bpfmanConfig)
if err != nil {
Expand Down
240 changes: 168 additions & 72 deletions controllers/bpfman-operator/configmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ package bpfmanoperator

import (
"context"
"fmt"
"os"
"path/filepath"
"reflect"
"testing"

osv1 "github.com/openshift/api/security/v1"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand All @@ -30,28 +34,40 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"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"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/bpfman/bpfman-operator/internal"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)

func TestBpfmanConfigReconcileAndDelete(t *testing.T) {
var (
name = "bpfman-config"
namespace = "bpfman"
staticDsPath = "../../config/bpfman-deployment/daemonset.yaml"
staticCsiPath = "../../config/bpfman-deployment/csidriverinfo.yaml"
ctx = context.TODO()
)

// setupTestEnvironment sets up the testing environment for the
// BpfmanConfigReconciler. It initialises a fake client with a
// ConfigMap, registers necessary types with the runtime scheme, and
// configures the reconciler with the fake client and scheme. The
// function will set the RestrictedSCC field on the reconciler object
// if the isOpenShift parameter is set to true, which is required for
// OpenShift-specific tests.
//
// Parameters:
// - isOpenShift (bool):
// A flag indicating whether to set up the test environment for
// OpenShift, which includes setting the RestrictedSCC field on the
// reconciler object.
//
// Returns:
// - *BpfmanConfigReconciler: The configured reconciler.
// - *corev1.ConfigMap: The test ConfigMap object.
// - reconcile.Request: The reconcile request object.
// - context.Context: The context for the reconcile functio
func setupTestEnvironment(isOpenShift bool) (*BpfmanConfigReconciler, *corev1.ConfigMap, reconcile.Request, context.Context, client.Client) {
// A configMap for bpfman with metadata and spec.
bpfmanConfig := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: internal.BpfmanConfigName,
Namespace: internal.BpfmanNs,
},
Data: map[string]string{
"bpfman.agent.image": "BPFMAN_AGENT_IS_SCARY",
Expand All @@ -68,83 +84,163 @@ func TestBpfmanConfigReconcileAndDelete(t *testing.T) {
s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.ConfigMap{})
s.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.DaemonSet{})
s.AddKnownTypes(storagev1.SchemeGroupVersion, &storagev1.CSIDriver{})
s.AddKnownTypes(osv1.GroupVersion, &osv1.SecurityContextConstraints{})

// Create a fake client to mock API calls.
cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build()

rc := ReconcilerCommon{
Client: cl,
Scheme: s,
}

// The expected bpfman daemonset
expectedBpfmanDs := LoadAndConfigureBpfmanDs(bpfmanConfig, staticDsPath)

// Set development Logger so we can see all logs in tests.
logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true})))

// Create a ReconcileMemcached object with the scheme and fake client.
r := &BpfmanConfigReconciler{ReconcilerCommon: rc, BpfmanStandardDeployment: staticDsPath, CsiDriverDeployment: staticCsiPath}
// Create a BpfmanConfigReconciler object with the scheme and
// fake client.
r := &BpfmanConfigReconciler{
ReconcilerCommon: ReconcilerCommon{Client: cl, Scheme: s},
BpfmanStandardDeployment: resolveConfigPath(internal.BpfmanDaemonManifestPath),
CsiDriverDeployment: resolveConfigPath(internal.BpfmanCsiDriverPath),
IsOpenshift: isOpenShift,
}
if isOpenShift {
r.RestrictedSCC = resolveConfigPath(internal.BpfmanRestrictedSCCPath)
}

// Mock request to simulate Reconcile() being called on an event for a
// watched resource .
// Mock request object to simulate Reconcile() being called on
// an event for a watched resource.
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: name,
Namespace: namespace,
Name: internal.BpfmanConfigName,
Namespace: internal.BpfmanNs,
},
}

// First reconcile will add bpfman-operator finalizer to bpfman configmap
res, err := r.Reconcile(ctx, req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}

// Require no requeue
require.False(t, res.Requeue)
return r, bpfmanConfig, req, context.TODO(), cl
}

// Check the BpfProgram Object was created successfully
err = cl.Get(ctx, types.NamespacedName{Name: bpfmanConfig.Name, Namespace: namespace}, bpfmanConfig)
require.NoError(t, err)
// resolveConfigPath adjusts the path for configuration files so that
// lookup of path resolves from the perspective of the calling test.
func resolveConfigPath(path string) string {
testDir, _ := os.Getwd()
return filepath.Clean(filepath.Join(testDir, getRelativePathToRoot(testDir), path))
}

// Check the bpfman-operator finalizer was successfully added
require.Contains(t, bpfmanConfig.GetFinalizers(), internal.BpfmanOperatorFinalizer)
// getRelativePathToRoot calculates the relative path from the current
// directory to the project root.
func getRelativePathToRoot(currentDir string) string {
projectRoot := mustFindProjectRoot(currentDir, projectRootPredicate)
relPath, _ := filepath.Rel(currentDir, projectRoot)
return relPath
}

// Second reconcile will create bpfman daemonset
res, err = r.Reconcile(ctx, req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
// mustFindProjectRoot traverses up the directory tree to find the
// project root. The project root is identified by the provided
// closure. This function panics if, after walking the tree and
// reaching the root of the filesystem, the project root is not found.
func mustFindProjectRoot(currentDir string, isProjectRoot func(string) bool) string {
for {
if isProjectRoot(currentDir) {
return currentDir
}
if currentDir == filepath.Dir(currentDir) {
panic("project root not found")
}
// Move up to the parent directory.
currentDir = filepath.Dir(currentDir)
}
}

// Require no requeue
require.False(t, res.Requeue)

// Check the bpfman daemonset was created successfully
actualBpfmanDs := &appsv1.DaemonSet{}

err = cl.Get(ctx, types.NamespacedName{Name: expectedBpfmanDs.Name, Namespace: namespace}, actualBpfmanDs)
require.NoError(t, err)

// Check the bpfman daemonset was created with the correct configuration.
require.True(t, reflect.DeepEqual(actualBpfmanDs.Spec, expectedBpfmanDs.Spec))

// Delete the bpfman configmap
err = cl.Delete(ctx, bpfmanConfig)
require.NoError(t, err)

// Third reconcile will delete bpfman daemonset
res, err = r.Reconcile(ctx, req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
// projectRootPredicate checks if the given directory is the project
// root by looking for the presence of a `go.mod` file and a `config`
// directory. Returns true if both are found, otherwise returns false.
func projectRootPredicate(dir string) bool {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); os.IsNotExist(err) {
return false
}
if _, err := os.Stat(filepath.Join(dir, "config")); os.IsNotExist(err) {
return false
}
return true
}

// Require no requeue
require.False(t, res.Requeue)

err = cl.Get(ctx, types.NamespacedName{Name: expectedBpfmanDs.Name, Namespace: namespace}, actualBpfmanDs)
require.True(t, errors.IsNotFound(err))

err = cl.Get(ctx, types.NamespacedName{Name: bpfmanConfig.Name, Namespace: namespace}, bpfmanConfig)
require.True(t, errors.IsNotFound(err))
func TestBpfmanConfigReconcileAndDeleteNEW(t *testing.T) {
for _, tc := range []struct {
isOpenShift bool
}{
{isOpenShift: false},
{isOpenShift: true},
} {
t.Run(fmt.Sprintf("isOpenShift: %v", tc.isOpenShift), func(t *testing.T) {
r, bpfmanConfig, req, ctx, cl := setupTestEnvironment(tc.isOpenShift)
require.Equal(t, tc.isOpenShift, r.RestrictedSCC != "", "RestrictedSCC should be non-empty for OpenShift and empty otherwise")

// The expected bpfman daemonset
expectedBpfmanDs := LoadAndConfigureBpfmanDs(bpfmanConfig, resolveConfigPath(internal.BpfmanDaemonManifestPath))

// First reconcile will add bpfman-operator finalizer to bpfman configmap
res, err := r.Reconcile(ctx, req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}

// Require no requeue
require.False(t, res.Requeue)

// Check the BpfProgram Object was created successfully
err = cl.Get(ctx, types.NamespacedName{Name: internal.BpfmanConfigName, Namespace: internal.BpfmanNs}, bpfmanConfig)
require.NoError(t, err)

// Check the bpfman-operator finalizer was successfully added
require.Contains(t, bpfmanConfig.GetFinalizers(), internal.BpfmanOperatorFinalizer)

// Second reconcile will create bpfman daemonset and, when isOpenshift holds true, a SCC.
res, err = r.Reconcile(ctx, req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}

// Require no requeue
require.False(t, res.Requeue)

// Check the bpfman daemonset was created successfully
actualBpfmanDs := &appsv1.DaemonSet{}

err = cl.Get(ctx, types.NamespacedName{Name: expectedBpfmanDs.Name, Namespace: expectedBpfmanDs.Namespace}, actualBpfmanDs)
require.NoError(t, err)

// Check the bpfman daemonset was created with the correct configuration.
require.True(t, reflect.DeepEqual(actualBpfmanDs.Spec, expectedBpfmanDs.Spec))

if tc.isOpenShift {
// Check the SCC was created with the correct configuration.
actualRestrictedSCC := &osv1.SecurityContextConstraints{}
expectedRestrictedSCC := LoadRestrictedSecurityContext(resolveConfigPath(internal.BpfmanRestrictedSCCPath))
err = cl.Get(ctx, types.NamespacedName{Name: internal.BpfmanRestrictedSccName, Namespace: corev1.NamespaceAll}, actualRestrictedSCC)
require.NoError(t, err)
// Match ResourceVersion's as that is the only expected difference (0 versus 1).
expectedRestrictedSCC.ResourceVersion = actualRestrictedSCC.ResourceVersion
require.True(t, reflect.DeepEqual(actualRestrictedSCC, expectedRestrictedSCC))
}

// Delete the bpfman configmap
err = cl.Delete(ctx, bpfmanConfig)
require.NoError(t, err)

// Third reconcile will delete bpfman daemonset
res, err = r.Reconcile(ctx, req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}

// Require no requeue
require.False(t, res.Requeue)

err = cl.Get(ctx, types.NamespacedName{Name: expectedBpfmanDs.Name, Namespace: expectedBpfmanDs.Namespace}, actualBpfmanDs)
require.True(t, errors.IsNotFound(err))

err = cl.Get(ctx, types.NamespacedName{Name: bpfmanConfig.Name, Namespace: bpfmanConfig.Namespace}, bpfmanConfig)
require.True(t, errors.IsNotFound(err))

err = cl.Get(ctx, types.NamespacedName{Name: internal.BpfmanRestrictedSccName, Namespace: corev1.NamespaceAll}, &osv1.SecurityContextConstraints{})
require.True(t, errors.IsNotFound(err))
})
}
}

0 comments on commit 18cb81a

Please sign in to comment.