Skip to content

Commit

Permalink
Merge pull request #349 from anfredette/unit-test-containers
Browse files Browse the repository at this point in the history
Add unit test for Uprobe in container
  • Loading branch information
mergify[bot] authored Dec 16, 2024
2 parents 1165a8c + 6828e71 commit 9191140
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 65 deletions.
7 changes: 7 additions & 0 deletions cmd/bpfman-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,19 @@ func main() {
os.Exit(1)
}

containerGetter, err := bpfmanagent.NewRealContainerGetter(nodeName)
if err != nil {
setupLog.Error(err, "unable to create containerGetter")
os.Exit(1)
}

common := bpfmanagent.ReconcilerCommon{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
GrpcConn: conn,
BpfmanClient: gobpfman.NewBpfmanClient(conn),
NodeName: nodeName,
Containers: containerGetter,
}

if err = (&bpfmanagent.XdpProgramReconciler{
Expand Down
1 change: 1 addition & 0 deletions controllers/bpfman-agent/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type ReconcilerCommon struct {
finalizer string
recType string
appOwner metav1.Object // Set if the owner is an application
Containers ContainerGetter
}

// bpfmanReconciler defines a generic bpfProgram K8s object reconciler which can
Expand Down
61 changes: 40 additions & 21 deletions controllers/bpfman-agent/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,46 @@ import (
"github.com/go-logr/logr"
)

// Figure out the list of container pids in which the program should be
// attached.
func getContainers(
ctx context.Context,
containerSelector *bpfmaniov1alpha1.ContainerSelector,
nodeName string,
logger logr.Logger) (*[]containerInfo, error) {
type ContainerInfo struct {
podName string
containerName string
pid int64
}

// Create an interface for getting the list of containers in which the program
// should be attached so we can mock it in unit tests.
type ContainerGetter interface {
// Get the list of containers on this node that match the containerSelector.
GetContainers(ctx context.Context, containerSelector *bpfmaniov1alpha1.ContainerSelector,
logger logr.Logger) (*[]ContainerInfo, error)
}

type RealContainerGetter struct {
nodeName string
clientSet kubernetes.Interface
}

func NewRealContainerGetter(nodeName string) (*RealContainerGetter, error) {
clientSet, err := getClientset()
if err != nil {
return nil, fmt.Errorf("failed to get clientset: %v", err)
}

containerGetter := RealContainerGetter{
nodeName: nodeName,
clientSet: clientSet,
}

return &containerGetter, nil
}

func (c *RealContainerGetter) GetContainers(
ctx context.Context,
containerSelector *bpfmaniov1alpha1.ContainerSelector,
logger logr.Logger) (*[]ContainerInfo, error) {

// Get the list of pods that match the selector.
podList, err := getPodsForNode(ctx, clientSet, containerSelector, nodeName)
podList, err := c.getPodsForNode(ctx, containerSelector)
if err != nil {
return nil, fmt.Errorf("failed to get pod list: %v", err)
}
Expand All @@ -64,8 +89,8 @@ func getContainers(

// getPodsForNode returns a list of pods on the given node that match the given
// container selector.
func getPodsForNode(ctx context.Context, clientset kubernetes.Interface,
containerSelector *bpfmaniov1alpha1.ContainerSelector, nodeName string) (*v1.PodList, error) {
func (c *RealContainerGetter) getPodsForNode(ctx context.Context,
containerSelector *bpfmaniov1alpha1.ContainerSelector) (*v1.PodList, error) {

selectorString := metav1.FormatLabelSelector(&containerSelector.Pods)

Expand All @@ -74,33 +99,27 @@ func getPodsForNode(ctx context.Context, clientset kubernetes.Interface,
}

listOptions := metav1.ListOptions{
FieldSelector: "spec.nodeName=" + nodeName,
FieldSelector: "spec.nodeName=" + c.nodeName,
}

if selectorString != "<none>" {
listOptions.LabelSelector = selectorString
}

podList, err := clientset.CoreV1().Pods(containerSelector.Namespace).List(ctx, listOptions)
podList, err := c.clientSet.CoreV1().Pods(containerSelector.Namespace).List(ctx, listOptions)
if err != nil {
return nil, fmt.Errorf("error getting pod list: %v", err)
}

return podList, nil
}

type containerInfo struct {
podName string
containerName string
pid int64
}

// getContainerInfo returns a list of containerInfo for the given pod list and container names.
func getContainerInfo(podList *v1.PodList, containerNames *[]string, logger logr.Logger) (*[]containerInfo, error) {
func getContainerInfo(podList *v1.PodList, containerNames *[]string, logger logr.Logger) (*[]ContainerInfo, error) {

crictl := "/usr/local/bin/crictl"

containers := []containerInfo{}
containers := []ContainerInfo{}

for i, pod := range podList.Items {
logger.V(1).Info("Pod", "index", i, "Name", pod.Name, "Namespace", pod.Namespace, "NodeName", pod.Spec.NodeName)
Expand Down Expand Up @@ -196,7 +215,7 @@ func getContainerInfo(podList *v1.PodList, containerNames *[]string, logger logr
continue
}

container := containerInfo{
container := ContainerInfo{
podName: pod.Name,
containerName: containerName,
pid: containerPid,
Expand Down
2 changes: 1 addition & 1 deletion controllers/bpfman-agent/tc-program.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func (r *TcProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfm

// There is a container selector, so see if there are any matching
// containers on this node.
containerInfo, err := getContainers(ctx, r.currentTcProgram.Spec.Containers, r.NodeName, r.Logger)
containerInfo, err := r.Containers.GetContainers(ctx, r.currentTcProgram.Spec.Containers, r.Logger)
if err != nil {
return nil, fmt.Errorf("failed to get container pids: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion controllers/bpfman-agent/tcx-program.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func (r *TcxProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpf

// There is a container selector, so see if there are any matching
// containers on this node.
containerInfo, err := getContainers(ctx, r.currentTcxProgram.Spec.Containers, r.NodeName, r.Logger)
containerInfo, err := r.Containers.GetContainers(ctx, r.currentTcxProgram.Spec.Containers, r.Logger)
if err != nil {
return nil, fmt.Errorf("failed to get container pids: %v", err)
}
Expand Down
84 changes: 84 additions & 0 deletions controllers/bpfman-agent/test_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package bpfmanagent

import (
"context"
"testing"

bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1"
"github.com/go-logr/logr"

"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientGoFake "k8s.io/client-go/kubernetes/fake"
)

type FakeContainerGetter struct {
containerList *[]ContainerInfo
}

func (f *FakeContainerGetter) GetContainers(ctx context.Context, containerSelector *bpfmaniov1alpha1.ContainerSelector,
logger logr.Logger) (*[]ContainerInfo, error) {
return f.containerList, nil
}

func TestGetPods(t *testing.T) {
ctx := context.TODO()

// Create a fake clientset
clientset := clientGoFake.NewSimpleClientset()

// Create a ContainerSelector
containerSelector := &bpfmaniov1alpha1.ContainerSelector{
Pods: metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "test",
},
},
Namespace: "default",
}

nodeName := "test-node"

// Create a Pod that matches the label selector and is on the correct node
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
Labels: map[string]string{
"app": "test",
},
},
Spec: v1.PodSpec{
NodeName: nodeName,
},
}

containerGetter := RealContainerGetter{
nodeName: nodeName,
clientSet: clientset,
}

// Add the Pod to the fake clientset
_, err := clientset.CoreV1().Pods("default").Create(ctx, pod, metav1.CreateOptions{})
require.NoError(t, err)

// Call getPods and check the returned PodList
podList, err := containerGetter.getPodsForNode(ctx, containerSelector)
require.NoError(t, err)
require.Len(t, podList.Items, 1)
require.Equal(t, "test-pod", podList.Items[0].Name)

// Try another selector
// Create a ContainerSelector
containerSelector = &bpfmaniov1alpha1.ContainerSelector{
Pods: metav1.LabelSelector{
MatchLabels: map[string]string{},
},
}

podList, err = containerGetter.getPodsForNode(ctx, containerSelector)
require.NoError(t, err)
require.Len(t, podList.Items, 1)
require.Equal(t, "test-pod", podList.Items[0].Name)
}
2 changes: 1 addition & 1 deletion controllers/bpfman-agent/uprobe-program.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func (r *UprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*

// There is a container selector, so see if there are any matching
// containers on this node.
containerInfo, err := getContainers(ctx, r.currentUprobeProgram.Spec.Containers, r.NodeName, r.Logger)
containerInfo, err := r.Containers.GetContainers(ctx, r.currentUprobeProgram.Spec.Containers, r.Logger)
if err != nil {
return nil, fmt.Errorf("failed to get container pids: %v", err)
}
Expand Down
Loading

0 comments on commit 9191140

Please sign in to comment.