Skip to content

Commit

Permalink
Add PodInfo Controller and Tests
Browse files Browse the repository at this point in the history
This commit adds the controller logic for reconciling PodInfo Resources
and also adds tests. Unit and Integration tests are added.

Signed-off-by: Prateek Singh <[email protected]>
Signed-off-by: Michi Mutsuzaki <[email protected]>
  • Loading branch information
prateek041 authored and michi-covalent committed Sep 18, 2023
1 parent d4aa97c commit 07ea3d1
Show file tree
Hide file tree
Showing 188 changed files with 26,774 additions and 2 deletions.
64 changes: 64 additions & 0 deletions .github/workflows/podinfo-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: PodInfo Integration Test
on:
pull_request:
types:
- opened
- synchronize
- reopened
paths-ignore:
- 'docs/**'
push:
branches:
- main
paths-ignore:
- 'docs/**'
jobs:
build:
runs-on: ubuntu-20.04
timeout-minutes: 40
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3

- name: Set Up Job Variables
id: vars
run: |
if [ ${{ github.event.issue.pull_request || github.event.pull_request }} ]; then
PR_API_JSON=$(curl \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
${{ github.event.issue.pull_request.url || github.event.pull_request.url }})
SHA=$(echo "$PR_API_JSON" | jq -r ".head.sha")
else
SHA=${{ github.sha }}
fi
echo "sha=${SHA}" >> $GITHUB_OUTPUT
echo "operatorImage=quay.io/cilium/tetragon-operator-ci:${SHA}" >> $GITHUB_OUTPUT
- name: Install Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
with:
# renovate: datasource=golang-version depName=go
go-version: '1.20.8'

- name: Install Kind and create cluster
uses: helm/[email protected]

- name: Pull Tetragon Images
uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd # v2.8.3
with:
timeout_minutes: 2
max_attempts: 30
retry_wait_seconds: 30
warning_on_retry: false
command: |
set -e
docker pull ${{ steps.vars.outputs.operatorImage }}
- name: Run go tests
run: |
helm upgrade --install tetragon ./install/kubernetes -n kube-system \
--set podWatcher.enabled=true \
--set tetragonOperator.image.override=${{ steps.vars.outputs.operatorImage }}
kubectl rollout status -n kube-system deployment/tetragon-operator
go test --tags=integration -v ./operator/...
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ clean: cli-clean tarball-clean

.PHONY: test
test: tester-progs tetragon-bpf
$(SUDO) $(GO) test -p 1 -parallel 1 $(GOFLAGS) -gcflags=$(GO_BUILD_GCFLAGS) -timeout $(GO_TEST_TIMEOUT) -failfast -cover ./pkg/... ./cmd/... ${EXTRA_TESTFLAGS}
$(SUDO) $(GO) test -p 1 -parallel 1 $(GOFLAGS) -gcflags=$(GO_BUILD_GCFLAGS) -timeout $(GO_TEST_TIMEOUT) -failfast -cover ./pkg/... ./cmd/... ./operator/... ${EXTRA_TESTFLAGS}

# Agent image to use for end-to-end tests
E2E_AGENT ?= "cilium/tetragon:$(DOCKER_IMAGE_TAG)"
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/cilium/tetragon
go 1.20

require (
github.com/bombsimon/logrusr/v4 v4.0.0
github.com/cilium/cilium v1.14.2
github.com/cilium/ebpf v0.11.0
github.com/cilium/little-vm-helper v0.0.13-0.20230822094713-37431633085a
Expand Down Expand Up @@ -53,6 +54,7 @@ require (
k8s.io/client-go v0.27.6
k8s.io/code-generator v0.27.6
k8s.io/klog/v2 v2.100.1
sigs.k8s.io/controller-runtime v0.15.0
sigs.k8s.io/controller-tools v0.12.1
sigs.k8s.io/e2e-framework v0.2.0
sigs.k8s.io/yaml v1.3.0
Expand Down Expand Up @@ -173,17 +175,18 @@ require (
golang.org/x/term v0.11.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.11.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/component-base v0.27.6 // indirect
k8s.io/gengo v0.0.0-20230306165830-ab3349d207d4 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
sigs.k8s.io/controller-runtime v0.15.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqO
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bombsimon/logrusr/v4 v4.0.0 h1:Pm0InGphX0wMhPqC02t31onlq9OVyJ98eP/Vh63t1Oo=
github.com/bombsimon/logrusr/v4 v4.0.0/go.mod h1:pjfHC5e59CvjTBIU3V3sGhFWFAnsnhOR03TRc6im0l8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
Expand Down Expand Up @@ -1232,6 +1234,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc=
gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
Expand Down Expand Up @@ -1415,6 +1418,8 @@ k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6g
k8s.io/code-generator v0.27.6 h1:1zkSDvylcA11s91aYg5U7fZ24EXMZ+KIDOj/Z3Ti4c8=
k8s.io/code-generator v0.27.6/go.mod h1:DPung1sI5vBgn4AGKtlPRQAyagj/ir/4jI55ipZHVww=
k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ=
k8s.io/component-base v0.27.6 h1:hF5WxX7Tpi9/dXAbLjPVkIA6CA6Pi6r9JOHyo0uCDYI=
k8s.io/component-base v0.27.6/go.mod h1:NvjLtaneUeb0GgMPpCBF+4LNB9GuhDHi16uUTjBhQfU=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
Expand Down
2 changes: 2 additions & 0 deletions operator/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/cilium/cilium/pkg/logging"
"github.com/cilium/cilium/pkg/logging/logfields"
"github.com/cilium/tetragon/operator/cmd/serve"
"github.com/cilium/tetragon/operator/crd"
operatorOption "github.com/cilium/tetragon/operator/option"
"github.com/cilium/tetragon/pkg/cmdref"
Expand Down Expand Up @@ -69,5 +70,6 @@ func New() *cobra.Command {

viper.BindPFlags(flags)

rootCmd.AddCommand(serve.New())
return rootCmd
}
92 changes: 92 additions & 0 deletions operator/cmd/serve/serve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon

package serve

import (
"fmt"

"github.com/bombsimon/logrusr/v4"
"github.com/cilium/cilium/pkg/logging"
"github.com/cilium/cilium/pkg/logging/logfields"

"github.com/cilium/tetragon/operator/podinfo"
ciliumiov1alpha1 "github.com/cilium/tetragon/pkg/k8s/apis/cilium.io/v1alpha1"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
)

var (
metricsAddr string
enableLeaderElection bool
probeAddr string
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)

func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(ciliumiov1alpha1.AddToScheme(scheme))
}

func New() *cobra.Command {
cmd := cobra.Command{
Use: "serve",
Short: "Run Tetragon operator",
RunE: func(cmd *cobra.Command, _ []string) error {
log := logrusr.New(logging.DefaultLogger.WithField(logfields.LogSubsys, "operator"))
ctrl.SetLogger(log)
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "f161f714.tetragon.cilium.io",
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
// speeds up voluntary leader transitions as the new leader don't have to wait
// LeaseDuration time first.
//
// In the default scaffold provided, the program ends immediately after
// the manager stops, so would be fine to enable this option. However,
// if you are doing or is intended to do any operation such as perform cleanups
// after the manager stops then its usage might be unsafe.
// LeaderElectionReleaseOnCancel: true,
})
if err != nil {
return fmt.Errorf("unable to start manager: %w", err)
}

if err = (&podinfo.Reconciler{
Client: mgr.GetClient(),
}).SetupWithManager(mgr); err != nil {
return fmt.Errorf("unable to create controller: %w %s %s", err, "controller", "podinfo")
}

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
return fmt.Errorf("unable to set up health check %w", err)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
return fmt.Errorf("unable to set up ready check %w", err)
}

setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
return fmt.Errorf("problem running manager %w", err)
}
return nil
},
}
cmd.Flags().StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
cmd.Flags().StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
cmd.Flags().BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
return &cmd
}
148 changes: 148 additions & 0 deletions operator/podinfo/podinfo_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon

package podinfo

import (
"context"
"reflect"

ciliumiov1alpha1 "github.com/cilium/tetragon/pkg/k8s/apis/cilium.io/v1alpha1"
"golang.org/x/exp/maps"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)

// Reconciler reconciles a PodInfo object
type Reconciler struct {
client.Client
//Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=cilium.io,resources=PodInfo,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=cilium.io,resources=PodInfo/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=cilium.io,resources=PodInfo/finalizers,verbs=update

// Reconcile gets notified about a pod and reconciles the corresponding PodInfo object.
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)

// Get the Pod.
pod := &corev1.Pod{}
if err := r.Get(ctx, req.NamespacedName, pod); err != nil {
if !errors.IsNotFound(err) {
// Error fetching the pod. Try again later.
l.Error(err, "unable to fetch Pod")
return ctrl.Result{}, err
}
// Pod is deleted. Nothing to reconcile.
return ctrl.Result{}, nil
}

// Wait until the necessary pod fields are available.
if !hasAllRequiredFields(pod) {
return ctrl.Result{Requeue: true}, nil
}

podInfo := &ciliumiov1alpha1.PodInfo{}
if err := r.Get(ctx, req.NamespacedName, podInfo); err != nil {
if !errors.IsNotFound(err) {
// Error fetching the pod info. Try again later.
return ctrl.Result{}, err
}
// Pod info does not exist. Create it.
return ctrl.Result{}, r.Create(ctx, generatePodInfo(pod))
}
if !equal(pod, podInfo) {
updatedPodInfo := generatePodInfo(pod)
updatedPodInfo.ResourceVersion = podInfo.ResourceVersion
return ctrl.Result{}, r.Update(ctx, updatedPodInfo)
}
return ctrl.Result{}, nil
}

// equal returns true if the given pod and pod info are equal.
func equal(pod *corev1.Pod, podInfo *ciliumiov1alpha1.PodInfo) bool {
if len(pod.Status.PodIPs) != len(podInfo.Status.PodIPs) {
return false
}
for i, podIP := range pod.Status.PodIPs {
if podIP.IP != podInfo.Status.PodIPs[i].IP {
return false
}
}

// check if ownerReference is changed.
controller := true
blockOwnerDeletion := true
expectedOwnerReference := metav1.OwnerReference{
APIVersion: "v1",
Kind: "Pod",
Name: pod.Name,
UID: pod.UID,
Controller: &controller,
BlockOwnerDeletion: &blockOwnerDeletion,
}
return pod.Name == podInfo.Name &&
pod.Namespace == podInfo.Namespace &&
pod.Status.PodIP == podInfo.Status.PodIP &&
maps.Equal(pod.Annotations, podInfo.Annotations) &&
maps.Equal(pod.Labels, podInfo.Labels) &&
len(podInfo.OwnerReferences) == 1 &&
reflect.DeepEqual(podInfo.OwnerReferences[0], expectedOwnerReference)
}

// hasAllRequiredFields checks if the necessary pod fields are available.
func hasAllRequiredFields(pod *corev1.Pod) bool {
return pod.UID != "" &&
pod.Name != "" &&
pod.Namespace != "" &&
pod.Status.PodIP != "" &&
len(pod.Status.PodIPs) > 0
}

// generatePodInfo creates a PodInfo from a Pod
func generatePodInfo(pod *corev1.Pod) *ciliumiov1alpha1.PodInfo {
var podIPs []ciliumiov1alpha1.PodIP
// Copy the Pod IPs into the PodInfo IPs.
for _, podIP := range pod.Status.PodIPs {
podIPs = append(podIPs, ciliumiov1alpha1.PodIP{IP: podIP.IP})
}
controller := true
blockOwnerDeletion := true
return &ciliumiov1alpha1.PodInfo{
ObjectMeta: metav1.ObjectMeta{
Name: pod.Name,
Namespace: pod.Namespace,
Labels: pod.Labels,
Annotations: pod.Annotations,
// setting up owner reference to the pod will ensure that the PodInfo resource is deleted when the pod is deleted.
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: pod.APIVersion,
Kind: pod.Kind,
Name: pod.Name,
UID: pod.UID,
Controller: &controller,
BlockOwnerDeletion: &blockOwnerDeletion,
},
},
},
Status: ciliumiov1alpha1.PodInfoStatus{
PodIP: pod.Status.PodIP,
PodIPs: podIPs,
},
}
}

// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Pod{}).
Owns(&ciliumiov1alpha1.PodInfo{}).
Complete(r)
}
Loading

0 comments on commit 07ea3d1

Please sign in to comment.