From 32aa0547a0883522239f9f53a19832e022d348ee Mon Sep 17 00:00:00 2001 From: ssttehrani Date: Sun, 3 Dec 2023 19:48:20 +0330 Subject: [PATCH 1/2] refactor: improve performance and functionality - Designed and implemented an in-memory cache to determine namespace exclusion status. This optimization replaces the previous, less efficient method of performing list operations on ciliumNetworkPolicy and networkPolicy, as well as get operations on event namespace in each reconcile loop. - Added functionality to check the MetalLB annotation `metallb.universe.tf/address-pool: vpn-access` in Service objects. This determines if a service should be exposed, and if so, adds the necessary rule to its associated CNP object. This change enhances service-level operation handling. - Updated the cilium Go package to integrate with the latest changes, ensuring compatibility and improved performance. --- cmd/main.go | 19 + .../Chart.yaml | 4 +- go.mod | 18 +- go.sum | 62 +- internal/consts/consts.go | 47 +- internal/controller/controller_test.go | 547 +++++++++++++----- internal/controller/service/controller.go | 50 +- internal/controller/service/helpers.go | 236 ++++++-- internal/controller/service/setup.go | 447 +++++++++++++- internal/controller/service/types.go | 49 +- internal/controller/suite_test.go | 19 + pkg/utils/meta.go | 34 ++ pkg/utils/utils.go | 17 - 13 files changed, 1271 insertions(+), 278 deletions(-) create mode 100644 pkg/utils/meta.go diff --git a/cmd/main.go b/cmd/main.go index fc2ab52..ded8346 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "context" "flag" "os" @@ -25,11 +26,13 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" "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/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -106,6 +109,22 @@ func main() { os.Exit(1) } + if err := mgr.GetFieldIndexer().IndexField( + context.Background(), + &corev1.Service{}, + "spec.type", + func(object client.Object) []string { + service, ok := object.(*corev1.Service) + if !ok { + return []string{} + } + + return []string{string(service.Spec.Type)} + }); err != nil { + setupLog.Error(err, "failed to create index for .spec.type", "controller", "Service") + os.Exit(1) + } + reconcilerExtended := controller.NewReconcilerExtended(mgr) if err = reconcilerExtended.SetupWithManager(mgr); err != nil { diff --git a/deploy/charts/svc-lb-to-cilium-netpolicy-operator/Chart.yaml b/deploy/charts/svc-lb-to-cilium-netpolicy-operator/Chart.yaml index db25a6d..9d9e3cd 100644 --- a/deploy/charts/svc-lb-to-cilium-netpolicy-operator/Chart.yaml +++ b/deploy/charts/svc-lb-to-cilium-netpolicy-operator/Chart.yaml @@ -13,9 +13,9 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.0.2 +version: 1.0.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.0.0" +appVersion: "1.1.0" diff --git a/go.mod b/go.mod index ed8d3ba..c5cef10 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,7 @@ module github.com/snapp-incubator/svc-lb-to-cilium-netpolicy go 1.21.0 require ( - github.com/cilium/cilium v1.13.1 - github.com/fsnotify/fsnotify v1.6.0 + github.com/cilium/cilium v1.14.4 github.com/go-logr/logr v1.2.4 github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.27.10 @@ -13,7 +12,6 @@ require ( k8s.io/api v0.28.1 k8s.io/apimachinery v0.28.1 k8s.io/client-go v0.28.1 - k8s.io/kubernetes v1.28.1 sigs.k8s.io/controller-runtime v0.15.1 ) @@ -22,10 +20,12 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cilium/ebpf v0.10.1-0.20230626090016-654491c8a500 // indirect github.com/cilium/proxy v0.0.0-20230623092907-8fddead4e52c // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect @@ -48,6 +48,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -74,7 +75,7 @@ require ( github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/shirou/gopsutil/v3 v3.23.5 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -93,14 +94,15 @@ require ( go.opentelemetry.io/otel v1.16.0 // indirect go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect + go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect - golang.org/x/net v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index e4aa26d..ba347c9 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20221118232415-3345c89a7c72 h1:kq78byqmxX6R9uk4uN3HD2F5tkZJAZMauuLSkNPS8to= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20221118232415-3345c89a7c72/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -53,23 +53,27 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/cilium v1.13.1 h1:6JzlYiZw1HqToqZJd5Hb6Xk1QasptOEUS1UUO4LneBQ= -github.com/cilium/cilium v1.13.1/go.mod h1:xHiUYiRvIsJTqkkTq7xNWXBDZZ5UqyrYJktYZJYuYjI= +github.com/cilium/checkmate v1.0.3 h1:CQC5eOmlAZeEjPrVZY3ZwEBH64lHlx9mXYdUehEwI5w= +github.com/cilium/checkmate v1.0.3/go.mod h1:KiBTasf39/F2hf2yAmHw21YFl3hcEyP4Yk6filxc12A= +github.com/cilium/cilium v1.14.4 h1:iJ/LgrFMTHhl0/fMas6T1IY7VWGhtkh/B4v921GcNQk= +github.com/cilium/cilium v1.14.4/go.mod h1:lz48ydfHJ2Q/dqAOIrAzQXmypA5w1GAi7vipQorETE8= +github.com/cilium/ebpf v0.10.1-0.20230626090016-654491c8a500 h1:eAn1/gEVvcamZLoF4JKznmG2zKABsF7mRisyfQtwa3Q= +github.com/cilium/ebpf v0.10.1-0.20230626090016-654491c8a500/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= github.com/cilium/proxy v0.0.0-20230623092907-8fddead4e52c h1:/NqY4jLr92f7VcUJe1gHS6CgSGWFUCeD2f4QhxO8tgE= github.com/cilium/proxy v0.0.0-20230623092907-8fddead4e52c/go.mod h1:iOlDXIgPGBabS7J0Npbq8MC5+gfvUGSBISnxXIJjfgs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 h1:KwaoQzs/WeUxxJqiJsZ4euOly1Az/IgZXXSxlD/UBNk= -github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= @@ -85,14 +89,14 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.2 h1:JiO+kJTpmYGjEodY7O1Zk8oZcNz1+f30UtwtXoFUPzE= -github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -251,6 +255,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.4 h1:7GHuZcgid37q8o5i3QI9KMT4nCWQQ3Kx3Ov6bb9MfK0= +github.com/hashicorp/golang-lru/v2 v2.0.4/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -346,8 +352,8 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= @@ -387,8 +393,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -436,6 +443,8 @@ go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDs go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= @@ -528,8 +537,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -606,11 +615,11 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.1-0.20230616193735-e0c3b6e6ae3b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -620,8 +629,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -752,6 +761,11 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e h1:AZX1ra8YbFMSb7+1pI8S9v4rrgRR7jU1FmuFSSjTVcQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -824,8 +838,6 @@ k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/kubernetes v1.28.1 h1:ZQuukGbpVjSbMypkjNErpbsSHni6RPgoqz+2zDBsuMY= -k8s.io/kubernetes v1.28.1/go.mod h1:rBQpjGYlLBV0KuOLw8EG45N5EBCskWiPpi0xy5liHMI= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 64820dd..8c855a7 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -21,26 +21,29 @@ import ( ) const ( - CiliumNetworkPolicyCreateError string = "failed to create the CiliumNetworkPolicy object: %w" - CiliumNetworkPolicyCreateSkipInfo string = "skip the CiliumNetworkPolicy object creation since the Service type is not LoadBalancer" - CiliumNetworkPolicyDeleteError string = "failed to delete the CiliumNetworkPolicy object: %w" - CiliumNetworkPolicyDeleteSkipInfo string = "skip the CiliumNetworkPolicy object deletion since the object is not found" - CiliumNetworkPolicyFindError string = "failed to find the CiliumNetworkPolicy object owned by the service object" - CiliumNetworkPolicyListError string = "failed to get the list of CiliumNetworkPolicy objects: %w" - CiliumNetworkPolicyUpdateError string = "failed to update the CiliumNetworkPolicy object: %w" - CiliumNetworkPolicyUpdateSkipInfo string = "skip update to the current CiliumNetworkPolicy object since it is the same as the desired CiliumNetworkPolicy object" - ControllerReferenceSetError string = "failed to set ownerReference on the CiliumNetworkPolicy object: %w" - NamespaceExcludedLabelsMatchInfo string = "Namespace matched against excluded labels matcher \"%s\"" - NamespaceExcludedNamesMatchInfo string = "Namespace matched against excluded names list" - NamespaceExcludedNoPolicyMatchInfo string = "Namespace matched against \"no existing unmanaged CiliumNetworkPolicy and NetworkPolicy object\" rule" - NamespaceExclusionGetNamespaceError string = "failed to get Namespace object: %w" - NamespaceExclusionStateError string = "failed to detect Namespace exclusion state: %w" - NetworkPolicyListError string = "failed to get the list of NetworkPolicy objects: %w" - ServiceAddFinalizerError string = "failed to add finalizer to the Service object: %w" - ServiceGetError string = "failed to get the Service object: %w" - ServiceFinalizerString string = "snappcloud.io/ensure-cnp-is-deleted" - ServiceNotFoundInfo string = "Service object not found; returned and not requeued" - ServiceRemoveFinalizerError string = "failed to remove finalizer from the Service object: %w" - ServiceTypeLoadBalancer corev1.ServiceType = "LoadBalancer" - ServiceTypeNotLoadBalancerInfo string = "Service type is not LoadBalancer; returned and not requeued" + CiliumNetworkPolicyCreateError string = "failed to create the CiliumNetworkPolicy object: %w" + CiliumNetworkPolicyCreateSkipInfo string = "skip the CiliumNetworkPolicy object creation since the Service type is not LoadBalancer" + CiliumNetworkPolicyDeleteError string = "failed to delete the CiliumNetworkPolicy object: %w" + CiliumNetworkPolicyDeleteSkipInfo string = "skip the CiliumNetworkPolicy object deletion since the object is not found" + CiliumNetworkPolicyFindError string = "failed to find the CiliumNetworkPolicy object owned by the service object" + CiliumNetworkPolicyListError string = "failed to get the list of CiliumNetworkPolicy objects: %w" + CiliumNetworkPolicyUpdateError string = "failed to update the CiliumNetworkPolicy object: %w" + CiliumNetworkPolicyUpdateSkipInfo string = "skip update to the current CiliumNetworkPolicy object since it is the same as the desired CiliumNetworkPolicy object" + ControllerReferenceSetError string = "failed to set ownerReference on the CiliumNetworkPolicy object: %w" + NamespaceExcludedLabelsMatchInfo string = "Namespace matched against excluded labels matcher \"%s\"" + NamespaceExcludedNamesMatchInfo string = "Namespace matched against excluded names list" + NamespaceExcludedNoCiliumNetworkPolicyMatchInfo string = "Namespace matched against \"no existing unmanaged CiliumNetworkPolicy object\" rule" + NamespaceExcludedNoNetworkPolicyMatchInfo string = "Namespace matched against \"no existing NetworkPolicy object\" rule" + NamespaceExclusionCacheEntryNotSetInfo string = "The namespace exclusion cache entry is not set yet" + NamespaceExclusionGetNamespaceError string = "failed to get Namespace object: %w" + NamespaceExclusionReasonAddedInfo string = " and specific exclusion reason is added to its cache entry" + NamespaceExclusionStateError string = "failed to determine Namespace exclusion state: %w" + NetworkPolicyListError string = "failed to get the list of NetworkPolicy objects: %w" + ServiceAddFinalizerError string = "failed to add finalizer to the Service object: %w" + ServiceGetError string = "failed to get the Service object: %w" + ServiceFinalizerString string = "snappcloud.io/ensure-cnp-is-deleted" + ServiceNotFoundInfo string = "Service object not found; returned and not requeued" + ServiceRemoveFinalizerError string = "failed to remove finalizer from the Service object: %w" + ServiceTypeLoadBalancer corev1.ServiceType = "LoadBalancer" + ServiceTypeNotLoadBalancerInfo string = "Service type is not LoadBalancer; returned and not requeued" ) diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index 8e962b8..55aa4e5 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -27,6 +27,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "golang.org/x/exp/maps" + networkingv1 "k8s.io/api/networking/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,35 +35,37 @@ import ( "k8s.io/apimachinery/pkg/types" ) +// Constants for test configuration const ( - DefaultNamespace string = "default" - DummyName string = "dummy" - // Both ServiceName and CiliumNetworkPolicyName must be equal. - CiliumNetworkPolicyName string = "test" - // Set base on the configuration - ExcludedNamespaceNameViaLabelsSet string = "ns2" + DefaultNamespace string = "default" + DummyName string = "dummy" + CiliumNetworkPolicyName string = "test" // Both ServiceName and CiliumNetworkPolicyName must be equal + ExcludedNamespaceNameViaLabelsSet string = "ns2" // Set based on the configuration ExcludedNamespaceNameViaName string = "ns1" KubernetesFinalizer corev1.FinalizerName = "kubernetes" ServiceFinalizerString string = "snappcloud.io/ensure-cnp-is-deleted" - // Both ServiceName and CiliumNetworkPolicyName must be equal. - ServiceName string = "test" - WaitInterval = 2 + ServiceName string = "test" // Both ServiceName and CiliumNetworkPolicyName must be equal + WaitInterval = 1 ) +// Global variables for test configuration var ( - CiliumNetworkPolicyAdditionalLabels map[string]string = map[string]string{"snappcloud.io/controller-managed": "true", "snappcloud.io/controller": "svc-lb-to-cilium-netpolicy"} - CiliumNetworkPolicyAllowedEntities cilium_policy_api.EntitySlice = []cilium_policy_api.Entity{"cluster"} - CiliumNetworkPolicyAnnotations map[string]string = map[string]string{"snappcloud.io/team": "snappcloud", "snappcloud.io/sub-team": "network"} - // Set base on the configuration - ExcludedNamespaceLabelsViaLabelsSet map[string]string = map[string]string{"k1": "v1", "k2": "v2"} - ServiceLabels map[string]string = map[string]string{"app.kubernetes.io/name": "test", "k2": "v2"} - ServicePorts []corev1.ServicePort = []corev1.ServicePort{{Name: "http", Port: 80}} - ServiceSelector map[string]string = map[string]string{"app.kubernetes.io/name": "test", "k2": "v2"} + CiliumNetworkPolicyControllerLabels map[string]string = map[string]string{"snappcloud.io/controller-managed": "true", "snappcloud.io/controller": "svc-lb-to-cilium-netpolicy"} + CiliumNetworkPolicyAllowedEntities cilium_policy_api.EntitySlice = []cilium_policy_api.Entity{cilium_policy_api.EntityCluster} + CiliumNetworkPolicyAllowedEntitiesForExposedServices cilium_policy_api.EntitySlice = []cilium_policy_api.Entity{cilium_policy_api.EntityCluster, cilium_policy_api.EntityWorld} + CiliumNetworkPolicyAnnotations map[string]string = map[string]string{"snappcloud.io/team": "snappcloud", "snappcloud.io/sub-team": "network"} + ExcludedNamespaceLabelsViaLabelsSet map[string]string = map[string]string{"k1": "v1", "k2": "v2"} // Set based on the configuration + MetallbAnnotations map[string]string = map[string]string{"metallb.universe.tf/address-pool": "vpn-access"} + ServiceLabels map[string]string = map[string]string{"app.kubernetes.io/name": "test", "k2": "v2"} + ServicePorts []corev1.ServicePort = []corev1.ServicePort{{Name: "http", Port: 80}} + ServiceSelector map[string]string = map[string]string{"app.kubernetes.io/name": "test", "k2": "v2"} ) +// Main block for testing 'LoadBalancer Service' to CiliumNetworkPolicy controller var _ = Describe("Testing LoadBalancer Service to CiliumNetworkPolicy Controller", func() { Context("Testing reconcile loop functionality", Ordered, func() { + // Utility function to get a sample namespace getSampleNamespace := func(name string, labels map[string]string) *corev1.Namespace { return &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -103,6 +106,7 @@ var _ = Describe("Testing LoadBalancer Service to CiliumNetworkPolicy Controller // }).Should(Succeed()) // } + // Utility function to get a sample Service getSampleService := func(name, namespace string) *corev1.Service { return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -118,6 +122,24 @@ var _ = Describe("Testing LoadBalancer Service to CiliumNetworkPolicy Controller } } + // Utility function to get a sample exposed Service + getSampleExposedService := func(name, namespace string) *corev1.Service { + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Labels: ServiceLabels, + Annotations: MetallbAnnotations, + }, + Spec: corev1.ServiceSpec{ + Ports: ServicePorts, + Type: corev1.ServiceTypeLoadBalancer, + Selector: ServiceSelector, + }, + } + } + + // Utility function to delete a Service deleteService := func(service *corev1.Service) { Expect(k8sClient.Delete(context.Background(), service)).To(Succeed()) @@ -130,6 +152,7 @@ var _ = Describe("Testing LoadBalancer Service to CiliumNetworkPolicy Controller }).Should(Succeed()) } + // Utility function to get labels for CiliumNetworkPolicy getCiliumNetworkPolicyLabels := func(service *corev1.Service) map[string]string { labels := make(map[string]string, len(service.Labels)) @@ -137,11 +160,12 @@ var _ = Describe("Testing LoadBalancer Service to CiliumNetworkPolicy Controller labels[k] = v } - maps.Copy(labels, CiliumNetworkPolicyAdditionalLabels) + maps.Copy(labels, CiliumNetworkPolicyControllerLabels) return labels } + // Utility function to get endpoint selector labels for CiliumNetworkPolicy getCiliumNetworkPolicyEndpointSelectorLabels := func(service *corev1.Service) map[string]string { endpointSelectorLabelsMap := make(map[string]string, len(service.Spec.Selector)) @@ -152,6 +176,33 @@ var _ = Describe("Testing LoadBalancer Service to CiliumNetworkPolicy Controller return endpointSelectorLabelsMap } + // Utility function to get a sample network policy + getSampleNetworkPolicy := func(name, namespace string) *networkingv1.NetworkPolicy { + return &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + }, + } + } + + // Utility function to delete a network policy + deleteNetworkPolicy := func(networkPolicy *networkingv1.NetworkPolicy) { + Expect(k8sClient.Delete(context.Background(), networkPolicy)).To(Succeed()) + + Eventually(func(g Gomega) { + err := k8sClient.Get(context.Background(), types.NamespacedName{ + Namespace: networkPolicy.Namespace, + Name: networkPolicy.Name, + }, &ciliumv2.CiliumNetworkPolicy{}) + g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }).Should(Succeed()) + } + + // Utility function to get a sample CiliumNetworkPolicy getSampleCiliumNetworkPolicy := func(name, namespace string) *ciliumv2.CiliumNetworkPolicy { return &ciliumv2.CiliumNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ @@ -177,6 +228,7 @@ var _ = Describe("Testing LoadBalancer Service to CiliumNetworkPolicy Controller } } + // Utility function to delete a CiliumNetworkPolicy deleteCiliumNetworkPolicy := func(ciliumNetworkPolicy *ciliumv2.CiliumNetworkPolicy) { Expect(k8sClient.Delete(context.Background(), ciliumNetworkPolicy)).To(Succeed()) @@ -189,426 +241,645 @@ var _ = Describe("Testing LoadBalancer Service to CiliumNetworkPolicy Controller }).Should(Succeed()) } + // Test case: Verifies that a CiliumNetworkPolicy object is not created when the Service type is ClusterIP and the Namespace is not excluded It("should not create CiliumNetworkPolicy object when 'Service type is ClusterIP' and 'Namespace is not excluded'", func() { + // Get a sample Service with type ClusterIP in the default namespace serviceObj := getSampleService(ServiceName, DefaultNamespace) serviceObj.Spec.Type = corev1.ServiceTypeClusterIP - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Get a dummy CiliumNetworkPolicy object dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + // Create the dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify it succeeds Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - + // Create the Service object and verify it succeeds Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, &corev1.Service{})).To(Succeed()) - + // Verify that the corresponding CiliumNetworkPolicy object does not exist (indicating it was not created) err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(serviceObj) }) + // Test case: Verifies that a CiliumNetworkPolicy object is not created when the Service type is NodePort and the Namespace is not excluded It("should not create CiliumNetworkPolicy object when 'Service type is NodePort' and 'Namespace is not excluded'", func() { + // Get a sample Service with type NodePort in the default namespace serviceObj := getSampleService(ServiceName, DefaultNamespace) serviceObj.Spec.Type = corev1.ServiceTypeNodePort - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Get a dummy CiliumNetworkPolicy object dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + // Create the dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify it succeeds Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - + // Create the Service object and verify it succeeds Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, &corev1.Service{})).To(Succeed()) - + // Verify that the corresponding CiliumNetworkPolicy object does not exist (indicating it was not created) err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(serviceObj) }) + // Test case: Verifies that a CiliumNetworkPolicy object is not created when the Service type is ExternalName and the Namespace is not excluded It("should not create CiliumNetworkPolicy object when 'Service type is ExternalName' and 'Namespace is not excluded'", func() { + // Get a sample Service with type ExternalName in the default namespace serviceObj := getSampleService(ServiceName, DefaultNamespace) serviceObj.Spec.Type = corev1.ServiceTypeExternalName - serviceObj.Spec.ExternalName = "snappcloud.io" + serviceObj.Spec.ExternalName = "test.local" - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Get a dummy CiliumNetworkPolicy object dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + // Create the dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify it succeeds Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - + // Create the Service object and verify it succeeds Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, &corev1.Service{})).To(Succeed()) - + // Verify that the corresponding CiliumNetworkPolicy object does not exist (indicating it was not created) err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(serviceObj) }) + // Test case: Verifies that a CiliumNetworkPolicy object is not created when the Service type is LoadBalancer and the Namespace is excluded based on its name It("should not create CiliumNetworkPolicy object when 'Service type is LoadBalancer' and 'Namespace is excluded based on its name'", func() { + // Get a sample Namespace that is meant to be excluded based on its name namespaceObj := getSampleNamespace(ExcludedNamespaceNameViaName, make(map[string]string, 0)) + + // Get a sample Service with type LoadBalancer in the excluded namespace serviceObj := getSampleService(ServiceName, ExcludedNamespaceNameViaName) - // Create a namespace with the name 'ExcludedNamespaceName' + // Create the Namespace object and verify it succeeds Expect(k8sClient.Create(context.Background(), namespaceObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: ExcludedNamespaceNameViaName}, &corev1.Namespace{})).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Get a dummy CiliumNetworkPolicy object dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, ExcludedNamespaceNameViaName) + // Create the dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify it succeeds Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: ExcludedNamespaceNameViaName, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - + // Create the Service object and verify it succeeds Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: ExcludedNamespaceNameViaName, Name: ServiceName}, &corev1.Service{})).To(Succeed()) - + // Verify that the corresponding CiliumNetworkPolicy object does not exist (indicating it was not created in the excluded namespace) err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: ExcludedNamespaceNameViaName, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(serviceObj) }) + // Test case: Verifies that a CiliumNetworkPolicy object is not created when the Service type is LoadBalancer and the Namespace is excluded based on its labels It("should not create CiliumNetworkPolicy object when 'Service type is LoadBalancer' and 'Namespace is excluded based on its labels'", func() { + // Get a sample Namespace that is meant to be excluded based on its labels namespaceObj := getSampleNamespace(ExcludedNamespaceNameViaLabelsSet, ExcludedNamespaceLabelsViaLabelsSet) + + // Get a sample Service with type LoadBalancer in the excluded namespace serviceObj := getSampleService(ServiceName, ExcludedNamespaceNameViaLabelsSet) - // Create a namespace with the name 'ExcludedNamespaceNameViaLabelsSet' and the labels 'ExcludedNamespaceLabelsViaLabelsSet' + // Create the Namespace object and verify it succeeds Expect(k8sClient.Create(context.Background(), namespaceObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: ExcludedNamespaceNameViaLabelsSet}, &corev1.Namespace{})).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Get a dummy CiliumNetworkPolicy object dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, ExcludedNamespaceNameViaLabelsSet) + // Create the dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify it succeeds Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: ExcludedNamespaceNameViaLabelsSet, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - + // Create the Service object and verify it succeeds Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: ExcludedNamespaceNameViaLabelsSet, Name: ServiceName}, &corev1.Service{})).To(Succeed()) - + // Verify that the corresponding CiliumNetworkPolicy object does not exist (indicating it was not created in the excluded namespace) err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: ExcludedNamespaceNameViaLabelsSet, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(serviceObj) }) - It("should not create CiliumNetworkPolicy object when 'Service type is LoadBalancer' and 'Namespace is excluded based on NoPolicy rule'", func() { + // Test case: Verifies that a CiliumNetworkPolicy object is not created when the Service type is LoadBalancer and the Namespace is excluded based on the NoPolicy rule + It("should not create CiliumNetworkPolicy object when 'Service type is LoadBalancer' and 'Namespace is excluded based on the NoPolicy rule'", func() { + // Get a sample Service with type LoadBalancer in the default namespace serviceObj := getSampleService(ServiceName, DefaultNamespace) + // Create the Service object and verify it succeeds Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, &corev1.Service{})).To(Succeed()) - + // Verify that a CiliumNetworkPolicy object does not exist for the Service + // This confirms that the NoPolicy rule is in effect, and the Service type being LoadBalancer doesn't lead to CiliumNetworkPolicy creation err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Cleanup: Delete the Service object deleteService(serviceObj) }) - It("should create CiliumNetworkPolicy object when 'Service type is LoadBalancer' and 'Namespace is not excluded'", func() { + // Test case: Verifies that a CiliumNetworkPolicy object is created when the Service type is LoadBalancer and the Namespace is not excluded due to the existence of a CiliumNetworkPolicy + It("should create CiliumNetworkPolicy object when 'Service type is LoadBalancer' and 'Namespace is not excluded due to the existence of a CiliumNetworkPolicy'", func() { + // Get a sample Service with type LoadBalancer in the default namespace serviceObj := getSampleService(ServiceName, DefaultNamespace) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Get a dummy CiliumNetworkPolicy object dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + // Create the dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify it succeeds Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - + // Create the Service object and verify it succeeds Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, &corev1.Service{})).To(Succeed()) + // Verify the creation of a CiliumNetworkPolicy object corresponding to the Service + // This confirms that the presence of a CiliumNetworkPolicy object leads to the creation of the actual CiliumNetworkPolicy for the LoadBalancer Service Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) + deleteService(serviceObj) + }) + + // Test case: Verifies that a CiliumNetworkPolicy object is created when the Service type is LoadBalancer and the Namespace is not excluded due to the existence of a NetworkPolicy + It("should create CiliumNetworkPolicy object when 'Service type is LoadBalancer' and 'Namespace is not excluded due to the existence of a NetworkPolicy'", func() { + // Get a sample Service with type LoadBalancer in the default namespace + serviceObj := getSampleService(ServiceName, DefaultNamespace) + + // Get a dummy NetworkPolicy object + dummyNetworkPolicyObj := getSampleNetworkPolicy(DummyName, DefaultNamespace) + + // Create the dummy NetworkPolicy object to bypass the 'NoPolicyRule' rule and verify it succeeds + Expect(k8sClient.Create(context.Background(), dummyNetworkPolicyObj)).To(Succeed()) + + // Create the Service object and verify it succeeds + Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + + // Verify the creation of a CiliumNetworkPolicy object corresponding to the Service + // This confirms that the presence of a NetworkPolicy object leads to the creation of the actual CiliumNetworkPolicy for the LoadBalancer Service + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &networkingv1.NetworkPolicy{})).To(Succeed()) + + // Cleanup: Delete the dummy NetworkPolicy and Service objects + deleteNetworkPolicy(dummyNetworkPolicyObj) deleteService(serviceObj) }) - It("should set CiliumNetworkPolicy object \"Spec.EndpointSelector.MatchLabels\" based on Service object \"Spec.Selector\"", func() { + // Test case: Verifies that all Service of type LoadBalancer within a Namespace are reconciled after the Namespace exclusion status is updated + It("should reconcile all Service of type LoadBalancer within a Namespace after the Namespace exclusion status is updated (Namespace update event)", func() { + // Get a sample Service with type LoadBalancer in the default namespace serviceObj := getSampleService(ServiceName, DefaultNamespace) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Create the Service object and verify it succeeds + Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + + // Get a dummy CiliumNetworkPolicy object dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + // Create the dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify it succeeds Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + + // Verify the creation of a CiliumNetworkPolicy object corresponding to the Service + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Retrieve and update the Namespace object to change its exclusion status + namespaceObj := &corev1.Namespace{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: DefaultNamespace}, namespaceObj)).To(Succeed()) + namespaceObj.Labels = ExcludedNamespaceLabelsViaLabelsSet + Expect(k8sClient.Update(context.Background(), namespaceObj)).To(Succeed()) + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, &corev1.Service{})).To(Succeed()) + // Verify the absence of CiliumNetworkPolicy after Namespace update + err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) - ciliumNetworkPolicyObj := ciliumv2.CiliumNetworkPolicy{} - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumNetworkPolicyObj)).To(Succeed()) - Expect(ciliumNetworkPolicyObj.Spec.EndpointSelector.MatchLabels).To(Equal(getCiliumNetworkPolicyEndpointSelectorLabels(serviceObj))) + // Reset the Namespace labels + namespaceObj = &corev1.Namespace{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: DefaultNamespace}, namespaceObj)).To(Succeed()) + namespaceObj.Labels = nil + Expect(k8sClient.Update(context.Background(), namespaceObj)).To(Succeed()) + + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + // Verify the presence of CiliumNetworkPolicy after Namespace update + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) + deleteService(serviceObj) + }) + + // Test case: Verifies reconciliation of all LoadBalancer type Services within a Namespace following a NetworkPolicy create/delete event + It("should reconcile all Service of type LoadBalancer objects within a Namespace after the Namespace exclusion status is updated (NetworkPolicy create/delete event)", func() { + // Get a sample Service with type LoadBalancer in the default namespace + serviceObj := getSampleService(ServiceName, DefaultNamespace) + + // Create and verify the Service object + Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + // Verify that a CiliumNetworkPolicy does not exist initially + err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + + // Get a dummy NetworkPolicy object + dummyNetworkPolicyObj := getSampleNetworkPolicy(DummyName, DefaultNamespace) + + // Create and verify the dummy NetworkPolicy object to trigger a create event + Expect(k8sClient.Create(context.Background(), dummyNetworkPolicyObj)).To(Succeed()) + + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + + // Verify the creation of CiliumNetworkPolicy following the NetworkPolicy create event + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + + // Delete the dummy NetworkPolicy object to trigger a delete event + deleteNetworkPolicy(dummyNetworkPolicyObj) + + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + + // Verify the absence of CiliumNetworkPolicy following the NetworkPolicy delete event + err = k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + + // Cleanup: Delete the Service object deleteService(serviceObj) }) - It("should set CiliumNetworkPolicy object \"Spec.Ingress[0].FromEntities\" based on CiliumNetworkPolicyAllowedEntities variable", func() { + // Test case: Ensures reconciliation of all LoadBalancer type Services within a Namespace in response to CiliumNetworkPolicy create/update/delete events + It("should reconcile all Service of type LoadBalancer objects within a Namespace after the Namespace exclusion status is updated (CiliumNetworkPolicy create/update/delete event)", func() { + // Create and verify a sample LoadBalancer Service in the default namespace serviceObj := getSampleService(ServiceName, DefaultNamespace) + Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. - dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + // Verify that CiliumNetworkPolicy does not exist initially + err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Create a dummy CiliumNetworkPolicy with controller-specific labels and verify + dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + dummyCiliumNetworkPolicyObj.Labels = CiliumNetworkPolicyControllerLabels Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Verify absence of CiliumNetworkPolicy following creation + err = k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + + // Update the dummy CiliumNetworkPolicy by removing controller-specific labels and verify + dummyCiliumNetworkPolicyObj = &ciliumv2.CiliumNetworkPolicy{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, dummyCiliumNetworkPolicyObj)).To(Succeed()) + dummyCiliumNetworkPolicyObj.Labels = nil + Expect(k8sClient.Update(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) + + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + + // Verify creation of CiliumNetworkPolicy following update + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + // Delete the dummy CiliumNetworkPolicy + deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) + + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, &corev1.Service{})).To(Succeed()) + // Verify absence of CiliumNetworkPolicy following update + err = k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + + // Cleanup: Delete the Service object + deleteService(serviceObj) + }) + + // Test case: Ensures that the CiliumNetworkPolicy object's Spec.EndpointSelector.MatchLabels are correctly set based on the Service object's Spec.Selector + It("should set CiliumNetworkPolicy object \"Spec.EndpointSelector.MatchLabels\" based on Service object \"Spec.Selector\"", func() { + // Create and verify a sample LoadBalancer Service in the default namespace + serviceObj := getSampleService(ServiceName, DefaultNamespace) + Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation + dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) + + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + + // Retrieve the created CiliumNetworkPolicy object and verify its EndpointSelector.MatchLabels ciliumNetworkPolicyObj := ciliumv2.CiliumNetworkPolicy{} Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumNetworkPolicyObj)).To(Succeed()) - Expect(ciliumNetworkPolicyObj.Spec.Ingress[0].FromEntities).To(Equal(CiliumNetworkPolicyAllowedEntities)) + Expect(ciliumNetworkPolicyObj.Spec.EndpointSelector.MatchLabels).To(Equal(getCiliumNetworkPolicyEndpointSelectorLabels(serviceObj))) + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(serviceObj) }) - It("should set CiliumNetworkPolicy object \"ObjectMeta.Annotations\" based on CiliumNetworkPolicyAnnotations variable", func() { + // Test case: Verifies that the CiliumNetworkPolicy object's Spec.Ingress[0].FromEntities are set based on the CiliumNetworkPolicyAllowedEntities variable for non-exposed services + It("should set CiliumNetworkPolicy object \"Spec.Ingress[0].FromEntities\" based on CiliumNetworkPolicyAllowedEntities variable if the Service is not exposed", func() { + // Create and verify a sample LoadBalancer Service in the default namespace serviceObj := getSampleService(ServiceName, DefaultNamespace) + Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) - Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + + // Retrieve the created CiliumNetworkPolicy object and verify its Ingress FromEntities + ciliumNetworkPolicyObj := ciliumv2.CiliumNetworkPolicy{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumNetworkPolicyObj)).To(Succeed()) + Expect(ciliumNetworkPolicyObj.Spec.Ingress[0].FromEntities).To(Equal(CiliumNetworkPolicyAllowedEntities)) + + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects + deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) + deleteService(serviceObj) + }) + // Test case: Verifies that the CiliumNetworkPolicy object's Spec.Ingress[0].FromEntities are set based on the CiliumNetworkPolicyAllowedEntitiesForExposedServices variable for exposed services + It("should set CiliumNetworkPolicy object \"Spec.Ingress[0].FromEntities\" based on CiliumNetworkPolicyAllowedEntities variable if the Service is exposed", func() { + // Create and verify a sample LoadBalancer Service in the default namespace + serviceObj := getSampleExposedService(ServiceName, DefaultNamespace) Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation + dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) + + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, &corev1.Service{})).To(Succeed()) + // Retrieve the created CiliumNetworkPolicy object and verify its Ingress FromEntities for exposed services + ciliumNetworkPolicyObj := ciliumv2.CiliumNetworkPolicy{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumNetworkPolicyObj)).To(Succeed()) + Expect(ciliumNetworkPolicyObj.Spec.Ingress[0].FromEntities).To(Equal(CiliumNetworkPolicyAllowedEntitiesForExposedServices)) + + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects + deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) + deleteService(serviceObj) + }) + + // Test case: Verifies that a CiliumNetworkPolicy object's annotations are set based on a predefined variable + It("should set CiliumNetworkPolicy object 'ObjectMeta.Annotations' based on CiliumNetworkPolicyAnnotations variable", func() { + // Create and verify a sample LoadBalancer Service in the default namespace + serviceObj := getSampleService(ServiceName, DefaultNamespace) + Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation + dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) + Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) + + // Wait for a specified interval to ensure the state is stable + time.Sleep(WaitInterval * time.Second) + + // Retrieve the created CiliumNetworkPolicy object ciliumNetworkPolicyObj := ciliumv2.CiliumNetworkPolicy{} Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumNetworkPolicyObj)).To(Succeed()) + + // Assert that each annotation in the CiliumNetworkPolicy object matches the predefined variable for k, v := range CiliumNetworkPolicyAnnotations { Expect(ciliumNetworkPolicyObj.ObjectMeta.Annotations[k]).To(Equal(v)) } + // Cleanup: Delete the dummy CiliumNetworkPolicy and Service objects deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(serviceObj) }) - It("should set CiliumNetworkPolicy object \"ObjectMeta.Labels\" based on Service object \"ObjectMeta.Labels\" and upsert additional labels defined in CiliumNetworkPolicyAdditionalLabels variable", func() { + // Test case: Checks if CiliumNetworkPolicy object labels are set based on Service object labels and additional predefined labels + It("should set CiliumNetworkPolicy object 'ObjectMeta.Labels' based on Service object 'ObjectMeta.Labels' and upsert additional labels defined in CiliumNetworkPolicyAdditionalLabels variable", func() { + // Create and verify a sample LoadBalancer Service in the default namespace serviceObj := getSampleService(ServiceName, DefaultNamespace) + Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) - Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - - Expect(k8sClient.Create(context.Background(), serviceObj)).To(Succeed()) - + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, &corev1.Service{})).To(Succeed()) - + // Retrieve the created CiliumNetworkPolicy object and validate its labels + time.Sleep(WaitInterval * time.Second) ciliumNetworkPolicyObj := ciliumv2.CiliumNetworkPolicy{} Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumNetworkPolicyObj)).To(Succeed()) Expect(ciliumNetworkPolicyObj.ObjectMeta.Labels).To(Equal(getCiliumNetworkPolicyLabels(serviceObj))) + // Cleanup: Delete the dummy CiliumNetworkPolicy and the Service object deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(serviceObj) }) + // Test case: Verifies that a finalizer string is added to a Service object after creating a CiliumNetworkPolicy object It("should add finalizer string to Service object after creating CiliumNetworkPolicy object", func() { + // Create and verify a sample LoadBalancer Service in the default namespace desiredServiceObj := getSampleService(ServiceName, DefaultNamespace) - currentServiceObj := corev1.Service{} + Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) - Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - - Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) + // Retrieve the created Service object, ensuring the finalizer string is present + currentServiceObj := corev1.Service{} Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) Expect(currentServiceObj.ObjectMeta.Finalizers).To(ContainElement(ServiceFinalizerString)) + // Cleanup: Delete the dummy CiliumNetworkPolicy and the Service object deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(desiredServiceObj) }) + // Test case: Verifies that a CiliumNetworkPolicy object is deleted when a Service type is transitioned from LoadBalancer to ClusterIP It("should delete CiliumNetworkPolicy object when Service type is transitioned from LoadBalancer into ClusterIP", func() { + // Create and verify a sample LoadBalancer Service in the default namespace desiredServiceObj := getSampleService(ServiceName, DefaultNamespace) - currentServiceObj := corev1.Service{} + Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) - Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - - Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) + // Retrieve the created CiliumNetworkPolicy object and verify it succeeds Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + // Update the Service object to change its type to ClusterIP + currentServiceObj := corev1.Service{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) currentServiceObj.Spec.Type = corev1.ServiceTypeClusterIP Expect(k8sClient.Update(context.Background(), ¤tServiceObj)).To(Succeed()) + // Wait again for the state update time.Sleep(WaitInterval * time.Second) + // Verify that the CiliumNetworkPolicy object is not found (i.e., deleted) after the Service type update err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Cleanup: Delete the dummy CiliumNetworkPolicy and the Service object deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(desiredServiceObj) }) + // Test case: Ensures that the CiliumNetworkPolicy object is deleted when a Service's type transitions from LoadBalancer to NodePort It("should delete CiliumNetworkPolicy object when Service type is transitioned from LoadBalancer into NodePort", func() { + // Create and verify a sample LoadBalancer Service in the default namespace desiredServiceObj := getSampleService(ServiceName, DefaultNamespace) - currentServiceObj := corev1.Service{} + Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) - Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - - Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) + // Retrieve the created CiliumNetworkPolicy object and verify it succeeds Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + // Update the Service object to change its type to NodePort + currentServiceObj := corev1.Service{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) currentServiceObj.Spec.Type = corev1.ServiceTypeNodePort Expect(k8sClient.Update(context.Background(), ¤tServiceObj)).To(Succeed()) + // Wait again for the state update time.Sleep(WaitInterval * time.Second) + // Verify that the CiliumNetworkPolicy object is not found (i.e., deleted) after the Service type update err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Cleanup: Delete the dummy CiliumNetworkPolicy and the Service object deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(desiredServiceObj) }) + // Test case: Ensures that the CiliumNetworkPolicy object is deleted when a Service's type transitions from LoadBalancer to ExternalName It("should delete CiliumNetworkPolicy object when Service type is transitioned from LoadBalancer into ExternalName", func() { + // Create and verify a sample LoadBalancer Service in the default namespace desiredServiceObj := getSampleService(ServiceName, DefaultNamespace) - currentServiceObj := corev1.Service{} + Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) - Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - - Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) + // Retrieve the created CiliumNetworkPolicy object and verify it succeeds Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + // Update the Service object to change its type to ExternalName + currentServiceObj := corev1.Service{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) currentServiceObj.Spec.Type = corev1.ServiceTypeExternalName currentServiceObj.Spec.ExternalName = "snappcloud.io" - Expect(k8sClient.Update(context.Background(), ¤tServiceObj)).To(Succeed()) + // Wait again for the state update time.Sleep(WaitInterval * time.Second) + // Verify that the CiliumNetworkPolicy object is not found (i.e., deleted) after the Service type update err := k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{}) Expect(apierrors.IsNotFound(err)).To(BeTrue()) + // Cleanup: Delete the dummy CiliumNetworkPolicy and the Service object deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(desiredServiceObj) }) + // Test case: Ensures that the finalizer string is removed from the Service object after deleting the associated CiliumNetworkPolicy object It("should delete finalizer string from Service object after deleting CiliumNetworkPolicy object", func() { + // Create and verify a sample LoadBalancer Service in the default namespace desiredServiceObj := getSampleService(ServiceName, DefaultNamespace) - currentServiceObj := corev1.Service{} + Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - // This CiliumNetworkPolicy object is dummy since it's only used to bypass the 'NoPolicyRule' rule. + // Create a dummy CiliumNetworkPolicy object to bypass the 'NoPolicyRule' rule and verify its creation dummyCiliumNetworkPolicyObj := getSampleCiliumNetworkPolicy(DummyName, DefaultNamespace) - Expect(k8sClient.Create(context.Background(), dummyCiliumNetworkPolicyObj)).To(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: DummyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) - - Expect(k8sClient.Create(context.Background(), desiredServiceObj)).To(Succeed()) - + // Wait for a specified interval to ensure the state is stable time.Sleep(WaitInterval * time.Second) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) + // Retrieve the created CiliumNetworkPolicy object and verify it succeeds Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: CiliumNetworkPolicyName}, &ciliumv2.CiliumNetworkPolicy{})).To(Succeed()) + // Fetch and validate the created Service object, ensuring the finalizer string is present + currentServiceObj := corev1.Service{} + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) + Expect(currentServiceObj.ObjectMeta.Finalizers).To(ContainElement(ServiceFinalizerString)) + + // Update the Service object to change its type to ClusterIP currentServiceObj.Spec.Type = corev1.ServiceTypeClusterIP Expect(k8sClient.Update(context.Background(), ¤tServiceObj)).To(Succeed()) + // Wait again for the state update time.Sleep(WaitInterval * time.Second) + // Fetch and verify that the finalizer string is removed from the Service object after the update Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: DefaultNamespace, Name: ServiceName}, ¤tServiceObj)).To(Succeed()) Expect(currentServiceObj.ObjectMeta.Finalizers).To(Not(ContainElement(ServiceFinalizerString))) + // Cleanup: Delete the dummy CiliumNetworkPolicy and the Service object deleteCiliumNetworkPolicy(dummyCiliumNetworkPolicyObj) - deleteService(desiredServiceObj) }) + }) }) diff --git a/internal/controller/service/controller.go b/internal/controller/service/controller.go index e1656c8..c27cff8 100644 --- a/internal/controller/service/controller.go +++ b/internal/controller/service/controller.go @@ -43,7 +43,7 @@ import ( // move the current state of the cluster closer to the desired state. // It instantiate a new ReconcilerExtended struct and start the reconciliation flow. func (re *ReconcilerExtended) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx).WithName("reconcile") + logger := log.FromContext(ctx).WithName("reconcile loop") re.cnp = nil re.logger = logger @@ -59,6 +59,8 @@ func (re *ReconcilerExtended) Reconcile(ctx context.Context, req ctrl.Request) ( func (re *ReconcilerExtended) manageLogic(ctx context.Context) (*ctrl.Result, error) { var logicFuncs []logicFunc + var shouldExcludeNS bool + err := re.Client.Get(ctx, re.request.NamespacedName, re.service) if client.IgnoreNotFound(err) != nil { @@ -72,23 +74,39 @@ func (re *ReconcilerExtended) manageLogic(ctx context.Context) (*ctrl.Result, er return &ctrl.Result{Requeue: false}, nil } + re.logger = re.logger.WithValues("namespace", re.request.Namespace) + + re.cacheLock.Lock() + // CiliumNetworkPolicy objects neither should be created nor exist in some namespaces. // Therefore, specific namespaces are excluded by some configurations. // shouldExcludeNamespace determine the namespace exclusion status based on the configurations defined. - shouldExcludeNS, err := re.shouldExcludeNamespace(ctx) - if err != nil { - return &ctrl.Result{Requeue: true}, fmt.Errorf(consts.NamespaceExclusionStateError, err) + if re.namespaceExclusionCache[re.request.Namespace] == nil { + re.logger.Info("The namespace exclusion cache entry is not set") + + err := re.determineExclusionReasons(ctx) + if err != nil { + return &ctrl.Result{Requeue: true}, fmt.Errorf(consts.NamespaceExclusionStateError, err) + } } - logicFuncs = append(logicFuncs, re.findCNPbyOwner) + exclusionReasons := re.namespaceExclusionCache[re.request.Namespace] + + re.cacheLock.Unlock() + + shouldExcludeNS = determineExclusionStatus(exclusionReasons) + + re.logger = re.logger.WithValues("namespaceExcluded", shouldExcludeNS) + + logicFuncs = append(logicFuncs, re.findCNPByOwner) if re.shouldDeleteCNP() || shouldExcludeNS { logicFuncs = append(logicFuncs, - re.handleCNPdelete, + re.handleCNPDelete, re.removeFinalizer) } else { logicFuncs = append(logicFuncs, - re.handleCNPupdateOrCreate, + re.handleCNPUpdateOrCreate, re.addFinalizer) } @@ -106,8 +124,8 @@ func (re *ReconcilerExtended) manageLogic(ctx context.Context) (*ctrl.Result, er return &ctrl.Result{Requeue: false}, nil } -// findCNPbyOwner tries to find the CiliumNetworkPolicy object owned by the Service object using OwnerReference. -func (re *ReconcilerExtended) findCNPbyOwner(ctx context.Context) (*ctrl.Result, error) { +// findCNPByOwner tries to find the CiliumNetworkPolicy object owned by the Service object using OwnerReference. +func (re *ReconcilerExtended) findCNPByOwner(ctx context.Context) (*ctrl.Result, error) { ciliumNetworkPolicyList := ciliumv2.CiliumNetworkPolicyList{} if err := re.Client.List(ctx, &ciliumNetworkPolicyList, client.InNamespace(re.service.Namespace)); err != nil { @@ -132,8 +150,8 @@ func (re *ReconcilerExtended) findCNPbyOwner(ctx context.Context) (*ctrl.Result, return nil, nil } -// handleCNPupdateOrCreate handles the creation or update of the CiliumNetworkPolicy object if the Service type is "LoadBalancer". -func (re *ReconcilerExtended) handleCNPupdateOrCreate(ctx context.Context) (*ctrl.Result, error) { +// handleCNPUpdateOrCreate handles the creation or update of the CiliumNetworkPolicy object if the Service type is "LoadBalancer". +func (re *ReconcilerExtended) handleCNPUpdateOrCreate(ctx context.Context) (*ctrl.Result, error) { var result *ctrl.Result var err error @@ -145,7 +163,7 @@ func (re *ReconcilerExtended) handleCNPupdateOrCreate(ctx context.Context) (*ctr return &ctrl.Result{Requeue: false}, nil } - if re.isCNPfound() { + if re.isCNPFound() { result, err = re.updateCNP(ctx) } else { result, err = re.createCNP(ctx) @@ -154,9 +172,9 @@ func (re *ReconcilerExtended) handleCNPupdateOrCreate(ctx context.Context) (*ctr return result, err } -// handleCNPdelete handles the deletion of the CiliumNetworkPolicy object. -func (re *ReconcilerExtended) handleCNPdelete(ctx context.Context) (*ctrl.Result, error) { - if !re.isCNPfound() { +// handleCNPDelete handles the deletion of the CiliumNetworkPolicy object. +func (re *ReconcilerExtended) handleCNPDelete(ctx context.Context) (*ctrl.Result, error) { + if !re.isCNPFound() { re.logger.Info(consts.CiliumNetworkPolicyDeleteSkipInfo) return nil, nil @@ -184,11 +202,13 @@ func (re *ReconcilerExtended) updateCNP(ctx context.Context) (*ctrl.Result, erro // Check if CiliumNetworkPolicy object labels map was changed, if so set as desired. if !reflect.DeepEqual(re.cnp.ObjectMeta.Labels, desiredCNP.ObjectMeta.Labels) { re.cnp.ObjectMeta.Labels = desiredCNP.ObjectMeta.Labels + shouldUpdate = true } // Check if CiliumNetworkPolicy object annotations map was changed, if so set as desired. if !reflect.DeepEqual(re.cnp.ObjectMeta.Annotations, desiredCNP.ObjectMeta.Annotations) { re.cnp.ObjectMeta.Annotations = desiredCNP.ObjectMeta.Annotations + shouldUpdate = true } diff --git a/internal/controller/service/helpers.go b/internal/controller/service/helpers.go index ad1c48d..a6123ed 100644 --- a/internal/controller/service/helpers.go +++ b/internal/controller/service/helpers.go @@ -30,10 +30,14 @@ import ( networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/snapp-incubator/svc-lb-to-cilium-netpolicy/internal/config" "github.com/snapp-incubator/svc-lb-to-cilium-netpolicy/internal/consts" @@ -41,12 +45,25 @@ import ( ) var ( - desiredCNPannotationsMap = map[string]string{"snappcloud.io/team": "snappcloud", "snappcloud.io/sub-team": "network"} - desiredCNPadditionalLabelsMap = map[string]string{"snappcloud.io/controller-managed": "true", "snappcloud.io/controller": "svc-lb-to-cilium-netpolicy"} + controllerAnnotationsMap = map[string]string{"snappcloud.io/team": "snappcloud", "snappcloud.io/sub-team": "network"} + controllerLabelsMap = map[string]string{"snappcloud.io/controller-managed": "true", "snappcloud.io/controller": "svc-lb-to-cilium-netpolicy"} ) // buildCNP builds and returns a CiliumNetworkPolicy struct based on the Service object. func (re *ReconcilerExtended) buildCNP() *ciliumv2.CiliumNetworkPolicy { + var fromEntities cilium_policy_api.EntitySlice + + if re.service.Annotations["metallb.universe.tf/address-pool"] == "vpn-access" { + fromEntities = cilium_policy_api.EntitySlice{ + cilium_policy_api.EntityCluster, + cilium_policy_api.EntityWorld, + } + } else { + fromEntities = cilium_policy_api.EntitySlice{ + cilium_policy_api.EntityCluster, + } + } + endpointSelectorLabelsMap := make(map[string]string, len(re.service.Spec.Selector)) // Cilium imports labels from different sources. @@ -73,8 +90,8 @@ func (re *ReconcilerExtended) buildCNP() *ciliumv2.CiliumNetworkPolicy { ObjectMeta: metav1.ObjectMeta{ Name: re.service.Name, Namespace: re.service.Namespace, - Labels: re.getCNPdesiredLabels(), - Annotations: desiredCNPannotationsMap, + Labels: re.getCNPDesiredLabels(), + Annotations: controllerAnnotationsMap, }, Spec: &cilium_policy_api.Rule{ EndpointSelector: cilium_policy_api.EndpointSelector{ @@ -85,9 +102,7 @@ func (re *ReconcilerExtended) buildCNP() *ciliumv2.CiliumNetworkPolicy { Ingress: []cilium_policy_api.IngressRule{ { IngressCommonRule: cilium_policy_api.IngressCommonRule{ - FromEntities: cilium_policy_api.EntitySlice{ - cilium_policy_api.EntityCluster, - }, + FromEntities: fromEntities, }, }, }, @@ -103,7 +118,7 @@ func (re *ReconcilerExtended) isServiceLoadBalancer() bool { } // isCNPfound returns true if the current CiliumNetworkPolicy is not nil and false otherwise. -func (re *ReconcilerExtended) isCNPfound() bool { +func (re *ReconcilerExtended) isCNPFound() bool { return (re.cnp != nil) } @@ -125,8 +140,8 @@ func (re *ReconcilerExtended) shouldDeleteCNP() bool { return result } -// getCNPdesiredLabels adds controller labels to the Service object labels and return it. -func (re *ReconcilerExtended) getCNPdesiredLabels() map[string]string { +// getCNPDesiredLabels adds controller labels to the Service object labels and return it. +func (re *ReconcilerExtended) getCNPDesiredLabels() map[string]string { labels := make(map[string]string, len(re.service.Labels)) for k, v := range re.service.Labels { @@ -134,44 +149,100 @@ func (re *ReconcilerExtended) getCNPdesiredLabels() map[string]string { } // Add controller labels to the labels map to be able to find CiliumNetworkPolicy objects controlled by this controllers. - maps.Copy(labels, desiredCNPadditionalLabelsMap) + maps.Copy(labels, controllerLabelsMap) return labels } -// shouldExcludeNamespace determine the namespace exclusion status based on the configurations defined. -func (re *ReconcilerExtended) shouldExcludeNamespace(ctx context.Context) (bool, error) { - var shouldExclude bool +// determineExclusionStatus determines the namespace exclusion status based on the exclusion reasons. +// It returns true if the namespace should be excluded and false otherwise. +func determineExclusionStatus(exclusionReasons map[namespaceExclusionReason]empty) bool { + if exclusionReasons == nil { + return false + } + + var nonNetworkPolicy bool + + var nonUnmanagedCiliumNetworkPolicy bool + + for reason := range exclusionReasons { + if reason == namespaceName || reason == namespaceLabels { + return true + } + + if reason == noNetworkPolicyExistence { + nonNetworkPolicy = true + } + + if reason == noUnmanagedCiliumNetworkPolicyExistence { + nonUnmanagedCiliumNetworkPolicy = true + } + + if nonNetworkPolicy && nonUnmanagedCiliumNetworkPolicy { + return true + } + } + + return false +} + +// determineExclusionReasons determine the namespace exclusion reasons based on the rules defined. +// +// Note: +// The function assumes that the cache is locked already so it must be called from a function that already has a lock on the cache. +func (re *ReconcilerExtended) determineExclusionReasons(ctx context.Context) error { + var isCiliumNetworkPolicyAbsent bool + + var isNetworkPolicyAbsent bool + + var isNamespaceNameExcluded bool + + var isNamespaceLabelExcluded bool var err error - shouldExclude, err = re.checkNoPolicyRule(ctx) + isCiliumNetworkPolicyAbsent, err = re.isCiliumNetworkPolicyAbsent(ctx) if err != nil { - return false, err + return err } - if shouldExclude { - return true, nil + isNetworkPolicyAbsent, err = re.isNetworkPolicyAbsent(ctx) + if err != nil { + return err } - if re.checkExcludedNamespaceNames() { - return true, nil - } + isNamespaceNameExcluded = re.isNamespaceNameExcluded() - shouldExclude, err = re.checkExcludedNamespaceLabels(ctx) + isNamespaceLabelExcluded, err = re.isNamespaceLabelExcluded(ctx) if err != nil { - return false, err + return err } - if shouldExclude { - return true, nil + reasons := make(map[namespaceExclusionReason]empty) + + if isCiliumNetworkPolicyAbsent { + reasons[noUnmanagedCiliumNetworkPolicyExistence] = struct{}{} } - return false, nil + if isNetworkPolicyAbsent { + reasons[noNetworkPolicyExistence] = struct{}{} + } + + if isNamespaceNameExcluded { + reasons[namespaceName] = struct{}{} + } + + if isNamespaceLabelExcluded { + reasons[namespaceLabels] = struct{}{} + } + + re.namespaceExclusionCache[re.request.Namespace] = reasons + + return nil } -// checkExcludedNamespaceNames checks the Service object's namespace name against the excluded names list configured and returns true if matched. -func (re *ReconcilerExtended) checkExcludedNamespaceNames() bool { +// isNamespaceNameExcluded checks the Service object's namespace name against the excluded names list configured and returns true if matched. +func (re *ReconcilerExtended) isNamespaceNameExcluded() bool { cfg := config.GetConfig() if slices.Contains(cfg.Controller.Exclude.NamespaceSelector.MatchNames, re.service.Namespace) { @@ -183,8 +254,8 @@ func (re *ReconcilerExtended) checkExcludedNamespaceNames() bool { return false } -// checkExcludedNamespaceLabels checks the Service object's namespace labels map against each excluded labels map configured and returns true if matched. -func (re *ReconcilerExtended) checkExcludedNamespaceLabels(ctx context.Context) (bool, error) { +// isNamespaceLabelExcluded checks the Service object's namespace labels map against each excluded labels map configured and returns true if matched. +func (re *ReconcilerExtended) isNamespaceLabelExcluded(ctx context.Context) (bool, error) { namespace := &corev1.Namespace{} cfg := config.GetConfig() @@ -204,16 +275,32 @@ func (re *ReconcilerExtended) checkExcludedNamespaceLabels(ctx context.Context) return false, nil } -// checkNoPolicyRule list all the NetworkPolicy and CiliumNetworkPolicy objects in the Service object's namespace and returns true if both lists are empty. -func (re *ReconcilerExtended) checkNoPolicyRule(ctx context.Context) (bool, error) { - ciliumNetworkPolicyList := &ciliumv2.CiliumNetworkPolicyList{} +// isNetworkPolicyAbsent list all the NetworkPolicy objects in the Service object's namespace and returns true if the list is empty. +func (re *ReconcilerExtended) isNetworkPolicyAbsent(ctx context.Context) (bool, error) { networkPolicyList := &networkingv1.NetworkPolicyList{} + if err := re.Client.List(ctx, networkPolicyList, client.InNamespace(re.service.Namespace)); err != nil { + return false, fmt.Errorf(consts.NetworkPolicyListError, err) + } + + if len(networkPolicyList.Items) == 0 { + re.logger.Info(consts.NamespaceExcludedNoNetworkPolicyMatchInfo) + + return true, nil + } + + return false, nil +} + +// isCiliumNetworkPolicyAbsent list all the CiliumNetworkPolicy objects in the Service object's namespace and returns true if the list is empty. +func (re *ReconcilerExtended) isCiliumNetworkPolicyAbsent(ctx context.Context) (bool, error) { + ciliumNetworkPolicyList := &ciliumv2.CiliumNetworkPolicyList{} + // Construct a label selector to list CiliumNetworkPolicy objects not containing the controller labels // This is necessary as the rule should check the presence of unmanaged policies. - CNPlabelSelector := labels.NewSelector() + labelSelector := labels.NewSelector() - for labelKey, labelValue := range desiredCNPadditionalLabelsMap { + for labelKey, labelValue := range controllerLabelsMap { vals := make([]string, 1) vals[0] = labelValue requirement, err := labels.NewRequirement(labelKey, selection.NotEquals, vals) @@ -222,22 +309,85 @@ func (re *ReconcilerExtended) checkNoPolicyRule(ctx context.Context) (bool, erro return false, err } - CNPlabelSelector = CNPlabelSelector.Add(*requirement) + labelSelector = labelSelector.Add(*requirement) } - if err := re.Client.List(ctx, ciliumNetworkPolicyList, client.InNamespace(re.service.Namespace), &client.ListOptions{LabelSelector: CNPlabelSelector}); err != nil { + if err := re.Client.List(ctx, ciliumNetworkPolicyList, client.InNamespace(re.service.Namespace), &client.ListOptions{LabelSelector: labelSelector}); err != nil { return false, fmt.Errorf(consts.CiliumNetworkPolicyListError, err) } - if err := re.Client.List(ctx, networkPolicyList, client.InNamespace(re.service.Namespace)); err != nil { - return false, fmt.Errorf(consts.NetworkPolicyListError, err) - } - - if len(ciliumNetworkPolicyList.Items) == 0 && len(networkPolicyList.Items) == 0 { - re.logger.Info(consts.NamespaceExcludedNoPolicyMatchInfo) + if len(ciliumNetworkPolicyList.Items) == 0 { + re.logger.Info(consts.NamespaceExcludedNoCiliumNetworkPolicyMatchInfo) return true, nil } return false, nil } + +// getOwnerReconcileRequest checks if a CiliumNetworkPolicy (cnp) has an owner that is a 'Service' object +// in the default Kubernetes API group ('v1'). If such an owner exists, the function constructs and returns +// a reconcile request for it. +// +// The function iterates over the owner references of the cnp. For each owner reference: +// - It first checks if the reference is marked as a controller. If not, it skips to the next owner reference. +// - Next, it attempts to parse the Group and Version from the OwnerReference's APIVersion. If parsing fails, +// it logs the error and continues with the next owner reference. +// - Then, it compares the parsed Group, Version, and Kind of the OwnerReference with the Service Group +// (” for core group), Version ('v1'), and Kind ('Service'). If they match, it creates a reconcile Request +// using the Name from the OwnerReference and the Namespace from the cnp. +// +// Note: +// The function assumes that the cnp's owner, if it's a Service, is part of the core Kubernetes API group ('v1'). +func getOwnerReconcileRequest(ctx context.Context, cnp *ciliumv2.CiliumNetworkPolicy) *ctrl.Request { + if cnp == nil { + return nil + } + + logger := log.FromContext(ctx).WithName("ciliumnetworkpolicy owner reference handler") + + for _, ref := range cnp.GetOwnerReferences() { + if ref.Controller == nil || !*ref.Controller { + continue + } + + // Parse the Group/Version out of the OwnerReference + refGV, err := schema.ParseGroupVersion(ref.APIVersion) + if err != nil { + logger.Error(err, "Could not parse OwnerReference APIVersion", + "apiVersion", ref.APIVersion) + + continue + } + + // Compare the OwnerReference Group/Version/Kind against the Service Group/Version/Kind. + // If the two match, create a Request for the objected referred to by + // the OwnerReference. Use the Name from the OwnerReference and the Namespace from the + // object in the event. + if refGV.Group == "" && refGV.Version == "v1" && ref.Kind == "Service" { + request := reconcile.Request{NamespacedName: types.NamespacedName{ + Namespace: cnp.GetNamespace(), + Name: ref.Name, + }} + + return &request + } + } + + return nil +} + +// deduplicateRequests removes duplicate ctrl.Request objects from a slice. +func deduplicateRequests(requests []ctrl.Request) []ctrl.Request { + seen := make(map[types.NamespacedName]bool) + result := []ctrl.Request{} + + for _, req := range requests { + if _, exists := seen[req.NamespacedName]; !exists { + result = append(result, req) + seen[req.NamespacedName] = true + } + } + + return result +} diff --git a/internal/controller/service/setup.go b/internal/controller/service/setup.go index 69aa9d0..baed127 100644 --- a/internal/controller/service/setup.go +++ b/internal/controller/service/setup.go @@ -17,17 +17,454 @@ limitations under the License. package controller import ( + "context" + "fmt" + "sync" + ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" + "github.com/snapp-incubator/svc-lb-to-cilium-netpolicy/internal/config" + "github.com/snapp-incubator/svc-lb-to-cilium-netpolicy/internal/consts" + "github.com/snapp-incubator/svc-lb-to-cilium-netpolicy/pkg/utils" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" ) +func newCustomEventHandler(handler customEventHandlerFunc) handler.EventHandler { + return &customEventHandler{ + handler: handler, + } +} + +func (h *customEventHandler) Create(ctx context.Context, evt event.CreateEvent, q workqueue.RateLimitingInterface) { + reqs := h.handler(ctx, evt.Object, nil, createEvent) + + for _, req := range reqs { + q.Add(req) + } +} + +func (h *customEventHandler) Update(ctx context.Context, evt event.UpdateEvent, q workqueue.RateLimitingInterface) { + reqs := h.handler(ctx, evt.ObjectNew, evt.ObjectOld, updateEvent) + + for _, req := range reqs { + q.Add(req) + } +} + +func (h *customEventHandler) Delete(ctx context.Context, evt event.DeleteEvent, q workqueue.RateLimitingInterface) { + reqs := h.handler(ctx, nil, evt.Object, deleteEvent) + + for _, req := range reqs { + q.Add(req) + } +} + +func (h *customEventHandler) Generic(_ context.Context, _ event.GenericEvent, _ workqueue.RateLimitingInterface) { + // no implementation yet +} + +//nolint:varnamelen +func (re *ReconcilerExtended) namespacEventHandler(ctx context.Context, objNew client.Object, objOld client.Object, et eventType) []ctrl.Request { + namespaceNew, ok := objNew.(*corev1.Namespace) + if objNew != nil && !ok { + return []ctrl.Request{} + } + + namespaceOld, ok := objOld.(*corev1.Namespace) + if objOld != nil && !ok { + return []ctrl.Request{} + } + + var namespace string + + switch et { + case createEvent, updateEvent: + namespace = namespaceNew.GetName() + case deleteEvent: + namespace = namespaceOld.GetName() + } + + if namespace == "" { + return []ctrl.Request{} + } + + logger := log.FromContext(ctx).WithName("namespace event handler").WithValues("namespace", namespace, "event", et) + + re.cacheLock.Lock() + defer re.cacheLock.Unlock() + + // Check if the Namespace is present in the exclusion cache. + // If the entry for this Namespace is nil, return empty array. + // This implies the processing will be handled in the reconcile loop. + cacheEntry := re.namespaceExclusionCache[namespace] + if cacheEntry == nil { + logger.Info(consts.NamespaceExclusionCacheEntryNotSetInfo) + + return []ctrl.Request{} + } + + // Determine the exclusion status of the specified Namespace from the cache. + // This oldStatus is used for comparison purposes later in the code to detect any changes + // in the exclusion status of the Namespace. + oldStatus := determineExclusionStatus(cacheEntry) + + switch et { + case updateEvent: + var namespaceLabelsExcluded bool + + cfg := config.GetConfig() + + for _, matchLabel := range cfg.Controller.Exclude.NamespaceSelector.MatchLabels { + if utils.IsMapSubset(namespaceNew.GetLabels(), matchLabel.Labels) { + namespaceLabelsExcluded = true + + // Add exclusion reason for the namespace indicating the source of the exclusion + cacheEntry[namespaceLabels] = empty{} + + logger.Info(fmt.Sprintf(consts.NamespaceExcludedLabelsMatchInfo, matchLabel.MatcherName) + + consts.NamespaceExclusionReasonAddedInfo) + + break + } + } + + if !namespaceLabelsExcluded { + // Remove exclusion reason if set + delete(cacheEntry, namespaceLabels) + + logger.Info("The exclusion reason 'namespaceLabels' is removed from the namespace cache entry") + } + + case createEvent: + return []ctrl.Request{} + + case deleteEvent: + return []ctrl.Request{} + } + + // Determine the exclusion status of the specified Namespace from the cache. + // This newStatus is used for comparison purposes to detect any changes + // in the exclusion status of the Namespace. + newStatus := determineExclusionStatus(cacheEntry) + + // Check if there has been a change in the exclusion status of the namespace. + // In case of a status change, trigger the reconciliation process for Service objects of type LoadBalancer within the specified namespace. + // This is achieved by returning these objects to be added to the queue. + // The reconciliation ensures that the cluster state is aligned with the updated exclusion status. + if newStatus != oldStatus { + serviceList := &corev1.ServiceList{} + + if err := re.Client.List(ctx, serviceList, client.InNamespace(namespace), client.MatchingFields{"spec.type": "LoadBalancer"}); err != nil { + logger.Error(err, "failed to list Service of type 'LoadBalancer' objects to queue for reconciling") + + return []ctrl.Request{} + } + + if len(serviceList.Items) == 0 { + return []ctrl.Request{} + } + + reqs := make([]ctrl.Request, len(serviceList.Items)) + + for i, service := range serviceList.Items { + reqs[i] = ctrl.Request{NamespacedName: types.NamespacedName{Namespace: service.Namespace, Name: service.Name}} + } + + return reqs + } + + return []ctrl.Request{} +} + +//nolint:varnamelen +func (re *ReconcilerExtended) networkPolicyEventHandler(ctx context.Context, objNew client.Object, objOld client.Object, et eventType) []ctrl.Request { + networkPolicyNew, ok := objNew.(*networkingv1.NetworkPolicy) + if objNew != nil && !ok { + return []ctrl.Request{} + } + + networkPolicyOld, ok := objOld.(*networkingv1.NetworkPolicy) + if objOld != nil && !ok { + return []ctrl.Request{} + } + + var namespace string + + switch et { + case createEvent, updateEvent: + namespace = networkPolicyNew.GetNamespace() + case deleteEvent: + namespace = networkPolicyOld.GetNamespace() + } + + if namespace == "" { + return []ctrl.Request{} + } + + logger := log.FromContext(ctx).WithName("networkpolicy event handler").WithValues("namespace", namespace, "event", et) + + re.cacheLock.Lock() + defer re.cacheLock.Unlock() + + // Check if the Namespace is present in the exclusion cache. + // If the entry for this Namespace is nil, return empty array. + // This implies the processing will be handled in the reconcile loop. + cacheEntry := re.namespaceExclusionCache[namespace] + if cacheEntry == nil { + logger.Info(consts.NamespaceExclusionCacheEntryNotSetInfo) + + return []ctrl.Request{} + } + + // Determine the exclusion status of the specified Namespace from the cache. + // This oldStatus is used for comparison purposes later in the code to detect any changes + // in the exclusion status of the Namespace. + oldStatus := determineExclusionStatus(cacheEntry) + + switch et { + case createEvent: + // Remove exclusion reason if set + delete(cacheEntry, noNetworkPolicyExistence) + + logger.Info("The exclusion reason 'noNetworkPolicyExistence' is removed from the namespace cache entry") + + case deleteEvent: + networkPolicyList := &networkingv1.NetworkPolicyList{} + + // Attempt to list NetworkPolicy objects within the specified namespace. + // In case of an error, the error state is signaled by setting 'cacheEntry' to nil. This action effectively resets the state + // of the cache for the namespace in question, ensuring that any stale or incorrect data is cleared. + if err := re.Client.List(ctx, networkPolicyList, client.InNamespace(namespace)); err != nil { + logger.Error(err, "failed to list NetworkPolicy objects") + + // The 'nolint:ineffassign,wastedassign' directive is used to bypass lint warnings about the ineffectiveness of the assignment, + // acknowledging that this operation is intentional for error handling and state reset purposes. + //nolint:ineffassign,wastedassign + cacheEntry = nil + + return []ctrl.Request{} + } + + if len(networkPolicyList.Items) > 0 { + // Remove exclusion reason if set + delete(cacheEntry, noNetworkPolicyExistence) + + logger.Info("The exclusion reason 'noNetworkPolicyExistence' is removed from the namespace cache entry") + } else { + // Add exclusion reason for the namespace indicating the source of the exclusion + cacheEntry[noNetworkPolicyExistence] = empty{} + } + + case updateEvent: + return []ctrl.Request{} + } + + // Determine the exclusion status of the specified Namespace from the cache. + // This newStatus is used for comparison purposes to detect any changes + // in the exclusion status of the Namespace. + newStatus := determineExclusionStatus(cacheEntry) + + // Check if there has been a change in the exclusion status of the namespace. + // In case of a status change, trigger the reconciliation process for Service objects of type LoadBalancer within the specified namespace. + // This is achieved by returning these objects to be added to the queue. + // The reconciliation ensures that the cluster state is aligned with the updated exclusion status. + if newStatus != oldStatus { + serviceList := &corev1.ServiceList{} + + if err := re.Client.List(ctx, serviceList, client.InNamespace(namespace), client.MatchingFields{"spec.type": "LoadBalancer"}); err != nil { + logger.Error(err, "failed to list Service of type 'LoadBalancer' objects to queue for reconciling") + + return []ctrl.Request{} + } + + if len(serviceList.Items) == 0 { + return []ctrl.Request{} + } + + reqs := make([]ctrl.Request, len(serviceList.Items)) + + for i, service := range serviceList.Items { + reqs[i] = ctrl.Request{NamespacedName: types.NamespacedName{Namespace: service.Namespace, Name: service.Name}} + } + + return reqs + } + + return []ctrl.Request{} +} + +//nolint:varnamelen +func (re *ReconcilerExtended) ciliumNetworkPolicyEventHandler(ctx context.Context, objNew client.Object, objOld client.Object, et eventType) []ctrl.Request { + ciliumNetworkPolicyNew, ok := objNew.(*ciliumv2.CiliumNetworkPolicy) + if objNew != nil && !ok { + return []ctrl.Request{} + } + + ciliumNetworkPolicyOld, ok := objOld.(*ciliumv2.CiliumNetworkPolicy) + if objOld != nil && !ok { + return []ctrl.Request{} + } + + var namespace string + + switch et { + case createEvent, updateEvent: + namespace = ciliumNetworkPolicyNew.GetNamespace() + case deleteEvent: + namespace = ciliumNetworkPolicyOld.GetNamespace() + } + + if namespace == "" { + return []ctrl.Request{} + } + + logger := log.FromContext(ctx).WithName("ciliumnetworkpolicy event handler").WithValues("namespace", namespace, "event", et) + + reqs := make([]ctrl.Request, 0) + + defer func() { + reqs = deduplicateRequests(reqs) + }() + + // Check for potential owner object of the ciliumNetworkPolicy. + if req := getOwnerReconcileRequest(ctx, ciliumNetworkPolicyNew); req != nil { + reqs = append(reqs, *req) + } + + // Similarly, check for owner object for the old state of the ciliumNetworkPolicy. + // This ensures that any changes in ownership between the old and new states are captured. + if req := getOwnerReconcileRequest(ctx, ciliumNetworkPolicyOld); req != nil { + reqs = append(reqs, *req) + } + + re.cacheLock.Lock() + defer re.cacheLock.Unlock() + + // Check if the Namespace is present in the exclusion cache. + // If the entry for this Namespace is nil, return empty array. + // This implies the processing will be handled in the reconcile loop. + cacheEntry := re.namespaceExclusionCache[namespace] + if cacheEntry == nil { + logger.Info(consts.NamespaceExclusionCacheEntryNotSetInfo) + + return reqs + } + + // Determine the exclusion status of the specified Namespace from the cache. + // This oldStatus is used for comparison purposes later in the code to detect any changes + // in the exclusion status of the Namespace. + oldStatus := determineExclusionStatus(cacheEntry) + + switch et { + case createEvent: + if utils.IsMapSubset(ciliumNetworkPolicyNew.GetLabels(), controllerLabelsMap) { + return reqs + } + + // Remove exclusion reason if set + delete(cacheEntry, noUnmanagedCiliumNetworkPolicyExistence) + + logger.Info("The exclusion reason 'noCiliumNetworkPolicyExistence' is removed from the namespace cache entry") + + case updateEvent, deleteEvent: + ciliumNetworkPolicyList := &ciliumv2.CiliumNetworkPolicyList{} + + // Attempt to construct a label selector to list CiliumNetworkPolicy objects not containing the controller labels + // This is necessary as the rule should check the presence of unmanaged policies. + // In case of an error, the error state is signaled by setting 'cacheEntry' to nil. This action effectively resets the state + // of the cache for the namespace in question, ensuring that any stale or incorrect data is cleared. + labelSelector := labels.NewSelector() + + for labelKey, labelValue := range controllerLabelsMap { + vals := make([]string, 1) + vals[0] = labelValue + requirement, err := labels.NewRequirement(labelKey, selection.NotEquals, vals) + + if err != nil { + // The 'nolint:ineffassign,wastedassign' directive is used to bypass lint warnings about the ineffectiveness of the assignment, + // acknowledging that this operation is intentional for error handling and state reset purposes. + //nolint:ineffassign,wastedassign + cacheEntry = nil + + return reqs + } + + labelSelector = labelSelector.Add(*requirement) + } + + // Attempt to list CiliumNetworkPolicy objects within the specified namespace. + // In case of an error, the error state is signaled by setting 'cacheEntry' to nil. This action effectively resets the state + // of the cache for the namespace in question, ensuring that any stale or incorrect data is cleared. + if err := re.Client.List(ctx, ciliumNetworkPolicyList, client.InNamespace(namespace), &client.ListOptions{LabelSelector: labelSelector}); err != nil { + logger.Error(err, "failed to list CiliumNetworkPolicy objects") + + // The 'nolint:ineffassign,wastedassign' directive is used to bypass lint warnings about the ineffectiveness of the assignment, + // acknowledging that this operation is intentional for error handling and state reset purposes. + //nolint:ineffassign,wastedassign + cacheEntry = nil + + return reqs + } + + if len(ciliumNetworkPolicyList.Items) > 0 { + // Remove exclusion reason if set + delete(cacheEntry, noUnmanagedCiliumNetworkPolicyExistence) + + logger.Info("The exclusion reason 'noCiliumNetworkPolicyExistence' is removed from the namespace cache entry") + } else { + // Add exclusion reason for the namespace indicating the source of the exclusion + cacheEntry[noUnmanagedCiliumNetworkPolicyExistence] = empty{} + } + } + + // Determine the exclusion status of the specified Namespace from the cache. + // This newStatus is used for comparison purposes to detect any changes + // in the exclusion status of the Namespace. + newStatus := determineExclusionStatus(cacheEntry) + + // Check if there has been a change in the exclusion status of the namespace. + // In case of a status change, trigger the reconciliation process for Service objects of type LoadBalancer within the specified namespace. + // This is achieved by returning these objects to be added to the queue. + // The reconciliation ensures that the cluster state is aligned with the updated exclusion status. + if newStatus != oldStatus { + serviceList := &corev1.ServiceList{} + + if err := re.Client.List(ctx, serviceList, client.InNamespace(namespace), client.MatchingFields{"spec.type": "LoadBalancer"}); err != nil { + logger.Error(err, "failed to list Service of type 'LoadBalancer' objects to queue for reconciling") + + return reqs + } + + if len(serviceList.Items) == 0 { + return reqs + } + + for _, service := range serviceList.Items { + req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: namespace, Name: service.Name}} + reqs = append(reqs, req) + } + } + + return reqs +} + // NewReconcilerExtended instantiate a new ReconcilerExtended struct and returns it. func NewReconcilerExtended(mgr manager.Manager) *ReconcilerExtended { return &ReconcilerExtended{ - Client: mgr.GetClient(), - scheme: mgr.GetScheme(), + cacheLock: &sync.Mutex{}, + Client: mgr.GetClient(), + namespaceExclusionCache: make(map[string]map[namespaceExclusionReason]empty), + scheme: mgr.GetScheme(), } } @@ -35,6 +472,10 @@ func NewReconcilerExtended(mgr manager.Manager) *ReconcilerExtended { func (re *ReconcilerExtended) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&corev1.Service{}). - Owns(&ciliumv2.CiliumNetworkPolicy{}). + // This watch EventHandler responds to create / delete / update events by *reconciling the owner object* ( equivalent of calling Owns(&ciliumv2.CiliumNetworkPolicy{}) ) + // in addition to handling cache update flow. + Watches(&ciliumv2.CiliumNetworkPolicy{}, newCustomEventHandler(re.ciliumNetworkPolicyEventHandler)). + Watches(&networkingv1.NetworkPolicy{}, newCustomEventHandler(re.networkPolicyEventHandler)). + Watches(&corev1.Namespace{}, newCustomEventHandler(re.namespacEventHandler)). Complete(re) } diff --git a/internal/controller/service/types.go b/internal/controller/service/types.go index d721a66..5d1930b 100644 --- a/internal/controller/service/types.go +++ b/internal/controller/service/types.go @@ -18,6 +18,7 @@ package controller import ( "context" + "sync" ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" "github.com/go-logr/logr" @@ -28,15 +29,53 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +type eventType string + +const ( + createEvent eventType = "CREATE" + updateEvent eventType = "UPDATE" + deleteEvent eventType = "DELETE" + genericEvent eventType = "GENERIC" +) + +type namespaceExclusionReason string + +const ( + namespaceLabels namespaceExclusionReason = "NamespaceLabels" + namespaceName namespaceExclusionReason = "NamespaceName" + noNetworkPolicyExistence namespaceExclusionReason = "NoNetworkPolicyExistence" + noUnmanagedCiliumNetworkPolicyExistence namespaceExclusionReason = "NoUnmanagedCiliumNetworkPolicyExistence" +) + +// empty is a struct type defined without any fields. +// In Go, it is frequently utilized to emulate set-like behavior in maps, +// especially when only keys are of interest and values are irrelevant. +// The zero-size characteristic of this struct ensures that it consumes minimal memory, +// making it an efficient choice for such implementations. +type empty struct{} + // logicFunc is a function definition representing separated reconciliation logic. type logicFunc func(context.Context) (*ctrl.Result, error) +// customEventHandlerFunc is a function definition representing different handlers. +type customEventHandlerFunc func(context.Context, client.Object, client.Object, eventType) []ctrl.Request + // ReconcilerExtended extends the Reconciler struct with more fields to share between functions. type ReconcilerExtended struct { + cacheLock *sync.Mutex client.Client - cnp *ciliumv2.CiliumNetworkPolicy - logger logr.Logger - request *reconcile.Request - scheme *runtime.Scheme - service *corev1.Service + cnp *ciliumv2.CiliumNetworkPolicy + logger logr.Logger + namespaceExclusionCache map[string]map[namespaceExclusionReason]empty + request *reconcile.Request + scheme *runtime.Scheme + service *corev1.Service +} + +// customEventHandler is a struct that implements the handler.EventHandler interface. +// It is specifically implemented to manage certain types of events using the 'customHandlerFunc' function type. +// This is essential because it deals with 'event types' crucial in the 'namespaceExclusionCache' handling logic and, +// such event types are abstracted away by standard handlers. +type customEventHandler struct { + handler customEventHandlerFunc } diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index ce6ebc1..0bb1359 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -17,6 +17,7 @@ limitations under the License. package controller import ( + "context" "path/filepath" "testing" "time" @@ -26,6 +27,7 @@ import ( ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" @@ -75,6 +77,9 @@ var _ = BeforeSuite(func() { err = corev1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = networkingv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = ciliumv2.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) @@ -90,6 +95,20 @@ var _ = BeforeSuite(func() { Expect(err).ToNot(HaveOccurred()) Expect(k8sManager).NotTo(BeNil()) + err = k8sManager.GetFieldIndexer().IndexField( + context.Background(), + &corev1.Service{}, + "spec.type", + func(object client.Object) []string { + service, ok := object.(*corev1.Service) + if !ok { + return []string{} + } + + return []string{string(service.Spec.Type)} + }) + Expect(err).ToNot(HaveOccurred()) + reconcilerExtended := controller.NewReconcilerExtended(k8sManager) err = reconcilerExtended.SetupWithManager(k8sManager) diff --git a/pkg/utils/meta.go b/pkg/utils/meta.go new file mode 100644 index 0000000..6e30476 --- /dev/null +++ b/pkg/utils/meta.go @@ -0,0 +1,34 @@ +/* +Copyright 2023. + +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 utils + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// IsDeleted checks if DeletionTimestamp field is set on the object. +func IsDeleted(obj metav1.Object) bool { + return !obj.GetDeletionTimestamp().IsZero() +} + +func OverWriteAnnotations(objectMeta *metav1.ObjectMeta, annotations map[string]string) { + objectMeta.Annotations = annotations +} + +func OverWriteLabels(objectMeta *metav1.ObjectMeta, labels map[string]string) { + objectMeta.Labels = labels +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index cc0e51a..f30fd69 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -16,15 +16,6 @@ limitations under the License. package utils -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// IsDeleted checks if DeletionTimestamp field is set on the object. -func IsDeleted(obj metav1.Object) bool { - return !obj.GetDeletionTimestamp().IsZero() -} - // IsMapSubset checks if a map is subset of another map. func IsMapSubset[K, V comparable](set, subset map[K]V) bool { if len(subset) > len(set) { @@ -39,11 +30,3 @@ func IsMapSubset[K, V comparable](set, subset map[K]V) bool { return true } - -func OverWriteAnnotations(objectMeta *metav1.ObjectMeta, annotations map[string]string) { - objectMeta.Annotations = annotations -} - -func OverWriteLabels(objectMeta *metav1.ObjectMeta, labels map[string]string) { - objectMeta.Labels = labels -} From ef4eb3dcefcd8b85432e90bb56b1e3ca59408d17 Mon Sep 17 00:00:00 2001 From: ssttehrani Date: Sun, 3 Dec 2023 20:40:51 +0330 Subject: [PATCH 2/2] chore: remove goimports lint rule --- .golangci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 458fa0c..f378bfe 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,7 +13,6 @@ linters: - godot # - godox - gofmt - - goimports - gosec - gosimple - govet @@ -39,6 +38,3 @@ linters: - wastedassign - whitespace - wsl -linters-settings: - goimports: - local-prefixes: github.com/snapp-incubator/svc-lb-to-cilium-netpolicy