mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-28 15:47:33 +09:00
test(sdn): port the securitygroup and serviceexposure suites to Chainsaw
Main added securitygroup.bats (#2922 membership model) and serviceexposure.bats (#3081 ExposureClass/ServiceExposure) after the Chainsaw migration branch was cut. - securitygroup: two Tests — the aggregated-API projection round-trip (backing CNP marker label, empty-value membership endpointSelector, lineage fromApp + membership fromSG peers, 1:1 rule translation, the view hiding storage internals, unmarked-CNP invisibility via an error op, delete propagation) and the controller membership-label stamp/clear lifecycle. Only the attachments annotation stays a jq script (it is a JSON string). Chainsaw auto-cleanup covers every fixture even on mid-test failure — the bats had no teardown hook and leaked its fixtures. - serviceexposure: the externalIPs backend resolve/Ready/assignedIPs path with the no-MetalLB-pool negative as an error op (ordered after resolvedBackend so it cannot false-pass), and the finalizer-removal test collapsed onto the delete op itself, which waits for full disappearance. - selection: kubernetes-application now also maps to the two OIDC render-side suites (they exercise the same app chart — a gap main's bats selection had too); the new dirs enter all_apps discovery automatically. Selector unit tests extended and green (13/13 under cozytest). - README: suites list updated. Assisted-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
This commit is contained in:
1 parent
cac07db8fc
commit
dd2f89d208
10 files changed
+433
-3
No files matched your search
@@ -18,7 +18,7 @@ hack/e2e-chainsaw/
|
||||
└── ...
|
||||
```
|
||||
|
||||
Suites: `postgres`, `bucket`, `mariadb`, `mongodb`, `redis`, `qdrant`, `clickhouse`, `kafka`, `etcd`, `openbao`, `harbor`, `foundationdb`, `external-dns`, `kuberture`, `vminstance`, `gateway`, `kubernetes-latest`, `kubernetes-previous`.
|
||||
Suites: `postgres`, `bucket`, `mariadb`, `mongodb`, `redis`, `qdrant`, `clickhouse`, `kafka`, `etcd`, `openbao`, `harbor`, `foundationdb`, `external-dns`, `kuberture`, `vminstance`, `gateway`, `kubernetes-latest`, `kubernetes-previous`, `kubernetes-oidc-system`, `kubernetes-oidc-customconfig`, `securitygroup`, `serviceexposure`.
|
||||
|
||||
## What Chainsaw buys over the BATS suite
|
||||
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
# Chainsaw port of hack/e2e-apps/securitygroup.bats.
|
||||
#
|
||||
# Exercises the SecurityGroup membership model against a live cluster. Two
|
||||
# things unit tests cannot prove: that the in-tree CiliumNetworkPolicy mirror
|
||||
# (membership endpointSelector, app/SG peer selectors, attachments annotation)
|
||||
# round-trips against the real cilium.io/v2 CRD, and that the
|
||||
# securitygroup-controller actually stamps the membership label onto an
|
||||
# attached application's pods.
|
||||
#
|
||||
# Everything both Tests create is chainsaw-applied, so auto-cleanup reclaims
|
||||
# it even on mid-test failure — strictly better than the bats, which had no
|
||||
# teardown hook and leaked its fixtures on failure. The one operator-created
|
||||
# object (the backing CNP) is the same storage object as the tracked
|
||||
# SecurityGroup, so no finally block is needed anywhere.
|
||||
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
name: securitygroup-projection
|
||||
spec:
|
||||
catch:
|
||||
- events: {}
|
||||
- describe:
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
name: sg-e2e
|
||||
steps:
|
||||
- name: create-securitygroup
|
||||
try:
|
||||
- apply:
|
||||
file: securitygroup.yaml
|
||||
|
||||
- name: assert-backing-cnp
|
||||
try:
|
||||
# The backing CNP must materialise with the marker label, the group's own
|
||||
# membership label as endpointSelector (the EMPTY value must round-trip
|
||||
# against the real cilium.io/v2 CRD), fromApp projected to the three
|
||||
# lineage labels, fromSG to the peer group's membership label (index
|
||||
# order fromApp-then-fromSG is code-guaranteed by projectIngress), and
|
||||
# the rule list translated 1:1 (port is a string in the CRD).
|
||||
- assert:
|
||||
timeout: 1m
|
||||
resource:
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: sg-e2e
|
||||
labels:
|
||||
sdn.cozystack.io/securitygroup: "true"
|
||||
spec:
|
||||
endpointSelector:
|
||||
matchLabels:
|
||||
securitygroup.sdn.cozystack.io/sg-e2e: ""
|
||||
(ingress[0].fromEndpoints[0].matchLabels):
|
||||
apps.cozystack.io/application.group: apps.cozystack.io
|
||||
apps.cozystack.io/application.kind: Kubernetes
|
||||
apps.cozystack.io/application.name: web
|
||||
(ingress[0].fromEndpoints[1].matchLabels):
|
||||
securitygroup.sdn.cozystack.io/sg-frontend-e2e: ""
|
||||
(ingress[0].toPorts[0].ports[0].port): "5432"
|
||||
(ingress[0].toPorts[0].ports[0].protocol): TCP
|
||||
# The attachments annotation is a JSON *string* on the backing policy —
|
||||
# parsed with jq, exactly like the bats (no reliance on JMESPath JSON
|
||||
# parsing in chainsaw v0.2.15).
|
||||
- script:
|
||||
timeout: 1m
|
||||
content: |
|
||||
set -eu
|
||||
att=$(kubectl -n "$NAMESPACE" get ciliumnetworkpolicy sg-e2e \
|
||||
-o jsonpath='{.metadata.annotations.sdn\.cozystack\.io/attachments}')
|
||||
echo "$att" | jq -e '.[0].kind == "Postgres" and .[0].name == "db"' >/dev/null
|
||||
check:
|
||||
($error == null): true
|
||||
|
||||
- name: assert-securitygroup-view
|
||||
try:
|
||||
# The SecurityGroup view reconstructs attachments and peers, and HIDES the
|
||||
# internal marker label + storage annotation.
|
||||
- assert:
|
||||
timeout: 1m
|
||||
resource:
|
||||
apiVersion: sdn.cozystack.io/v1alpha1
|
||||
kind: SecurityGroup
|
||||
metadata:
|
||||
name: sg-e2e
|
||||
spec:
|
||||
(attachments[0].kind): Postgres
|
||||
(attachments[0].name): db
|
||||
(ingress[0].fromApp[0].name): web
|
||||
(ingress[0].fromSG[0]): sg-frontend-e2e
|
||||
(metadata.labels."sdn.cozystack.io/securitygroup" == null): true
|
||||
(metadata.annotations."sdn.cozystack.io/attachments" == null): true
|
||||
|
||||
- name: unmarked-cnp-invisible
|
||||
try:
|
||||
- apply:
|
||||
file: plain-cnp.yaml
|
||||
# The aggregated API filters by the marker label synchronously, so
|
||||
# non-existence of the view is deterministic.
|
||||
- error:
|
||||
timeout: 30s
|
||||
resource:
|
||||
apiVersion: sdn.cozystack.io/v1alpha1
|
||||
kind: SecurityGroup
|
||||
metadata:
|
||||
name: plain-cnp-e2e
|
||||
|
||||
- name: delete-propagates
|
||||
try:
|
||||
# Deleting the SecurityGroup removes its backing policy (the delete op
|
||||
# waits for full disappearance of the view); the unmarked CNP survives.
|
||||
- delete:
|
||||
timeout: 1m
|
||||
ref:
|
||||
apiVersion: sdn.cozystack.io/v1alpha1
|
||||
kind: SecurityGroup
|
||||
name: sg-e2e
|
||||
- error:
|
||||
timeout: 1m
|
||||
resource:
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: sg-e2e
|
||||
- assert:
|
||||
timeout: 30s
|
||||
resource:
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: plain-cnp-e2e
|
||||
---
|
||||
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
name: securitygroup-membership
|
||||
spec:
|
||||
catch:
|
||||
- events: {}
|
||||
- describe:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
name: sg-member-pod-e2e
|
||||
- podLogs:
|
||||
namespace: cozy-securitygroup-controller
|
||||
selector: app=securitygroup-controller
|
||||
tail: 100
|
||||
steps:
|
||||
- name: create-member-pod-and-securitygroup
|
||||
try:
|
||||
- apply:
|
||||
file: member-pod.yaml
|
||||
- apply:
|
||||
file: securitygroup-member.yaml
|
||||
|
||||
- name: membership-label-stamped
|
||||
try:
|
||||
# The controller must stamp the membership label onto the member pod;
|
||||
# the value is the empty string (reconciler sets pod.Labels[key] = "").
|
||||
- assert:
|
||||
timeout: 2m
|
||||
resource:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: sg-member-pod-e2e
|
||||
labels:
|
||||
securitygroup.sdn.cozystack.io/sg-member-e2e: ""
|
||||
|
||||
- name: delete-clears-membership
|
||||
try:
|
||||
# Deleting the SecurityGroup while a pod is still a member must strip the
|
||||
# membership label off that pod (the controller's finalizer runs before
|
||||
# the backing policy is garbage-collected), leaving no orphaned label.
|
||||
# This is the delete-with-members path, distinct from detach.
|
||||
- delete:
|
||||
timeout: 2m
|
||||
ref:
|
||||
apiVersion: sdn.cozystack.io/v1alpha1
|
||||
kind: SecurityGroup
|
||||
name: sg-member-e2e
|
||||
- error:
|
||||
timeout: 1m
|
||||
resource:
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: sg-member-e2e
|
||||
- assert:
|
||||
timeout: 1m
|
||||
resource:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: sg-member-pod-e2e
|
||||
(metadata.labels."securitygroup.sdn.cozystack.io/sg-member-e2e" == null): true
|
||||
@@ -0,0 +1,27 @@
|
||||
# A pod carrying the lineage labels of a managed application. The lineage
|
||||
# webhook skips it (managed-by is already set), so these labels are exactly
|
||||
# what the securitygroup-controller resolves the attachment to. The full
|
||||
# restricted-profile securityContext is required by tenant-test PSA.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: sg-member-pod-e2e
|
||||
labels:
|
||||
internal.cozystack.io/managed-by-cozystack: "true"
|
||||
apps.cozystack.io/application.group: apps.cozystack.io
|
||||
apps.cozystack.io/application.kind: Postgres
|
||||
apps.cozystack.io/application.name: sg-member-app
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: pause
|
||||
image: registry.k8s.io/pause:3.9
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: plain-cnp-e2e
|
||||
spec:
|
||||
endpointSelector:
|
||||
matchLabels:
|
||||
app: untouched
|
||||
# A rule is mandatory: the CiliumNetworkPolicy CRD requires at least one of
|
||||
# ingress/ingressDeny/egress/egressDeny (spec anyOf), so a selector-only
|
||||
# policy is rejected at apply time. This keeps the fixture a valid, applyable
|
||||
# CNP while leaving it unmarked (no securitygroup label) — so it must stay
|
||||
# invisible to the SecurityGroup API.
|
||||
ingress:
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
app: peer
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: sdn.cozystack.io/v1alpha1
|
||||
kind: SecurityGroup
|
||||
metadata:
|
||||
name: sg-member-e2e
|
||||
spec:
|
||||
attachments:
|
||||
- kind: Postgres
|
||||
name: sg-member-app
|
||||
@@ -0,0 +1,22 @@
|
||||
# SecurityGroup created through the aggregated API (storage-backed by the
|
||||
# CiliumNetworkPolicy itself). It attaches to a managed application; the
|
||||
# backing endpointSelector is the group's own membership label, and peers are
|
||||
# expressed as fromApp/fromSG.
|
||||
apiVersion: sdn.cozystack.io/v1alpha1
|
||||
kind: SecurityGroup
|
||||
metadata:
|
||||
name: sg-e2e
|
||||
spec:
|
||||
attachments:
|
||||
- kind: Postgres
|
||||
name: db
|
||||
ingress:
|
||||
- fromApp:
|
||||
- kind: Kubernetes
|
||||
name: web
|
||||
fromSG:
|
||||
- sg-frontend-e2e
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "5432"
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,120 @@
|
||||
# Chainsaw port of hack/e2e-apps/serviceexposure.bats.
|
||||
#
|
||||
# Smoke test for the ServiceExposure controller (network.cozystack.io): the
|
||||
# CRDs are installed, the controller resolves a ServiceExposure to its
|
||||
# ExposureClass, renders NO foreign backend CRs for the externalIPs backend,
|
||||
# and reports status. See serviceexposure.yaml for why externalIPs.
|
||||
#
|
||||
# Both Tests reuse the same fixture names — safe under the global
|
||||
# execution.parallel: 1 (the first Test's auto-cleanup completes before the
|
||||
# second starts).
|
||||
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
name: serviceexposure-externalips
|
||||
spec:
|
||||
catch:
|
||||
- events: {}
|
||||
- describe:
|
||||
apiVersion: network.cozystack.io/v1alpha1
|
||||
kind: ServiceExposure
|
||||
name: se-e2e
|
||||
- get:
|
||||
apiVersion: network.cozystack.io/v1alpha1
|
||||
kind: ExposureClass
|
||||
name: se-e2e-externalips
|
||||
- podLogs:
|
||||
namespace: cozy-system
|
||||
selector: app=cozystack-controller
|
||||
tail: 100
|
||||
steps:
|
||||
- name: create-fixtures
|
||||
try:
|
||||
- apply:
|
||||
file: serviceexposure.yaml
|
||||
|
||||
- name: resolved-backend
|
||||
try:
|
||||
# The controller stamps status.resolvedBackend once it has reconciled.
|
||||
- assert:
|
||||
timeout: 2m
|
||||
resource:
|
||||
apiVersion: network.cozystack.io/v1alpha1
|
||||
kind: ServiceExposure
|
||||
metadata:
|
||||
name: se-e2e
|
||||
status:
|
||||
resolvedBackend: externalIPs
|
||||
|
||||
- name: no-backend-pool
|
||||
try:
|
||||
# externalIPs backend must not create a MetalLB pool
|
||||
# (backend.PoolName = cozystack-<class>). resolvedBackend above already
|
||||
# proved the reconcile ran, so a short non-existence poll is race-free —
|
||||
# same ordering as the bats.
|
||||
- error:
|
||||
timeout: 30s
|
||||
resource:
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: IPAddressPool
|
||||
metadata:
|
||||
name: cozystack-se-e2e-externalips
|
||||
namespace: cozy-metallb
|
||||
|
||||
- name: ready-and-assigned-ips
|
||||
try:
|
||||
# The pinned externalIPs make the exposure Ready.
|
||||
- assert:
|
||||
timeout: 1m
|
||||
resource:
|
||||
apiVersion: network.cozystack.io/v1alpha1
|
||||
kind: ServiceExposure
|
||||
metadata:
|
||||
name: se-e2e
|
||||
status:
|
||||
(conditions[?type == 'Ready']):
|
||||
- status: "True"
|
||||
(assignedIPs[0]): 203.0.113.10
|
||||
---
|
||||
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
name: serviceexposure-delete
|
||||
spec:
|
||||
catch:
|
||||
- events: {}
|
||||
- describe:
|
||||
apiVersion: network.cozystack.io/v1alpha1
|
||||
kind: ServiceExposure
|
||||
name: se-e2e
|
||||
- podLogs:
|
||||
namespace: cozy-system
|
||||
selector: app=cozystack-controller
|
||||
tail: 100
|
||||
steps:
|
||||
- name: create-fixtures
|
||||
try:
|
||||
- apply:
|
||||
file: serviceexposure.yaml
|
||||
- assert:
|
||||
timeout: 2m
|
||||
resource:
|
||||
apiVersion: network.cozystack.io/v1alpha1
|
||||
kind: ServiceExposure
|
||||
metadata:
|
||||
name: se-e2e
|
||||
status:
|
||||
resolvedBackend: externalIPs
|
||||
|
||||
- name: delete-completes-despite-finalizer
|
||||
try:
|
||||
# Deletion must complete despite the cleanup finalizer (nothing to GC for
|
||||
# the externalIPs backend, so the finalizer is removed promptly). The
|
||||
# chainsaw delete op waits for the object to be fully gone — the delete
|
||||
# IS the test. 3m covers the bats' 2m delete + 1m wait-for-delete.
|
||||
- delete:
|
||||
timeout: 3m
|
||||
ref:
|
||||
apiVersion: network.cozystack.io/v1alpha1
|
||||
kind: ServiceExposure
|
||||
name: se-e2e
|
||||
@@ -0,0 +1,37 @@
|
||||
# The externalIPs path is chosen deliberately so the test creates no
|
||||
# MetalLB/Cilium pool — it cannot collide with the cluster's existing
|
||||
# LoadBalancer address pools or depend on real L2 reachability
|
||||
# (203.0.113.10 is TEST-NET-3), keeping it deterministic. The metallb and
|
||||
# cilium rendering paths (pool + announcer CRs, garbage collection, take-over
|
||||
# guard) are covered exhaustively by the Go unit tests in
|
||||
# internal/controller/serviceexposure.
|
||||
#
|
||||
# The ExposureClass is cluster-scoped; Chainsaw tracks cluster-scoped applies
|
||||
# too, so auto-cleanup reclaims all three objects in reverse apply order.
|
||||
apiVersion: network.cozystack.io/v1alpha1
|
||||
kind: ExposureClass
|
||||
metadata:
|
||||
name: se-e2e-externalips
|
||||
spec:
|
||||
backend: externalIPs
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: se-e2e-svc
|
||||
spec:
|
||||
type: ClusterIP
|
||||
externalIPs:
|
||||
- 203.0.113.10
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
---
|
||||
apiVersion: network.cozystack.io/v1alpha1
|
||||
kind: ServiceExposure
|
||||
metadata:
|
||||
name: se-e2e
|
||||
spec:
|
||||
serviceRef:
|
||||
name: se-e2e-svc
|
||||
exposureClassName: se-e2e-externalips
|
||||
+1
-1
@@ -33,7 +33,7 @@ src_to_suites() {
|
||||
case "$1" in
|
||||
postgres-application) echo postgres ;;
|
||||
vm-instance-application) echo vminstance ;;
|
||||
kubernetes-application) echo "kubernetes-latest kubernetes-previous" ;;
|
||||
kubernetes-application) echo "kubernetes-latest kubernetes-previous kubernetes-oidc-system kubernetes-oidc-customconfig" ;;
|
||||
external-dns) echo external-dns ;;
|
||||
*-application) echo "${1%-application}" ;;
|
||||
*) echo "$1" ;;
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
[ -z "$output" ]
|
||||
}
|
||||
|
||||
@test "kubernetes-application maps to two suites" {
|
||||
@test "kubernetes-application maps to the four kubernetes suites" {
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
cp -r packages/core/platform/sources "$tmp/sources"
|
||||
@@ -93,6 +93,10 @@
|
||||
output=$(hack/select-e2e.sh "$tmp/diff" "$tmp/sources")
|
||||
echo "$output" | grep -q "kubernetes-latest"
|
||||
echo "$output" | grep -q "kubernetes-previous"
|
||||
# The OIDC render-side suites exercise the same kubernetes app chart, so a
|
||||
# chart-only change must select them too.
|
||||
echo "$output" | grep -q "kubernetes-oidc-system"
|
||||
echo "$output" | grep -q "kubernetes-oidc-customconfig"
|
||||
}
|
||||
|
||||
@test "dashboards-only diff selects nothing (path is plural)" {
|
||||
|
||||
Reference in new issue
Block a user